From b4105be21e967f79d819749c44eff6ed4311f65d Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Sat, 28 Apr 2012 12:42:03 +1200 Subject: Initial checkin. --- .gitignore | 8 + libpathod/__init__.py | 23 + libpathod/contrib/__init__.py | 0 libpathod/contrib/pyparsing.py | 3749 ++++++++++++++++++++++++++++++++++++ libpathod/handlers.py | 45 + libpathod/http.py | 103 + libpathod/rparse.py | 429 +++++ libpathod/static/bootstrap.min.css | 543 ++++++ libpathod/templates/frame.html | 41 + libpathod/templates/help.html | 4 + libpathod/templates/index.html | 12 + libpathod/templates/log.html | 5 + libpathod/utils.py | 31 + notes | 102 + pathod | 21 + test/.pry | 5 + test/test_rparse.py | 262 +++ test/test_utils.py | 8 + todo | 6 + 19 files changed, 5397 insertions(+) create mode 100644 .gitignore create mode 100644 libpathod/__init__.py create mode 100644 libpathod/contrib/__init__.py create mode 100644 libpathod/contrib/pyparsing.py create mode 100644 libpathod/handlers.py create mode 100644 libpathod/http.py create mode 100644 libpathod/rparse.py create mode 100644 libpathod/static/bootstrap.min.css create mode 100644 libpathod/templates/frame.html create mode 100644 libpathod/templates/help.html create mode 100644 libpathod/templates/index.html create mode 100644 libpathod/templates/log.html create mode 100644 libpathod/utils.py create mode 100644 notes create mode 100755 pathod create mode 100644 test/.pry create mode 100644 test/test_rparse.py create mode 100644 test/test_utils.py create mode 100644 todo diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..08d66342 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +# Python object files +*.py[cd] +MANIFEST +/build +/dist +# Vim swap files +*.swp +/doc diff --git a/libpathod/__init__.py b/libpathod/__init__.py new file mode 100644 index 00000000..e841a6ab --- /dev/null +++ b/libpathod/__init__.py @@ -0,0 +1,23 @@ +from tornado import web, template +import handlers, utils + +class PathodApp(web.Application): + def __init__(self, *args, **kwargs): + self.templates = template.Loader(utils.data.path("templates")) + web.Application.__init__(self, *args, **kwargs) + + +def application(**settings): + return PathodApp( + [ + (r"/", handlers.Index), + (r"/log", handlers.Log), + (r"/help", handlers.Help), + (r"/preview", handlers.Preview), + (r"/p/.*", handlers.Pathod, settings), + ], + debug=True, + static_path = utils.data.path("static"), + template_path = utils.data.path("templates"), + ) + diff --git a/libpathod/contrib/__init__.py b/libpathod/contrib/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/libpathod/contrib/pyparsing.py b/libpathod/contrib/pyparsing.py new file mode 100644 index 00000000..9be97dc2 --- /dev/null +++ b/libpathod/contrib/pyparsing.py @@ -0,0 +1,3749 @@ +# module pyparsing.py +# +# Copyright (c) 2003-2011 Paul T. McGuire +# +# 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. +# +#from __future__ import generators + +__doc__ = \ +""" +pyparsing module - Classes and methods to define and execute parsing grammars + +The pyparsing module is an alternative approach to creating and executing simple grammars, +vs. the traditional lex/yacc approach, or the use of regular expressions. With pyparsing, you +don't need to learn a new syntax for defining grammars or matching expressions - the parsing module +provides a library of classes that you use to construct the grammar directly in Python. + +Here is a program to parse "Hello, World!" (or any greeting of the form C{", !"}):: + + from pyparsing import Word, alphas + + # define grammar of a greeting + greet = Word( alphas ) + "," + Word( alphas ) + "!" + + hello = "Hello, World!" + print hello, "->", greet.parseString( hello ) + +The program outputs the following:: + + Hello, World! -> ['Hello', ',', 'World', '!'] + +The Python representation of the grammar is quite readable, owing to the self-explanatory +class names, and the use of '+', '|' and '^' operators. + +The parsed results returned from C{parseString()} can be accessed as a nested list, a dictionary, or an +object with named attributes. + +The pyparsing module handles some of the problems that are typically vexing when writing text parsers: + - extra or missing whitespace (the above program will also handle "Hello,World!", "Hello , World !", etc.) + - quoted strings + - embedded comments +""" + +__version__ = "1.5.6" +__versionTime__ = "26 June 2011 10:53" +__author__ = "Paul McGuire " + +import string +from weakref import ref as wkref +import copy +import sys +import warnings +import re +import sre_constants +#~ sys.stderr.write( "testing pyparsing module, version %s, %s\n" % (__version__,__versionTime__ ) ) + +__all__ = [ +'And', 'CaselessKeyword', 'CaselessLiteral', 'CharsNotIn', 'Combine', 'Dict', 'Each', 'Empty', +'FollowedBy', 'Forward', 'GoToColumn', 'Group', 'Keyword', 'LineEnd', 'LineStart', 'Literal', +'MatchFirst', 'NoMatch', 'NotAny', 'OneOrMore', 'OnlyOnce', 'Optional', 'Or', +'ParseBaseException', 'ParseElementEnhance', 'ParseException', 'ParseExpression', 'ParseFatalException', +'ParseResults', 'ParseSyntaxException', 'ParserElement', 'QuotedString', 'RecursiveGrammarException', +'Regex', 'SkipTo', 'StringEnd', 'StringStart', 'Suppress', 'Token', 'TokenConverter', 'Upcase', +'White', 'Word', 'WordEnd', 'WordStart', 'ZeroOrMore', +'alphanums', 'alphas', 'alphas8bit', 'anyCloseTag', 'anyOpenTag', 'cStyleComment', 'col', +'commaSeparatedList', 'commonHTMLEntity', 'countedArray', 'cppStyleComment', 'dblQuotedString', +'dblSlashComment', 'delimitedList', 'dictOf', 'downcaseTokens', 'empty', 'getTokensEndLoc', 'hexnums', +'htmlComment', 'javaStyleComment', 'keepOriginalText', 'line', 'lineEnd', 'lineStart', 'lineno', +'makeHTMLTags', 'makeXMLTags', 'matchOnlyAtCol', 'matchPreviousExpr', 'matchPreviousLiteral', +'nestedExpr', 'nullDebugAction', 'nums', 'oneOf', 'opAssoc', 'operatorPrecedence', 'printables', +'punc8bit', 'pythonStyleComment', 'quotedString', 'removeQuotes', 'replaceHTMLEntity', +'replaceWith', 'restOfLine', 'sglQuotedString', 'srange', 'stringEnd', +'stringStart', 'traceParseAction', 'unicodeString', 'upcaseTokens', 'withAttribute', +'indentedBlock', 'originalTextFor', +] + +""" +Detect if we are running version 3.X and make appropriate changes +Robert A. Clark +""" +_PY3K = sys.version_info[0] > 2 +if _PY3K: + _MAX_INT = sys.maxsize + basestring = str + unichr = chr + _ustr = str + alphas = string.ascii_lowercase + string.ascii_uppercase +else: + _MAX_INT = sys.maxint + range = xrange + set = lambda s : dict( [(c,0) for c in s] ) + alphas = string.lowercase + string.uppercase + + def _ustr(obj): + """Drop-in replacement for str(obj) that tries to be Unicode friendly. It first tries + str(obj). If that fails with a UnicodeEncodeError, then it tries unicode(obj). It + then < returns the unicode object | encodes it with the default encoding | ... >. + """ + if isinstance(obj,unicode): + return obj + + try: + # If this works, then _ustr(obj) has the same behaviour as str(obj), so + # it won't break any existing code. + return str(obj) + + except UnicodeEncodeError: + # The Python docs (http://docs.python.org/ref/customization.html#l2h-182) + # state that "The return value must be a string object". However, does a + # unicode object (being a subclass of basestring) count as a "string + # object"? + # If so, then return a unicode object: + return unicode(obj) + # Else encode it... but how? There are many choices... :) + # Replace unprintables with escape codes? + #return unicode(obj).encode(sys.getdefaultencoding(), 'backslashreplace_errors') + # Replace unprintables with question marks? + #return unicode(obj).encode(sys.getdefaultencoding(), 'replace') + # ... + + alphas = string.lowercase + string.uppercase + +# build list of single arg builtins, tolerant of Python version, that can be used as parse actions +singleArgBuiltins = [] +import __builtin__ +for fname in "sum len enumerate sorted reversed list tuple set any all".split(): + try: + singleArgBuiltins.append(getattr(__builtin__,fname)) + except AttributeError: + continue + +def _xml_escape(data): + """Escape &, <, >, ", ', etc. in a string of data.""" + + # ampersand must be replaced first + from_symbols = '&><"\'' + to_symbols = ['&'+s+';' for s in "amp gt lt quot apos".split()] + for from_,to_ in zip(from_symbols, to_symbols): + data = data.replace(from_, to_) + return data + +class _Constants(object): + pass + +nums = string.digits +hexnums = nums + "ABCDEFabcdef" +alphanums = alphas + nums +_bslash = chr(92) +printables = "".join( [ c for c in string.printable if c not in string.whitespace ] ) + +class ParseBaseException(Exception): + """base exception class for all parsing runtime exceptions""" + # Performance tuning: we construct a *lot* of these, so keep this + # constructor as small and fast as possible + def __init__( self, pstr, loc=0, msg=None, elem=None ): + self.loc = loc + if msg is None: + self.msg = pstr + self.pstr = "" + else: + self.msg = msg + self.pstr = pstr + self.parserElement = elem + + def __getattr__( self, aname ): + """supported attributes by name are: + - lineno - returns the line number of the exception text + - col - returns the column number of the exception text + - line - returns the line containing the exception text + """ + if( aname == "lineno" ): + return lineno( self.loc, self.pstr ) + elif( aname in ("col", "column") ): + return col( self.loc, self.pstr ) + elif( aname == "line" ): + return line( self.loc, self.pstr ) + else: + raise AttributeError(aname) + + def __str__( self ): + return "%s (at char %d), (line:%d, col:%d)" % \ + ( self.msg, self.loc, self.lineno, self.column ) + def __repr__( self ): + return _ustr(self) + def markInputline( self, markerString = ">!<" ): + """Extracts the exception line from the input string, and marks + the location of the exception with a special symbol. + """ + line_str = self.line + line_column = self.column - 1 + if markerString: + line_str = "".join( [line_str[:line_column], + markerString, line_str[line_column:]]) + return line_str.strip() + def __dir__(self): + return "loc msg pstr parserElement lineno col line " \ + "markInputLine __str__ __repr__".split() + +class ParseException(ParseBaseException): + """exception thrown when parse expressions don't match class; + supported attributes by name are: + - lineno - returns the line number of the exception text + - col - returns the column number of the exception text + - line - returns the line containing the exception text + """ + pass + +class ParseFatalException(ParseBaseException): + """user-throwable exception thrown when inconsistent parse content + is found; stops all parsing immediately""" + pass + +class ParseSyntaxException(ParseFatalException): + """just like C{ParseFatalException}, but thrown internally when an + C{ErrorStop} ('-' operator) indicates that parsing is to stop immediately because + an unbacktrackable syntax error has been found""" + def __init__(self, pe): + super(ParseSyntaxException, self).__init__( + pe.pstr, pe.loc, pe.msg, pe.parserElement) + +#~ class ReparseException(ParseBaseException): + #~ """Experimental class - parse actions can raise this exception to cause + #~ pyparsing to reparse the input string: + #~ - with a modified input string, and/or + #~ - with a modified start location + #~ Set the values of the ReparseException in the constructor, and raise the + #~ exception in a parse action to cause pyparsing to use the new string/location. + #~ Setting the values as None causes no change to be made. + #~ """ + #~ def __init_( self, newstring, restartLoc ): + #~ self.newParseText = newstring + #~ self.reparseLoc = restartLoc + +class RecursiveGrammarException(Exception): + """exception thrown by C{validate()} if the grammar could be improperly recursive""" + def __init__( self, parseElementList ): + self.parseElementTrace = parseElementList + + def __str__( self ): + return "RecursiveGrammarException: %s" % self.parseElementTrace + +class _ParseResultsWithOffset(object): + def __init__(self,p1,p2): + self.tup = (p1,p2) + def __getitem__(self,i): + return self.tup[i] + def __repr__(self): + return repr(self.tup) + def setOffset(self,i): + self.tup = (self.tup[0],i) + +class ParseResults(object): + """Structured parse results, to provide multiple means of access to the parsed data: + - as a list (C{len(results)}) + - by list index (C{results[0], results[1]}, etc.) + - by attribute (C{results.}) + """ + #~ __slots__ = ( "__toklist", "__tokdict", "__doinit", "__name", "__parent", "__accumNames", "__weakref__" ) + def __new__(cls, toklist, name=None, asList=True, modal=True ): + if isinstance(toklist, cls): + return toklist + retobj = object.__new__(cls) + retobj.__doinit = True + return retobj + + # Performance tuning: we construct a *lot* of these, so keep this + # constructor as small and fast as possible + def __init__( self, toklist, name=None, asList=True, modal=True, isinstance=isinstance ): + if self.__doinit: + self.__doinit = False + self.__name = None + self.__parent = None + self.__accumNames = {} + if isinstance(toklist, list): + self.__toklist = toklist[:] + else: + self.__toklist = [toklist] + self.__tokdict = dict() + + if name is not None and name: + if not modal: + self.__accumNames[name] = 0 + if isinstance(name,int): + name = _ustr(name) # will always return a str, but use _ustr for consistency + self.__name = name + if not toklist in (None,'',[]): + if isinstance(toklist,basestring): + toklist = [ toklist ] + if asList: + if isinstance(toklist,ParseResults): + self[name] = _ParseResultsWithOffset(toklist.copy(),0) + else: + self[name] = _ParseResultsWithOffset(ParseResults(toklist[0]),0) + self[name].__name = name + else: + try: + self[name] = toklist[0] + except (KeyError,TypeError,IndexError): + self[name] = toklist + + def __getitem__( self, i ): + if isinstance( i, (int,slice) ): + return self.__toklist[i] + else: + if i not in self.__accumNames: + return self.__tokdict[i][-1][0] + else: + return ParseResults([ v[0] for v in self.__tokdict[i] ]) + + def __setitem__( self, k, v, isinstance=isinstance ): + if isinstance(v,_ParseResultsWithOffset): + self.__tokdict[k] = self.__tokdict.get(k,list()) + [v] + sub = v[0] + elif isinstance(k,int): + self.__toklist[k] = v + sub = v + else: + self.__tokdict[k] = self.__tokdict.get(k,list()) + [_ParseResultsWithOffset(v,0)] + sub = v + if isinstance(sub,ParseResults): + sub.__parent = wkref(self) + + def __delitem__( self, i ): + if isinstance(i,(int,slice)): + mylen = len( self.__toklist ) + del self.__toklist[i] + + # convert int to slice + if isinstance(i, int): + if i < 0: + i += mylen + i = slice(i, i+1) + # get removed indices + removed = list(range(*i.indices(mylen))) + removed.reverse() + # fixup indices in token dictionary + for name in self.__tokdict: + occurrences = self.__tokdict[name] + for j in removed: + for k, (value, position) in enumerate(occurrences): + occurrences[k] = _ParseResultsWithOffset(value, position - (position > j)) + else: + del self.__tokdict[i] + + def __contains__( self, k ): + return k in self.__tokdict + + def __len__( self ): return len( self.__toklist ) + def __bool__(self): return len( self.__toklist ) > 0 + __nonzero__ = __bool__ + def __iter__( self ): return iter( self.__toklist ) + def __reversed__( self ): return iter( self.__toklist[::-1] ) + def keys( self ): + """Returns all named result keys.""" + return self.__tokdict.keys() + + def pop( self, index=-1 ): + """Removes and returns item at specified index (default=last). + Will work with either numeric indices or dict-key indicies.""" + ret = self[index] + del self[index] + return ret + + def get(self, key, defaultValue=None): + """Returns named result matching the given key, or if there is no + such name, then returns the given C{defaultValue} or C{None} if no + C{defaultValue} is specified.""" + if key in self: + return self[key] + else: + return defaultValue + + def insert( self, index, insStr ): + """Inserts new element at location index in the list of parsed tokens.""" + self.__toklist.insert(index, insStr) + # fixup indices in token dictionary + for name in self.__tokdict: + occurrences = self.__tokdict[name] + for k, (value, position) in enumerate(occurrences): + occurrences[k] = _ParseResultsWithOffset(value, position + (position > index)) + + def items( self ): + """Returns all named result keys and values as a list of tuples.""" + return [(k,self[k]) for k in self.__tokdict] + + def values( self ): + """Returns all named result values.""" + return [ v[-1][0] for v in self.__tokdict.values() ] + + def __getattr__( self, name ): + if True: #name not in self.__slots__: + if name in self.__tokdict: + if name not in self.__accumNames: + return self.__tokdict[name][-1][0] + else: + return ParseResults([ v[0] for v in self.__tokdict[name] ]) + else: + return "" + return None + + def __add__( self, other ): + ret = self.copy() + ret += other + return ret + + def __iadd__( self, other ): + if other.__tokdict: + offset = len(self.__toklist) + addoffset = ( lambda a: (a<0 and offset) or (a+offset) ) + otheritems = other.__tokdict.items() + otherdictitems = [(k, _ParseResultsWithOffset(v[0],addoffset(v[1])) ) + for (k,vlist) in otheritems for v in vlist] + for k,v in otherdictitems: + self[k] = v + if isinstance(v[0],ParseResults): + v[0].__parent = wkref(self) + + self.__toklist += other.__toklist + self.__accumNames.update( other.__accumNames ) + return self + + def __radd__(self, other): + if isinstance(other,int) and other == 0: + return self.copy() + + def __repr__( self ): + return "(%s, %s)" % ( repr( self.__toklist ), repr( self.__tokdict ) ) + + def __str__( self ): + out = "[" + sep = "" + for i in self.__toklist: + if isinstance(i, ParseResults): + out += sep + _ustr(i) + else: + out += sep + repr(i) + sep = ", " + out += "]" + return out + + def _asStringList( self, sep='' ): + out = [] + for item in self.__toklist: + if out and sep: + out.append(sep) + if isinstance( item, ParseResults ): + out += item._asStringList() + else: + out.append( _ustr(item) ) + return out + + def asList( self ): + """Returns the parse results as a nested list of matching tokens, all converted to strings.""" + out = [] + for res in self.__toklist: + if isinstance(res,ParseResults): + out.append( res.asList() ) + else: + out.append( res ) + return out + + def asDict( self ): + """Returns the named parse results as dictionary.""" + return dict( self.items() ) + + def copy( self ): + """Returns a new copy of a C{ParseResults} object.""" + ret = ParseResults( self.__toklist ) + ret.__tokdict = self.__tokdict.copy() + ret.__parent = self.__parent + ret.__accumNames.update( self.__accumNames ) + ret.__name = self.__name + return ret + + def asXML( self, doctag=None, namedItemsOnly=False, indent="", formatted=True ): + """Returns the parse results as XML. Tags are created for tokens and lists that have defined results names.""" + nl = "\n" + out = [] + namedItems = dict( [ (v[1],k) for (k,vlist) in self.__tokdict.items() + for v in vlist ] ) + nextLevelIndent = indent + " " + + # collapse out indents if formatting is not desired + if not formatted: + indent = "" + nextLevelIndent = "" + nl = "" + + selfTag = None + if doctag is not None: + selfTag = doctag + else: + if self.__name: + selfTag = self.__name + + if not selfTag: + if namedItemsOnly: + return "" + else: + selfTag = "ITEM" + + out += [ nl, indent, "<", selfTag, ">" ] + + worklist = self.__toklist + for i,res in enumerate(worklist): + if isinstance(res,ParseResults): + if i in namedItems: + out += [ res.asXML(namedItems[i], + namedItemsOnly and doctag is None, + nextLevelIndent, + formatted)] + else: + out += [ res.asXML(None, + namedItemsOnly and doctag is None, + nextLevelIndent, + formatted)] + else: + # individual token, see if there is a name for it + resTag = None + if i in namedItems: + resTag = namedItems[i] + if not resTag: + if namedItemsOnly: + continue + else: + resTag = "ITEM" + xmlBodyText = _xml_escape(_ustr(res)) + out += [ nl, nextLevelIndent, "<", resTag, ">", + xmlBodyText, + "" ] + + out += [ nl, indent, "" ] + return "".join(out) + + def __lookup(self,sub): + for k,vlist in self.__tokdict.items(): + for v,loc in vlist: + if sub is v: + return k + return None + + def getName(self): + """Returns the results name for this token expression.""" + if self.__name: + return self.__name + elif self.__parent: + par = self.__parent() + if par: + return par.__lookup(self) + else: + return None + elif (len(self) == 1 and + len(self.__tokdict) == 1 and + self.__tokdict.values()[0][0][1] in (0,-1)): + return self.__tokdict.keys()[0] + else: + return None + + def dump(self,indent='',depth=0): + """Diagnostic method for listing out the contents of a C{ParseResults}. + Accepts an optional C{indent} argument so that this string can be embedded + in a nested display of other data.""" + out = [] + out.append( indent+_ustr(self.asList()) ) + keys = self.items() + keys.sort() + for k,v in keys: + if out: + out.append('\n') + out.append( "%s%s- %s: " % (indent,(' '*depth), k) ) + if isinstance(v,ParseResults): + if v.keys(): + out.append( v.dump(indent,depth+1) ) + else: + out.append(_ustr(v)) + else: + out.append(_ustr(v)) + return "".join(out) + + # add support for pickle protocol + def __getstate__(self): + return ( self.__toklist, + ( self.__tokdict.copy(), + self.__parent is not None and self.__parent() or None, + self.__accumNames, + self.__name ) ) + + def __setstate__(self,state): + self.__toklist = state[0] + (self.__tokdict, + par, + inAccumNames, + self.__name) = state[1] + self.__accumNames = {} + self.__accumNames.update(inAccumNames) + if par is not None: + self.__parent = wkref(par) + else: + self.__parent = None + + def __dir__(self): + return dir(super(ParseResults,self)) + self.keys() + +def col (loc,strg): + """Returns current column within a string, counting newlines as line separators. + The first column is number 1. + + Note: the default parsing behavior is to expand tabs in the input string + before starting the parsing process. See L{I{ParserElement.parseString}} for more information + on parsing strings containing s, and suggested methods to maintain a + consistent view of the parsed string, the parse location, and line and column + positions within the parsed string. + """ + return (loc} for more information + on parsing strings containing s, and suggested methods to maintain a + consistent view of the parsed string, the parse location, and line and column + positions within the parsed string. + """ + return strg.count("\n",0,loc) + 1 + +def line( loc, strg ): + """Returns the line of text containing loc within a string, counting newlines as line separators. + """ + lastCR = strg.rfind("\n", 0, loc) + nextCR = strg.find("\n", loc) + if nextCR >= 0: + return strg[lastCR+1:nextCR] + else: + return strg[lastCR+1:] + +def _defaultStartDebugAction( instring, loc, expr ): + print ("Match " + _ustr(expr) + " at loc " + _ustr(loc) + "(%d,%d)" % ( lineno(loc,instring), col(loc,instring) )) + +def _defaultSuccessDebugAction( instring, startloc, endloc, expr, toks ): + print ("Matched " + _ustr(expr) + " -> " + str(toks.asList())) + +def _defaultExceptionDebugAction( instring, loc, expr, exc ): + print ("Exception raised:" + _ustr(exc)) + +def nullDebugAction(*args): + """'Do-nothing' debug action, to suppress debugging output during parsing.""" + pass + +'decorator to trim function calls to match the arity of the target' +if not _PY3K: + def _trim_arity(func, maxargs=2): + limit = [0] + def wrapper(*args): + while 1: + try: + return func(*args[limit[0]:]) + except TypeError: + if limit[0] <= maxargs: + limit[0] += 1 + continue + raise + return wrapper +else: + def _trim_arity(func, maxargs=2): + limit = maxargs + def wrapper(*args): + #~ nonlocal limit + while 1: + try: + return func(*args[limit:]) + except TypeError: + if limit: + limit -= 1 + continue + raise + return wrapper + +class ParserElement(object): + """Abstract base level parser element class.""" + DEFAULT_WHITE_CHARS = " \n\t\r" + verbose_stacktrace = False + + def setDefaultWhitespaceChars( chars ): + """Overrides the default whitespace chars + """ + ParserElement.DEFAULT_WHITE_CHARS = chars + setDefaultWhitespaceChars = staticmethod(setDefaultWhitespaceChars) + + def __init__( self, savelist=False ): + self.parseAction = list() + self.failAction = None + #~ self.name = "" # don't define self.name, let subclasses try/except upcall + self.strRepr = None + self.resultsName = None + self.saveAsList = savelist + self.skipWhitespace = True + self.whiteChars = ParserElement.DEFAULT_WHITE_CHARS + self.copyDefaultWhiteChars = True + self.mayReturnEmpty = False # used when checking for left-recursion + self.keepTabs = False + self.ignoreExprs = list() + self.debug = False + self.streamlined = False + self.mayIndexError = True # used to optimize exception handling for subclasses that don't advance parse index + self.errmsg = "" + self.modalResults = True # used to mark results names as modal (report only last) or cumulative (list all) + self.debugActions = ( None, None, None ) #custom debug actions + self.re = None + self.callPreparse = True # used to avoid redundant calls to preParse + self.callDuringTry = False + + def copy( self ): + """Make a copy of this C{ParserElement}. Useful for defining different parse actions + for the same parsing pattern, using copies of the original parse element.""" + cpy = copy.copy( self ) + cpy.parseAction = self.parseAction[:] + cpy.ignoreExprs = self.ignoreExprs[:] + if self.copyDefaultWhiteChars: + cpy.whiteChars = ParserElement.DEFAULT_WHITE_CHARS + return cpy + + def setName( self, name ): + """Define name for this expression, for use in debugging.""" + self.name = name + self.errmsg = "Expected " + self.name + if hasattr(self,"exception"): + self.exception.msg = self.errmsg + return self + + def setResultsName( self, name, listAllMatches=False ): + """Define name for referencing matching tokens as a nested attribute + of the returned parse results. + NOTE: this returns a *copy* of the original C{ParserElement} object; + this is so that the client can define a basic element, such as an + integer, and reference it in multiple places with different names. + + You can also set results names using the abbreviated syntax, + C{expr("name")} in place of C{expr.setResultsName("name")} - + see L{I{__call__}<__call__>}. + """ + newself = self.copy() + if name.endswith("*"): + name = name[:-1] + listAllMatches=True + newself.resultsName = name + newself.modalResults = not listAllMatches + return newself + + def setBreak(self,breakFlag = True): + """Method to invoke the Python pdb debugger when this element is + about to be parsed. Set C{breakFlag} to True to enable, False to + disable. + """ + if breakFlag: + _parseMethod = self._parse + def breaker(instring, loc, doActions=True, callPreParse=True): + import pdb + pdb.set_trace() + return _parseMethod( instring, loc, doActions, callPreParse ) + breaker._originalParseMethod = _parseMethod + self._parse = breaker + else: + if hasattr(self._parse,"_originalParseMethod"): + self._parse = self._parse._originalParseMethod + return self + + def setParseAction( self, *fns, **kwargs ): + """Define action to perform when successfully matching parse element definition. + Parse action fn is a callable method with 0-3 arguments, called as C{fn(s,loc,toks)}, + C{fn(loc,toks)}, C{fn(toks)}, or just C{fn()}, where: + - s = the original string being parsed (see note below) + - loc = the location of the matching substring + - toks = a list of the matched tokens, packaged as a ParseResults object + If the functions in fns modify the tokens, they can return them as the return + value from fn, and the modified list of tokens will replace the original. + Otherwise, fn does not need to return any value. + + Note: the default parsing behavior is to expand tabs in the input string + before starting the parsing process. See L{I{parseString}} for more information + on parsing strings containing s, and suggested methods to maintain a + consistent view of the parsed string, the parse location, and line and column + positions within the parsed string. + """ + self.parseAction = list(map(_trim_arity, list(fns))) + self.callDuringTry = ("callDuringTry" in kwargs and kwargs["callDuringTry"]) + return self + + def addParseAction( self, *fns, **kwargs ): + """Add parse action to expression's list of parse actions. See L{I{setParseAction}}.""" + self.parseAction += list(map(_trim_arity, list(fns))) + self.callDuringTry = self.callDuringTry or ("callDuringTry" in kwargs and kwargs["callDuringTry"]) + return self + + def setFailAction( self, fn ): + """Define action to perform if parsing fails at this expression. + Fail acton fn is a callable function that takes the arguments + C{fn(s,loc,expr,err)} where: + - s = string being parsed + - loc = location where expression match was attempted and failed + - expr = the parse expression that failed + - err = the exception thrown + The function returns no value. It may throw C{ParseFatalException} + if it is desired to stop parsing immediately.""" + self.failAction = fn + return self + + def _skipIgnorables( self, instring, loc ): + exprsFound = True + while exprsFound: + exprsFound = False + for e in self.ignoreExprs: + try: + while 1: + loc,dummy = e._parse( instring, loc ) + exprsFound = True + except ParseException: + pass + return loc + + def preParse( self, instring, loc ): + if self.ignoreExprs: + loc = self._skipIgnorables( instring, loc ) + + if self.skipWhitespace: + wt = self.whiteChars + instrlen = len(instring) + while loc < instrlen and instring[loc] in wt: + loc += 1 + + return loc + + def parseImpl( self, instring, loc, doActions=True ): + return loc, [] + + def postParse( self, instring, loc, tokenlist ): + return tokenlist + + #~ @profile + def _parseNoCache( self, instring, loc, doActions=True, callPreParse=True ): + debugging = ( self.debug ) #and doActions ) + + if debugging or self.failAction: + #~ print ("Match",self,"at loc",loc,"(%d,%d)" % ( lineno(loc,instring), col(loc,instring) )) + if (self.debugActions[0] ): + self.debugActions[0]( instring, loc, self ) + if callPreParse and self.callPreparse: + preloc = self.preParse( instring, loc ) + else: + preloc = loc + tokensStart = preloc + try: + try: + loc,tokens = self.parseImpl( instring, preloc, doActions ) + except IndexError: + raise ParseException( instring, len(instring), self.errmsg, self ) + except ParseBaseException: + #~ print ("Exception raised:", err) + err = None + if self.debugActions[2]: + err = sys.exc_info()[1] + self.debugActions[2]( instring, tokensStart, self, err ) + if self.failAction: + if err is None: + err = sys.exc_info()[1] + self.failAction( instring, tokensStart, self, err ) + raise + else: + if callPreParse and self.callPreparse: + preloc = self.preParse( instring, loc ) + else: + preloc = loc + tokensStart = preloc + if self.mayIndexError or loc >= len(instring): + try: + loc,tokens = self.parseImpl( instring, preloc, doActions ) + except IndexError: + raise ParseException( instring, len(instring), self.errmsg, self ) + else: + loc,tokens = self.parseImpl( instring, preloc, doActions ) + + tokens = self.postParse( instring, loc, tokens ) + + retTokens = ParseResults( tokens, self.resultsName, asList=self.saveAsList, modal=self.modalResults ) + if self.parseAction and (doActions or self.callDuringTry): + if debugging: + try: + for fn in self.parseAction: + tokens = fn( instring, tokensStart, retTokens ) + if tokens is not None: + retTokens = ParseResults( tokens, + self.resultsName, + asList=self.saveAsList and isinstance(tokens,(ParseResults,list)), + modal=self.modalResults ) + except ParseBaseException: + #~ print "Exception raised in user parse action:", err + if (self.debugActions[2] ): + err = sys.exc_info()[1] + self.debugActions[2]( instring, tokensStart, self, err ) + raise + else: + for fn in self.parseAction: + tokens = fn( instring, tokensStart, retTokens ) + if tokens is not None: + retTokens = ParseResults( tokens, + self.resultsName, + asList=self.saveAsList and isinstance(tokens,(ParseResults,list)), + modal=self.modalResults ) + + if debugging: + #~ print ("Matched",self,"->",retTokens.asList()) + if (self.debugActions[1] ): + self.debugActions[1]( instring, tokensStart, loc, self, retTokens ) + + return loc, retTokens + + def tryParse( self, instring, loc ): + try: + return self._parse( instring, loc, doActions=False )[0] + except ParseFatalException: + raise ParseException( instring, loc, self.errmsg, self) + + # this method gets repeatedly called during backtracking with the same arguments - + # we can cache these arguments and save ourselves the trouble of re-parsing the contained expression + def _parseCache( self, instring, loc, doActions=True, callPreParse=True ): + lookup = (self,instring,loc,callPreParse,doActions) + if lookup in ParserElement._exprArgCache: + value = ParserElement._exprArgCache[ lookup ] + if isinstance(value, Exception): + raise value + return (value[0],value[1].copy()) + else: + try: + value = self._parseNoCache( instring, loc, doActions, callPreParse ) + ParserElement._exprArgCache[ lookup ] = (value[0],value[1].copy()) + return value + except ParseBaseException: + pe = sys.exc_info()[1] + ParserElement._exprArgCache[ lookup ] = pe + raise + + _parse = _parseNoCache + + # argument cache for optimizing repeated calls when backtracking through recursive expressions + _exprArgCache = {} + def resetCache(): + ParserElement._exprArgCache.clear() + resetCache = staticmethod(resetCache) + + _packratEnabled = False + def enablePackrat(): + """Enables "packrat" parsing, which adds memoizing to the parsing logic. + Repeated parse attempts at the same string location (which happens + often in many complex grammars) can immediately return a cached value, + instead of re-executing parsing/validating code. Memoizing is done of + both valid results and parsing exceptions. + + This speedup may break existing programs that use parse actions that + have side-effects. For this reason, packrat parsing is disabled when + you first import pyparsing. To activate the packrat feature, your + program must call the class method C{ParserElement.enablePackrat()}. If + your program uses C{psyco} to "compile as you go", you must call + C{enablePackrat} before calling C{psyco.full()}. If you do not do this, + Python will crash. For best results, call C{enablePackrat()} immediately + after importing pyparsing. + """ + if not ParserElement._packratEnabled: + ParserElement._packratEnabled = True + ParserElement._parse = ParserElement._parseCache + enablePackrat = staticmethod(enablePackrat) + + def parseString( self, instring, parseAll=False ): + """Execute the parse expression with the given string. + This is the main interface to the client code, once the complete + expression has been built. + + If you want the grammar to require that the entire input string be + successfully parsed, then set C{parseAll} to True (equivalent to ending + the grammar with C{StringEnd()}). + + Note: C{parseString} implicitly calls C{expandtabs()} on the input string, + in order to report proper column numbers in parse actions. + If the input string contains tabs and + the grammar uses parse actions that use the C{loc} argument to index into the + string being parsed, you can ensure you have a consistent view of the input + string by: + - calling C{parseWithTabs} on your grammar before calling C{parseString} + (see L{I{parseWithTabs}}) + - define your parse action using the full C{(s,loc,toks)} signature, and + reference the input string using the parse action's C{s} argument + - explictly expand the tabs in your input string before calling + C{parseString} + """ + ParserElement.resetCache() + if not self.streamlined: + self.streamline() + #~ self.saveAsList = True + for e in self.ignoreExprs: + e.streamline() + if not self.keepTabs: + instring = instring.expandtabs() + try: + loc, tokens = self._parse( instring, 0 ) + if parseAll: + loc = self.preParse( instring, loc ) + se = Empty() + StringEnd() + se._parse( instring, loc ) + except ParseBaseException: + if ParserElement.verbose_stacktrace: + raise + else: + # catch and re-raise exception from here, clears out pyparsing internal stack trace + exc = sys.exc_info()[1] + raise exc + else: + return tokens + + def scanString( self, instring, maxMatches=_MAX_INT, overlap=False ): + """Scan the input string for expression matches. Each match will return the + matching tokens, start location, and end location. May be called with optional + C{maxMatches} argument, to clip scanning after 'n' matches are found. If + C{overlap} is specified, then overlapping matches will be reported. + + Note that the start and end locations are reported relative to the string + being parsed. See L{I{parseString}} for more information on parsing + strings with embedded tabs.""" + if not self.streamlined: + self.streamline() + for e in self.ignoreExprs: + e.streamline() + + if not self.keepTabs: + instring = _ustr(instring).expandtabs() + instrlen = len(instring) + loc = 0 + preparseFn = self.preParse + parseFn = self._parse + ParserElement.resetCache() + matches = 0 + try: + while loc <= instrlen and matches < maxMatches: + try: + preloc = preparseFn( instring, loc ) + nextLoc,tokens = parseFn( instring, preloc, callPreParse=False ) + except ParseException: + loc = preloc+1 + else: + if nextLoc > loc: + matches += 1 + yield tokens, preloc, nextLoc + if overlap: + nextloc = preparseFn( instring, loc ) + if nextloc > loc: + loc = nextLoc + else: + loc += 1 + else: + loc = nextLoc + else: + loc = preloc+1 + except ParseBaseException: + if ParserElement.verbose_stacktrace: + raise + else: + # catch and re-raise exception from here, clears out pyparsing internal stack trace + exc = sys.exc_info()[1] + raise exc + + def transformString( self, instring ): + """Extension to C{scanString}, to modify matching text with modified tokens that may + be returned from a parse action. To use C{transformString}, define a grammar and + attach a parse action to it that modifies the returned token list. + Invoking C{transformString()} on a target string will then scan for matches, + and replace the matched text patterns according to the logic in the parse + action. C{transformString()} returns the resulting transformed string.""" + out = [] + lastE = 0 + # force preservation of s, to minimize unwanted transformation of string, and to + # keep string locs straight between transformString and scanString + self.keepTabs = True + try: + for t,s,e in self.scanString( instring ): + out.append( instring[lastE:s] ) + if t: + if isinstance(t,ParseResults): + out += t.asList() + elif isinstance(t,list): + out += t + else: + out.append(t) + lastE = e + out.append(instring[lastE:]) + out = [o for o in out if o] + return "".join(map(_ustr,_flatten(out))) + except ParseBaseException: + if ParserElement.verbose_stacktrace: + raise + else: + # catch and re-raise exception from here, clears out pyparsing internal stack trace + exc = sys.exc_info()[1] + raise exc + + def searchString( self, instring, maxMatches=_MAX_INT ): + """Another extension to C{scanString}, simplifying the access to the tokens found + to match the given parse expression. May be called with optional + C{maxMatches} argument, to clip searching after 'n' matches are found. + """ + try: + return ParseResults([ t for t,s,e in self.scanString( instring, maxMatches ) ]) + except ParseBaseException: + if ParserElement.verbose_stacktrace: + raise + else: + # catch and re-raise exception from here, clears out pyparsing internal stack trace + exc = sys.exc_info()[1] + raise exc + + def __add__(self, other ): + """Implementation of + operator - returns And""" + if isinstance( other, basestring ): + other = Literal( other ) + if not isinstance( other, ParserElement ): + warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), + SyntaxWarning, stacklevel=2) + return None + return And( [ self, other ] ) + + def __radd__(self, other ): + """Implementation of + operator when left operand is not a C{ParserElement}""" + if isinstance( other, basestring ): + other = Literal( other ) + if not isinstance( other, ParserElement ): + warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), + SyntaxWarning, stacklevel=2) + return None + return other + self + + def __sub__(self, other): + """Implementation of - operator, returns C{And} with error stop""" + if isinstance( other, basestring ): + other = Literal( other ) + if not isinstance( other, ParserElement ): + warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), + SyntaxWarning, stacklevel=2) + return None + return And( [ self, And._ErrorStop(), other ] ) + + def __rsub__(self, other ): + """Implementation of - operator when left operand is not a C{ParserElement}""" + if isinstance( other, basestring ): + other = Literal( other ) + if not isinstance( other, ParserElement ): + warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), + SyntaxWarning, stacklevel=2) + return None + return other - self + + def __mul__(self,other): + """Implementation of * operator, allows use of C{expr * 3} in place of + C{expr + expr + expr}. Expressions may also me multiplied by a 2-integer + tuple, similar to C{{min,max}} multipliers in regular expressions. Tuples + may also include C{None} as in: + - C{expr*(n,None)} or C{expr*(n,)} is equivalent + to C{expr*n + ZeroOrMore(expr)} + (read as "at least n instances of C{expr}") + - C{expr*(None,n)} is equivalent to C{expr*(0,n)} + (read as "0 to n instances of C{expr}") + - C{expr*(None,None)} is equivalent to C{ZeroOrMore(expr)} + - C{expr*(1,None)} is equivalent to C{OneOrMore(expr)} + + Note that C{expr*(None,n)} does not raise an exception if + more than n exprs exist in the input stream; that is, + C{expr*(None,n)} does not enforce a maximum number of expr + occurrences. If this behavior is desired, then write + C{expr*(None,n) + ~expr} + + """ + if isinstance(other,int): + minElements, optElements = other,0 + elif isinstance(other,tuple): + other = (other + (None, None))[:2] + if other[0] is None: + other = (0, other[1]) + if isinstance(other[0],int) and other[1] is None: + if other[0] == 0: + return ZeroOrMore(self) + if other[0] == 1: + return OneOrMore(self) + else: + return self*other[0] + ZeroOrMore(self) + elif isinstance(other[0],int) and isinstance(other[1],int): + minElements, optElements = other + optElements -= minElements + else: + raise TypeError("cannot multiply 'ParserElement' and ('%s','%s') objects", type(other[0]),type(other[1])) + else: + raise TypeError("cannot multiply 'ParserElement' and '%s' objects", type(other)) + + if minElements < 0: + raise ValueError("cannot multiply ParserElement by negative value") + if optElements < 0: + raise ValueError("second tuple value must be greater or equal to first tuple value") + if minElements == optElements == 0: + raise ValueError("cannot multiply ParserElement by 0 or (0,0)") + + if (optElements): + def makeOptionalList(n): + if n>1: + return Optional(self + makeOptionalList(n-1)) + else: + return Optional(self) + if minElements: + if minElements == 1: + ret = self + makeOptionalList(optElements) + else: + ret = And([self]*minElements) + makeOptionalList(optElements) + else: + ret = makeOptionalList(optElements) + else: + if minElements == 1: + ret = self + else: + ret = And([self]*minElements) + return ret + + def __rmul__(self, other): + return self.__mul__(other) + + def __or__(self, other ): + """Implementation of | operator - returns C{MatchFirst}""" + if isinstance( other, basestring ): + other = Literal( other ) + if not isinstance( other, ParserElement ): + warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), + SyntaxWarning, stacklevel=2) + return None + return MatchFirst( [ self, other ] ) + + def __ror__(self, other ): + """Implementation of | operator when left operand is not a C{ParserElement}""" + if isinstance( other, basestring ): + other = Literal( other ) + if not isinstance( other, ParserElement ): + warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), + SyntaxWarning, stacklevel=2) + return None + return other | self + + def __xor__(self, other ): + """Implementation of ^ operator - returns C{Or}""" + if isinstance( other, basestring ): + other = Literal( other ) + if not isinstance( other, ParserElement ): + warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), + SyntaxWarning, stacklevel=2) + return None + return Or( [ self, other ] ) + + def __rxor__(self, other ): + """Implementation of ^ operator when left operand is not a C{ParserElement}""" + if isinstance( other, basestring ): + other = Literal( other ) + if not isinstance( other, ParserElement ): + warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), + SyntaxWarning, stacklevel=2) + return None + return other ^ self + + def __and__(self, other ): + """Implementation of & operator - returns C{Each}""" + if isinstance( other, basestring ): + other = Literal( other ) + if not isinstance( other, ParserElement ): + warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), + SyntaxWarning, stacklevel=2) + return None + return Each( [ self, other ] ) + + def __rand__(self, other ): + """Implementation of & operator when left operand is not a C{ParserElement}""" + if isinstance( other, basestring ): + other = Literal( other ) + if not isinstance( other, ParserElement ): + warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), + SyntaxWarning, stacklevel=2) + return None + return other & self + + def __invert__( self ): + """Implementation of ~ operator - returns C{NotAny}""" + return NotAny( self ) + + def __call__(self, name): + """Shortcut for C{setResultsName}, with C{listAllMatches=default}:: + userdata = Word(alphas).setResultsName("name") + Word(nums+"-").setResultsName("socsecno") + could be written as:: + userdata = Word(alphas)("name") + Word(nums+"-")("socsecno") + + If C{name} is given with a trailing C{'*'} character, then C{listAllMatches} will be + passed as C{True}. + """ + return self.setResultsName(name) + + def suppress( self ): + """Suppresses the output of this C{ParserElement}; useful to keep punctuation from + cluttering up returned output. + """ + return Suppress( self ) + + def leaveWhitespace( self ): + """Disables the skipping of whitespace before matching the characters in the + C{ParserElement}'s defined pattern. This is normally only used internally by + the pyparsing module, but may be needed in some whitespace-sensitive grammars. + """ + self.skipWhitespace = False + return self + + def setWhitespaceChars( self, chars ): + """Overrides the default whitespace chars + """ + self.skipWhitespace = True + self.whiteChars = chars + self.copyDefaultWhiteChars = False + return self + + def parseWithTabs( self ): + """Overrides default behavior to expand C{}s to spaces before parsing the input string. + Must be called before C{parseString} when the input grammar contains elements that + match C{} characters.""" + self.keepTabs = True + return self + + def ignore( self, other ): + """Define expression to be ignored (e.g., comments) while doing pattern + matching; may be called repeatedly, to define multiple comment or other + ignorable patterns. + """ + if isinstance( other, Suppress ): + if other not in self.ignoreExprs: + self.ignoreExprs.append( other.copy() ) + else: + self.ignoreExprs.append( Suppress( other.copy() ) ) + return self + + def setDebugActions( self, startAction, successAction, exceptionAction ): + """Enable display of debugging messages while doing pattern matching.""" + self.debugActions = (startAction or _defaultStartDebugAction, + successAction or _defaultSuccessDebugAction, + exceptionAction or _defaultExceptionDebugAction) + self.debug = True + return self + + def setDebug( self, flag=True ): + """Enable display of debugging messages while doing pattern matching. + Set C{flag} to True to enable, False to disable.""" + if flag: + self.setDebugActions( _defaultStartDebugAction, _defaultSuccessDebugAction, _defaultExceptionDebugAction ) + else: + self.debug = False + return self + + def __str__( self ): + return self.name + + def __repr__( self ): + return _ustr(self) + + def streamline( self ): + self.streamlined = True + self.strRepr = None + return self + + def checkRecursion( self, parseElementList ): + pass + + def validate( self, validateTrace=[] ): + """Check defined expressions for valid structure, check for infinite recursive definitions.""" + self.checkRecursion( [] ) + + def parseFile( self, file_or_filename, parseAll=False ): + """Execute the parse expression on the given file or filename. + If a filename is specified (instead of a file object), + the entire file is opened, read, and closed before parsing. + """ + try: + file_contents = file_or_filename.read() + except AttributeError: + f = open(file_or_filename, "rb") + file_contents = f.read() + f.close() + try: + return self.parseString(file_contents, parseAll) + except ParseBaseException: + # catch and re-raise exception from here, clears out pyparsing internal stack trace + exc = sys.exc_info()[1] + raise exc + + def getException(self): + return ParseException("",0,self.errmsg,self) + + def __getattr__(self,aname): + if aname == "myException": + self.myException = ret = self.getException(); + return ret; + else: + raise AttributeError("no such attribute " + aname) + + def __eq__(self,other): + if isinstance(other, ParserElement): + return self is other or self.__dict__ == other.__dict__ + elif isinstance(other, basestring): + try: + self.parseString(_ustr(other), parseAll=True) + return True + except ParseBaseException: + return False + else: + return super(ParserElement,self)==other + + def __ne__(self,other): + return not (self == other) + + def __hash__(self): + return hash(id(self)) + + def __req__(self,other): + return self == other + + def __rne__(self,other): + return not (self == other) + + +class Token(ParserElement): + """Abstract C{ParserElement} subclass, for defining atomic matching patterns.""" + def __init__( self ): + super(Token,self).__init__( savelist=False ) + + def setName(self, name): + s = super(Token,self).setName(name) + self.errmsg = "Expected " + self.name + return s + + +class Empty(Token): + """An empty token, will always match.""" + def __init__( self ): + super(Empty,self).__init__() + self.name = "Empty" + self.mayReturnEmpty = True + self.mayIndexError = False + + +class NoMatch(Token): + """A token that will never match.""" + def __init__( self ): + super(NoMatch,self).__init__() + self.name = "NoMatch" + self.mayReturnEmpty = True + self.mayIndexError = False + self.errmsg = "Unmatchable token" + + def parseImpl( self, instring, loc, doActions=True ): + exc = self.myException + exc.loc = loc + exc.pstr = instring + raise exc + + +class Literal(Token): + """Token to exactly match a specified string.""" + def __init__( self, matchString ): + super(Literal,self).__init__() + self.match = matchString + self.matchLen = len(matchString) + try: + self.firstMatchChar = matchString[0] + except IndexError: + warnings.warn("null string passed to Literal; use Empty() instead", + SyntaxWarning, stacklevel=2) + self.__class__ = Empty + self.name = '"%s"' % _ustr(self.match) + self.errmsg = "Expected " + self.name + self.mayReturnEmpty = False + self.mayIndexError = False + + # Performance tuning: this routine gets called a *lot* + # if this is a single character match string and the first character matches, + # short-circuit as quickly as possible, and avoid calling startswith + #~ @profile + def parseImpl( self, instring, loc, doActions=True ): + if (instring[loc] == self.firstMatchChar and + (self.matchLen==1 or instring.startswith(self.match,loc)) ): + return loc+self.matchLen, self.match + #~ raise ParseException( instring, loc, self.errmsg ) + exc = self.myException + exc.loc = loc + exc.pstr = instring + raise exc +_L = Literal + +class Keyword(Token): + """Token to exactly match a specified string as a keyword, that is, it must be + immediately followed by a non-keyword character. Compare with C{Literal}:: + Literal("if") will match the leading C{'if'} in C{'ifAndOnlyIf'}. + Keyword("if") will not; it will only match the leading C{'if'} in C{'if x=1'}, or C{'if(y==2)'} + Accepts two optional constructor arguments in addition to the keyword string: + C{identChars} is a string of characters that would be valid identifier characters, + defaulting to all alphanumerics + "_" and "$"; C{caseless} allows case-insensitive + matching, default is C{False}. + """ + DEFAULT_KEYWORD_CHARS = alphanums+"_$" + + def __init__( self, matchString, identChars=DEFAULT_KEYWORD_CHARS, caseless=False ): + super(Keyword,self).__init__() + self.match = matchString + self.matchLen = len(matchString) + try: + self.firstMatchChar = matchString[0] + except IndexError: + warnings.warn("null string passed to Keyword; use Empty() instead", + SyntaxWarning, stacklevel=2) + self.name = '"%s"' % self.match + self.errmsg = "Expected " + self.name + self.mayReturnEmpty = False + self.mayIndexError = False + self.caseless = caseless + if caseless: + self.caselessmatch = matchString.upper() + identChars = identChars.upper() + self.identChars = set(identChars) + + def parseImpl( self, instring, loc, doActions=True ): + if self.caseless: + if ( (instring[ loc:loc+self.matchLen ].upper() == self.caselessmatch) and + (loc >= len(instring)-self.matchLen or instring[loc+self.matchLen].upper() not in self.identChars) and + (loc == 0 or instring[loc-1].upper() not in self.identChars) ): + return loc+self.matchLen, self.match + else: + if (instring[loc] == self.firstMatchChar and + (self.matchLen==1 or instring.startswith(self.match,loc)) and + (loc >= len(instring)-self.matchLen or instring[loc+self.matchLen] not in self.identChars) and + (loc == 0 or instring[loc-1] not in self.identChars) ): + return loc+self.matchLen, self.match + #~ raise ParseException( instring, loc, self.errmsg ) + exc = self.myException + exc.loc = loc + exc.pstr = instring + raise exc + + def copy(self): + c = super(Keyword,self).copy() + c.identChars = Keyword.DEFAULT_KEYWORD_CHARS + return c + + def setDefaultKeywordChars( chars ): + """Overrides the default Keyword chars + """ + Keyword.DEFAULT_KEYWORD_CHARS = chars + setDefaultKeywordChars = staticmethod(setDefaultKeywordChars) + +class CaselessLiteral(Literal): + """Token to match a specified string, ignoring case of letters. + Note: the matched results will always be in the case of the given + match string, NOT the case of the input text. + """ + def __init__( self, matchString ): + super(CaselessLiteral,self).__init__( matchString.upper() ) + # Preserve the defining literal. + self.returnString = matchString + self.name = "'%s'" % self.returnString + self.errmsg = "Expected " + self.name + + def parseImpl( self, instring, loc, doActions=True ): + if instring[ loc:loc+self.matchLen ].upper() == self.match: + return loc+self.matchLen, self.returnString + #~ raise ParseException( instring, loc, self.errmsg ) + exc = self.myException + exc.loc = loc + exc.pstr = instring + raise exc + +class CaselessKeyword(Keyword): + def __init__( self, matchString, identChars=Keyword.DEFAULT_KEYWORD_CHARS ): + super(CaselessKeyword,self).__init__( matchString, identChars, caseless=True ) + + def parseImpl( self, instring, loc, doActions=True ): + if ( (instring[ loc:loc+self.matchLen ].upper() == self.caselessmatch) and + (loc >= len(instring)-self.matchLen or instring[loc+self.matchLen].upper() not in self.identChars) ): + return loc+self.matchLen, self.match + #~ raise ParseException( instring, loc, self.errmsg ) + exc = self.myException + exc.loc = loc + exc.pstr = instring + raise exc + +class Word(Token): + """Token for matching words composed of allowed character sets. + Defined with string containing all allowed initial characters, + an optional string containing allowed body characters (if omitted, + defaults to the initial character set), and an optional minimum, + maximum, and/or exact length. The default value for C{min} is 1 (a + minimum value < 1 is not valid); the default values for C{max} and C{exact} + are 0, meaning no maximum or exact length restriction. An optional + C{exclude} parameter can list characters that might be found in + the input C{bodyChars} string; useful to define a word of all printables + except for one or two characters, for instance. + """ + def __init__( self, initChars, bodyChars=None, min=1, max=0, exact=0, asKeyword=False, excludeChars=None ): + super(Word,self).__init__() + if excludeChars: + initChars = ''.join([c for c in initChars if c not in excludeChars]) + if bodyChars: + bodyChars = ''.join([c for c in bodyChars if c not in excludeChars]) + self.initCharsOrig = initChars + self.initChars = set(initChars) + if bodyChars : + self.bodyCharsOrig = bodyChars + self.bodyChars = set(bodyChars) + else: + self.bodyCharsOrig = initChars + self.bodyChars = set(initChars) + + self.maxSpecified = max > 0 + + if min < 1: + raise ValueError("cannot specify a minimum length < 1; use Optional(Word()) if zero-length word is permitted") + + self.minLen = min + + if max > 0: + self.maxLen = max + else: + self.maxLen = _MAX_INT + + if exact > 0: + self.maxLen = exact + self.minLen = exact + + self.name = _ustr(self) + self.errmsg = "Expected " + self.name + self.mayIndexError = False + self.asKeyword = asKeyword + + if ' ' not in self.initCharsOrig+self.bodyCharsOrig and (min==1 and max==0 and exact==0): + if self.bodyCharsOrig == self.initCharsOrig: + self.reString = "[%s]+" % _escapeRegexRangeChars(self.initCharsOrig) + elif len(self.bodyCharsOrig) == 1: + self.reString = "%s[%s]*" % \ + (re.escape(self.initCharsOrig), + _escapeRegexRangeChars(self.bodyCharsOrig),) + else: + self.reString = "[%s][%s]*" % \ + (_escapeRegexRangeChars(self.initCharsOrig), + _escapeRegexRangeChars(self.bodyCharsOrig),) + if self.asKeyword: + self.reString = r"\b"+self.reString+r"\b" + try: + self.re = re.compile( self.reString ) + except: + self.re = None + + def parseImpl( self, instring, loc, doActions=True ): + if self.re: + result = self.re.match(instring,loc) + if not result: + exc = self.myException + exc.loc = loc + exc.pstr = instring + raise exc + + loc = result.end() + return loc, result.group() + + if not(instring[ loc ] in self.initChars): + #~ raise ParseException( instring, loc, self.errmsg ) + exc = self.myException + exc.loc = loc + exc.pstr = instring + raise exc + start = loc + loc += 1 + instrlen = len(instring) + bodychars = self.bodyChars + maxloc = start + self.maxLen + maxloc = min( maxloc, instrlen ) + while loc < maxloc and instring[loc] in bodychars: + loc += 1 + + throwException = False + if loc - start < self.minLen: + throwException = True + if self.maxSpecified and loc < instrlen and instring[loc] in bodychars: + throwException = True + if self.asKeyword: + if (start>0 and instring[start-1] in bodychars) or (loc4: + return s[:4]+"..." + else: + return s + + if ( self.initCharsOrig != self.bodyCharsOrig ): + self.strRepr = "W:(%s,%s)" % ( charsAsStr(self.initCharsOrig), charsAsStr(self.bodyCharsOrig) ) + else: + self.strRepr = "W:(%s)" % charsAsStr(self.initCharsOrig) + + return self.strRepr + + +class Regex(Token): + """Token for matching strings that match a given regular expression. + Defined with string specifying the regular expression in a form recognized by the inbuilt Python re module. + """ + compiledREtype = type(re.compile("[A-Z]")) + def __init__( self, pattern, flags=0): + """The parameters C{pattern} and C{flags} are passed to the C{re.compile()} function as-is. See the Python C{re} module for an explanation of the acceptable patterns and flags.""" + super(Regex,self).__init__() + + if isinstance(pattern, basestring): + if len(pattern) == 0: + warnings.warn("null string passed to Regex; use Empty() instead", + SyntaxWarning, stacklevel=2) + + self.pattern = pattern + self.flags = flags + + try: + self.re = re.compile(self.pattern, self.flags) + self.reString = self.pattern + except sre_constants.error: + warnings.warn("invalid pattern (%s) passed to Regex" % pattern, + SyntaxWarning, stacklevel=2) + raise + + elif isinstance(pattern, Regex.compiledREtype): + self.re = pattern + self.pattern = \ + self.reString = str(pattern) + self.flags = flags + + else: + raise ValueError("Regex may only be constructed with a string or a compiled RE object") + + self.name = _ustr(self) + self.errmsg = "Expected " + self.name + self.mayIndexError = False + self.mayReturnEmpty = True + + def parseImpl( self, instring, loc, doActions=True ): + result = self.re.match(instring,loc) + if not result: + exc = self.myException + exc.loc = loc + exc.pstr = instring + raise exc + + loc = result.end() + d = result.groupdict() + ret = ParseResults(result.group()) + if d: + for k in d: + ret[k] = d[k] + return loc,ret + + def __str__( self ): + try: + return super(Regex,self).__str__() + except: + pass + + if self.strRepr is None: + self.strRepr = "Re:(%s)" % repr(self.pattern) + + return self.strRepr + + +class QuotedString(Token): + """Token for matching strings that are delimited by quoting characters. + """ + def __init__( self, quoteChar, escChar=None, escQuote=None, multiline=False, unquoteResults=True, endQuoteChar=None): + """ + Defined with the following parameters: + - quoteChar - string of one or more characters defining the quote delimiting string + - escChar - character to escape quotes, typically backslash (default=None) + - escQuote - special quote sequence to escape an embedded quote string (such as SQL's "" to escape an embedded ") (default=None) + - multiline - boolean indicating whether quotes can span multiple lines (default=False) + - unquoteResults - boolean indicating whether the matched text should be unquoted (default=True) + - endQuoteChar - string of one or more characters defining the end of the quote delimited string (default=None => same as quoteChar) + """ + super(QuotedString,self).__init__() + + # remove white space from quote chars - wont work anyway + quoteChar = quoteChar.strip() + if len(quoteChar) == 0: + warnings.warn("quoteChar cannot be the empty string",SyntaxWarning,stacklevel=2) + raise SyntaxError() + + if endQuoteChar is None: + endQuoteChar = quoteChar + else: + endQuoteChar = endQuoteChar.strip() + if len(endQuoteChar) == 0: + warnings.warn("endQuoteChar cannot be the empty string",SyntaxWarning,stacklevel=2) + raise SyntaxError() + + self.quoteChar = quoteChar + self.quoteCharLen = len(quoteChar) + self.firstQuoteChar = quoteChar[0] + self.endQuoteChar = endQuoteChar + self.endQuoteCharLen = len(endQuoteChar) + self.escChar = escChar + self.escQuote = escQuote + self.unquoteResults = unquoteResults + + if multiline: + self.flags = re.MULTILINE | re.DOTALL + self.pattern = r'%s(?:[^%s%s]' % \ + ( re.escape(self.quoteChar), + _escapeRegexRangeChars(self.endQuoteChar[0]), + (escChar is not None and _escapeRegexRangeChars(escChar) or '') ) + else: + self.flags = 0 + self.pattern = r'%s(?:[^%s\n\r%s]' % \ + ( re.escape(self.quoteChar), + _escapeRegexRangeChars(self.endQuoteChar[0]), + (escChar is not None and _escapeRegexRangeChars(escChar) or '') ) + if len(self.endQuoteChar) > 1: + self.pattern += ( + '|(?:' + ')|(?:'.join(["%s[^%s]" % (re.escape(self.endQuoteChar[:i]), + _escapeRegexRangeChars(self.endQuoteChar[i])) + for i in range(len(self.endQuoteChar)-1,0,-1)]) + ')' + ) + if escQuote: + self.pattern += (r'|(?:%s)' % re.escape(escQuote)) + if escChar: + self.pattern += (r'|(?:%s.)' % re.escape(escChar)) + charset = ''.join(set(self.quoteChar[0]+self.endQuoteChar[0])).replace('^',r'\^').replace('-',r'\-') + self.escCharReplacePattern = re.escape(self.escChar)+("([%s])" % charset) + self.pattern += (r')*%s' % re.escape(self.endQuoteChar)) + + try: + self.re = re.compile(self.pattern, self.flags) + self.reString = self.pattern + except sre_constants.error: + warnings.warn("invalid pattern (%s) passed to Regex" % self.pattern, + SyntaxWarning, stacklevel=2) + raise + + self.name = _ustr(self) + self.errmsg = "Expected " + self.name + self.mayIndexError = False + self.mayReturnEmpty = True + + def parseImpl( self, instring, loc, doActions=True ): + result = instring[loc] == self.firstQuoteChar and self.re.match(instring,loc) or None + if not result: + exc = self.myException + exc.loc = loc + exc.pstr = instring + raise exc + + loc = result.end() + ret = result.group() + + if self.unquoteResults: + + # strip off quotes + ret = ret[self.quoteCharLen:-self.endQuoteCharLen] + + if isinstance(ret,basestring): + # replace escaped characters + if self.escChar: + ret = re.sub(self.escCharReplacePattern,"\g<1>",ret) + + # replace escaped quotes + if self.escQuote: + ret = ret.replace(self.escQuote, self.endQuoteChar) + + return loc, ret + + def __str__( self ): + try: + return super(QuotedString,self).__str__() + except: + pass + + if self.strRepr is None: + self.strRepr = "quoted string, starting with %s ending with %s" % (self.quoteChar, self.endQuoteChar) + + return self.strRepr + + +class CharsNotIn(Token): + """Token for matching words composed of characters *not* in a given set. + Defined with string containing all disallowed characters, and an optional + minimum, maximum, and/or exact length. The default value for C{min} is 1 (a + minimum value < 1 is not valid); the default values for C{max} and C{exact} + are 0, meaning no maximum or exact length restriction. + """ + def __init__( self, notChars, min=1, max=0, exact=0 ): + super(CharsNotIn,self).__init__() + self.skipWhitespace = False + self.notChars = notChars + + if min < 1: + raise ValueError("cannot specify a minimum length < 1; use Optional(CharsNotIn()) if zero-length char group is permitted") + + self.minLen = min + + if max > 0: + self.maxLen = max + else: + self.maxLen = _MAX_INT + + if exact > 0: + self.maxLen = exact + self.minLen = exact + + self.name = _ustr(self) + self.errmsg = "Expected " + self.name + self.mayReturnEmpty = ( self.minLen == 0 ) + self.mayIndexError = False + + def parseImpl( self, instring, loc, doActions=True ): + if instring[loc] in self.notChars: + #~ raise ParseException( instring, loc, self.errmsg ) + exc = self.myException + exc.loc = loc + exc.pstr = instring + raise exc + + start = loc + loc += 1 + notchars = self.notChars + maxlen = min( start+self.maxLen, len(instring) ) + while loc < maxlen and \ + (instring[loc] not in notchars): + loc += 1 + + if loc - start < self.minLen: + #~ raise ParseException( instring, loc, self.errmsg ) + exc = self.myException + exc.loc = loc + exc.pstr = instring + raise exc + + return loc, instring[start:loc] + + def __str__( self ): + try: + return super(CharsNotIn, self).__str__() + except: + pass + + if self.strRepr is None: + if len(self.notChars) > 4: + self.strRepr = "!W:(%s...)" % self.notChars[:4] + else: + self.strRepr = "!W:(%s)" % self.notChars + + return self.strRepr + +class White(Token): + """Special matching class for matching whitespace. Normally, whitespace is ignored + by pyparsing grammars. This class is included when some whitespace structures + are significant. Define with a string containing the whitespace characters to be + matched; default is C{" \\t\\r\\n"}. Also takes optional C{min}, C{max}, and C{exact} arguments, + as defined for the C{Word} class.""" + whiteStrs = { + " " : "", + "\t": "", + "\n": "", + "\r": "", + "\f": "", + } + def __init__(self, ws=" \t\r\n", min=1, max=0, exact=0): + super(White,self).__init__() + self.matchWhite = ws + self.setWhitespaceChars( "".join([c for c in self.whiteChars if c not in self.matchWhite]) ) + #~ self.leaveWhitespace() + self.name = ("".join([White.whiteStrs[c] for c in self.matchWhite])) + self.mayReturnEmpty = True + self.errmsg = "Expected " + self.name + + self.minLen = min + + if max > 0: + self.maxLen = max + else: + self.maxLen = _MAX_INT + + if exact > 0: + self.maxLen = exact + self.minLen = exact + + def parseImpl( self, instring, loc, doActions=True ): + if not(instring[ loc ] in self.matchWhite): + #~ raise ParseException( instring, loc, self.errmsg ) + exc = self.myException + exc.loc = loc + exc.pstr = instring + raise exc + start = loc + loc += 1 + maxloc = start + self.maxLen + maxloc = min( maxloc, len(instring) ) + while loc < maxloc and instring[loc] in self.matchWhite: + loc += 1 + + if loc - start < self.minLen: + #~ raise ParseException( instring, loc, self.errmsg ) + exc = self.myException + exc.loc = loc + exc.pstr = instring + raise exc + + return loc, instring[start:loc] + + +class _PositionToken(Token): + def __init__( self ): + super(_PositionToken,self).__init__() + self.name=self.__class__.__name__ + self.mayReturnEmpty = True + self.mayIndexError = False + +class GoToColumn(_PositionToken): + """Token to advance to a specific column of input text; useful for tabular report scraping.""" + def __init__( self, colno ): + super(GoToColumn,self).__init__() + self.col = colno + + def preParse( self, instring, loc ): + if col(loc,instring) != self.col: + instrlen = len(instring) + if self.ignoreExprs: + loc = self._skipIgnorables( instring, loc ) + while loc < instrlen and instring[loc].isspace() and col( loc, instring ) != self.col : + loc += 1 + return loc + + def parseImpl( self, instring, loc, doActions=True ): + thiscol = col( loc, instring ) + if thiscol > self.col: + raise ParseException( instring, loc, "Text not in expected column", self ) + newloc = loc + self.col - thiscol + ret = instring[ loc: newloc ] + return newloc, ret + +class LineStart(_PositionToken): + """Matches if current position is at the beginning of a line within the parse string""" + def __init__( self ): + super(LineStart,self).__init__() + self.setWhitespaceChars( ParserElement.DEFAULT_WHITE_CHARS.replace("\n","") ) + self.errmsg = "Expected start of line" + + def preParse( self, instring, loc ): + preloc = super(LineStart,self).preParse(instring,loc) + if instring[preloc] == "\n": + loc += 1 + return loc + + def parseImpl( self, instring, loc, doActions=True ): + if not( loc==0 or + (loc == self.preParse( instring, 0 )) or + (instring[loc-1] == "\n") ): #col(loc, instring) != 1: + #~ raise ParseException( instring, loc, "Expected start of line" ) + exc = self.myException + exc.loc = loc + exc.pstr = instring + raise exc + return loc, [] + +class LineEnd(_PositionToken): + """Matches if current position is at the end of a line within the parse string""" + def __init__( self ): + super(LineEnd,self).__init__() + self.setWhitespaceChars( ParserElement.DEFAULT_WHITE_CHARS.replace("\n","") ) + self.errmsg = "Expected end of line" + + def parseImpl( self, instring, loc, doActions=True ): + if loc len(instring): + return loc, [] + else: + exc = self.myException + exc.loc = loc + exc.pstr = instring + raise exc + +class WordStart(_PositionToken): + """Matches if the current position is at the beginning of a Word, and + is not preceded by any character in a given set of C{wordChars} + (default=C{printables}). To emulate the C{\b} behavior of regular expressions, + use C{WordStart(alphanums)}. C{WordStart} will also match at the beginning of + the string being parsed, or at the beginning of a line. + """ + def __init__(self, wordChars = printables): + super(WordStart,self).__init__() + self.wordChars = set(wordChars) + self.errmsg = "Not at the start of a word" + + def parseImpl(self, instring, loc, doActions=True ): + if loc != 0: + if (instring[loc-1] in self.wordChars or + instring[loc] not in self.wordChars): + exc = self.myException + exc.loc = loc + exc.pstr = instring + raise exc + return loc, [] + +class WordEnd(_PositionToken): + """Matches if the current position is at the end of a Word, and + is not followed by any character in a given set of C{wordChars} + (default=C{printables}). To emulate the C{\b} behavior of regular expressions, + use C{WordEnd(alphanums)}. C{WordEnd} will also match at the end of + the string being parsed, or at the end of a line. + """ + def __init__(self, wordChars = printables): + super(WordEnd,self).__init__() + self.wordChars = set(wordChars) + self.skipWhitespace = False + self.errmsg = "Not at the end of a word" + + def parseImpl(self, instring, loc, doActions=True ): + instrlen = len(instring) + if instrlen>0 and loc maxExcLoc: + maxException = err + maxExcLoc = err.loc + except IndexError: + if len(instring) > maxExcLoc: + maxException = ParseException(instring,len(instring),e.errmsg,self) + maxExcLoc = len(instring) + else: + if loc2 > maxMatchLoc: + maxMatchLoc = loc2 + maxMatchExp = e + + if maxMatchLoc < 0: + if maxException is not None: + raise maxException + else: + raise ParseException(instring, loc, "no defined alternatives to match", self) + + return maxMatchExp._parse( instring, loc, doActions ) + + def __ixor__(self, other ): + if isinstance( other, basestring ): + other = Literal( other ) + return self.append( other ) #Or( [ self, other ] ) + + def __str__( self ): + if hasattr(self,"name"): + return self.name + + if self.strRepr is None: + self.strRepr = "{" + " ^ ".join( [ _ustr(e) for e in self.exprs ] ) + "}" + + return self.strRepr + + def checkRecursion( self, parseElementList ): + subRecCheckList = parseElementList[:] + [ self ] + for e in self.exprs: + e.checkRecursion( subRecCheckList ) + + +class MatchFirst(ParseExpression): + """Requires that at least one C{ParseExpression} is found. + If two expressions match, the first one listed is the one that will match. + May be constructed using the C{'|'} operator. + """ + def __init__( self, exprs, savelist = False ): + super(MatchFirst,self).__init__(exprs, savelist) + if exprs: + self.mayReturnEmpty = False + for e in self.exprs: + if e.mayReturnEmpty: + self.mayReturnEmpty = True + break + else: + self.mayReturnEmpty = True + + def parseImpl( self, instring, loc, doActions=True ): + maxExcLoc = -1 + maxException = None + for e in self.exprs: + try: + ret = e._parse( instring, loc, doActions ) + return ret + except ParseException, err: + if err.loc > maxExcLoc: + maxException = err + maxExcLoc = err.loc + except IndexError: + if len(instring) > maxExcLoc: + maxException = ParseException(instring,len(instring),e.errmsg,self) + maxExcLoc = len(instring) + + # only got here if no expression matched, raise exception for match that made it the furthest + else: + if maxException is not None: + raise maxException + else: + raise ParseException(instring, loc, "no defined alternatives to match", self) + + def __ior__(self, other ): + if isinstance( other, basestring ): + other = Literal( other ) + return self.append( other ) #MatchFirst( [ self, other ] ) + + def __str__( self ): + if hasattr(self,"name"): + return self.name + + if self.strRepr is None: + self.strRepr = "{" + " | ".join( [ _ustr(e) for e in self.exprs ] ) + "}" + + return self.strRepr + + def checkRecursion( self, parseElementList ): + subRecCheckList = parseElementList[:] + [ self ] + for e in self.exprs: + e.checkRecursion( subRecCheckList ) + + +class Each(ParseExpression): + """Requires all given C{ParseExpression}s to be found, but in any order. + Expressions may be separated by whitespace. + May be constructed using the C{'&'} operator. + """ + def __init__( self, exprs, savelist = True ): + super(Each,self).__init__(exprs, savelist) + self.mayReturnEmpty = True + for e in self.exprs: + if not e.mayReturnEmpty: + self.mayReturnEmpty = False + break + self.skipWhitespace = True + self.initExprGroups = True + + def parseImpl( self, instring, loc, doActions=True ): + if self.initExprGroups: + opt1 = [ e.expr for e in self.exprs if isinstance(e,Optional) ] + opt2 = [ e for e in self.exprs if e.mayReturnEmpty and e not in opt1 ] + self.optionals = opt1 + opt2 + self.multioptionals = [ e.expr for e in self.exprs if isinstance(e,ZeroOrMore) ] + self.multirequired = [ e.expr for e in self.exprs if isinstance(e,OneOrMore) ] + self.required = [ e for e in self.exprs if not isinstance(e,(Optional,ZeroOrMore,OneOrMore)) ] + self.required += self.multirequired + self.initExprGroups = False + tmpLoc = loc + tmpReqd = self.required[:] + tmpOpt = self.optionals[:] + matchOrder = [] + + keepMatching = True + while keepMatching: + tmpExprs = tmpReqd + tmpOpt + self.multioptionals + self.multirequired + failed = [] + for e in tmpExprs: + try: + tmpLoc = e.tryParse( instring, tmpLoc ) + except ParseException: + failed.append(e) + else: + matchOrder.append(e) + if e in tmpReqd: + tmpReqd.remove(e) + elif e in tmpOpt: + tmpOpt.remove(e) + if len(failed) == len(tmpExprs): + keepMatching = False + + if tmpReqd: + missing = ", ".join( [ _ustr(e) for e in tmpReqd ] ) + raise ParseException(instring,loc,"Missing one or more required elements (%s)" % missing ) + + # add any unmatched Optionals, in case they have default values defined + matchOrder += [e for e in self.exprs if isinstance(e,Optional) and e.expr in tmpOpt] + + resultlist = [] + for e in matchOrder: + loc,results = e._parse(instring,loc,doActions) + resultlist.append(results) + + finalResults = ParseResults([]) + for r in resultlist: + dups = {} + for k in r.keys(): + if k in finalResults.keys(): + tmp = ParseResults(finalResults[k]) + tmp += ParseResults(r[k]) + dups[k] = tmp + finalResults += ParseResults(r) + for k,v in dups.items(): + finalResults[k] = v + return loc, finalResults + + def __str__( self ): + if hasattr(self,"name"): + return self.name + + if self.strRepr is None: + self.strRepr = "{" + " & ".join( [ _ustr(e) for e in self.exprs ] ) + "}" + + return self.strRepr + + def checkRecursion( self, parseElementList ): + subRecCheckList = parseElementList[:] + [ self ] + for e in self.exprs: + e.checkRecursion( subRecCheckList ) + + +class ParseElementEnhance(ParserElement): + """Abstract subclass of C{ParserElement}, for combining and post-processing parsed tokens.""" + def __init__( self, expr, savelist=False ): + super(ParseElementEnhance,self).__init__(savelist) + if isinstance( expr, basestring ): + expr = Literal(expr) + self.expr = expr + self.strRepr = None + if expr is not None: + self.mayIndexError = expr.mayIndexError + self.mayReturnEmpty = expr.mayReturnEmpty + self.setWhitespaceChars( expr.whiteChars ) + self.skipWhitespace = expr.skipWhitespace + self.saveAsList = expr.saveAsList + self.callPreparse = expr.callPreparse + self.ignoreExprs.extend(expr.ignoreExprs) + + def parseImpl( self, instring, loc, doActions=True ): + if self.expr is not None: + return self.expr._parse( instring, loc, doActions, callPreParse=False ) + else: + raise ParseException("",loc,self.errmsg,self) + + def leaveWhitespace( self ): + self.skipWhitespace = False + self.expr = self.expr.copy() + if self.expr is not None: + self.expr.leaveWhitespace() + return self + + def ignore( self, other ): + if isinstance( other, Suppress ): + if other not in self.ignoreExprs: + super( ParseElementEnhance, self).ignore( other ) + if self.expr is not None: + self.expr.ignore( self.ignoreExprs[-1] ) + else: + super( ParseElementEnhance, self).ignore( other ) + if self.expr is not None: + self.expr.ignore( self.ignoreExprs[-1] ) + return self + + def streamline( self ): + super(ParseElementEnhance,self).streamline() + if self.expr is not None: + self.expr.streamline() + return self + + def checkRecursion( self, parseElementList ): + if self in parseElementList: + raise RecursiveGrammarException( parseElementList+[self] ) + subRecCheckList = parseElementList[:] + [ self ] + if self.expr is not None: + self.expr.checkRecursion( subRecCheckList ) + + def validate( self, validateTrace=[] ): + tmp = validateTrace[:]+[self] + if self.expr is not None: + self.expr.validate(tmp) + self.checkRecursion( [] ) + + def __str__( self ): + try: + return super(ParseElementEnhance,self).__str__() + except: + pass + + if self.strRepr is None and self.expr is not None: + self.strRepr = "%s:(%s)" % ( self.__class__.__name__, _ustr(self.expr) ) + return self.strRepr + + +class FollowedBy(ParseElementEnhance): + """Lookahead matching of the given parse expression. C{FollowedBy} + does *not* advance the parsing position within the input string, it only + verifies that the specified parse expression matches at the current + position. C{FollowedBy} always returns a null token list.""" + def __init__( self, expr ): + super(FollowedBy,self).__init__(expr) + self.mayReturnEmpty = True + + def parseImpl( self, instring, loc, doActions=True ): + self.expr.tryParse( instring, loc ) + return loc, [] + + +class NotAny(ParseElementEnhance): + """Lookahead to disallow matching with the given parse expression. C{NotAny} + does *not* advance the parsing position within the input string, it only + verifies that the specified parse expression does *not* match at the current + position. Also, C{NotAny} does *not* skip over leading whitespace. C{NotAny} + always returns a null token list. May be constructed using the '~' operator.""" + def __init__( self, expr ): + super(NotAny,self).__init__(expr) + #~ self.leaveWhitespace() + self.skipWhitespace = False # do NOT use self.leaveWhitespace(), don't want to propagate to exprs + self.mayReturnEmpty = True + self.errmsg = "Found unwanted token, "+_ustr(self.expr) + + def parseImpl( self, instring, loc, doActions=True ): + try: + self.expr.tryParse( instring, loc ) + except (ParseException,IndexError): + pass + else: + #~ raise ParseException(instring, loc, self.errmsg ) + exc = self.myException + exc.loc = loc + exc.pstr = instring + raise exc + return loc, [] + + def __str__( self ): + if hasattr(self,"name"): + return self.name + + if self.strRepr is None: + self.strRepr = "~{" + _ustr(self.expr) + "}" + + return self.strRepr + + +class ZeroOrMore(ParseElementEnhance): + """Optional repetition of zero or more of the given expression.""" + def __init__( self, expr ): + super(ZeroOrMore,self).__init__(expr) + self.mayReturnEmpty = True + + def parseImpl( self, instring, loc, doActions=True ): + tokens = [] + try: + loc, tokens = self.expr._parse( instring, loc, doActions, callPreParse=False ) + hasIgnoreExprs = ( len(self.ignoreExprs) > 0 ) + while 1: + if hasIgnoreExprs: + preloc = self._skipIgnorables( instring, loc ) + else: + preloc = loc + loc, tmptokens = self.expr._parse( instring, preloc, doActions ) + if tmptokens or tmptokens.keys(): + tokens += tmptokens + except (ParseException,IndexError): + pass + + return loc, tokens + + def __str__( self ): + if hasattr(self,"name"): + return self.name + + if self.strRepr is None: + self.strRepr = "[" + _ustr(self.expr) + "]..." + + return self.strRepr + + def setResultsName( self, name, listAllMatches=False ): + ret = super(ZeroOrMore,self).setResultsName(name,listAllMatches) + ret.saveAsList = True + return ret + + +class OneOrMore(ParseElementEnhance): + """Repetition of one or more of the given expression.""" + def parseImpl( self, instring, loc, doActions=True ): + # must be at least one + loc, tokens = self.expr._parse( instring, loc, doActions, callPreParse=False ) + try: + hasIgnoreExprs = ( len(self.ignoreExprs) > 0 ) + while 1: + if hasIgnoreExprs: + preloc = self._skipIgnorables( instring, loc ) + else: + preloc = loc + loc, tmptokens = self.expr._parse( instring, preloc, doActions ) + if tmptokens or tmptokens.keys(): + tokens += tmptokens + except (ParseException,IndexError): + pass + + return loc, tokens + + def __str__( self ): + if hasattr(self,"name"): + return self.name + + if self.strRepr is None: + self.strRepr = "{" + _ustr(self.expr) + "}..." + + return self.strRepr + + def setResultsName( self, name, listAllMatches=False ): + ret = super(OneOrMore,self).setResultsName(name,listAllMatches) + ret.saveAsList = True + return ret + +class _NullToken(object): + def __bool__(self): + return False + __nonzero__ = __bool__ + def __str__(self): + return "" + +_optionalNotMatched = _NullToken() +class Optional(ParseElementEnhance): + """Optional matching of the given expression. + A default return string can also be specified, if the optional expression + is not found. + """ + def __init__( self, exprs, default=_optionalNotMatched ): + super(Optional,self).__init__( exprs, savelist=False ) + self.defaultValue = default + self.mayReturnEmpty = True + + def parseImpl( self, instring, loc, doActions=True ): + try: + loc, tokens = self.expr._parse( instring, loc, doActions, callPreParse=False ) + except (ParseException,IndexError): + if self.defaultValue is not _optionalNotMatched: + if self.expr.resultsName: + tokens = ParseResults([ self.defaultValue ]) + tokens[self.expr.resultsName] = self.defaultValue + else: + tokens = [ self.defaultValue ] + else: + tokens = [] + return loc, tokens + + def __str__( self ): + if hasattr(self,"name"): + return self.name + + if self.strRepr is None: + self.strRepr = "[" + _ustr(self.expr) + "]" + + return self.strRepr + + +class SkipTo(ParseElementEnhance): + """Token for skipping over all undefined text until the matched expression is found. + If C{include} is set to true, the matched expression is also parsed (the skipped text + and matched expression are returned as a 2-element list). The C{ignore} + argument is used to define grammars (typically quoted strings and comments) that + might contain false matches. + """ + def __init__( self, other, include=False, ignore=None, failOn=None ): + super( SkipTo, self ).__init__( other ) + self.ignoreExpr = ignore + self.mayReturnEmpty = True + self.mayIndexError = False + self.includeMatch = include + self.asList = False + if failOn is not None and isinstance(failOn, basestring): + self.failOn = Literal(failOn) + else: + self.failOn = failOn + self.errmsg = "No match found for "+_ustr(self.expr) + + def parseImpl( self, instring, loc, doActions=True ): + startLoc = loc + instrlen = len(instring) + expr = self.expr + failParse = False + while loc <= instrlen: + try: + if self.failOn: + try: + self.failOn.tryParse(instring, loc) + except ParseBaseException: + pass + else: + failParse = True + raise ParseException(instring, loc, "Found expression " + str(self.failOn)) + failParse = False + if self.ignoreExpr is not None: + while 1: + try: + loc = self.ignoreExpr.tryParse(instring,loc) + # print "found ignoreExpr, advance to", loc + except ParseBaseException: + break + expr._parse( instring, loc, doActions=False, callPreParse=False ) + skipText = instring[startLoc:loc] + if self.includeMatch: + loc,mat = expr._parse(instring,loc,doActions,callPreParse=False) + if mat: + skipRes = ParseResults( skipText ) + skipRes += mat + return loc, [ skipRes ] + else: + return loc, [ skipText ] + else: + return loc, [ skipText ] + except (ParseException,IndexError): + if failParse: + raise + else: + loc += 1 + exc = self.myException + exc.loc = loc + exc.pstr = instring + raise exc + +class Forward(ParseElementEnhance): + """Forward declaration of an expression to be defined later - + used for recursive grammars, such as algebraic infix notation. + When the expression is known, it is assigned to the C{Forward} variable using the '<<' operator. + + Note: take care when assigning to C{Forward} not to overlook precedence of operators. + Specifically, '|' has a lower precedence than '<<', so that:: + fwdExpr << a | b | c + will actually be evaluated as:: + (fwdExpr << a) | b | c + thereby leaving b and c out as parseable alternatives. It is recommended that you + explicitly group the values inserted into the C{Forward}:: + fwdExpr << (a | b | c) + """ + def __init__( self, other=None ): + super(Forward,self).__init__( other, savelist=False ) + + def __lshift__( self, other ): + if isinstance( other, basestring ): + other = Literal(other) + self.expr = other + self.mayReturnEmpty = other.mayReturnEmpty + self.strRepr = None + self.mayIndexError = self.expr.mayIndexError + self.mayReturnEmpty = self.expr.mayReturnEmpty + self.setWhitespaceChars( self.expr.whiteChars ) + self.skipWhitespace = self.expr.skipWhitespace + self.saveAsList = self.expr.saveAsList + self.ignoreExprs.extend(self.expr.ignoreExprs) + return None + + def leaveWhitespace( self ): + self.skipWhitespace = False + return self + + def streamline( self ): + if not self.streamlined: + self.streamlined = True + if self.expr is not None: + self.expr.streamline() + return self + + def validate( self, validateTrace=[] ): + if self not in validateTrace: + tmp = validateTrace[:]+[self] + if self.expr is not None: + self.expr.validate(tmp) + self.checkRecursion([]) + + def __str__( self ): + if hasattr(self,"name"): + return self.name + + self._revertClass = self.__class__ + self.__class__ = _ForwardNoRecurse + try: + if self.expr is not None: + retString = _ustr(self.expr) + else: + retString = "None" + finally: + self.__class__ = self._revertClass + return self.__class__.__name__ + ": " + retString + + def copy(self): + if self.expr is not None: + return super(Forward,self).copy() + else: + ret = Forward() + ret << self + return ret + +class _ForwardNoRecurse(Forward): + def __str__( self ): + return "..." + +class TokenConverter(ParseElementEnhance): + """Abstract subclass of C{ParseExpression}, for converting parsed results.""" + def __init__( self, expr, savelist=False ): + super(TokenConverter,self).__init__( expr )#, savelist ) + self.saveAsList = False + +class Upcase(TokenConverter): + """Converter to upper case all matching tokens.""" + def __init__(self, *args): + super(Upcase,self).__init__(*args) + warnings.warn("Upcase class is deprecated, use upcaseTokens parse action instead", + DeprecationWarning,stacklevel=2) + + def postParse( self, instring, loc, tokenlist ): + return list(map( string.upper, tokenlist )) + + +class Combine(TokenConverter): + """Converter to concatenate all matching tokens to a single string. + By default, the matching patterns must also be contiguous in the input string; + this can be disabled by specifying C{'adjacent=False'} in the constructor. + """ + def __init__( self, expr, joinString="", adjacent=True ): + super(Combine,self).__init__( expr ) + # suppress whitespace-stripping in contained parse expressions, but re-enable it on the Combine itself + if adjacent: + self.leaveWhitespace() + self.adjacent = adjacent + self.skipWhitespace = True + self.joinString = joinString + self.callPreparse = True + + def ignore( self, other ): + if self.adjacent: + ParserElement.ignore(self, other) + else: + super( Combine, self).ignore( other ) + return self + + def postParse( self, instring, loc, tokenlist ): + retToks = tokenlist.copy() + del retToks[:] + retToks += ParseResults([ "".join(tokenlist._asStringList(self.joinString)) ], modal=self.modalResults) + + if self.resultsName and len(retToks.keys())>0: + return [ retToks ] + else: + return retToks + +class Group(TokenConverter): + """Converter to return the matched tokens as a list - useful for returning tokens of C{ZeroOrMore} and C{OneOrMore} expressions.""" + def __init__( self, expr ): + super(Group,self).__init__( expr ) + self.saveAsList = True + + def postParse( self, instring, loc, tokenlist ): + return [ tokenlist ] + +class Dict(TokenConverter): + """Converter to return a repetitive expression as a list, but also as a dictionary. + Each element can also be referenced using the first token in the expression as its key. + Useful for tabular report scraping when the first column can be used as a item key. + """ + def __init__( self, exprs ): + super(Dict,self).__init__( exprs ) + self.saveAsList = True + + def postParse( self, instring, loc, tokenlist ): + for i,tok in enumerate(tokenlist): + if len(tok) == 0: + continue + ikey = tok[0] + if isinstance(ikey,int): + ikey = _ustr(tok[0]).strip() + if len(tok)==1: + tokenlist[ikey] = _ParseResultsWithOffset("",i) + elif len(tok)==2 and not isinstance(tok[1],ParseResults): + tokenlist[ikey] = _ParseResultsWithOffset(tok[1],i) + else: + dictvalue = tok.copy() #ParseResults(i) + del dictvalue[0] + if len(dictvalue)!= 1 or (isinstance(dictvalue,ParseResults) and dictvalue.keys()): + tokenlist[ikey] = _ParseResultsWithOffset(dictvalue,i) + else: + tokenlist[ikey] = _ParseResultsWithOffset(dictvalue[0],i) + + if self.resultsName: + return [ tokenlist ] + else: + return tokenlist + + +class Suppress(TokenConverter): + """Converter for ignoring the results of a parsed expression.""" + def postParse( self, instring, loc, tokenlist ): + return [] + + def suppress( self ): + return self + + +class OnlyOnce(object): + """Wrapper for parse actions, to ensure they are only called once.""" + def __init__(self, methodCall): + self.callable = _trim_arity(methodCall) + self.called = False + def __call__(self,s,l,t): + if not self.called: + results = self.callable(s,l,t) + self.called = True + return results + raise ParseException(s,l,"") + def reset(self): + self.called = False + +def traceParseAction(f): + """Decorator for debugging parse actions.""" + f = _trim_arity(f) + def z(*paArgs): + thisFunc = f.func_name + s,l,t = paArgs[-3:] + if len(paArgs)>3: + thisFunc = paArgs[0].__class__.__name__ + '.' + thisFunc + sys.stderr.write( ">>entering %s(line: '%s', %d, %s)\n" % (thisFunc,line(l,s),l,t) ) + try: + ret = f(*paArgs) + except Exception: + exc = sys.exc_info()[1] + sys.stderr.write( "<", "|".join( [ _escapeRegexChars(sym) for sym in symbols] )) + try: + if len(symbols)==len("".join(symbols)): + return Regex( "[%s]" % "".join( [ _escapeRegexRangeChars(sym) for sym in symbols] ) ) + else: + return Regex( "|".join( [ re.escape(sym) for sym in symbols] ) ) + except: + warnings.warn("Exception creating Regex for oneOf, building MatchFirst", + SyntaxWarning, stacklevel=2) + + + # last resort, just use MatchFirst + return MatchFirst( [ parseElementClass(sym) for sym in symbols ] ) + +def dictOf( key, value ): + """Helper to easily and clearly define a dictionary by specifying the respective patterns + for the key and value. Takes care of defining the C{Dict}, C{ZeroOrMore}, and C{Group} tokens + in the proper order. The key pattern can include delimiting markers or punctuation, + as long as they are suppressed, thereby leaving the significant key text. The value + pattern can include named results, so that the C{Dict} results can include named token + fields. + """ + return Dict( ZeroOrMore( Group ( key + value ) ) ) + +def originalTextFor(expr, asString=True): + """Helper to return the original, untokenized text for a given expression. Useful to + restore the parsed fields of an HTML start tag into the raw tag text itself, or to + revert separate tokens with intervening whitespace back to the original matching + input text. Simpler to use than the parse action C{L{keepOriginalText}}, and does not + require the inspect module to chase up the call stack. By default, returns a + string containing the original parsed text. + + If the optional C{asString} argument is passed as C{False}, then the return value is a + C{ParseResults} containing any results names that were originally matched, and a + single token containing the original matched text from the input string. So if + the expression passed to C{L{originalTextFor}} contains expressions with defined + results names, you must set C{asString} to C{False} if you want to preserve those + results name values.""" + locMarker = Empty().setParseAction(lambda s,loc,t: loc) + endlocMarker = locMarker.copy() + endlocMarker.callPreparse = False + matchExpr = locMarker("_original_start") + expr + endlocMarker("_original_end") + if asString: + extractText = lambda s,l,t: s[t._original_start:t._original_end] + else: + def extractText(s,l,t): + del t[:] + t.insert(0, s[t._original_start:t._original_end]) + del t["_original_start"] + del t["_original_end"] + matchExpr.setParseAction(extractText) + return matchExpr + +def ungroup(expr): + """Helper to undo pyparsing's default grouping of And expressions, even + if all but one are non-empty.""" + return TokenConverter(expr).setParseAction(lambda t:t[0]) + +# convenience constants for positional expressions +empty = Empty().setName("empty") +lineStart = LineStart().setName("lineStart") +lineEnd = LineEnd().setName("lineEnd") +stringStart = StringStart().setName("stringStart") +stringEnd = StringEnd().setName("stringEnd") + +_escapedPunc = Word( _bslash, r"\[]-*.$+^?()~ ", exact=2 ).setParseAction(lambda s,l,t:t[0][1]) +_printables_less_backslash = "".join([ c for c in printables if c not in r"\]" ]) +_escapedHexChar = Regex(r"\\0?[xX][0-9a-fA-F]+").setParseAction(lambda s,l,t:unichr(int(t[0][1:],16))) +_escapedOctChar = Regex(r"\\0[0-7]+").setParseAction(lambda s,l,t:unichr(int(t[0][1:],8))) +_singleChar = _escapedPunc | _escapedHexChar | _escapedOctChar | Word(_printables_less_backslash,exact=1) +_charRange = Group(_singleChar + Suppress("-") + _singleChar) +_reBracketExpr = Literal("[") + Optional("^").setResultsName("negate") + Group( OneOrMore( _charRange | _singleChar ) ).setResultsName("body") + "]" + +_expanded = lambda p: (isinstance(p,ParseResults) and ''.join([ unichr(c) for c in range(ord(p[0]),ord(p[1])+1) ]) or p) + +def srange(s): + r"""Helper to easily define string ranges for use in Word construction. Borrows + syntax from regexp '[]' string range definitions:: + srange("[0-9]") -> "0123456789" + srange("[a-z]") -> "abcdefghijklmnopqrstuvwxyz" + srange("[a-z$_]") -> "abcdefghijklmnopqrstuvwxyz$_" + The input string must be enclosed in []'s, and the returned string is the expanded + character set joined into a single string. + The values enclosed in the []'s may be:: + a single character + an escaped character with a leading backslash (such as \- or \]) + an escaped hex character with a leading '\x' (\x21, which is a '!' character) + (\0x## is also supported for backwards compatibility) + an escaped octal character with a leading '\0' (\041, which is a '!' character) + a range of any of the above, separated by a dash ('a-z', etc.) + any combination of the above ('aeiouy', 'a-zA-Z0-9_$', etc.) + """ + try: + return "".join([_expanded(part) for part in _reBracketExpr.parseString(s).body]) + except: + return "" + +def matchOnlyAtCol(n): + """Helper method for defining parse actions that require matching at a specific + column in the input text. + """ + def verifyCol(strg,locn,toks): + if col(locn,strg) != n: + raise ParseException(strg,locn,"matched token not at column %d" % n) + return verifyCol + +def replaceWith(replStr): + """Helper method for common parse actions that simply return a literal value. Especially + useful when used with C{transformString()}. + """ + def _replFunc(*args): + return [replStr] + return _replFunc + +def removeQuotes(s,l,t): + """Helper parse action for removing quotation marks from parsed quoted strings. + To use, add this parse action to quoted string using:: + quotedString.setParseAction( removeQuotes ) + """ + return t[0][1:-1] + +def upcaseTokens(s,l,t): + """Helper parse action to convert tokens to upper case.""" + return [ tt.upper() for tt in map(_ustr,t) ] + +def downcaseTokens(s,l,t): + """Helper parse action to convert tokens to lower case.""" + return [ tt.lower() for tt in map(_ustr,t) ] + +def keepOriginalText(s,startLoc,t): + """DEPRECATED - use new helper method C{originalTextFor}. + Helper parse action to preserve original parsed text, + overriding any nested parse actions.""" + try: + endloc = getTokensEndLoc() + except ParseException: + raise ParseFatalException("incorrect usage of keepOriginalText - may only be called as a parse action") + del t[:] + t += ParseResults(s[startLoc:endloc]) + return t + +def getTokensEndLoc(): + """Method to be called from within a parse action to determine the end + location of the parsed tokens.""" + import inspect + fstack = inspect.stack() + try: + # search up the stack (through intervening argument normalizers) for correct calling routine + for f in fstack[2:]: + if f[3] == "_parseNoCache": + endloc = f[0].f_locals["loc"] + return endloc + else: + raise ParseFatalException("incorrect usage of getTokensEndLoc - may only be called from within a parse action") + finally: + del fstack + +def _makeTags(tagStr, xml): + """Internal helper to construct opening and closing tag expressions, given a tag name""" + if isinstance(tagStr,basestring): + resname = tagStr + tagStr = Keyword(tagStr, caseless=not xml) + else: + resname = tagStr.name + + tagAttrName = Word(alphas,alphanums+"_-:") + if (xml): + tagAttrValue = dblQuotedString.copy().setParseAction( removeQuotes ) + openTag = Suppress("<") + tagStr("tag") + \ + Dict(ZeroOrMore(Group( tagAttrName + Suppress("=") + tagAttrValue ))) + \ + Optional("/",default=[False]).setResultsName("empty").setParseAction(lambda s,l,t:t[0]=='/') + Suppress(">") + else: + printablesLessRAbrack = "".join( [ c for c in printables if c not in ">" ] ) + tagAttrValue = quotedString.copy().setParseAction( removeQuotes ) | Word(printablesLessRAbrack) + openTag = Suppress("<") + tagStr("tag") + \ + Dict(ZeroOrMore(Group( tagAttrName.setParseAction(downcaseTokens) + \ + Optional( Suppress("=") + tagAttrValue ) ))) + \ + Optional("/",default=[False]).setResultsName("empty").setParseAction(lambda s,l,t:t[0]=='/') + Suppress(">") + closeTag = Combine(_L("") + + openTag = openTag.setResultsName("start"+"".join(resname.replace(":"," ").title().split())).setName("<%s>" % tagStr) + closeTag = closeTag.setResultsName("end"+"".join(resname.replace(":"," ").title().split())).setName("" % tagStr) + openTag.tag = resname + closeTag.tag = resname + return openTag, closeTag + +def makeHTMLTags(tagStr): + """Helper to construct opening and closing tag expressions for HTML, given a tag name""" + return _makeTags( tagStr, False ) + +def makeXMLTags(tagStr): + """Helper to construct opening and closing tag expressions for XML, given a tag name""" + return _makeTags( tagStr, True ) + +def withAttribute(*args,**attrDict): + """Helper to create a validating parse action to be used with start tags created + with C{makeXMLTags} or C{makeHTMLTags}. Use C{withAttribute} to qualify a starting tag + with a required attribute value, to avoid false matches on common tags such as + C{} or C{
}. + + Call C{withAttribute} with a series of attribute names and values. Specify the list + of filter attributes names and values as: + - keyword arguments, as in C{(align="right")}, or + - as an explicit dict with C{**} operator, when an attribute name is also a Python + reserved word, as in C{**{"class":"Customer", "align":"right"}} + - a list of name-value tuples, as in ( ("ns1:class", "Customer"), ("ns2:align","right") ) + For attribute names with a namespace prefix, you must use the second form. Attribute + names are matched insensitive to upper/lower case. + + To verify that the attribute exists, but without specifying a value, pass + C{withAttribute.ANY_VALUE} as the value. + """ + if args: + attrs = args[:] + else: + attrs = attrDict.items() + attrs = [(k,v) for k,v in attrs] + def pa(s,l,tokens): + for attrName,attrValue in attrs: + if attrName not in tokens: + raise ParseException(s,l,"no matching attribute " + attrName) + if attrValue != withAttribute.ANY_VALUE and tokens[attrName] != attrValue: + raise ParseException(s,l,"attribute '%s' has value '%s', must be '%s'" % + (attrName, tokens[attrName], attrValue)) + return pa +withAttribute.ANY_VALUE = object() + +opAssoc = _Constants() +opAssoc.LEFT = object() +opAssoc.RIGHT = object() + +def operatorPrecedence( baseExpr, opList ): + """Helper method for constructing grammars of expressions made up of + operators working in a precedence hierarchy. Operators may be unary or + binary, left- or right-associative. Parse actions can also be attached + to operator expressions. + + Parameters: + - baseExpr - expression representing the most basic element for the nested + - opList - list of tuples, one for each operator precedence level in the + expression grammar; each tuple is of the form + (opExpr, numTerms, rightLeftAssoc, parseAction), where: + - opExpr is the pyparsing expression for the operator; + may also be a string, which will be converted to a Literal; + if numTerms is 3, opExpr is a tuple of two expressions, for the + two operators separating the 3 terms + - numTerms is the number of terms for this operator (must + be 1, 2, or 3) + - rightLeftAssoc is the indicator whether the operator is + right or left associative, using the pyparsing-defined + constants opAssoc.RIGHT and opAssoc.LEFT. + - parseAction is the parse action to be associated with + expressions matching this operator expression (the + parse action tuple member may be omitted) + """ + ret = Forward() + lastExpr = baseExpr | ( Suppress('(') + ret + Suppress(')') ) + for i,operDef in enumerate(opList): + opExpr,arity,rightLeftAssoc,pa = (operDef + (None,))[:4] + if arity == 3: + if opExpr is None or len(opExpr) != 2: + raise ValueError("if numterms=3, opExpr must be a tuple or list of two expressions") + opExpr1, opExpr2 = opExpr + thisExpr = Forward()#.setName("expr%d" % i) + if rightLeftAssoc == opAssoc.LEFT: + if arity == 1: + matchExpr = FollowedBy(lastExpr + opExpr) + Group( lastExpr + OneOrMore( opExpr ) ) + elif arity == 2: + if opExpr is not None: + matchExpr = FollowedBy(lastExpr + opExpr + lastExpr) + Group( lastExpr + OneOrMore( opExpr + lastExpr ) ) + else: + matchExpr = FollowedBy(lastExpr+lastExpr) + Group( lastExpr + OneOrMore(lastExpr) ) + elif arity == 3: + matchExpr = FollowedBy(lastExpr + opExpr1 + lastExpr + opExpr2 + lastExpr) + \ + Group( lastExpr + opExpr1 + lastExpr + opExpr2 + lastExpr ) + else: + raise ValueError("operator must be unary (1), binary (2), or ternary (3)") + elif rightLeftAssoc == opAssoc.RIGHT: + if arity == 1: + # try to avoid LR with this extra test + if not isinstance(opExpr, Optional): + opExpr = Optional(opExpr) + matchExpr = FollowedBy(opExpr.expr + thisExpr) + Group( opExpr + thisExpr ) + elif arity == 2: + if opExpr is not None: + matchExpr = FollowedBy(lastExpr + opExpr + thisExpr) + Group( lastExpr + OneOrMore( opExpr + thisExpr ) ) + else: + matchExpr = FollowedBy(lastExpr + thisExpr) + Group( lastExpr + OneOrMore( thisExpr ) ) + elif arity == 3: + matchExpr = FollowedBy(lastExpr + opExpr1 + thisExpr + opExpr2 + thisExpr) + \ + Group( lastExpr + opExpr1 + thisExpr + opExpr2 + thisExpr ) + else: + raise ValueError("operator must be unary (1), binary (2), or ternary (3)") + else: + raise ValueError("operator must indicate right or left associativity") + if pa: + matchExpr.setParseAction( pa ) + thisExpr << ( matchExpr | lastExpr ) + lastExpr = thisExpr + ret << lastExpr + return ret + +dblQuotedString = Regex(r'"(?:[^"\n\r\\]|(?:"")|(?:\\x[0-9a-fA-F]+)|(?:\\.))*"').setName("string enclosed in double quotes") +sglQuotedString = Regex(r"'(?:[^'\n\r\\]|(?:'')|(?:\\x[0-9a-fA-F]+)|(?:\\.))*'").setName("string enclosed in single quotes") +quotedString = Regex(r'''(?:"(?:[^"\n\r\\]|(?:"")|(?:\\x[0-9a-fA-F]+)|(?:\\.))*")|(?:'(?:[^'\n\r\\]|(?:'')|(?:\\x[0-9a-fA-F]+)|(?:\\.))*')''').setName("quotedString using single or double quotes") +unicodeString = Combine(_L('u') + quotedString.copy()) + +def nestedExpr(opener="(", closer=")", content=None, ignoreExpr=quotedString.copy()): + """Helper method for defining nested lists enclosed in opening and closing + delimiters ("(" and ")" are the default). + + Parameters: + - opener - opening character for a nested list (default="("); can also be a pyparsing expression + - closer - closing character for a nested list (default=")"); can also be a pyparsing expression + - content - expression for items within the nested lists (default=None) + - ignoreExpr - expression for ignoring opening and closing delimiters (default=quotedString) + + If an expression is not provided for the content argument, the nested + expression will capture all whitespace-delimited content between delimiters + as a list of separate values. + + Use the C{ignoreExpr} argument to define expressions that may contain + opening or closing characters that should not be treated as opening + or closing characters for nesting, such as quotedString or a comment + expression. Specify multiple expressions using an C{L{Or}} or C{L{MatchFirst}}. + The default is L{quotedString}, but if no expressions are to be ignored, + then pass C{None} for this argument. + """ + if opener == closer: + raise ValueError("opening and closing strings cannot be the same") + if content is None: + if isinstance(opener,basestring) and isinstance(closer,basestring): + if len(opener) == 1 and len(closer)==1: + if ignoreExpr is not None: + content = (Combine(OneOrMore(~ignoreExpr + + CharsNotIn(opener+closer+ParserElement.DEFAULT_WHITE_CHARS,exact=1)) + ).setParseAction(lambda t:t[0].strip())) + else: + content = (empty.copy()+CharsNotIn(opener+closer+ParserElement.DEFAULT_WHITE_CHARS + ).setParseAction(lambda t:t[0].strip())) + else: + if ignoreExpr is not None: + content = (Combine(OneOrMore(~ignoreExpr + + ~Literal(opener) + ~Literal(closer) + + CharsNotIn(ParserElement.DEFAULT_WHITE_CHARS,exact=1)) + ).setParseAction(lambda t:t[0].strip())) + else: + content = (Combine(OneOrMore(~Literal(opener) + ~Literal(closer) + + CharsNotIn(ParserElement.DEFAULT_WHITE_CHARS,exact=1)) + ).setParseAction(lambda t:t[0].strip())) + else: + raise ValueError("opening and closing arguments must be strings if no content expression is given") + ret = Forward() + if ignoreExpr is not None: + ret << Group( Suppress(opener) + ZeroOrMore( ignoreExpr | ret | content ) + Suppress(closer) ) + else: + ret << Group( Suppress(opener) + ZeroOrMore( ret | content ) + Suppress(closer) ) + return ret + +def indentedBlock(blockStatementExpr, indentStack, indent=True): + """Helper method for defining space-delimited indentation blocks, such as + those used to define block statements in Python source code. + + Parameters: + - blockStatementExpr - expression defining syntax of statement that + is repeated within the indented block + - indentStack - list created by caller to manage indentation stack + (multiple statementWithIndentedBlock expressions within a single grammar + should share a common indentStack) + - indent - boolean indicating whether block must be indented beyond the + the current level; set to False for block of left-most statements + (default=True) + + A valid block must contain at least one C{blockStatement}. + """ + def checkPeerIndent(s,l,t): + if l >= len(s): return + curCol = col(l,s) + if curCol != indentStack[-1]: + if curCol > indentStack[-1]: + raise ParseFatalException(s,l,"illegal nesting") + raise ParseException(s,l,"not a peer entry") + + def checkSubIndent(s,l,t): + curCol = col(l,s) + if curCol > indentStack[-1]: + indentStack.append( curCol ) + else: + raise ParseException(s,l,"not a subentry") + + def checkUnindent(s,l,t): + if l >= len(s): return + curCol = col(l,s) + if not(indentStack and curCol < indentStack[-1] and curCol <= indentStack[-2]): + raise ParseException(s,l,"not an unindent") + indentStack.pop() + + NL = OneOrMore(LineEnd().setWhitespaceChars("\t ").suppress()) + INDENT = Empty() + Empty().setParseAction(checkSubIndent) + PEER = Empty().setParseAction(checkPeerIndent) + UNDENT = Empty().setParseAction(checkUnindent) + if indent: + smExpr = Group( Optional(NL) + + #~ FollowedBy(blockStatementExpr) + + INDENT + (OneOrMore( PEER + Group(blockStatementExpr) + Optional(NL) )) + UNDENT) + else: + smExpr = Group( Optional(NL) + + (OneOrMore( PEER + Group(blockStatementExpr) + Optional(NL) )) ) + blockStatementExpr.ignore(_bslash + LineEnd()) + return smExpr + +alphas8bit = srange(r"[\0xc0-\0xd6\0xd8-\0xf6\0xf8-\0xff]") +punc8bit = srange(r"[\0xa1-\0xbf\0xd7\0xf7]") + +anyOpenTag,anyCloseTag = makeHTMLTags(Word(alphas,alphanums+"_:")) +commonHTMLEntity = Combine(_L("&") + oneOf("gt lt amp nbsp quot").setResultsName("entity") +";").streamline() +_htmlEntityMap = dict(zip("gt lt amp nbsp quot".split(),'><& "')) +replaceHTMLEntity = lambda t : t.entity in _htmlEntityMap and _htmlEntityMap[t.entity] or None + +# it's easy to get these comment structures wrong - they're very common, so may as well make them available +cStyleComment = Regex(r"/\*(?:[^*]*\*+)+?/").setName("C style comment") + +htmlComment = Regex(r"") +restOfLine = Regex(r".*").leaveWhitespace() +dblSlashComment = Regex(r"\/\/(\\\n|.)*").setName("// comment") +cppStyleComment = Regex(r"/(?:\*(?:[^*]*\*+)+?/|/[^\n]*(?:\n[^\n]*)*?(?:(?" + str(tokenlist)) + print ("tokens = " + str(tokens)) + print ("tokens.columns = " + str(tokens.columns)) + print ("tokens.tables = " + str(tokens.tables)) + print (tokens.asXML("SQL",True)) + except ParseBaseException: + err = sys.exc_info()[1] + print (teststring + "->") + print (err.line) + print (" "*(err.column-1) + "^") + print (err) + print() + + selectToken = CaselessLiteral( "select" ) + fromToken = CaselessLiteral( "from" ) + + ident = Word( alphas, alphanums + "_$" ) + columnName = delimitedList( ident, ".", combine=True ).setParseAction( upcaseTokens ) + columnNameList = Group( delimitedList( columnName ) )#.setName("columns") + tableName = delimitedList( ident, ".", combine=True ).setParseAction( upcaseTokens ) + tableNameList = Group( delimitedList( tableName ) )#.setName("tables") + simpleSQL = ( selectToken + \ + ( '*' | columnNameList ).setResultsName( "columns" ) + \ + fromToken + \ + tableNameList.setResultsName( "tables" ) ) + + test( "SELECT * from XYZZY, ABC" ) + test( "select * from SYS.XYZZY" ) + test( "Select A from Sys.dual" ) + test( "Select AA,BB,CC from Sys.dual" ) + test( "Select A, B, C from Sys.dual" ) + test( "Select A, B, C from Sys.dual" ) + test( "Xelect A, B, C from Sys.dual" ) + test( "Select A, B, C frox Sys.dual" ) + test( "Select" ) + test( "Select ^^^ frox Sys.dual" ) + test( "Select A, B, C from Sys.dual, Table2 " ) diff --git a/libpathod/handlers.py b/libpathod/handlers.py new file mode 100644 index 00000000..a697304c --- /dev/null +++ b/libpathod/handlers.py @@ -0,0 +1,45 @@ +import urllib +import tornado.web +import rparse + +class _Page(tornado.web.RequestHandler): + def render(self, name, **kwargs): + b = self.application.templates.load(name).generate(**kwargs) + self.write(b) + + +class Index(_Page): + def get(self): + self.render("index.html", section="main") + + +class Preview(_Page): + def get(self): + self.render("index.html", section="main") + + +class Help(_Page): + def get(self): + self.render("help.html", section="help") + + +class Log(_Page): + def get(self): + self.render("log.html", section="log") + + +class Pathod(object): + anchor = "/p/" + def __init__(self, application, request, **settings): + self.application, self.request, self.settings = application, request, settings + spec = urllib.unquote(self.request.uri)[len(self.anchor):] + try: + self.response = rparse.parse(self.settings, spec) + except rparse.ParseException, v: + self.response = rparse.StubResponse( + 800, + "Error parsing response spec:" + str(v) + ) + + def _execute(self, transforms, *args, **kwargs): + self.response.render(self.request) diff --git a/libpathod/http.py b/libpathod/http.py new file mode 100644 index 00000000..9f3f7e15 --- /dev/null +++ b/libpathod/http.py @@ -0,0 +1,103 @@ + +CONTINUE = 100 +SWITCHING = 101 +OK = 200 +CREATED = 201 +ACCEPTED = 202 +NON_AUTHORITATIVE_INFORMATION = 203 +NO_CONTENT = 204 +RESET_CONTENT = 205 +PARTIAL_CONTENT = 206 +MULTI_STATUS = 207 + +MULTIPLE_CHOICE = 300 +MOVED_PERMANENTLY = 301 +FOUND = 302 +SEE_OTHER = 303 +NOT_MODIFIED = 304 +USE_PROXY = 305 +TEMPORARY_REDIRECT = 307 + +BAD_REQUEST = 400 +UNAUTHORIZED = 401 +PAYMENT_REQUIRED = 402 +FORBIDDEN = 403 +NOT_FOUND = 404 +NOT_ALLOWED = 405 +NOT_ACCEPTABLE = 406 +PROXY_AUTH_REQUIRED = 407 +REQUEST_TIMEOUT = 408 +CONFLICT = 409 +GONE = 410 +LENGTH_REQUIRED = 411 +PRECONDITION_FAILED = 412 +REQUEST_ENTITY_TOO_LARGE = 413 +REQUEST_URI_TOO_LONG = 414 +UNSUPPORTED_MEDIA_TYPE = 415 +REQUESTED_RANGE_NOT_SATISFIABLE = 416 +EXPECTATION_FAILED = 417 + +INTERNAL_SERVER_ERROR = 500 +NOT_IMPLEMENTED = 501 +BAD_GATEWAY = 502 +SERVICE_UNAVAILABLE = 503 +GATEWAY_TIMEOUT = 504 +HTTP_VERSION_NOT_SUPPORTED = 505 +INSUFFICIENT_STORAGE_SPACE = 507 +NOT_EXTENDED = 510 + +RESPONSES = { + # 100 + CONTINUE: "Continue", + SWITCHING: "Switching Protocols", + + # 200 + OK: "OK", + CREATED: "Created", + ACCEPTED: "Accepted", + NON_AUTHORITATIVE_INFORMATION: "Non-Authoritative Information", + NO_CONTENT: "No Content", + RESET_CONTENT: "Reset Content.", + PARTIAL_CONTENT: "Partial Content", + MULTI_STATUS: "Multi-Status", + + # 300 + MULTIPLE_CHOICE: "Multiple Choices", + MOVED_PERMANENTLY: "Moved Permanently", + FOUND: "Found", + SEE_OTHER: "See Other", + NOT_MODIFIED: "Not Modified", + USE_PROXY: "Use Proxy", + # 306 not defined?? + TEMPORARY_REDIRECT: "Temporary Redirect", + + # 400 + BAD_REQUEST: "Bad Request", + UNAUTHORIZED: "Unauthorized", + PAYMENT_REQUIRED: "Payment Required", + FORBIDDEN: "Forbidden", + NOT_FOUND: "Not Found", + NOT_ALLOWED: "Method Not Allowed", + NOT_ACCEPTABLE: "Not Acceptable", + PROXY_AUTH_REQUIRED: "Proxy Authentication Required", + REQUEST_TIMEOUT: "Request Time-out", + CONFLICT: "Conflict", + GONE: "Gone", + LENGTH_REQUIRED: "Length Required", + PRECONDITION_FAILED: "Precondition Failed", + REQUEST_ENTITY_TOO_LARGE: "Request Entity Too Large", + REQUEST_URI_TOO_LONG: "Request-URI Too Long", + UNSUPPORTED_MEDIA_TYPE: "Unsupported Media Type", + REQUESTED_RANGE_NOT_SATISFIABLE: "Requested Range not satisfiable", + EXPECTATION_FAILED: "Expectation Failed", + + # 500 + INTERNAL_SERVER_ERROR: "Internal Server Error", + NOT_IMPLEMENTED: "Not Implemented", + BAD_GATEWAY: "Bad Gateway", + SERVICE_UNAVAILABLE: "Service Unavailable", + GATEWAY_TIMEOUT: "Gateway Time-out", + HTTP_VERSION_NOT_SUPPORTED: "HTTP Version not supported", + INSUFFICIENT_STORAGE_SPACE: "Insufficient Storage Space", + NOT_EXTENDED: "Not Extended" +} diff --git a/libpathod/rparse.py b/libpathod/rparse.py new file mode 100644 index 00000000..f3985d04 --- /dev/null +++ b/libpathod/rparse.py @@ -0,0 +1,429 @@ +import operator, string, random, sys, time +import contrib.pyparsing as pp +import http, utils +import tornado.ioloop + +TESTING = False + +class ParseException(Exception): pass + + +DATATYPES = dict( + ascii_letters = string.ascii_letters, + ascii_lowercase = string.ascii_lowercase, + ascii_uppercase = string.ascii_uppercase, + digits = string.digits, + hexdigits = string.hexdigits, + letters = string.letters, + lowercase = string.lowercase, + octdigits = string.octdigits, + printable = string.printable, + punctuation = string.punctuation, + uppercase = string.uppercase, + whitespace = string.whitespace, + ascii = string.printable, + bytes = "".join(chr(i) for i in range(256)) +) + + +v_integer = pp.Regex(r"[+-]?\d+")\ + .setName("integer")\ + .setParseAction(lambda toks: int(toks[0])) + +v_string = pp.MatchFirst( + [ + pp.QuotedString("\"", escChar="\\", unquoteResults=True), + pp.QuotedString("'", escChar="\\", unquoteResults=True), + ] +) + +v_literal = pp.MatchFirst( + [ + v_string, + pp.Word("".join(i for i in pp.printables if i not in ",:")) + ] +) + + +class LiteralGenerator: + def __init__(self, s): + self.s = s + + def __eq__(self, other): + return self[:] == other + + def __len__(self): + return len(self.s) + + def __getitem__(self, x): + return self.s.__getitem__(x) + + def __getslice__(self, a, b): + return self.s.__getslice__(a, b) + + +class RandomGenerator: + def __init__(self, chars, length): + self.chars = chars + self.length = length + + def __len__(self): + return self.length + + def __getitem__(self, x): + return random.choice(self.chars) + + def __getslice__(self, a, b): + b = min(b, self.length) + return "".join(random.choice(self.chars) for x in range(a, b)) + + +class FileGenerator: + def __init__(self, path): + self.path = path + + +class ValueLiteral: + def __init__(self, val): + self.val = val + + def get_generator(self, settings): + return LiteralGenerator(self.val) + + @classmethod + def expr(klass): + e = v_literal.copy() + return e.setParseAction(lambda x: klass(*x)) + + def __str__(self): + return self.val + + +class ValueGenerate: + UNITS = dict( + b = 1024**0, + k = 1024**1, + m = 1024**2, + g = 1024**3, + t = 1024**4, + ) + 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 * self.UNITS[self.unit] + + def get_generator(self, settings): + return RandomGenerator(DATATYPES[self.datatype], self.bytes()) + + @classmethod + def expr(klass): + e = pp.Literal("!").suppress() + v_integer + + u = reduce(operator.or_, [pp.Literal(i) for i in klass.UNITS.keys()]) + e = e + pp.Optional(u, default=None) + + s = pp.Literal(":").suppress() + s += reduce(operator.or_, [pp.Literal(i) for i in DATATYPES.keys()]) + e += pp.Optional(s, default="bytes") + return e.setParseAction(lambda x: klass(*x)) + + def __str__(self): + return "!%s%s:%s"%(self.usize, self.unit, self.datatype) + + +class ValueFile: + def __init__(self, path): + self.path = path + + @classmethod + def expr(klass): + e = pp.Literal("<").suppress() + e = e + v_literal + return e.setParseAction(lambda x: klass(*x)) + + def get_generator(self, settings): + raise NotImplementedError + + def __str__(self): + return "<%s"%(self.path) + + +Value = pp.MatchFirst( + [ + ValueGenerate.expr(), + ValueFile.expr(), + ValueLiteral.expr() + ] +) + + +class Body: + def __init__(self, value): + self.value = value + + def mod_response(self, settings, r): + r.body = self.value.get_generator(settings) + + @classmethod + def expr(klass): + e = pp.Literal("b:").suppress() + e = e + Value + return e.setParseAction(lambda x: klass(*x)) + + +class _Pause: + def __init__(self, value): + self.value = value + + @classmethod + def expr(klass): + e = pp.Literal("p%s:"%klass.sub).suppress() + e = e + pp.MatchFirst( + [ + v_integer, + pp.Literal("forever") + ] + ) + return e.setParseAction(lambda x: klass(*x)) + + +class PauseBefore(_Pause): + sub = "b" + def mod_response(self, settings, r): + r.pauses.append((0, self.value)) + + +class PauseAfter(_Pause): + sub = "a" + def mod_response(self, settings, r): + r.pauses.append((sys.maxint, self.value)) + + +class PauseRandom(_Pause): + sub = "r" + def mod_response(self, settings, r): + r.pauses.append(("random", self.value)) + + + +class _Disconnect: + def __init__(self, value): + self.value = value + + @classmethod + def expr(klass): + e = pp.Literal("d%s"%klass.sub) + return e.setParseAction(klass) + + +class DisconnectBefore(_Disconnect): + sub = "b" + def mod_response(self, settings, r): + r.pauses.append((0, self.value)) + + +class DisconnectRandom(_Disconnect): + sub = "r" + def mod_response(self, settings, r): + r.pauses.append(("random", self.value)) + + +class Header: + def __init__(self, key, value): + self.key, self.value = key, value + + def mod_response(self, settings, r): + r.headers.append( + ( + self.key.get_generator(settings), + self.value.get_generator(settings) + ) + ) + + @classmethod + def expr(klass): + e = pp.Literal("h:").suppress() + e += Value + e += pp.Literal(":").suppress() + e += Value + return e.setParseAction(lambda x: klass(*x)) + + +class Code: + def __init__(self, code, msg=None): + self.code, self.msg = code, msg + if msg is None: + self.msg = ValueLiteral(http.RESPONSES.get(self.code, "Unknown code")) + + def mod_response(self, settings, r): + r.code = self.code + r.msg = self.msg.get_generator(settings) + + @classmethod + def expr(klass): + e = v_integer + e = e + pp.Optional( + pp.Literal(":").suppress() + Value + ) + return e.setParseAction(lambda x: klass(*x)) + + +BLOCKSIZE = 1024 +class Response: + comps = [ + Body, + Header, + PauseBefore, + PauseAfter, + PauseRandom, + DisconnectBefore, + DisconnectRandom, + ] + version = "HTTP/1.1" + code = 200 + msg = LiteralGenerator(http.RESPONSES[code]) + body = LiteralGenerator("OK") + def __init__(self, settings, tokens): + self.tokens = tokens + self.headers = [] + self.pauses = [] + for i in tokens: + i.mod_response(settings, self) + if self.body and not self.get_header("Content-Length"): + self.headers.append( + ( + LiteralGenerator("Content-Length"), + LiteralGenerator(str(len(self.body))), + ) + ) + + def get_header(self, hdr): + for k, v in self.headers: + if k[:len(hdr)].lower() == hdr: + return v + return None + + @classmethod + def expr(klass): + parts = [i.expr() for i in klass.comps] + atom = pp.MatchFirst(parts) + resp = pp.And( + [ + Code.expr(), + pp.ZeroOrMore(pp.Literal(",").suppress() + atom) + ] + ) + return resp + + def length(self): + l = len("%s %s "%(self.version, self.code)) + l += len(self.msg) + l += 2 + for i in self.headers: + l += len(i[0]) + len(i[1]) + l += 4 + l += 2 + l += len(self.body) + return l + + def ready_randoms(self, l, lst): + ret = [] + for k, v in lst: + if k == "random": + ret.append((random.randrange(l), v)) + else: + ret.append((k, v)) + ret.sort() + return ret + + def add_timeout(self, s, callback): + if TESTING: + callback() + else: + tornado.ioloop.IOLoop.instance().add_timeout(time.time() + s, callback) + + def write_values(self, fp, vals, pauses, disconnect, sofar=0, skip=0, blocksize=BLOCKSIZE): + if disconnect == "before": + fp.finish() + return + while vals: + part = vals.pop() + for i in range(skip, len(part), blocksize): + d = part[i:i+blocksize] + if pauses and pauses[-1][0] < (sofar + len(d)): + p = pauses.pop() + offset = p[0]-sofar + vals.append(part) + def pause_callback(): + self.write_values( + fp, vals, pauses, disconnect, + sofar=sofar+offset, + skip=i+offset, + blocksize=blocksize + ) + def flushed_callback(): + # Data has been flushed, set the timeout. + self.add_timeout(p[1], pause_callback) + fp.write(d[:offset], callback=flushed_callback) + return + fp.write(d) + sofar += len(d) + skip = 0 + fp.finish() + + def render(self, fp): + hdrs = [] + for k, v in self.headers: + hdrs.extend([ + k, + ": ", + v, + "\r\n", + ]) + vals = [ + "%s %s "%(self.version, self.code), + self.msg, + "\r\n", + ] + vals.extend(hdrs) + vals.extend([ + "\r\n", + self.body + ]) + vals.reverse() + pauses = self.ready_randoms(self.length(), self.pauses) + pauses.reverse() + return self.write_values(fp, vals, pauses, None) + + def __str__(self): + parts = [ + "%s %s"%(self.code, self.msg[:]) + ] + return "\n".join(parts) + + +class StubResponse: + def __init__(self, code, body): + self.code = code + self.msg = LiteralGenerator(http.RESPONSES.get(code, "Unknown error")) + self.body = LiteralGenerator(body) + self.headers = [ + ( + LiteralGenerator("Content-Type"), + LiteralGenerator("text/plain") + ), + ( + LiteralGenerator("Content-Length"), + LiteralGenerator(str(len(self.body))) + ) + ] + + +def parse(settings, s): + try: + return Response(settings, Response.expr().parseString(s, parseAll=True)) + except pp.ParseException, v: + raise ParseException(v) diff --git a/libpathod/static/bootstrap.min.css b/libpathod/static/bootstrap.min.css new file mode 100644 index 00000000..f8b9ab37 --- /dev/null +++ b/libpathod/static/bootstrap.min.css @@ -0,0 +1,543 @@ +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;} +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{max-width:100%;height:auto;border:0;-ms-interpolation-mode:bicubic;} +button,input,select,textarea{margin:0;font-size:100%;vertical-align:baseline;*vertical-align:middle;} +button,input{*overflow:visible;line-height:normal;} +button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0;} +button,input[type="button"],input[type="reset"],input[type="submit"]{cursor:pointer;-webkit-appearance:button;} +input[type="search"]{-webkit-appearance:textfield;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;} +input[type="search"]::-webkit-search-decoration,input[type="search"]::-webkit-search-cancel-button{-webkit-appearance:none;} +textarea{overflow:auto;vertical-align:top;} +body{margin:0;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:13px;line-height:18px;color:#555555;background-color:#ffffff;} +.container{width:940px;margin-left:auto;margin-right:auto;*zoom:1;}.container:before,.container:after{display:table;content:"";} +.container:after{clear:both;} +.fluid-container{position:relative;min-width:940px;padding-left:20px;padding-right:20px;*zoom:1;}.fluid-container:before,.fluid-container:after{display:table;content:"";} +.fluid-container:after{clear:both;} +.fluid-sidebar{width:220px;margin:0 20px 18px;} +.sidebar-left{padding-left:260px;} +.sidebar-right{padding-right:260px;} +.sidebar-left .fluid-sidebar{float:left;margin-left:-240px;} +.sidebar-right .fluid-sidebar{float:right;margin-right:-240px;} +.fluid-content{float:left;width:100%;} +a{color:#0088cc;text-decoration:none;}a:hover{color:#005580;text-decoration:underline;} +.row{margin-left:-20px;*zoom:1;}.row:before,.row:after{display:table;content:"";} +.row:after{clear:both;} +[class*="span"]{float:left;margin-left:20px;} +.span1{width:60px;} +.span2{width:140px;} +.span3{width:220px;} +.span4{width:300px;} +.span5{width:380px;} +.span6{width:460px;} +.span7{width:540px;} +.span8{width:620px;} +.span9{width:700px;} +.span10{width:780px;} +.span11{width:860px;} +.span12{width:940px;} +.offset1{margin-left:100px;} +.offset2{margin-left:180px;} +.offset3{margin-left:260px;} +.offset4{margin-left:340px;} +.offset5{margin-left:420px;} +.offset6{margin-left:500px;} +.offset7{margin-left:580px;} +.offset8{margin-left:660px;} +.offset9{margin-left:740px;} +.offset10{margin-left:820px;} +.offset11{margin-left:900px;} +p{margin:0 0 9px;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:13px;line-height:18px;}p small{font-size:11px;color:#999999;} +h1,h2,h3,h4,h5,h6{margin:0;font-weight:bold;color:#333333;text-rendering:optimizelegibility;}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small{font-weight:normal;color:#999999;} +h1{font-size:30px;line-height:36px;}h1 small{font-size:18px;} +h2{font-size:24px;line-height:36px;}h2 small{font-size:18px;} +h3{line-height:27px;font-size:18px;}h3 small{font-size:14px;} +h4,h5,h6{line-height:18px;} +h4{font-size:14px;}h4 small{font-size:12px;} +h5{font-size:12px;} +h6{font-size:11px;color:#999999;text-transform:uppercase;} +ul,ol{padding:0;margin:0 0 9px 25px;} +ul ul,ul ol,ol ol,ol ul{margin-bottom:0;} +ul{list-style:disc;} +ol{list-style:decimal;} +li{line-height:18px;} +ul.unstyled{margin-left:0;list-style:none;} +dl{margin-bottom:18px;} +dt,dd{line-height:18px;} +dt{font-weight:bold;} +dd{margin-left:9px;} +hr{margin:18px 0;border:0;border-top:1px solid #e5e5e5;border-bottom:1px solid #ffffff;} +strong{font-weight:bold;} +em{font-style:italic;} +.muted{color:#999999;} +abbr{font-size:90%;text-transform:uppercase;border-bottom:1px dotted #ddd;cursor:help;} +blockquote{padding:0 0 0 15px;margin:0 0 18px;border-left:5px solid #eeeeee;}blockquote p{margin-bottom:0;font-size:16px;font-weight:300;line-height:22.5px;} +blockquote small{display:block;line-height:18px;color:#999999;}blockquote small:before{content:'\2014 \00A0';} +blockquote.pull-right{float:right;padding-left:0;padding-right:15px;border-left:0;border-right:5px solid #eeeeee;}blockquote.pull-right p,blockquote.pull-right small{text-align:right;} +q:before,q:after,blockquote:before,blockquote:after{content:"";} +address{display:block;margin-bottom:18px;line-height:18px;font-style:normal;} +code,pre{padding:0 3px 2px;font-family:Menlo,Monaco,"Courier New",monospace;font-size:12px;color:#333333;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;} +code{padding:3px 4px;background-color:#fee9cc;color:#d14;background-color:#f7f7f9;border:1px solid #e1e1e8;} +pre{display:block;padding:8.5px;margin:0 0 9px;font-size:12px;line-height:18px;background-color:#f5f5f5;border:1px solid #ccc;border:1px solid rgba(0, 0, 0, 0.15);-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;white-space:pre;white-space:pre-wrap;word-break:break-all;}pre.prettyprint{margin-bottom:18px;} +pre code{padding:0;background-color:transparent;} +small{font-size:100%;} +cite{font-style:normal;} +form{margin:0 0 18px;} +fieldset{padding:0;margin:0;border:0;} +legend{display:block;width:100%;padding:0;margin-bottom:27px;font-size:19.5px;line-height:36px;color:#333333;border:0;border-bottom:1px solid #eee;-webkit-margin-collapse:separate;} +label,input,button,select,textarea{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:13px;font-weight:normal;line-height:18px;} +label{display:block;margin-bottom:5px;color:#333333;} +input,textarea,select,.uneditable-input{display:block;width:210px;height:18px;padding:4px;margin-bottom:9px;font-size:13px;line-height:18px;color:#555555;border:1px solid #ccc;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;} +input[type=image],input[type=checkbox],input[type=radio]{width:auto;height:auto;padding:0;margin:3px 0;*margin-top:0;line-height:normal;border:none;cursor:pointer;} +input[type=file]{padding:initial;line-height:initial;border:initial;background-color:#ffffff;background-color:initial;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;} +input[type=button],input[type=reset],input[type=submit]{width:auto;height:auto;} +select,input[type=file]{height:27px;*margin-top:4px;line-height:27px;} +select{width:220px;vertical-align:middle;background-color:#ffffff;} +select[multiple],select[size]{height:inherit;} +input[type=image]{-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;} +textarea{height:auto;} +.radio,.checkbox{padding-left:18px;} +.radio input[type=radio],.checkbox input[type=checkbox]{float:left;margin-left:-18px;} +.controls>.radio:first-child,.controls>.checkbox:first-child{padding-top:6px;} +.radio.inline,.checkbox.inline{display:inline-block;margin-bottom:0;} +.radio.inline+.radio.inline,.checkbox.inline+.checkbox.inline{margin-left:10px;} +input,textarea{-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.1);-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.1);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.1);-webkit-transition:border linear 0.2s,box-shadow linear 0.2s;-moz-transition:border linear 0.2s,box-shadow linear 0.2s;-ms-transition:border linear 0.2s,box-shadow linear 0.2s;-o-transition:border linear 0.2s,box-shadow linear 0.2s;transition:border linear 0.2s,box-shadow linear 0.2s;} +input:focus,textarea:focus{border-color:rgba(82, 168, 236, 0.8);-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.1),0 0 8px rgba(82, 168, 236, 0.6);-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.1),0 0 8px rgba(82, 168, 236, 0.6);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.1),0 0 8px rgba(82, 168, 236, 0.6);outline:0;} +input[type=file]:focus,input[type=checkbox]:focus,select:focus{-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;outline:1px dotted #666;} +.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{float:none;margin-left:0;} +input.span1,textarea.span1,.uneditable-input.span1{width:50px;} +input.span2,textarea.span2,.uneditable-input.span2{width:130px;} +input.span3,textarea.span3,.uneditable-input.span3{width:210px;} +input.span4,textarea.span4,.uneditable-input.span4{width:290px;} +input.span5,textarea.span5,.uneditable-input.span5{width:370px;} +input.span6,textarea.span6,.uneditable-input.span6{width:450px;} +input.span7,textarea.span7,.uneditable-input.span7{width:530px;} +input.span8,textarea.span8,.uneditable-input.span8{width:610px;} +input.span9,textarea.span9,.uneditable-input.span9{width:690px;} +input.span10,textarea.span10,.uneditable-input.span10{width:770px;} +input.span11,textarea.span11,.uneditable-input.span11{width:850px;} +input.span12,textarea.span12,.uneditable-input.span12{width:930px;} +select.span1{width:70px;} +select.span2{width:150px;} +select.span3{width:230px;} +select.span4{width:310px;} +select.span5{width:390px;} +select.span6{width:470px;} +select.span7{width:550px;} +select.span8{width:630px;} +select.span9{width:710px;} +select.span10{width:790px;} +select.span11{width:870px;} +select.span12{width:950px;} +input[disabled],select[disabled],textarea[disabled],input[readonly],select[readonly],textarea[readonly]{background-color:#f5f5f5;border-color:#ddd;cursor:not-allowed;} +.control-group.error>label,.control-group.error .help-block,.control-group.error .help-inline{color:#b94a48;} +.control-group.error input,.control-group.error textarea{color:#b94a48;border-color:#ee5f5b;}.control-group.error input:focus,.control-group.error textarea:focus{border-color:#e9322d;-webkit-box-shadow:0 0 6px #f8b9b7;-moz-box-shadow:0 0 6px #f8b9b7;box-shadow:0 0 6px #f8b9b7;} +.control-group.error .input-prepend .add-on,.control-group.error .input-append .add-on{color:#b94a48;background-color:#fce6e6;border-color:#b94a48;} +.control-group.warning>label,.control-group.warning .help-block,.control-group.warning .help-inline{color:#c09853;} +.control-group.warning input,.control-group.warning textarea{color:#c09853;border-color:#ccae64;}.control-group.warning input:focus,.control-group.warning textarea:focus{border-color:#be9a3f;-webkit-box-shadow:0 0 6px #e5d6b1;-moz-box-shadow:0 0 6px #e5d6b1;box-shadow:0 0 6px #e5d6b1;} +.control-group.warning .input-prepend .add-on,.control-group.warning .input-append .add-on{color:#c09853;background-color:#d2b877;border-color:#c09853;} +.control-group.success>label,.control-group.success .help-block,.control-group.success .help-inline{color:#468847;} +.control-group.success input,.control-group.success textarea{color:#468847;border-color:#57a957;}.control-group.success input:focus,.control-group.success textarea:focus{border-color:#458845;-webkit-box-shadow:0 0 6px #9acc9a;-moz-box-shadow:0 0 6px #9acc9a;box-shadow:0 0 6px #9acc9a;} +.control-group.success .input-prepend .add-on,.control-group.success .input-append .add-on{color:#468847;background-color:#bcddbc;border-color:#468847;} +input:focus:required:invalid,textarea:focus:required:invalid,select:focus:required:invalid{color:#b94a48;border-color:#ee5f5b;}input:focus:required:invalid:focus,textarea:focus:required:invalid:focus,select:focus:required: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:17px 20px 18px;margin-top:18px;margin-bottom:18px;background-color:#f5f5f5;border-top:1px solid #ddd;} +.uneditable-input{display:block;background-color:#ffffff;border-color:#eee;-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);cursor:not-allowed;} +:-moz-placeholder{color:#999999;} +::-webkit-input-placeholder{color:#999999;} +.help-block{margin-top:5px;margin-bottom:0;color:#999999;} +.help-inline{*position:relative;*top:-5px;display:inline;padding-left:5px;} +.input-prepend,.input-append{margin-bottom:5px;*zoom:1;}.input-prepend:before,.input-append:before,.input-prepend:after,.input-append:after{display:table;content:"";} +.input-prepend:after,.input-append:after{clear:both;} +.input-prepend input,.input-append input,.input-prepend .uneditable-input,.input-append .uneditable-input{-webkit-border-radius:0 3px 3px 0;-moz-border-radius:0 3px 3px 0;border-radius:0 3px 3px 0;} +.input-prepend .uneditable-input,.input-append .uneditable-input{border-left-color:#ccc;} +.input-prepend .add-on,.input-append .add-on{float:left;display:block;width:auto;min-width:16px;height:18px;margin-right:-1px;padding:4px 4px 4px 5px;font-weight:normal;line-height:18px;color:#999999;text-align:center;text-shadow:0 1px 0 #ffffff;background-color:#f5f5f5;border:1px solid #ccc;-webkit-border-radius:3px 0 0 3px;-moz-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px;} +.input-prepend .active,.input-append .active{background-color:#a9dba9;border-color:#46a546;} +.input-prepend .add-on{*margin-top:1px;} +.input-append input,.input-append .uneditable-input{float:left;-webkit-border-radius:3px 0 0 3px;-moz-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px;} +.input-append .uneditable-input{border-right-color:#ccc;} +.input-append .add-on{margin-right:0;margin-left:-1px;-webkit-border-radius:0 3px 3px 0;-moz-border-radius:0 3px 3px 0;border-radius:0 3px 3px 0;} +.search-query{padding-left:14px;padding-right:14px;margin-bottom:0;-webkit-border-radius:14px;-moz-border-radius:14px;border-radius: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 .uneditable-input,.form-inline .uneditable-input,.form-horizontal .uneditable-input{display:inline-block;margin-bottom:0;} +.form-search label,.form-inline label{display:inline-block;} +.control-group{margin-bottom:9px;} +.form-horizontal .control-group{margin-bottom:18px;} +.form-horizontal .control-group>label{float:left;width:140px;padding-top:5px;text-align:right;} +.form-horizontal .controls{margin-left:160px;} +.form-horizontal .form-actions{padding-left:160px;} +table{max-width:100%;border-collapse:collapse;border-spacing:0;} +.table{width:100%;margin-bottom:18px;}.table th,.table td{padding:8px;line-height:18px;text-align:left;border-top:1px solid #ddd;} +.table th{font-weight:bold;vertical-align:bottom;} +.table td{vertical-align:top;} +.table thead:first-child tr th,.table thead:first-child tr td{border-top:0;} +.table tbody+tbody{border-top:2px solid #ddd;} +.table-condensed th,.table-condensed td{padding:4px 5px;} +.table-bordered{border:1px solid #ddd;border-collapse:separate;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}.table-bordered th+th,.table-bordered td+td,.table-bordered th+td,.table-bordered td+th{border-left:1px solid #ddd;} +.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{-webkit-border-radius:4px 0 0 0;-moz-border-radius:4px 0 0 0;border-radius:4px 0 0 0;} +.table-bordered thead:first-child tr:first-child th:last-child,.table-bordered tbody:first-child tr:first-child td:last-child{-webkit-border-radius:0 4px 0 0;-moz-border-radius:0 4px 0 0;border-radius:0 4px 0 0;} +.table-bordered thead:last-child tr:last-child th:first-child,.table-bordered tbody:last-child tr:last-child td:first-child{-webkit-border-radius:0 0 0 4px;-moz-border-radius:0 0 0 4px;border-radius:0 0 0 4px;} +.table-bordered thead:last-child tr:last-child th:last-child,.table-bordered tbody:last-child tr:last-child td:last-child{-webkit-border-radius:0 0 4px 0;-moz-border-radius:0 0 4px 0;border-radius:0 0 4px 0;} +.table-striped tbody tr:nth-child(odd) td,.table-striped tbody tr:nth-child(odd) th{background-color:#f9f9f9;} +table .span1{float:none;width:44px;margin-left:0;} +table .span2{float:none;width:124px;margin-left:0;} +table .span3{float:none;width:204px;margin-left:0;} +table .span4{float:none;width:284px;margin-left:0;} +table .span5{float:none;width:364px;margin-left:0;} +table .span6{float:none;width:444px;margin-left:0;} +table .span7{float:none;width:524px;margin-left:0;} +table .span8{float:none;width:604px;margin-left:0;} +table .span9{float:none;width:684px;margin-left:0;} +table .span10{float:none;width:764px;margin-left:0;} +table .span11{float:none;width:844px;margin-left:0;} +table .span12{float:none;width:924px;margin-left:0;} +table .header{cursor:pointer;}table .header:after{content:"";float:right;margin-top:7px;border-width:0 4px 4px;border-style:solid;border-color:#000000 transparent;visibility:hidden;} +table .headerSortUp,table .headerSortDown{background-color:#f7f7f9;text-shadow:0 1px 1px rgba(255, 255, 255, 0.75);} +table .header:hover:after{visibility:visible;} +table .headerSortDown:after,table .headerSortDown:hover:after{visibility:visible;filter:alpha(opacity=60);-moz-opacity:0.6;opacity:0.6;} +table .headerSortUp:after{border-bottom:none;border-left:4px solid transparent;border-right:4px solid transparent;border-top:4px solid #000000;visibility:visible;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;filter:alpha(opacity=60);-moz-opacity:0.6;opacity:0.6;} +i{background-image:url(docs/assets/img/glyphicons-halflings-sprite.png);background-position:0 0;background-repeat:no-repeat;display:inline-block;vertical-align:text-top;width:14px;height:14px;} +.glass{background-position:0 0;} +.music{background-position:-24px 0;} +.search{background-position:-48px 0;} +.envelope{background-position:-72px 0;} +.heart{background-position:-96px 0;} +.star{background-position:-120px 0;} +.star-empty{background-position:-144px 0;} +.user{background-position:-168px 0;} +.film{background-position:-192px 0;} +.th-large{background-position:-216px 0;} +.th{background-position:-240px 0;} +.th-list{background-position:-264px 0;} +.ok{background-position:-288px 0;} +.remove{background-position:-312px 0;} +.zoom-in{background-position:-336px 0;} +.zoom-out{background-position:-360px 0;} +.off{background-position:-384px 0;} +.signal{background-position:-408px 0;} +.cog{background-position:-432px 0;} +.trash{background-position:-456px 0;} +.home{background-position:0 -24px;} +.file{background-position:-24px -24px;} +.time{background-position:-48px -24px;} +.road{background-position:-72px -24px;} +.download-alt{background-position:-96px -24px;} +.download{background-position:-120px -24px;} +.upload{background-position:-144px -24px;} +.inbox{background-position:-168px -24px;} +.play-circle{background-position:-192px -24px;} +.repeat{background-position:-216px -24px;} +.refresh{background-position:-240px -24px;} +.calendar{background-position:-264px -24px;} +.lock{background-position:-288px -24px;} +.flag{background-position:-312px -24px;} +.headphones{background-position:-336px -24px;} +.volume-off{background-position:-360px -24px;} +.volume-down{background-position:-384px -24px;} +.volume-up{background-position:-408px -24px;} +.qrcode{background-position:-432px -24px;} +.barcode{background-position:-456px -24px;} +.tag{background-position:0 -48px;} +.tags{background-position:-24px -48px;} +.book{background-position:-48px -48px;} +.bookmark{background-position:-72px -48px;} +.print{background-position:-96px -48px;} +.camera{background-position:-120px -48px;} +.font{background-position:-144px -48px;} +.bold{background-position:-168px -48px;} +.italic{background-position:-192px -48px;} +.text-height{background-position:-216px -48px;} +.text-width{background-position:-240px -48px;} +.align-left{background-position:-264px -48px;} +.align-center{background-position:-288px -48px;} +.align-right{background-position:-312px -48px;} +.align-justify{background-position:-336px -48px;} +.list{background-position:-360px -48px;} +.indent-left{background-position:-384px -48px;} +.indent-right{background-position:-408px -48px;} +.facetime-video{background-position:-432px -48px;} +.picture{background-position:-456px -48px;} +.pencil{background-position:0 -72px;} +.map-marker{background-position:-24px -72px;} +.adjust{background-position:-48px -72px;} +.tint{background-position:-72px -72px;} +.edit{background-position:-96px -72px;} +.share{background-position:-120px -72px;} +.check{background-position:-144px -72px;} +.move{background-position:-168px -72px;} +.step-backward{background-position:-192px -72px;} +.fast-backward{background-position:-216px -72px;} +.backward{background-position:-240px -72px;} +.play{background-position:-264px -72px;} +.pause{background-position:-288px -72px;} +.stop{background-position:-312px -72px;} +.forward{background-position:-336px -72px;} +.fast-forward{background-position:-360px -72px;} +.step-forward{background-position:-384px -72px;} +.eject{background-position:-408px -72px;} +.chevron-left{background-position:-432px -72px;} +.chevron-right{background-position:-456px -72px;} +.arrow-left{background-position:-240px -96px;} +.arrow-right{background-position:-264px -96px;} +.arrow-up{background-position:-288px -96px;} +.arrow-down{background-position:-312px -96px;} +.share-alt{background-position:-336px -96px;} +.resize-full{background-position:-360px -96px;} +.resize-small{background-position:-384px -96px;} +.plus{background-position:-408px -96px;} +.minus{background-position:-432px -96px;} +.asterisk{background-position:-456px -96px;} +.dropdown{position:relative;} +.caret{display:inline-block;width:0;height:0;text-indent:-99999px;vertical-align:top;border-left:4px solid transparent;border-right:4px solid transparent;border-top:4px solid #000000;filter:alpha(opacity=30);-moz-opacity:0.3;opacity:0.3;content:"\2193";} +.dropdown .caret{margin-top:8px;margin-left:2px;*margin-top:7px;} +.dropdown:hover .caret,.open.dropdown .caret{filter:alpha(opacity=100);-moz-opacity:1;opacity:1;} +.dropdown-menu{position:absolute;top:100%;z-index:1000;float:left;display:none;min-width:160px;max-width:220px;_width:160px;padding:4px 0;margin:0;list-style:none;background-color:#ffffff;border-color:#ccc;border-color:rgba(0, 0, 0, 0.2);border-style:solid;border-width:1px;-webkit-border-radius:0 0 5px 5px;-moz-border-radius:0 0 5px 5px;border-radius:0 0 5px 5px;-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;zoom:1;*border-right-width:2px;*border-bottom-width:2px;}.dropdown-menu.bottom-up{top:auto;bottom:100%;margin-bottom:2px;} +.dropdown-menu .divider{height:1px;margin:5px 1px;overflow:hidden;background-color:#e5e5e5;border-bottom:1px solid #ffffff;} +.dropdown-menu a{display:block;padding:3px 15px;clear:both;font-weight:normal;line-height:18px;color:#555555;white-space:nowrap;} +.dropdown-menu li>a:hover,.dropdown-menu .active>a,.dropdown-menu .active>a:hover{color:#ffffff;text-decoration:none;background-color:#0088cc;} +.dropdown.open .dropdown-toggle{color:#ffffff;background:#ccc;background:rgba(0, 0, 0, 0.3);} +.dropdown.open .dropdown-menu{display:block;} +.typeahead{margin-top:2px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;} +.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #eee;border:1px solid rgba(0, 0, 0, 0.05);-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-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);} +.fade{-webkit-transition:opacity 0.15s linear;-moz-transition:opacity 0.15s linear;-ms-transition:opacity 0.15s linear;-o-transition:opacity 0.15s linear;transition:opacity 0.15s linear;opacity:0;}.fade.in{opacity:1;} +.collapse{-webkit-transition:height 0.35s ease;-moz-transition:height 0.35s ease;-ms-transition:height 0.35s ease;-o-transition:height 0.35s ease;transition:height 0.35s ease;position:relative;overflow:hidden;height:0;}.collapse.in{height:auto;} +.close{float:right;font-size:20px;font-weight:bold;line-height:12px;color:#000000;text-shadow:0 1px 0 #ffffff;filter:alpha(opacity=20);-moz-opacity:0.2;opacity:0.2;}.close:hover{color:#000000;text-decoration:none;filter:alpha(opacity=40);-moz-opacity:0.4;opacity:0.4;cursor:pointer;} +.nav{margin-left:0;margin-bottom:18px;list-style:none;} +.nav>li>a{display:block;} +.nav>li>a:hover{text-decoration:none;background-color:#eeeeee;} +.nav.list{padding-left:14px;padding-right:14px;margin-bottom:0;} +.nav.list>li>a,.nav.list .nav-header{display:block;padding:3px 15px;margin-left:-15px;margin-right:-15px;text-shadow:0 1px 0 rgba(255, 255, 255, 0.5);} +.nav.list .nav-header{font-size:11px;font-weight:bold;line-height:18px;color:#999999;text-transform:uppercase;} +.nav.list>li+.nav-header{margin-top:9px;} +.nav.list .active>a{color:#ffffff;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.2);background-color:#0088cc;} +.tabs,.pills{*zoom:1;}.tabs:before,.pills:before,.tabs:after,.pills:after{display:table;content:"";} +.tabs:after,.pills:after{clear:both;} +.tabs>li,.pills>li{float:left;} +.tabs>li>a,.pills>li>a{padding-right:12px;padding-left:12px;margin-right:2px;line-height:14px;} +.tabs{border-bottom:1px solid #ddd;} +.tabs>li{margin-bottom:-1px;} +.tabs>li>a{padding-top:9px;padding-bottom:9px;border:1px solid transparent;-webkit-border-radius:4px 4px 0 0;-moz-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0;}.tabs>li>a:hover{border-color:#eeeeee #eeeeee #dddddd;} +.tabs>.active>a,.tabs>.active>a:hover{color:#555555;background-color:#ffffff;border:1px solid #ddd;border-bottom-color:transparent;cursor:default;} +.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;} +.pills .active>a,.pills .active>a:hover{color:#ffffff;background-color:#0088cc;} +.nav.stacked>li{float:none;} +.nav.stacked>li>a{margin-right:0;} +.tabs.stacked{border-bottom:0;} +.tabs.stacked>li>a{border:1px solid #ddd;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;} +.tabs.stacked>li:first-child>a{-webkit-border-radius:4px 4px 0 0;-moz-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0;} +.tabs.stacked>li:last-child>a{-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px;} +.tabs.stacked>li>a:hover{border-color:#ddd;z-index:2;} +.pills.stacked>li>a{margin-bottom:3px;} +.pills.stacked>li:last-child>a{margin-bottom:1px;} +.pills .dropdown-menu,.tabs .dropdown-menu{margin-top:1px;border-width:1px;} +.pills .dropdown-menu{-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;} +.tabs .dropdown-toggle .caret,.pills .dropdown-toggle .caret{border-top-color:#0088cc;margin-top:6px;} +.tabs .dropdown-toggle:hover .caret,.pills .dropdown-toggle:hover .caret{border-top-color:#005580;} +.tabs .active .dropdown-toggle .caret,.pills .active .dropdown-toggle .caret{border-top-color:#333333;} +.nav>.dropdown.active>a:hover{color:#000000;cursor:pointer;} +.tabs .open .dropdown-toggle,.pills .open .dropdown-toggle,.nav>.open.active>a:hover{color:#ffffff;background-color:#999999;border-color:#999999;} +.nav .open .caret,.nav .open.active .caret,.nav .open a:hover .caret{border-top-color:#ffffff;filter:alpha(opacity=100);-moz-opacity:1;opacity:1;} +.tabs.stacked .open>a:hover{border-color:#999999;} +.tabbable{*zoom:1;}.tabbable:before,.tabbable:after{display:table;content:"";} +.tabbable:after{clear:both;} +.tabs-below .tabs,.tabs-right .tabs,.tabs-left .tabs{border-bottom:0;} +.tab-content>.tab-pane,.pill-content>.pill-pane{display:none;} +.tab-content>.active,.pill-content>.active{display:block;} +.tabs-below .tabs{border-top:1px solid #ddd;} +.tabs-below .tabs>li{margin-top:-1px;margin-bottom:0;} +.tabs-below .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 .tabs>li>a:hover{border-bottom-color:transparent;border-top-color:#ddd;} +.tabs-below .tabs .active>a,.tabs-below .tabs .active>a:hover{border-color:transparent #ddd #ddd #ddd;} +.tabs-left .tabs>li,.tabs-right .tabs>li{float:none;} +.tabs-left .tabs>li>a,.tabs-right .tabs>li>a{min-width:74px;margin-right:0;margin-bottom:3px;} +.tabs-left .tabs{float:left;margin-right:19px;border-right:1px solid #ddd;} +.tabs-left .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 .tabs>li>a:hover{border-color:#eeeeee #dddddd #eeeeee #eeeeee;} +.tabs-left .tabs .active>a,.tabs-left .tabs .active>a:hover{border-color:#ddd transparent #ddd #ddd;} +.tabs-right .tabs{float:right;margin-left:19px;border-left:1px solid #ddd;} +.tabs-right .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 .tabs>li>a:hover{border-color:#eeeeee #eeeeee #eeeeee #dddddd;} +.tabs-right .tabs .active>a,.tabs-right .tabs .active>a:hover{border-color:#ddd #ddd #ddd transparent;} +.navbar{overflow:visible;} +.navbar-inner{background-color:#222222;background-color:#222222;background-image:-khtml-gradient(linear, left top, left bottom, from(#333333), to(#222222));background-image:-moz-linear-gradient(top, #333333, #222222);background-image:-ms-linear-gradient(top, #333333, #222222);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #333333), color-stop(100%, #222222));background-image:-webkit-linear-gradient(top, #333333, #222222);background-image:-o-linear-gradient(top, #333333, #222222);background-image:linear-gradient(top, #333333, #222222);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#333333', endColorstr='#222222', GradientType=0);-webkit-box-shadow:0 1px 3px rgba(0, 0, 0, 0.25),inset 0 -1px 0 rgba(0, 0, 0, 0.1);-moz-box-shadow:0 1px 3px rgba(0, 0, 0, 0.25),inset 0 -1px 0 rgba(0, 0, 0, 0.1);box-shadow:0 1px 3px rgba(0, 0, 0, 0.25),inset 0 -1px 0 rgba(0, 0, 0, 0.1);} +.navbar .brand:hover{color:#ffffff;text-decoration:none;} +.navbar .brand{float:left;display:block;padding:8px 20px 12px;margin-left:-20px;font-size:20px;font-weight:200;line-height:1;color:#ffffff;} +.navbar p{margin:0;line-height:40px;}.navbar p a:hover{color:#ffffff;background-color:transparent;} +.navbar .btn,.navbar .btn-group{margin-top:5px;} +.navbar .btn-group .btn{margin-top:0;} +.navbar-form{margin-bottom:0;}.navbar-form input,.navbar-form select{display:inline-block;margin-bottom:0;} +.navbar-search{position:relative;float:left;margin-top:6px;margin-bottom:0;}.navbar-search .search-query{padding:4px 9px;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:13px;font-weight:normal;line-height:1;color:#ffffff;color:rgba(255, 255, 255, 0.75);background:#444;background:rgba(255, 255, 255, 0.3);border:1px solid #111;-webkit-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1),0 1px 0px rgba(255, 255, 255, 0.15);-moz-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1),0 1px 0px rgba(255, 255, 255, 0.15);box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1),0 1px 0px rgba(255, 255, 255, 0.15);-webkit-transition:none;-moz-transition:none;-ms-transition:none;-o-transition:none;transition:none;}.navbar-search .search-query :-moz-placeholder{color:#eeeeee;} +.navbar-search .search-query ::-webkit-input-placeholder{color:#eeeeee;} +.navbar-search .search-query:hover{color:#ffffff;background-color:#999999;background-color:rgba(255, 255, 255, 0.5);} +.navbar-search .search-query:focus,.navbar-search .search-query.focused{padding:5px 10px;color:#333333;text-shadow:0 1px 0 #ffffff;background-color:#ffffff;border: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);outline:0;} +.navbar-static{margin-bottom:18px;} +.navbar-static .navbar-inner{padding-left:20px;padding-right:20px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;} +.navbar-fixed{position:fixed;top:0;right:0;left:0;z-index:1030;} +.navbar .nav{position:relative;left:0;display:block;float:left;margin:0 10px 0 0;} +.navbar .nav.pull-right{float:right;} +.navbar .nav>li{display:block;float:left;} +.navbar .nav>li>a{float:none;padding:10px 10px 11px;line-height:19px;color:#999999;text-decoration:none;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);} +.navbar .nav>li>a:hover{background-color:transparent;color:#ffffff;text-decoration:none;} +.navbar .nav .active>a{color:#ffffff;text-decoration:none;background-color:#222222;background-color:rgba(0, 0, 0, 0.5);} +.navbar .vertical-divider{height:40px;width:1px;margin:0 5px;overflow:hidden;background-color:#222222;border-right:1px solid #444;} +.navbar .nav.pull-right{margin-left:10px;margin-right:0;} +.navbar .dropdown-menu{margin-top:1px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}.navbar .dropdown-menu:before{content:'';display:inline-block;border-left:7px solid transparent;border-right:7px solid transparent;border-bottom:7px solid #ccc;border-bottom-color:rgba(0, 0, 0, 0.2);position:absolute;top:-7px;left:9px;} +.navbar .dropdown-menu:after{content:'';display:inline-block;border-left:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid #ffffff;position:absolute;top:-6px;left:10px;} +.navbar .nav .dropdown-toggle .caret,.navbar .nav .open.dropdown .caret{border-top-color:#ffffff;} +.navbar .nav .active .caret{filter:alpha(opacity=100);-moz-opacity:1;opacity:1;} +.navbar .nav .open>.dropdown-toggle,.navbar .nav .active>.dropdown-toggle,.navbar .nav .open.active>.dropdown-toggle{background-color:transparent;} +.navbar .nav .active>.dropdown-toggle:hover{color:#ffffff;} +.navbar .nav.pull-right .dropdown-menu{right:0;}.navbar .nav.pull-right .dropdown-menu:before{left:auto;right:12px;} +.navbar .nav.pull-right .dropdown-menu:after{left:auto;right:13px;} +.breadcrumb{padding:7px 14px;margin:0 0 18px;background-color:#f5f5f5;background-image:-khtml-gradient(linear, left top, left bottom, from(#ffffff), to(#f5f5f5));background-image:-moz-linear-gradient(top, #ffffff, #f5f5f5);background-image:-ms-linear-gradient(top, #ffffff, #f5f5f5);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #ffffff), color-stop(100%, #f5f5f5));background-image:-webkit-linear-gradient(top, #ffffff, #f5f5f5);background-image:-o-linear-gradient(top, #ffffff, #f5f5f5);background-image:linear-gradient(top, #ffffff, #f5f5f5);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#f5f5f5', GradientType=0);border:1px solid #ddd;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;-webkit-box-shadow:inset 0 1px 0 #ffffff;-moz-box-shadow:inset 0 1px 0 #ffffff;box-shadow:inset 0 1px 0 #ffffff;}.breadcrumb li{display:inline;text-shadow:0 1px 0 #ffffff;} +.breadcrumb .divider{padding:0 5px;color:#999999;} +.breadcrumb .active a{color:#333333;} +.pagination{height:36px;margin:18px 0;} +.pagination ul{display:inline-block;*display:inline;*zoom:1;margin-left:0;margin-bottom:0;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;-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 li{display:inline;} +.pagination a{float:left;padding:0 14px;line-height:34px;text-decoration:none;border:1px solid #ddd;border-left-width:0;} +.pagination a:hover,.pagination .active a{background-color:#f5f5f5;} +.pagination .active a{color:#999999;} +.pagination .disabled a,.pagination .disabled a:hover{color:#999999;background-color:transparent;cursor:default;} +.pagination li:first-child a{border-left-width:1px;-webkit-border-radius:3px 0 0 3px;-moz-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px;} +.pagination li:last-child a{-webkit-border-radius:0 3px 3px 0;-moz-border-radius:0 3px 3px 0;border-radius:0 3px 3px 0;} +.pagination-centered{text-align:center;} +.pagination-right{text-align:right;} +.pager{margin-left:0;margin-bottom:18px;list-style:none;text-align:center;*zoom:1;}.pager:before,.pager:after{display:table;content:"";} +.pager:after{clear:both;} +.pager li{display:inline;} +.pager a{display:inline-block;padding:6px 15px;background-color:#f5f5f5;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px;} +.pager .next a{float:right;} +.pager .previous a{float:left;} +.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000000;}.modal-backdrop.fade{opacity:0;} +.modal-backdrop,.modal-backdrop.fade.in{filter:alpha(opacity=80);-moz-opacity:0.8;opacity:0.8;} +.modal{position:fixed;top:50%;left:50%;z-index:1050;max-height:500px;overflow:auto;width:560px;margin:-250px 0 0 -280px;background-color:#ffffff;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;-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{-webkit-transition:opacity .3s linear, top .3s ease-out;-moz-transition:opacity .3s linear, top .3s ease-out;-ms-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;top:-25%;} +.modal.fade.in{top:50%;} +.modal-header{padding:5px 15px;border-bottom:1px solid #eee;}.modal-header .close{margin-top:7px;} +.modal-body{padding:15px;} +.modal-footer{padding:14px 15px 15px;margin-bottom:0;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;-webkit-box-shadow:inset 0 1px 0 #ffffff;-moz-box-shadow:inset 0 1px 0 #ffffff;box-shadow:inset 0 1px 0 #ffffff;*zoom:1;}.modal-footer:before,.modal-footer:after{display:table;content:"";} +.modal-footer:after{clear:both;} +.modal-footer .btn{float:right;margin-left:5px;} +.tooltip{position:absolute;z-index:1020;display:block;visibility:visible;padding:5px;font-size:11px;filter:alpha(opacity=0);-moz-opacity:0;opacity:0;}.tooltip.in{filter:alpha(opacity=80);-moz-opacity:0.8;opacity:0.8;} +.tooltip.top{margin-top:-2px;} +.tooltip.right{margin-left:2px;} +.tooltip.bottom{margin-top:2px;} +.tooltip.left{margin-left:-2px;} +.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-left:5px solid transparent;border-right:5px solid transparent;border-top:5px solid #000000;} +.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-top:5px solid transparent;border-bottom:5px solid transparent;border-left:5px solid #000000;} +.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-left:5px solid transparent;border-right:5px solid transparent;border-bottom:5px solid #000000;} +.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-top:5px solid transparent;border-bottom:5px solid transparent;border-right:5px solid #000000;} +.tooltip-inner{max-width:200px;padding:3px 8px;color:#ffffff;text-align:center;text-decoration:none;background-color:#000000;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;} +.tooltip-arrow{position:absolute;width:0;height:0;} +.popover{position:absolute;top:0;left:0;z-index:1010;display:none;padding:5px;}.popover.top{margin-top:-5px;} +.popover.right{margin-left:5px;} +.popover.bottom{margin-top:5px;} +.popover.left{margin-left:-5px;} +.popover.top .arrow{bottom:0;left:50%;margin-left:-5px;border-left:5px solid transparent;border-right:5px solid transparent;border-top:5px solid #000000;} +.popover.right .arrow{top:50%;left:0;margin-top:-5px;border-top:5px solid transparent;border-bottom:5px solid transparent;border-right:5px solid #000000;} +.popover.bottom .arrow{top:0;left:50%;margin-left:-5px;border-left:5px solid transparent;border-right:5px solid transparent;border-bottom:5px solid #000000;} +.popover.left .arrow{top:50%;right:0;margin-top:-5px;border-top:5px solid transparent;border-bottom:5px solid transparent;border-left:5px solid #000000;} +.popover .arrow{position:absolute;width:0;height:0;} +.popover .inner{padding:3px;width:280px;overflow:hidden;background:#000000;background:rgba(0, 0, 0, 0.8);-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-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);} +.popover .title{padding:9px 15px;line-height:1;background-color:#f5f5f5;border-bottom:1px solid #eee;-webkit-border-radius:3px 3px 0 0;-moz-border-radius:3px 3px 0 0;border-radius:3px 3px 0 0;} +.popover .content{padding:14px;background-color:#ffffff;-webkit-border-radius:0 0 3px 3px;-moz-border-radius:0 0 3px 3px;border-radius:0 0 3px 3px;-webkit-background-clip:padding-box;-moz-background-clip:padding-box;background-clip:padding-box;}.popover .content p,.popover .content ul,.popover .content ol{margin-bottom:0;} +.btn.danger,.btn.danger:hover,.btn.success,.btn.success:hover,.btn.info,.btn.info:hover{text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);color:#ffffff;} +.btn.danger{background-color:#c43c35;background-image:-khtml-gradient(linear, left top, left bottom, from(#ee5f5b), to(#c43c35));background-image:-moz-linear-gradient(top, #ee5f5b, #c43c35);background-image:-ms-linear-gradient(top, #ee5f5b, #c43c35);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #ee5f5b), color-stop(100%, #c43c35));background-image:-webkit-linear-gradient(top, #ee5f5b, #c43c35);background-image:-o-linear-gradient(top, #ee5f5b, #c43c35);background-image:linear-gradient(top, #ee5f5b, #c43c35);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ee5f5b', endColorstr='#c43c35', GradientType=0);border-color:#c43c35 #c43c35 #882a25;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);} +.btn.success{background-color:#57a957;background-image:-khtml-gradient(linear, left top, left bottom, from(#62c462), to(#57a957));background-image:-moz-linear-gradient(top, #62c462, #57a957);background-image:-ms-linear-gradient(top, #62c462, #57a957);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #62c462), color-stop(100%, #57a957));background-image:-webkit-linear-gradient(top, #62c462, #57a957);background-image:-o-linear-gradient(top, #62c462, #57a957);background-image:linear-gradient(top, #62c462, #57a957);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#62c462', endColorstr='#57a957', GradientType=0);border-color:#57a957 #57a957 #3d773d;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);} +.btn.info{background-color:#339bb9;background-image:-khtml-gradient(linear, left top, left bottom, from(#5bc0de), to(#339bb9));background-image:-moz-linear-gradient(top, #5bc0de, #339bb9);background-image:-ms-linear-gradient(top, #5bc0de, #339bb9);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #5bc0de), color-stop(100%, #339bb9));background-image:-webkit-linear-gradient(top, #5bc0de, #339bb9);background-image:-o-linear-gradient(top, #5bc0de, #339bb9);background-image:linear-gradient(top, #5bc0de, #339bb9);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#5bc0de', endColorstr='#339bb9', GradientType=0);border-color:#339bb9 #339bb9 #22697d;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);} +.btn{display:inline-block;padding:5px 10px 6px;font-size:13px;line-height:normal;color:#333333;text-shadow:0 1px 1px rgba(255, 255, 255, 0.75);background-color:#e6e6e6;background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), color-stop(25%, #ffffff), to(#e6e6e6));background-image:-webkit-linear-gradient(#ffffff, #ffffff 25%, #e6e6e6);background-image:-moz-linear-gradient(top, #ffffff, #ffffff 25%, #e6e6e6);background-image:-ms-linear-gradient(#ffffff, #ffffff 25%, #e6e6e6);background-image:-o-linear-gradient(#ffffff, #ffffff 25%, #e6e6e6);background-image:linear-gradient(#ffffff, #ffffff 25%, #e6e6e6);background-repeat:no-repeat;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#e6e6e6', GradientType=0);border:1px solid #ccc;border-bottom-color:#bbb;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-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);cursor:pointer;-webkit-transition:0.1s linear all;-moz-transition:0.1s linear all;-ms-transition:0.1s linear all;-o-transition:0.1s linear all;transition:0.1s linear all;}.btn:hover{color:#333333;text-decoration:none;background-position:0 -15px;} +.btn:focus{outline:1px dotted #666;} +.btn.primary{color:#ffffff;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);background-color:#0064cd;background-image:-khtml-gradient(linear, left top, left bottom, from(#049cdb), to(#0064cd));background-image:-moz-linear-gradient(top, #049cdb, #0064cd);background-image:-ms-linear-gradient(top, #049cdb, #0064cd);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #049cdb), color-stop(100%, #0064cd));background-image:-webkit-linear-gradient(top, #049cdb, #0064cd);background-image:-o-linear-gradient(top, #049cdb, #0064cd);background-image:linear-gradient(top, #049cdb, #0064cd);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#049cdb', endColorstr='#0064cd', GradientType=0);border-color:#0064cd #0064cd #003f81;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);} +.btn.active,.btn:active{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.disabled{cursor:default;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=65);-moz-opacity:0.65;opacity:0.65;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;} +.btn[disabled]{cursor:default;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=65);-moz-opacity:0.65;opacity:0.65;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;} +.btn.large{padding:9px 14px 9px;font-size:15px;line-height:normal;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px;} +.btn.small{padding:7px 9px 7px;font-size:11px;} +:root .btn{border-radius:0 \0;} +button.btn::-moz-focus-inner,input[type=submit].btn::-moz-focus-inner{padding:0;border:0;} +.btn-group{position:relative;*zoom:1;}.btn-group:before,.btn-group:after{display:table;content:"";} +.btn-group:after{clear:both;} +.btn-group+.btn-group{margin-left:5px;} +.btn-toolbar .btn-group{display:inline-block;} +.btn-group .btn{position:relative;float:left;margin-left:-1px;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;} +.btn-group .btn:first-child{margin-left:0;-webkit-border-top-left-radius:4px;-moz-border-radius-topleft:4px;border-top-left-radius:4px;-webkit-border-bottom-left-radius:4px;-moz-border-radius-bottomleft:4px;border-bottom-left-radius:4px;} +.btn-group .btn:last-child,.btn-group .dropdown-toggle{-webkit-border-top-right-radius:4px;-moz-border-radius-topright:4px;border-top-right-radius:4px;-webkit-border-bottom-right-radius:4px;-moz-border-radius-bottomright:4px;border-bottom-right-radius:4px;} +.btn-group .btn.large:first-child{margin-left:0;-webkit-border-top-left-radius:6px;-moz-border-radius-topleft:6px;border-top-left-radius:6px;-webkit-border-bottom-left-radius:6px;-moz-border-radius-bottomleft:6px;border-bottom-left-radius:6px;} +.btn-group .btn.large:last-child,.btn-group .large.dropdown-toggle{-webkit-border-top-right-radius:6px;-moz-border-radius-topright:6px;border-top-right-radius:6px;-webkit-border-bottom-right-radius:6px;-moz-border-radius-bottomright:6px;border-bottom-right-radius:6px;} +.btn-group .btn:hover,.btn-group .btn:focus,.btn-group .btn:active{z-index:2;} +.btn-group .dropdown-toggle{padding-left:8px;padding-right:8px;-webkit-box-shadow:inset 1px 0 0 rgba(255, 255, 255, 0.125),0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 1px 0 0 rgba(255, 255, 255, 0.125),0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:inset 1px 0 0 rgba(255, 255, 255, 0.125),0 1px 2px rgba(0, 0, 0, 0.05);} +.btn-group.open .dropdown-menu{display:block;margin-top:1px;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px;}.btn-group.open .dropdown-menu.large{top:40px;} +.btn-group.open .dropdown-toggle{background-image:none;-webkit-box-shadow:inset 0 1px 6px rgba(0, 0, 0, 0.15),0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 0 1px 6px rgba(0, 0, 0, 0.15),0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:inset 0 1px 6px rgba(0, 0, 0, 0.15),0 1px 2px rgba(0, 0, 0, 0.05);} +.btn .caret{margin-top:6px;margin-left:0;} +.primary .caret,.danger .caret,.info .caret,.success .caret{border-top-color:#ffffff;filter:alpha(opacity=75);-moz-opacity:0.75;opacity:0.75;} +.alert{padding:8px 35px 8px 14px;margin-bottom:18px;text-shadow:0 1px 0 rgba(255, 255, 255, 0.5);background-color:#fcf8e3;border:1px solid #f3edd2;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;} +.alert,.alert-heading{color:#c09853;} +.alert .close{*margin-top:3px;position:relative;right:-21px;} +.alert-success{background-color:#dff0d8;border-color:#cfe8c4;} +.alert-success,.alert-success .alert-heading{color:#468847;} +.alert-danger,.alert-error{background-color:#f2dede;border-color:#e9c7c7;} +.alert-danger,.alert-error,.alert-danger .alert-heading,.alert-error .alert-heading{color:#B94A48;} +.alert-info{background-color:#d9edf7;border-color:#bfe1f2;} +.alert-info,.alert-info .alert-heading{color:#3a87ad;} +.alert-block{padding-top:14px;padding-bottom:14px;} +.alert-block>p,.alert-block>ul{margin-bottom:0;} +.alert-block p+p{margin-top:5px;} +.thumbnails{margin-left:-20px;list-style:none;*zoom:1;}.thumbnails:before,.thumbnails:after{display:table;content:"";} +.thumbnails:after{clear:both;} +.thumbnails>li{float:left;margin:0 0 18px 20px;} +.thumbnail{display:block;padding:4px;line-height:1;border:1px solid #ddd;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0, 0, 0, 0.075);-moz-box-shadow:0 1px 1px rgba(0, 0, 0, 0.075);box-shadow:0 1px 1px rgba(0, 0, 0, 0.075);} +a.thumbnail:hover{border-color:#0088cc;-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%;} +.thumbnail .caption{padding:9px;} +.label{padding:1px 3px 2px;font-size:9.75px;font-weight:bold;color:#ffffff;text-transform:uppercase;background-color:#999999;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;}.label.important{background-color:#c43c35;} +.label.warning{background-color:#f89406;} +.label.success{background-color:#46a546;} +.label.notice{background-color:#62cffc;} +@-webkit-keyframes progress-bar-stripes{from{background-position:0 0;} to{background-position:40px 0;}}@keyframes progress-bar-stripes{from{background-position:0 0;} to{background-position:40px 0;}}.progress,.progress .bar{-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;} +.progress{height:18px;margin-bottom:18px;background-color:#f9f9f9;background-image:-khtml-gradient(linear, left top, left bottom, from(#f5f5f5), to(#f9f9f9));background-image:-moz-linear-gradient(top, #f5f5f5, #f9f9f9);background-image:-ms-linear-gradient(top, #f5f5f5, #f9f9f9);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #f5f5f5), color-stop(100%, #f9f9f9));background-image:-webkit-linear-gradient(top, #f5f5f5, #f9f9f9);background-image:-o-linear-gradient(top, #f5f5f5, #f9f9f9);background-image:linear-gradient(top, #f5f5f5, #f9f9f9);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#f5f5f5', endColorstr='#f9f9f9', 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{width:0%;height:18px;background-color:#0480be;background-image:-khtml-gradient(linear, left top, left bottom, from(#149bdf), to(#0480be));background-image:-moz-linear-gradient(top, #149bdf, #0480be);background-image:-ms-linear-gradient(top, #149bdf, #0480be);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #149bdf), color-stop(100%, #0480be));background-image:-webkit-linear-gradient(top, #149bdf, #0480be);background-image:-o-linear-gradient(top, #149bdf, #0480be);background-image:linear-gradient(top, #149bdf, #0480be);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#149bdf', endColorstr='#0480be', 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 0.6s ease;-moz-transition:width 0.6s ease;-ms-transition:width 0.6s ease;-o-transition:width 0.6s ease;transition:width 0.6s ease;} +.progress.striped .bar{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:-ms-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;animation:progress-bar-stripes 2s linear infinite;} +.progress.danger .bar{background-color:#c43c35;background-image:-khtml-gradient(linear, left top, left bottom, from(#ee5f5b), to(#c43c35));background-image:-moz-linear-gradient(top, #ee5f5b, #c43c35);background-image:-ms-linear-gradient(top, #ee5f5b, #c43c35);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #ee5f5b), color-stop(100%, #c43c35));background-image:-webkit-linear-gradient(top, #ee5f5b, #c43c35);background-image:-o-linear-gradient(top, #ee5f5b, #c43c35);background-image:linear-gradient(top, #ee5f5b, #c43c35);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ee5f5b', endColorstr='#c43c35', GradientType=0);} +.progress.danger.striped .bar{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:-ms-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{background-color:#57a957;background-image:-khtml-gradient(linear, left top, left bottom, from(#62c462), to(#57a957));background-image:-moz-linear-gradient(top, #62c462, #57a957);background-image:-ms-linear-gradient(top, #62c462, #57a957);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #62c462), color-stop(100%, #57a957));background-image:-webkit-linear-gradient(top, #62c462, #57a957);background-image:-o-linear-gradient(top, #62c462, #57a957);background-image:linear-gradient(top, #62c462, #57a957);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#62c462', endColorstr='#57a957', GradientType=0);} +.progress.success.striped .bar{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:-ms-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{background-color:#339bb9;background-image:-khtml-gradient(linear, left top, left bottom, from(#5bc0de), to(#339bb9));background-image:-moz-linear-gradient(top, #5bc0de, #339bb9);background-image:-ms-linear-gradient(top, #5bc0de, #339bb9);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #5bc0de), color-stop(100%, #339bb9));background-image:-webkit-linear-gradient(top, #5bc0de, #339bb9);background-image:-o-linear-gradient(top, #5bc0de, #339bb9);background-image:linear-gradient(top, #5bc0de, #339bb9);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#5bc0de', endColorstr='#339bb9', GradientType=0);} +.progress.info.striped .bar{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:-ms-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:18px;} +.accordion-group{background-color:#f5f5f5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;} +.accordion-heading{padding:8px 15px;border-bottom:0;} +.accordion-body{margin-bottom:2px;} +.accordion-inner{padding:9px 15px;border-top:1px solid #e5e5e5;} +.carousel{position:relative;line-height:1;} +.carousel-inner{overflow:hidden;width:100%;position:relative;} +.carousel .item{display:none;position:relative;-webkit-transition:0.6s ease-in-out left;-moz-transition:0.6s ease-in-out left;-ms-transition:0.6s ease-in-out left;-o-transition:0.6s ease-in-out left;transition:0.6s ease-in-out left;} +.carousel .active,.carousel .next,.carousel .prev{display:block;} +.carousel .active{left:0;} +.carousel .next,.carousel .prev{position:absolute;top:0;width:100%;} +.carousel .next{left:100%;} +.carousel .prev{left:-100%;} +.carousel .next.left,.carousel .prev.right{left:0;} +.carousel .active.left{left:-100%;} +.carousel .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:#ccc;text-align:center;background:#999999;background:rgba(0, 0, 0, 0.5);-webkit-border-radius:20px;-moz-border-radius:20px;border-radius:20px;}.carousel-control.right{left:auto;right:15px;} +.carousel-control:hover{color:#ffffff;text-decoration:none;background:#333333;background:rgba(0, 0, 0, 0.75);} +.carousel-caption{position:absolute;left:0;right:0;bottom:0;padding:10px 15px 5px;background:#333333;background:rgba(0, 0, 0, 0.75);} +.carousel-caption h4,.carousel-caption p{color:#ffffff;} +.pull-right{float:right;} +.pull-left{float:left;} +.hide{display:none;} +.show{display:block;} +.invisible{visibility:hidden;} +.hidden{display:none;visibility:hidden;} +@media (max-width: 480px){.navbar .nav{position:absolute;top:0;left:0;width:180px;padding-top:40px;list-style:none;} .navbar .nav,.navbar .nav>li:last-child a{-webkit-border-radius:0 0 4px 0;-moz-border-radius:0 0 4px 0;border-radius:0 0 4px 0;} .navbar .nav>li{float:none;display:none;} .navbar .nav>li>a{float:none;background-color:#222;} .navbar .nav>.active{display:block;position:absolute;top:0;left:0;} .navbar .nav>.active>a{background-color:transparent;} .navbar .nav>.active>a:hover{background-color:#333;} .navbar .nav>.active>a:after{display:inline-block;width:0;height:0;margin-top:8px;margin-left:6px;text-indent:-99999px;vertical-align:top;border-left:4px solid transparent;border-right:4px solid transparent;border-top:4px solid #ffffff;filter:alpha(opacity=100);-moz-opacity:1;opacity:1;content:"↓";} .navbar .nav:hover>li{display:block;} .navbar .nav:hover>li>a:hover{background-color:#333;} .form-horizontal .control-group>label{float:none;width:auto;padding-top:0;text-align:left;} .form-horizontal .controls{margin-left:0;} .form-horizontal .control-list{padding-top:0;} .form-horizontal .form-actions{padding-left:0;} .modal{position:absolute;top:20px;left:20px;right:20px;width:auto;margin:0;}.modal.fade.in{top:auto;} .modal-header .close{padding:10px;}}@media (max-width: 768px){.navbar-fixed{position:absolute;} .navbar-fixed .nav{float:none;} .container{width:auto;padding:0 20px;} .row{margin-left:0;} .row>[class*="span"]{float:none;display:block;width:auto;margin:0;}}@media (min-width: 768px) and (max-width: 940px){.container{width:748px;} .span1{width:44px;} .span2{width:108px;} .span3{width:172px;} .span4{width:236px;} .span5{width:300px;} .span6{width:364px;} .span7{width:428px;} .span8{width:492px;} .span9{width:556px;} .span10{width:620px;} .span11{width:684px;} .span12{width:748px;} .offset1{margin-left:64px;} .offset2{margin-left:128px;} .offset3{margin-left:192px;} .offset4{margin-left:256px;} .offset5{margin-left:320px;} .offset6{margin-left:384px;} .offset7{margin-left:448px;} .offset8{margin-left:512px;} .offset9{margin-left:576px;} .offset10{margin-left:640px;} .offset11{margin-left:704px;} .offset12{margin-left:768px;}} diff --git a/libpathod/templates/frame.html b/libpathod/templates/frame.html new file mode 100644 index 00000000..f985aaa1 --- /dev/null +++ b/libpathod/templates/frame.html @@ -0,0 +1,41 @@ + + + + + Omnid + + + + + + + + +
+ + {% block body %} + {% end %} + + + +
+ + + diff --git a/libpathod/templates/help.html b/libpathod/templates/help.html new file mode 100644 index 00000000..20d884c5 --- /dev/null +++ b/libpathod/templates/help.html @@ -0,0 +1,4 @@ +{% extends frame.html %} +{% block body %} +

Help

+{% end %} diff --git a/libpathod/templates/index.html b/libpathod/templates/index.html new file mode 100644 index 00000000..38b35dd8 --- /dev/null +++ b/libpathod/templates/index.html @@ -0,0 +1,12 @@ +{% extends frame.html %} +{% block body %} + +
+
+ + explain + +
+
+ +{% end %} diff --git a/libpathod/templates/log.html b/libpathod/templates/log.html new file mode 100644 index 00000000..3539291a --- /dev/null +++ b/libpathod/templates/log.html @@ -0,0 +1,5 @@ +{% extends frame.html %} +{% block body %} +

Log

+{% end %} + diff --git a/libpathod/utils.py b/libpathod/utils.py new file mode 100644 index 00000000..104ee148 --- /dev/null +++ b/libpathod/utils.py @@ -0,0 +1,31 @@ +import copy, os + +class Data: + def __init__(self, name): + m = __import__(name) + dirname, _ = os.path.split(m.__file__) + self.dirname = os.path.abspath(dirname) + + def path(self, path): + """ + Returns a path to the package data housed at 'path' under this + module.Path can be a path to a file, or to a directory. + + This function will raise ValueError if the path does not exist. + """ + fullpath = os.path.join(self.dirname, path) + if not os.path.exists(fullpath): + raise ValueError, "dataPath: %s does not exist."%fullpath + return fullpath + + def read(self, path): + """ + Returns a path to the package data housed at 'path' under this + module.Path can be a path to a file, or to a directory. + + This function will raise ValueError if the path does not exist. + """ + p = self.path(path) + return open(p).read() + +data = Data(__name__) diff --git a/notes b/notes new file mode 100644 index 00000000..0513cb42 --- /dev/null +++ b/notes @@ -0,0 +1,102 @@ + +Value Specifiers: + + !500k - 500k of random data + !500k:utf8 - 500k of utf8. Other specifiers: utf8,alphanum,alpha,printable + "foo" - literal + foo - literal + destination + code>destination + + 301:VALUE>VALUE + 301>http://foo.bar + + + Content-type: + + t:content-type + + +Examples: + 200,b:500k + + 404,p:5s,b:1k:printable + + 200,t:text/json,p:5s,b:1k + + 200,b:1k,xr + + + +Sequences: + + 200 * 2 | !forever + + 200 | 404 | 200,b:500g + + + +Anchors: + + Passed on command-line? + --anchor /foo/bar 200:!/foo + + + +Built-in help + + /help + + /explain/expression + diff --git a/pathod b/pathod new file mode 100755 index 00000000..ab8cc93f --- /dev/null +++ b/pathod @@ -0,0 +1,21 @@ +#!/usr/bin/env python +import argparse +import libomnid +import tornado.ioloop + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description='Process some integers.') + parser.add_argument("-p", dest='port', default=8888, type=int, help='Port.') + parser.add_argument( + "-s", dest='staticdir', default=None, type=str, + help='Directory for static files.' + ) + args = parser.parse_args() + + libomnid.application(staticdir=args.staticdir).listen(args.port) + print "omnid listening on port %s"%args.port + try: + tornado.ioloop.IOLoop.instance().start() + except KeyboardInterrupt: + pass + diff --git a/test/.pry b/test/.pry new file mode 100644 index 00000000..4b6802be --- /dev/null +++ b/test/.pry @@ -0,0 +1,5 @@ +base = .. +coverage = ../libpathod +exclude = . + ../libpathod/contrib + diff --git a/test/test_rparse.py b/test/test_rparse.py new file mode 100644 index 00000000..b872b1f3 --- /dev/null +++ b/test/test_rparse.py @@ -0,0 +1,262 @@ +import StringIO, sys +import libpry +from libpathod import rparse + +rparse.TESTING = True + +class uMisc(libpry.AutoTree): + def test_generators(self): + v = rparse.Value.parseString("val")[0] + g = v.get_generator({}) + assert g[:] == "val" + + def test_randomgenerator(self): + g = rparse.RandomGenerator("one", 100) + assert len(g[:10]) == 10 + assert len(g[1:10]) == 9 + assert len(g[:1000]) == 100 + assert len(g[1000:1001]) == 0 + assert g[0] + + def test_literalgenerator(self): + g = rparse.LiteralGenerator("one") + assert g == "one" + assert g[:] == "one" + assert g[1] == "n" + + def test_valueliteral(self): + v = rparse.ValueLiteral("foo") + assert v.expr() + assert str(v) + + def test_generated_value(self): + v = rparse.Value.parseString("!10b")[0] + assert v.usize == 10 + assert v.unit == "b" + assert v.bytes() == 10 + v = rparse.Value.parseString("!10")[0] + assert v.unit == "b" + v = rparse.Value.parseString("!10k")[0] + assert v.bytes() == 10240 + v = rparse.Value.parseString("!10g")[0] + assert v.bytes() == 1024**3 * 10 + + v = rparse.Value.parseString("!10g:digits")[0] + assert v.datatype == "digits" + g = v.get_generator({}) + assert g[:100] + + v = rparse.Value.parseString("!10:digits")[0] + assert v.unit == "b" + assert v.datatype == "digits" + + def test_value(self): + assert rparse.Value.parseString("val")[0].val == "val" + assert rparse.Value.parseString("'val'")[0].val == "val" + assert rparse.Value.parseString('"val"')[0].val == "val" + assert rparse.Value.parseString('"\'val\'"')[0].val == "'val'" + + v = rparse.Value.parseString(" Date: Sat, 28 Apr 2012 13:16:51 +1200 Subject: First pass at static file serving. --- libpathod/rparse.py | 22 ++++++++++++++++++++-- pathod | 6 +++--- test/test_rparse.py | 39 +++++++++++++++++++++++++++++++++------ 3 files changed, 56 insertions(+), 11 deletions(-) diff --git a/libpathod/rparse.py b/libpathod/rparse.py index f3985d04..d1714fb8 100644 --- a/libpathod/rparse.py +++ b/libpathod/rparse.py @@ -1,4 +1,4 @@ -import operator, string, random, sys, time +import operator, string, random, sys, time, mmap, os import contrib.pyparsing as pp import http, utils import tornado.ioloop @@ -6,6 +6,7 @@ import tornado.ioloop TESTING = False class ParseException(Exception): pass +class ServerError(Exception): pass DATATYPES = dict( @@ -81,6 +82,17 @@ class RandomGenerator: class FileGenerator: def __init__(self, path): self.path = path + self.fp = file(path, "r") + self.map = mmap.mmap(self.fp.fileno(), 0, prot=mmap.PROT_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) class ValueLiteral: @@ -145,7 +157,13 @@ class ValueFile: return e.setParseAction(lambda x: klass(*x)) def get_generator(self, settings): - raise NotImplementedError + sd = settings.get("staticdir") + if not sd: + raise ServerError("No static directory specified.") + path = os.path.join(sd, self.path) + if not os.path.exists(path): + raise ServerError("Static file does not exist: %s"%path) + return FileGenerator(path) def __str__(self): return "<%s"%(self.path) diff --git a/pathod b/pathod index ab8cc93f..4340e2d1 100755 --- a/pathod +++ b/pathod @@ -1,6 +1,6 @@ #!/usr/bin/env python import argparse -import libomnid +import libpathod import tornado.ioloop if __name__ == "__main__": @@ -12,8 +12,8 @@ if __name__ == "__main__": ) args = parser.parse_args() - libomnid.application(staticdir=args.staticdir).listen(args.port) - print "omnid listening on port %s"%args.port + libpathod.application(staticdir=args.staticdir).listen(args.port) + print "pathod listening on port %s"%args.port try: tornado.ioloop.IOLoop.instance().start() except KeyboardInterrupt: diff --git a/test/test_rparse.py b/test/test_rparse.py index b872b1f3..0ee3aae4 100644 --- a/test/test_rparse.py +++ b/test/test_rparse.py @@ -1,4 +1,4 @@ -import StringIO, sys +import StringIO, sys, os import libpry from libpathod import rparse @@ -24,11 +24,43 @@ class uMisc(libpry.AutoTree): assert g[:] == "one" assert g[1] == "n" + def test_filegenerator(self): + t = self.tmpdir() + path = os.path.join(t, "foo") + f = open(path, "w") + f.write("x"*10000) + f.close() + g = rparse.FileGenerator(path) + assert len(g) == 10000 + assert g[0] == "x" + assert g[-1] == "x" + assert g[0:5] == "xxxxx" + def test_valueliteral(self): v = rparse.ValueLiteral("foo") assert v.expr() assert str(v) + def test_file_value(self): + v = rparse.Value.parseString("<'one two'")[0] + assert v.path == "one two" + + v = rparse.Value.parseString(" Date: Sat, 28 Apr 2012 14:43:57 +1200 Subject: Better internal error pages. --- libpathod/__init__.py | 1 - libpathod/handlers.py | 4 ++-- libpathod/rparse.py | 47 ++++++++++++++++++++++++++++++++--------------- test/test_rparse.py | 31 +++++++++++++++++++------------ todo | 4 ++-- 5 files changed, 55 insertions(+), 32 deletions(-) diff --git a/libpathod/__init__.py b/libpathod/__init__.py index e841a6ab..4ef52a84 100644 --- a/libpathod/__init__.py +++ b/libpathod/__init__.py @@ -16,7 +16,6 @@ def application(**settings): (r"/preview", handlers.Preview), (r"/p/.*", handlers.Pathod, settings), ], - debug=True, static_path = utils.data.path("static"), template_path = utils.data.path("templates"), ) diff --git a/libpathod/handlers.py b/libpathod/handlers.py index a697304c..ebf85b03 100644 --- a/libpathod/handlers.py +++ b/libpathod/handlers.py @@ -36,9 +36,9 @@ class Pathod(object): try: self.response = rparse.parse(self.settings, spec) except rparse.ParseException, v: - self.response = rparse.StubResponse( + self.response = rparse.InternalResponse( 800, - "Error parsing response spec:" + str(v) + "Error parsing response spec: %s\n"%v.msg + v.marked() ) def _execute(self, transforms, *args, **kwargs): diff --git a/libpathod/rparse.py b/libpathod/rparse.py index d1714fb8..5c6d0257 100644 --- a/libpathod/rparse.py +++ b/libpathod/rparse.py @@ -5,7 +5,17 @@ import tornado.ioloop TESTING = False -class ParseException(Exception): 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) + "^") + + class ServerError(Exception): pass @@ -304,19 +314,9 @@ class Response: code = 200 msg = LiteralGenerator(http.RESPONSES[code]) body = LiteralGenerator("OK") - def __init__(self, settings, tokens): - self.tokens = tokens + def __init__(self): self.headers = [] self.pauses = [] - for i in tokens: - i.mod_response(settings, self) - if self.body and not self.get_header("Content-Length"): - self.headers.append( - ( - LiteralGenerator("Content-Length"), - LiteralGenerator(str(len(self.body))), - ) - ) def get_header(self, hdr): for k, v in self.headers: @@ -393,6 +393,14 @@ class Response: fp.finish() def render(self, fp): + if self.body and not self.get_header("Content-Length"): + self.headers.append( + ( + LiteralGenerator("Content-Length"), + LiteralGenerator(str(len(self.body))), + ) + ) + hdrs = [] for k, v in self.headers: hdrs.extend([ @@ -423,8 +431,17 @@ class Response: return "\n".join(parts) -class StubResponse: +class CraftedResponse(Response): + def __init__(self, settings, tokens): + Response.__init__(self) + self.tokens = tokens + for i in tokens: + i.mod_response(settings, self) + + +class InternalResponse(Response): def __init__(self, code, body): + Response.__init__(self) self.code = code self.msg = LiteralGenerator(http.RESPONSES.get(code, "Unknown error")) self.body = LiteralGenerator(body) @@ -442,6 +459,6 @@ class StubResponse: def parse(settings, s): try: - return Response(settings, Response.expr().parseString(s, parseAll=True)) + return CraftedResponse(settings, Response.expr().parseString(s, parseAll=True)) except pp.ParseException, v: - raise ParseException(v) + raise ParseException(v.msg, v.line, v.col) diff --git a/test/test_rparse.py b/test/test_rparse.py index 0ee3aae4..8e9da6f0 100644 --- a/test/test_rparse.py +++ b/test/test_rparse.py @@ -4,6 +4,17 @@ from libpathod import rparse rparse.TESTING = True +class DummyRequest(StringIO.StringIO): + def write(self, d, callback=None): + StringIO.StringIO.write(self, d) + if callback: + callback() + + def finish(self): + return + + + class uMisc(libpry.AutoTree): def test_generators(self): v = rparse.Value.parseString("val")[0] @@ -130,8 +141,10 @@ class uMisc(libpry.AutoTree): r = e.parseString('10')[0] assert r.msg.val == "Unknown code" - def test_stub_response(self): - s = rparse.StubResponse(400, "foo") + def test_internal_response(self): + d = DummyRequest() + s = rparse.InternalResponse(400, "foo") + s.render(d) class uDisconnects(libpry.AutoTree): @@ -175,6 +188,10 @@ class uPauses(libpry.AutoTree): class uparse(libpry.AutoTree): def test_parse_err(self): libpry.raises(rparse.ParseException, rparse.parse, {}, "400:msg,b:") + try: + rparse.parse({}, "400:msg,b:") + except rparse.ParseException, v: + print v.marked() def test_parse_header(self): r = rparse.parse({}, "400,h:foo:bar") @@ -193,16 +210,6 @@ class uparse(libpry.AutoTree): assert ("random", 10) in r.pauses -class DummyRequest(StringIO.StringIO): - def write(self, d, callback=None): - StringIO.StringIO.write(self, d) - if callback: - callback() - - def finish(self): - return - - class uResponse(libpry.AutoTree): def dummy_response(self): return rparse.parse({}, "400:msg") diff --git a/todo b/todo index 5ad3c4b8..14b2a9dc 100644 --- a/todo +++ b/todo @@ -1,6 +1,6 @@ -- Files - Shortcuts - HTTPS -- Sequences - Anchors +- Sequences + -- cgit v1.2.3 From a779aac9db96b05acb2c4e1b62417bbf37f160f8 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Sat, 28 Apr 2012 17:12:39 +1200 Subject: Make specification language more terse, and more regular. --- libpathod/rparse.py | 27 +++++++++++----- notes | 91 +++++++++++++++-------------------------------------- test/test_rparse.py | 57 +++++++++++++++++---------------- 3 files changed, 73 insertions(+), 102 deletions(-) diff --git a/libpathod/rparse.py b/libpathod/rparse.py index 5c6d0257..18d05a69 100644 --- a/libpathod/rparse.py +++ b/libpathod/rparse.py @@ -15,6 +15,9 @@ class ParseException(Exception): def marked(self): return "%s\n%s"%(self.s, " "*(self.col-1) + "^") + def __str__(self): + return self.msg + class ServerError(Exception): pass @@ -41,16 +44,17 @@ v_integer = pp.Regex(r"[+-]?\d+")\ .setName("integer")\ .setParseAction(lambda toks: int(toks[0])) -v_string = pp.MatchFirst( + +v_literal = pp.MatchFirst( [ pp.QuotedString("\"", escChar="\\", unquoteResults=True), pp.QuotedString("'", escChar="\\", unquoteResults=True), ] ) -v_literal = pp.MatchFirst( +v_naked_literal = pp.MatchFirst( [ - v_string, + v_literal, pp.Word("".join(i for i in pp.printables if i not in ",:")) ] ) @@ -121,6 +125,13 @@ class ValueLiteral: return self.val +class ValueNakedLiteral(ValueLiteral): + @classmethod + def expr(klass): + e = v_naked_literal.copy() + return e.setParseAction(lambda x: klass(*x)) + + class ValueGenerate: UNITS = dict( b = 1024**0, @@ -163,7 +174,7 @@ class ValueFile: @classmethod def expr(klass): e = pp.Literal("<").suppress() - e = e + v_literal + e = e + v_naked_literal return e.setParseAction(lambda x: klass(*x)) def get_generator(self, settings): @@ -197,7 +208,7 @@ class Body: @classmethod def expr(klass): - e = pp.Literal("b:").suppress() + e = pp.Literal("b").suppress() e = e + Value return e.setParseAction(lambda x: klass(*x)) @@ -208,7 +219,7 @@ class _Pause: @classmethod def expr(klass): - e = pp.Literal("p%s:"%klass.sub).suppress() + e = pp.Literal("p%s"%klass.sub).suppress() e = e + pp.MatchFirst( [ v_integer, @@ -273,7 +284,7 @@ class Header: @classmethod def expr(klass): - e = pp.Literal("h:").suppress() + e = pp.Literal("h").suppress() e += Value e += pp.Literal(":").suppress() e += Value @@ -294,7 +305,7 @@ class Code: def expr(klass): e = v_integer e = e + pp.Optional( - pp.Literal(":").suppress() + Value + Value ) return e.setParseAction(lambda x: klass(*x)) diff --git a/notes b/notes index 0513cb42..bd315ebe 100644 --- a/notes +++ b/notes @@ -1,92 +1,53 @@ -Value Specifiers: - - !500k - 500k of random data - !500k:utf8 - 500k of utf8. Other specifiers: utf8,alphanum,alpha,printable - "foo" - literal - foo - literal - destination - code>destination - - 301:VALUE>VALUE - 301>http://foo.bar - - - Content-type: - - t:content-type + !500k - 500k of random data + !500k:utf8 - 500k of utf8. Other specifiers: utf8,alphanum,alpha,printable + "foo" - literal + Date: Sat, 28 Apr 2012 17:28:40 +1200 Subject: More language tweaks. Separators this time - move to colon-separated features, use = for header key/value separation. --- libpathod/rparse.py | 8 ++++---- notes | 30 ++++++++++-------------------- test/test_rparse.py | 28 ++++++++++++++-------------- 3 files changed, 28 insertions(+), 38 deletions(-) diff --git a/libpathod/rparse.py b/libpathod/rparse.py index 18d05a69..79f133df 100644 --- a/libpathod/rparse.py +++ b/libpathod/rparse.py @@ -158,13 +158,13 @@ class ValueGenerate: u = reduce(operator.or_, [pp.Literal(i) for i in klass.UNITS.keys()]) e = e + pp.Optional(u, default=None) - s = pp.Literal(":").suppress() + s = pp.Literal("-").suppress() s += reduce(operator.or_, [pp.Literal(i) for i in DATATYPES.keys()]) e += pp.Optional(s, default="bytes") return e.setParseAction(lambda x: klass(*x)) def __str__(self): - return "!%s%s:%s"%(self.usize, self.unit, self.datatype) + return "!%s%s-%s"%(self.usize, self.unit, self.datatype) class ValueFile: @@ -286,7 +286,7 @@ class Header: def expr(klass): e = pp.Literal("h").suppress() e += Value - e += pp.Literal(":").suppress() + e += pp.Literal("=").suppress() e += Value return e.setParseAction(lambda x: klass(*x)) @@ -342,7 +342,7 @@ class Response: resp = pp.And( [ Code.expr(), - pp.ZeroOrMore(pp.Literal(",").suppress() + atom) + pp.ZeroOrMore(pp.Literal(":").suppress() + atom) ] ) return resp diff --git a/notes b/notes index bd315ebe..eac26c1b 100644 --- a/notes +++ b/notes @@ -1,45 +1,36 @@ Response: - code[msg],[comma-separated features] + code[msg]:[colon-separated features] Features: - hVALUE:VALUE Set header + hVALUE=VALUE Set header bVALUE Set body db Disconnect before sending data dr Disconnect randomly - pbTIME Pause before sending data for NUM seconds or forever - paTIME Pause after sending all data for NUM seconds or forever - prTIME Pause randomly for NUM seconds or forever - + pbNUM|forever Pause before sending data for NUM seconds or forever + paNUM|forever Pause after sending all data for NUM seconds or forever + prNUM|forever Pause randomly for NUM seconds or forever cVALUE Set Content-Type header lVALUE Set Location header -Time Specifiers: - - 5 - 5 seconds - 5s - 5 seconds - 5m - 5 minutes - 5h - 5 hours - - Value Specifiers: !500k - 500k of random data - !500k:utf8 - 500k of utf8. Other specifiers: utf8,alphanum,alpha,printable + !500k-utf8 - 500k of utf8. Other specifiers: utf8,alphanum,alpha,printable "foo" - literal Date: Sat, 28 Apr 2012 22:28:28 +1200 Subject: Unify pause and disconnect event frameworks. --- libpathod/rparse.py | 68 +++++++++++++++++++++++++---------------------------- notes | 2 -- test/test_rparse.py | 32 ++++++++++++++++++------- 3 files changed, 56 insertions(+), 46 deletions(-) diff --git a/libpathod/rparse.py b/libpathod/rparse.py index 79f133df..62f48d78 100644 --- a/libpathod/rparse.py +++ b/libpathod/rparse.py @@ -125,13 +125,6 @@ class ValueLiteral: return self.val -class ValueNakedLiteral(ValueLiteral): - @classmethod - def expr(klass): - e = v_naked_literal.copy() - return e.setParseAction(lambda x: klass(*x)) - - class ValueGenerate: UNITS = dict( b = 1024**0, @@ -232,19 +225,19 @@ class _Pause: class PauseBefore(_Pause): sub = "b" def mod_response(self, settings, r): - r.pauses.append((0, self.value)) + r.actions.append((0, "pause", self.value)) class PauseAfter(_Pause): sub = "a" def mod_response(self, settings, r): - r.pauses.append((sys.maxint, self.value)) + r.actions.append((sys.maxint, "pause", self.value)) class PauseRandom(_Pause): sub = "r" def mod_response(self, settings, r): - r.pauses.append(("random", self.value)) + r.actions.append(("random", "pause", self.value)) @@ -261,13 +254,13 @@ class _Disconnect: class DisconnectBefore(_Disconnect): sub = "b" def mod_response(self, settings, r): - r.pauses.append((0, self.value)) + r.actions.append((0, "disconnect")) class DisconnectRandom(_Disconnect): sub = "r" def mod_response(self, settings, r): - r.pauses.append(("random", self.value)) + r.actions.append(("random", "disconnect")) class Header: @@ -312,7 +305,7 @@ class Code: BLOCKSIZE = 1024 class Response: - comps = [ + comps = ( Body, Header, PauseBefore, @@ -320,14 +313,14 @@ class Response: PauseRandom, DisconnectBefore, DisconnectRandom, - ] + ) version = "HTTP/1.1" code = 200 msg = LiteralGenerator(http.RESPONSES[code]) body = LiteralGenerator("OK") def __init__(self): self.headers = [] - self.pauses = [] + self.actions = [] def get_header(self, hdr): for k, v in self.headers: @@ -374,30 +367,33 @@ class Response: else: tornado.ioloop.IOLoop.instance().add_timeout(time.time() + s, callback) - def write_values(self, fp, vals, pauses, disconnect, sofar=0, skip=0, blocksize=BLOCKSIZE): - if disconnect == "before": - fp.finish() - return + def write_values(self, fp, vals, actions, sofar=0, skip=0, blocksize=BLOCKSIZE): while vals: part = vals.pop() for i in range(skip, len(part), blocksize): d = part[i:i+blocksize] - if pauses and pauses[-1][0] < (sofar + len(d)): - p = pauses.pop() + if actions and actions[-1][0] < (sofar + len(d)): + p = actions.pop() offset = p[0]-sofar vals.append(part) - def pause_callback(): - self.write_values( - fp, vals, pauses, disconnect, - sofar=sofar+offset, - skip=i+offset, - blocksize=blocksize - ) - def flushed_callback(): - # Data has been flushed, set the timeout. - self.add_timeout(p[1], pause_callback) - fp.write(d[:offset], callback=flushed_callback) - return + if p[1] == "pause": + def pause_callback(): + self.write_values( + fp, vals, actions, + sofar=sofar+offset, + skip=i+offset, + blocksize=blocksize + ) + def flushed_callback(): + # Data has been flushed, set the timeout. + self.add_timeout(p[2], pause_callback) + fp.write(d[:offset], callback=flushed_callback) + return + elif p[1] == "disconnect": + fp.write(d[:offset]) + fp.finish() + fp.connection.stream.close() + return fp.write(d) sofar += len(d) skip = 0 @@ -431,9 +427,9 @@ class Response: self.body ]) vals.reverse() - pauses = self.ready_randoms(self.length(), self.pauses) - pauses.reverse() - return self.write_values(fp, vals, pauses, None) + actions = self.ready_randoms(self.length(), self.actions) + actions.reverse() + return self.write_values(fp, vals, actions) def __str__(self): parts = [ diff --git a/notes b/notes index eac26c1b..c70021fe 100644 --- a/notes +++ b/notes @@ -48,6 +48,4 @@ Anchors: Built-in help /help - /explain/expression - diff --git a/test/test_rparse.py b/test/test_rparse.py index c378f3e7..7c7ffa27 100644 --- a/test/test_rparse.py +++ b/test/test_rparse.py @@ -4,7 +4,17 @@ from libpathod import rparse rparse.TESTING = True + +class Sponge: + def __getattr__(self, x): + return Sponge() + + def __call__(self, *args, **kwargs): + pass + + class DummyRequest(StringIO.StringIO): + connection = Sponge() def write(self, d, callback=None): StringIO.StringIO.write(self, d) if callback: @@ -54,6 +64,7 @@ class uMisc(libpry.AutoTree): def test_file_value(self): v = rparse.Value.parseString("<'one two'")[0] + assert str(v) assert v.path == "one two" v = rparse.Value.parseString(" Date: Sat, 28 Apr 2012 22:51:36 +1200 Subject: Add a shortcut for setting content-type. --- libpathod/__init__.py | 1 - libpathod/rparse.py | 23 ++++++++++++++++++++++- test/test_rparse.py | 15 ++++++++++----- 3 files changed, 32 insertions(+), 7 deletions(-) diff --git a/libpathod/__init__.py b/libpathod/__init__.py index 4ef52a84..ff1b88f8 100644 --- a/libpathod/__init__.py +++ b/libpathod/__init__.py @@ -19,4 +19,3 @@ def application(**settings): static_path = utils.data.path("static"), template_path = utils.data.path("templates"), ) - diff --git a/libpathod/rparse.py b/libpathod/rparse.py index 62f48d78..4f7cc12d 100644 --- a/libpathod/rparse.py +++ b/libpathod/rparse.py @@ -192,6 +192,25 @@ Value = pp.MatchFirst( ) +class ShortcutContentType: + def __init__(self, value): + self.value = value + + def mod_response(self, settings, r): + r.headers.append( + ( + LiteralGenerator("Content-Type"), + self.value.get_generator(settings) + ) + ) + + @classmethod + def expr(klass): + e = pp.Literal("c").suppress() + e = e + Value + return e.setParseAction(lambda x: klass(*x)) + + class Body: def __init__(self, value): self.value = value @@ -240,7 +259,6 @@ class PauseRandom(_Pause): r.actions.append(("random", "pause", self.value)) - class _Disconnect: def __init__(self, value): self.value = value @@ -313,6 +331,7 @@ class Response: PauseRandom, DisconnectBefore, DisconnectRandom, + ShortcutContentType, ) version = "HTTP/1.1" code = 200 @@ -364,8 +383,10 @@ class Response: def add_timeout(self, s, callback): if TESTING: callback() + # begin nocover else: tornado.ioloop.IOLoop.instance().add_timeout(time.time() + s, callback) + # end nocover def write_values(self, fp, vals, actions, sofar=0, skip=0, blocksize=BLOCKSIZE): while vals: diff --git a/test/test_rparse.py b/test/test_rparse.py index 7c7ffa27..5a3cb53c 100644 --- a/test/test_rparse.py +++ b/test/test_rparse.py @@ -24,7 +24,6 @@ class DummyRequest(StringIO.StringIO): return - class uMisc(libpry.AutoTree): def test_generators(self): v = rparse.Value.parseString("'val'")[0] @@ -82,7 +81,6 @@ class uMisc(libpry.AutoTree): libpry.raises(rparse.ServerError, v.get_generator, dict(staticdir=t)) libpry.raises("no static directory", v.get_generator, dict()) - def test_generated_value(self): v = rparse.Value.parseString("!10b")[0] assert v.usize == 10 @@ -159,8 +157,9 @@ class uMisc(libpry.AutoTree): class uDisconnects(libpry.AutoTree): def test_parse(self): - assert rparse.parse({}, "400:db") - + assert (0, "disconnect") in rparse.parse({}, "400:db").actions + assert ("random", "disconnect") in rparse.parse({}, "400:dr").actions + def test_before(self): e = rparse.DisconnectBefore.expr() v = e.parseString("db")[0] @@ -178,6 +177,11 @@ class uDisconnects(libpry.AutoTree): assert isinstance(v, rparse.DisconnectRandom) +class uShortcutContentType(libpry.AutoTree): + def test_parse(self): + assert len(rparse.parse({}, "400:c'foo'").headers) == 1 + + class uPauses(libpry.AutoTree): def test_before(self): e = rparse.PauseBefore.expr() @@ -307,5 +311,6 @@ tests = [ uPauses(), uDisconnects(), uMisc(), - uparse() + uparse(), + uShortcutContentType() ] -- cgit v1.2.3 From ee909e265b40743479ca2f3e8b518f76c91f83f8 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Sat, 28 Apr 2012 22:54:45 +1200 Subject: Add a shortcut for setting Location header. --- libpathod/rparse.py | 21 +++++++++++++++++++++ test/test_rparse.py | 7 ++++--- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/libpathod/rparse.py b/libpathod/rparse.py index 4f7cc12d..b47681cc 100644 --- a/libpathod/rparse.py +++ b/libpathod/rparse.py @@ -211,6 +211,26 @@ class ShortcutContentType: return e.setParseAction(lambda x: klass(*x)) + +class ShortcutLocation: + def __init__(self, value): + self.value = value + + def mod_response(self, settings, r): + r.headers.append( + ( + LiteralGenerator("Location"), + self.value.get_generator(settings) + ) + ) + + @classmethod + def expr(klass): + e = pp.Literal("l").suppress() + e = e + Value + return e.setParseAction(lambda x: klass(*x)) + + class Body: def __init__(self, value): self.value = value @@ -332,6 +352,7 @@ class Response: DisconnectBefore, DisconnectRandom, ShortcutContentType, + ShortcutLocation, ) version = "HTTP/1.1" code = 200 diff --git a/test/test_rparse.py b/test/test_rparse.py index 5a3cb53c..c854b0c6 100644 --- a/test/test_rparse.py +++ b/test/test_rparse.py @@ -177,9 +177,10 @@ class uDisconnects(libpry.AutoTree): assert isinstance(v, rparse.DisconnectRandom) -class uShortcutContentType(libpry.AutoTree): +class uShortcuts(libpry.AutoTree): def test_parse(self): - assert len(rparse.parse({}, "400:c'foo'").headers) == 1 + assert rparse.parse({}, "400:c'foo'").headers[0][0][:] == "Content-Type" + assert rparse.parse({}, "400:l'foo'").headers[0][0][:] == "Location" class uPauses(libpry.AutoTree): @@ -312,5 +313,5 @@ tests = [ uDisconnects(), uMisc(), uparse(), - uShortcutContentType() + uShortcuts() ] -- cgit v1.2.3 From 77eca33f2695eea690dff7999c0e1bd3df0e1733 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Sun, 29 Apr 2012 10:56:33 +1200 Subject: Refactor application definitions and startup. Also, create one of the dodgiest web testing trusses in history. Tornado just seems to have no nice way of doing this. --- libpathod/__init__.py | 20 ---------- libpathod/app.py | 83 ++++++++++++++++++++++++++++++++++++++++++ libpathod/handlers.py | 45 ----------------------- libpathod/templates/frame.html | 4 +- pathod | 9 +++-- test/test_app.py | 35 ++++++++++++++++++ todo | 5 ++- 7 files changed, 128 insertions(+), 73 deletions(-) create mode 100644 libpathod/app.py delete mode 100644 libpathod/handlers.py create mode 100644 test/test_app.py diff --git a/libpathod/__init__.py b/libpathod/__init__.py index ff1b88f8..8b137891 100644 --- a/libpathod/__init__.py +++ b/libpathod/__init__.py @@ -1,21 +1 @@ -from tornado import web, template -import handlers, utils -class PathodApp(web.Application): - def __init__(self, *args, **kwargs): - self.templates = template.Loader(utils.data.path("templates")) - web.Application.__init__(self, *args, **kwargs) - - -def application(**settings): - return PathodApp( - [ - (r"/", handlers.Index), - (r"/log", handlers.Log), - (r"/help", handlers.Help), - (r"/preview", handlers.Preview), - (r"/p/.*", handlers.Pathod, settings), - ], - static_path = utils.data.path("static"), - template_path = utils.data.path("templates"), - ) diff --git a/libpathod/app.py b/libpathod/app.py new file mode 100644 index 00000000..15d99023 --- /dev/null +++ b/libpathod/app.py @@ -0,0 +1,83 @@ +import urllib +import tornado.web, tornado.template, tornado.ioloop, tornado.httpserver +import rparse, utils + +class _Page(tornado.web.RequestHandler): + def render(self, name, **kwargs): + b = self.application.templates.load(name + ".html").generate(**kwargs) + self.write(b) + + +class Index(_Page): + name = "index" + section = "main" + def get(self): + self.render(self.name, section=self.section) + + +class Preview(_Page): + name = "preview" + section = "main" + def get(self): + self.render(self.name, section=self.section) + + +class Help(_Page): + name = "help" + section = "help" + def get(self): + self.render(self.name, section=self.section) + + +class Log(_Page): + name = "log" + section = "log" + def get(self): + self.render(self.name, section=self.section) + + +class Pathod(object): + anchor = "/p/" + def __init__(self, application, request, **settings): + self.application, self.request, self.settings = application, request, settings + spec = urllib.unquote(self.request.uri)[len(self.anchor):] + try: + self.response = rparse.parse(self.settings, spec) + except rparse.ParseException, v: + self.response = rparse.InternalResponse( + 800, + "Error parsing response spec: %s\n"%v.msg + v.marked() + ) + + def _execute(self, transforms, *args, **kwargs): + self.response.render(self.request) + + +class PathodApp(tornado.web.Application): + def __init__(self, **settings): + self.templates = tornado.template.Loader(utils.data.path("templates")) + tornado.web.Application.__init__( + self, + [ + (r"/", Index), + (r"/log", Log), + (r"/help", Help), + (r"/preview", Preview), + (r"/p/.*", Pathod, settings), + ], + static_path = utils.data.path("static"), + template_path = utils.data.path("templates"), + debug=True + ) + + +# begin nocover +def run(application, port, ssl_options): + http_server = tornado.httpserver.HTTPServer( + application, + ssl_options=ssl_options + ) + http_server.listen(port) + tornado.ioloop.IOLoop.instance().start() + + diff --git a/libpathod/handlers.py b/libpathod/handlers.py deleted file mode 100644 index ebf85b03..00000000 --- a/libpathod/handlers.py +++ /dev/null @@ -1,45 +0,0 @@ -import urllib -import tornado.web -import rparse - -class _Page(tornado.web.RequestHandler): - def render(self, name, **kwargs): - b = self.application.templates.load(name).generate(**kwargs) - self.write(b) - - -class Index(_Page): - def get(self): - self.render("index.html", section="main") - - -class Preview(_Page): - def get(self): - self.render("index.html", section="main") - - -class Help(_Page): - def get(self): - self.render("help.html", section="help") - - -class Log(_Page): - def get(self): - self.render("log.html", section="log") - - -class Pathod(object): - anchor = "/p/" - def __init__(self, application, request, **settings): - self.application, self.request, self.settings = application, request, settings - spec = urllib.unquote(self.request.uri)[len(self.anchor):] - try: - self.response = rparse.parse(self.settings, spec) - except rparse.ParseException, v: - self.response = rparse.InternalResponse( - 800, - "Error parsing response spec: %s\n"%v.msg + v.marked() - ) - - def _execute(self, transforms, *args, **kwargs): - self.response.render(self.request) diff --git a/libpathod/templates/frame.html b/libpathod/templates/frame.html index f985aaa1..08dbd1ae 100644 --- a/libpathod/templates/frame.html +++ b/libpathod/templates/frame.html @@ -2,7 +2,7 @@ - Omnid + Pathod - - + -- cgit v1.2.3 From 1d1098687cd397966cf810b52cbf9fe1bc8b1ec4 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Mon, 23 Jul 2012 17:42:44 +1200 Subject: 100% test coverage for pathoc.py --- libpathod/templates/docs_pathod.html | 2 +- test/test_pathoc.py | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/libpathod/templates/docs_pathod.html b/libpathod/templates/docs_pathod.html index 96866579..977642c6 100644 --- a/libpathod/templates/docs_pathod.html +++ b/libpathod/templates/docs_pathod.html @@ -54,7 +54,7 @@ various other goodies. Try it by visiting the server root:

200"YAY"
-

The quoted string here is an example of a Value +

The quoted string here is an example of a Value Specifier, a syntax that is used throughout the pathod response specification language. In this case, the quotes mean we're specifying a literal string, but there are many other fun things we can do. For example, we diff --git a/test/test_pathoc.py b/test/test_pathoc.py index 784e2ee3..15493e96 100644 --- a/test/test_pathoc.py +++ b/test/test_pathoc.py @@ -24,6 +24,17 @@ class TestDaemon: _, _, _, _, content = c.request("get:/api/info") assert tuple(json.loads(content)["version"]) == version.IVERSION + def test_timeout(self): + c = pathoc.Pathoc("127.0.0.1", self.d.port) + c.connect() + c.settimeout(0.01) + + s = cStringIO.StringIO() + c.print_requests( + ["get:'/p/200:p10,0'"], True, True, s + ) + assert "Timeout" in s.getvalue() + def tval(self, requests, verbose=False): c = pathoc.Pathoc("127.0.0.1", self.d.port) c.connect() -- cgit v1.2.3 From 763e1ff7862e8784170e7c23a015eceaa19e2f70 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Mon, 23 Jul 2012 17:53:17 +1200 Subject: pathod.py unit tests++ --- libpathod/pathod.py | 2 +- libpathod/rparse.py | 12 +++--------- test/test_pathod.py | 5 +++++ test/test_rparse.py | 2 +- 4 files changed, 10 insertions(+), 11 deletions(-) diff --git a/libpathod/pathod.py b/libpathod/pathod.py index f6b5e0f9..12719d13 100644 --- a/libpathod/pathod.py +++ b/libpathod/pathod.py @@ -129,7 +129,7 @@ class PathodHandler(tcp.BaseHandler): try: if not self.handle_request(): return - except tcp.NetLibDisconnect: + except tcp.NetLibDisconnect: # pragma: no cover self.info("Disconnect") self.server.add_log( dict( diff --git a/libpathod/rparse.py b/libpathod/rparse.py index bdce0dd7..e4b62822 100644 --- a/libpathod/rparse.py +++ b/libpathod/rparse.py @@ -22,8 +22,6 @@ class ParseException(Exception): return "%s at offset %s of %s"%(self.msg, self.col, repr(self.s)) -class ServerError(Exception): pass - def actions_log(lst): ret = [] @@ -250,15 +248,15 @@ class ValueFile: uf = settings.get("unconstrained_file_access") sd = settings.get("staticdir") if not sd: - raise ServerError("File access disabled.") + raise FileAccessDenied("File access disabled.") sd = os.path.normpath(os.path.abspath(sd)) s = os.path.expanduser(self.path) s = os.path.normpath(os.path.abspath(os.path.join(sd, s))) if not uf and not s.startswith(sd): - raise ServerError("File access outside of configured directory") + raise FileAccessDenied("File access outside of configured directory") if not os.path.isfile(s): - raise ServerError("File not readable") + raise FileAccessDenied("File not readable") return FileGenerator(s) def __str__(self): @@ -707,10 +705,6 @@ class PathodErrorResponse(Response): LiteralGenerator("Content-Type"), LiteralGenerator("text/plain") ), - ( - LiteralGenerator("Content-Length"), - LiteralGenerator(str(len(self.body))) - ) ] def serve(self, fp, check=None): diff --git a/test/test_pathod.py b/test/test_pathod.py index fae00ec2..0adba8e6 100644 --- a/test/test_pathod.py +++ b/test/test_pathod.py @@ -85,6 +85,11 @@ class CommonTests(tutils.DaemonTests): rsp = self.get("=nonexistent") assert rsp.status_code == 800 + def test_source_access_denied(self): + rsp = self.get("200:b Date: Mon, 23 Jul 2012 19:25:57 +1200 Subject: 100% unit test coverage --- libpathod/rparse.py | 4 ++-- test/test_rparse.py | 7 ++++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/libpathod/rparse.py b/libpathod/rparse.py index e4b62822..5e435628 100644 --- a/libpathod/rparse.py +++ b/libpathod/rparse.py @@ -84,7 +84,7 @@ def write_values(fp, vals, actions, sofar=0, skip=0, blocksize=BLOCKSIZE): send_chunk(fp, a[2], blocksize, 0, len(a[2])) send_chunk(fp, v, blocksize, offset, len(v)) sofar += len(v) - except tcp.NetLibDisconnect: + except tcp.NetLibDisconnect: # pragma: no cover return True @@ -188,7 +188,7 @@ class _Value: def get_generator(self, settings): return LiteralGenerator(self.val) - def __str__(self): + def __repr__(self): return self.val diff --git a/test/test_rparse.py b/test/test_rparse.py index 88c1b617..70395bdc 100644 --- a/test/test_rparse.py +++ b/test/test_rparse.py @@ -38,6 +38,7 @@ class TestMisc: assert g[0] == "x" assert g[-1] == "x" assert g[0:5] == "xxxxx" + assert repr(g) def test_valueliteral(self): v = rparse.ValueLiteral("foo") @@ -47,10 +48,12 @@ class TestMisc: v = rparse.ValueLiteral(r"foo\n") assert v.expr() assert v.val == "foo\n" + assert repr(v) def test_valuenakedliteral(self): v = rparse.ValueNakedLiteral("foo") assert v.expr() + assert repr(v) def test_file_value(self): v = rparse.Value.parseString("<'one two'")[0] @@ -104,7 +107,9 @@ class TestMisc: def test_path(self): e = rparse.Path.expr() assert e.parseString('"/foo"')[0].value.val == "/foo" - + e = rparse.Path("/foo") + assert e.value.val == "/foo" + def test_method(self): e = rparse.Method.expr() assert e.parseString("get")[0].value.val == "GET" -- cgit v1.2.3 From 622a2b560753f2e4aa96bfb4e97bfcac4d0bedbd Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Mon, 23 Jul 2012 19:55:33 +1200 Subject: Add a --noweb option to turn web iface off, refactor unit tests. --- libpathod/pathod.py | 16 +++++++++++++--- libpathod/test.py | 10 ++++++---- pathod | 20 ++++++++++---------- test/test_pathod.py | 18 +++++++++++++++--- test/tutils.py | 13 ++++++++----- 5 files changed, 52 insertions(+), 25 deletions(-) diff --git a/libpathod/pathod.py b/libpathod/pathod.py index 12719d13..c95a8ed0 100644 --- a/libpathod/pathod.py +++ b/libpathod/pathod.py @@ -92,7 +92,13 @@ class PathodHandler(tcp.BaseHandler): ) ) if response_log["disconnect"]: - return + return False + return True + + if self.server.noweb: + crafted = rparse.PathodErrorResponse("Access Denied") + crafted.serve(self.wfile, self.server.check_size) + return False else: cc = wsgi.ClientConn(self.client_address) req = wsgi.Request(cc, "http", method, path, headers, content) @@ -105,7 +111,7 @@ class PathodHandler(tcp.BaseHandler): ) app.serve(req, self.wfile) self.debug("%s %s"%(method, path)) - return True + return True def handle(self): if self.server.ssloptions: @@ -142,7 +148,10 @@ class PathodHandler(tcp.BaseHandler): class Pathod(tcp.TCPServer): LOGBUF = 500 - def __init__(self, addr, ssloptions=None, prefix="/p/", staticdir=None, anchors=None, sizelimit=None): + def __init__( self, + addr, ssloptions=None, prefix="/p/", staticdir=None, anchors=None, + sizelimit=None, noweb=False + ): """ addr: (address, port) tuple. If port is 0, a free port will be automatically chosen. @@ -158,6 +167,7 @@ class Pathod(tcp.TCPServer): self.prefix = prefix self.sizelimit = sizelimit self.app = app.app + self.noweb = noweb self.app.config["pathod"] = self self.log = [] self.logid = 0 diff --git a/libpathod/test.py b/libpathod/test.py index ff5dac30..e8ca536a 100644 --- a/libpathod/test.py +++ b/libpathod/test.py @@ -5,9 +5,9 @@ import pathod, utils IFACE = "127.0.0.1" class Daemon: - def __init__(self, staticdir=None, anchors=(), ssl=None, sizelimit=None): + def __init__(self, staticdir=None, anchors=(), ssl=None, sizelimit=None, noweb=False): self.q = Queue.Queue() - self.thread = PaThread(self.q, staticdir, anchors, ssl, sizelimit) + self.thread = PaThread(self.q, staticdir, anchors, ssl, sizelimit, noweb) self.thread.start() self.port = self.q.get(True, 5) self.urlbase = "%s://%s:%s"%("https" if ssl else "http", IFACE, self.port) @@ -42,9 +42,10 @@ class Daemon: class PaThread(threading.Thread): - def __init__(self, q, staticdir, anchors, ssl, sizelimit): + def __init__(self, q, staticdir, anchors, ssl, sizelimit, noweb): threading.Thread.__init__(self) self.q, self.staticdir, self.anchors, self.ssl, self.sizelimit = q, staticdir, anchors, ssl, sizelimit + self.noweb = noweb def run(self): if self.ssl is True: @@ -59,7 +60,8 @@ class PaThread(threading.Thread): ssloptions = ssloptions, anchors = self.anchors, staticdir = self.staticdir, - sizelimit = self.sizelimit + sizelimit = self.sizelimit, + noweb = self.noweb ) self.q.put(self.server.port) self.server.serve_forever() diff --git a/pathod b/pathod index 052b94bb..d2635ff5 100755 --- a/pathod +++ b/pathod @@ -15,28 +15,27 @@ if __name__ == "__main__": help='Directory for static files.' ) parser.add_argument( - "--debug", dest='debug', default=False, - action="store_true", + "--debug", dest='debug', default=False, action="store_true", help='Enable debug output.' ) parser.add_argument( - "-s", dest='ssl', default=False, - action="store_true", + "-s", dest='ssl', default=False, action="store_true", help='Serve with SSL.' ) parser.add_argument( - "--limit-size", dest='sizelimit', default=None, - type=str, + "--limit-size", dest='sizelimit', default=None, type=str, help='Size limit of served responses. Understands size suffixes, i.e. 100k.' ) parser.add_argument( - "--keyfile", dest='ssl_keyfile', default=None, - type=str, + "--noweb", dest='noweb', default=False, action="store_true", + help='Disable web interface and API.' + ) + parser.add_argument( + "--keyfile", dest='ssl_keyfile', default=None, type=str, help='SSL key file. If not specified, a default key is used.' ) parser.add_argument( - "--certfile", dest='ssl_certfile', default=None, - type=str, + "--certfile", dest='ssl_certfile', default=None, type=str, help='SSL cert file. If not specified, a default cert is used.' ) args = parser.parse_args() @@ -86,6 +85,7 @@ if __name__ == "__main__": staticdir = args.staticdir, anchors = alst, sizelimit = sizelimit, + noweb = args.noweb ) except pathod.PathodError, v: parser.error(str(v)) diff --git a/test/test_pathod.py b/test/test_pathod.py index 0adba8e6..58477620 100644 --- a/test/test_pathod.py +++ b/test/test_pathod.py @@ -1,5 +1,6 @@ from libpathod import pathod, version from netlib import tcp, http +import requests import tutils class TestPathod: @@ -26,6 +27,17 @@ class TestPathod: assert len(p.get_log()) <= p.LOGBUF +class TestNoWeb(tutils.DaemonTests): + noweb = True + def setUp(self): + # Over ride log clearing + pass + + def test_noweb(self): + assert self.get("200").status_code == 200 + assert self.getpath("/").status_code == 800 + + class CommonTests(tutils.DaemonTests): def test_sizelimit(self): r = self.get("200:b@1g") @@ -67,7 +79,7 @@ class CommonTests(tutils.DaemonTests): def test_invalid_first_line(self): c = tcp.TCPClient("localhost", self.d.port) c.connect() - if self.SSL: + if self.ssl: c.convert_to_ssl() c.wfile.write("foo\n\n\n") c.wfile.flush() @@ -92,11 +104,11 @@ class CommonTests(tutils.DaemonTests): class TestDaemon(CommonTests): - SSL = False + ssl = False class TestDaemonSSL(CommonTests): - SSL = True + ssl = True def test_ssl_conn_failure(self): c = tcp.TCPClient("localhost", self.d.port) c.rbufsize = 0 diff --git a/test/tutils.py b/test/tutils.py index 3b430825..b1e277e7 100644 --- a/test/tutils.py +++ b/test/tutils.py @@ -4,13 +4,16 @@ from libpathod import utils, test, pathoc import requests class DaemonTests: + noweb = False + ssl = False @classmethod def setUpAll(self): self.d = test.Daemon( staticdir=test_data.path("data"), anchors=[("/anchor/.*", "202")], - ssl = self.SSL, - sizelimit=1*1024*1024 + ssl = self.ssl, + sizelimit=1*1024*1024, + noweb = self.noweb ) @classmethod @@ -21,19 +24,19 @@ class DaemonTests: self.d.clear_log() def getpath(self, path, params=None): - scheme = "https" if self.SSL else "http" + scheme = "https" if self.ssl else "http" return requests.get( "%s://localhost:%s/%s"%(scheme, self.d.port, path), verify=False, params=params ) def get(self, spec): - scheme = "https" if self.SSL else "http" + scheme = "https" if self.ssl else "http" return requests.get("%s://localhost:%s/p/%s"%(scheme, self.d.port, spec), verify=False) def pathoc(self, spec, timeout=None): c = pathoc.Pathoc("localhost", self.d.port) c.connect() - if self.SSL: + if self.ssl: c.convert_to_ssl() if timeout: c.settimeout(timeout) -- cgit v1.2.3 From 190392ea13f998fe298d48738131779f522d62e9 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Mon, 23 Jul 2012 21:39:31 +1200 Subject: Add a --nocraft option to pathod that turns off crafting. --- libpathod/pathod.py | 63 +++++++++++++++++++++++++++-------------------------- pathod | 7 +++++- 2 files changed, 38 insertions(+), 32 deletions(-) diff --git a/libpathod/pathod.py b/libpathod/pathod.py index c95a8ed0..4d1f9e2c 100644 --- a/libpathod/pathod.py +++ b/libpathod/pathod.py @@ -18,6 +18,19 @@ class PathodHandler(tcp.BaseHandler): def handle_sni(self, connection): self.sni = connection.get_servername() + def serve_crafted(self, crafted, request_log): + response_log = crafted.serve(self.wfile, self.server.check_size) + self.server.add_log( + dict( + type = "crafted", + request=request_log, + response=response_log + ) + ) + if response_log["disconnect"]: + return False + return True + def handle_request(self): """ Returns True if handling should continue. @@ -39,9 +52,18 @@ class PathodHandler(tcp.BaseHandler): ) ) return - method, path, httpversion = parts + method, path, httpversion = parts headers = http.read_headers(self.rfile) + request_log = dict( + path = path, + method = method, + headers = headers.lst, + httpversion = httpversion, + sni = self.sni, + remote_address = self.client_address, + ) + try: content = http.read_http_body_request( self.rfile, self.wfile, headers, httpversion, None @@ -57,45 +79,23 @@ class PathodHandler(tcp.BaseHandler): ) return - crafted = None for i in self.server.anchors: if i[0].match(path): - crafted = i[1] + return self.serve_crafted(i[1], request_log) - if not crafted and path.startswith(self.server.prefix): + if not self.server.nocraft and path.startswith(self.server.prefix): spec = urllib.unquote(path)[len(self.server.prefix):] try: crafted = rparse.parse_response(self.server.request_settings, spec) except rparse.ParseException, v: crafted = rparse.PathodErrorResponse( - "Parse Error", - "Error parsing response spec: %s\n"%v.msg + v.marked() - ) + "Parse Error", + "Error parsing response spec: %s\n"%v.msg + v.marked() + ) except rparse.FileAccessDenied: crafted = rparse.PathodErrorResponse("Access Denied") - - request_log = dict( - path = path, - method = method, - headers = headers.lst, - sni = self.sni, - remote_address = self.client_address, - httpversion = httpversion, - ) - if crafted: - response_log = crafted.serve(self.wfile, self.server.check_size) - self.server.add_log( - dict( - type = "crafted", - request=request_log, - response=response_log - ) - ) - if response_log["disconnect"]: - return False - return True - - if self.server.noweb: + return self.serve_crafted(crafted, request_log) + elif self.server.noweb: crafted = rparse.PathodErrorResponse("Access Denied") crafted.serve(self.wfile, self.server.check_size) return False @@ -150,7 +150,7 @@ class Pathod(tcp.TCPServer): LOGBUF = 500 def __init__( self, addr, ssloptions=None, prefix="/p/", staticdir=None, anchors=None, - sizelimit=None, noweb=False + sizelimit=None, noweb=False, nocraft=False ): """ addr: (address, port) tuple. If port is 0, a free port will be @@ -168,6 +168,7 @@ class Pathod(tcp.TCPServer): self.sizelimit = sizelimit self.app = app.app self.noweb = noweb + self.nocraft = nocraft self.app.config["pathod"] = self self.log = [] self.logid = 0 diff --git a/pathod b/pathod index d2635ff5..0bb8da31 100755 --- a/pathod +++ b/pathod @@ -30,6 +30,10 @@ if __name__ == "__main__": "--noweb", dest='noweb', default=False, action="store_true", help='Disable 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( "--keyfile", dest='ssl_keyfile', default=None, type=str, help='SSL key file. If not specified, a default key is used.' @@ -85,7 +89,8 @@ if __name__ == "__main__": staticdir = args.staticdir, anchors = alst, sizelimit = sizelimit, - noweb = args.noweb + noweb = args.noweb, + nocraft = args.nocraft ) except pathod.PathodError, v: parser.error(str(v)) -- cgit v1.2.3 From a950a4d7a3097685d54f325f32a169034e9435f1 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Mon, 23 Jul 2012 23:31:26 +1200 Subject: Add pathod --noapi to turn off the service API. --- libpathod/app.py | 29 +++++++++++++++-------------- libpathod/pathod.py | 7 ++++--- pathod | 9 +++++++-- 3 files changed, 26 insertions(+), 19 deletions(-) diff --git a/libpathod/app.py b/libpathod/app.py index c94e61ca..2c4cea23 100644 --- a/libpathod/app.py +++ b/libpathod/app.py @@ -5,24 +5,25 @@ import version, rparse, utils logging.basicConfig(level="DEBUG") app = Flask(__name__) -@app.route('/api/info') -def api_info(): - return jsonify( - version = version.IVERSION - ) +def api(): + @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/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" + @app.route('/api/clear_log') + def api_clear_log(): + app.config["pathod"].clear_log() + return "OK" @app.route('/') diff --git a/libpathod/pathod.py b/libpathod/pathod.py index 4d1f9e2c..90064581 100644 --- a/libpathod/pathod.py +++ b/libpathod/pathod.py @@ -150,7 +150,7 @@ class Pathod(tcp.TCPServer): LOGBUF = 500 def __init__( self, addr, ssloptions=None, prefix="/p/", staticdir=None, anchors=None, - sizelimit=None, noweb=False, nocraft=False + sizelimit=None, noweb=False, nocraft=False, noapi=False ): """ addr: (address, port) tuple. If port is 0, a free port will be @@ -166,9 +166,10 @@ class Pathod(tcp.TCPServer): self.staticdir = staticdir self.prefix = prefix self.sizelimit = sizelimit + self.noweb, self.nocraft, self.noapi = noweb, nocraft, noapi + if not noapi: + app.api() self.app = app.app - self.noweb = noweb - self.nocraft = nocraft self.app.config["pathod"] = self self.log = [] self.logid = 0 diff --git a/pathod b/pathod index 0bb8da31..a5cc02ca 100755 --- a/pathod +++ b/pathod @@ -26,9 +26,13 @@ if __name__ == "__main__": "--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( "--noweb", dest='noweb', default=False, action="store_true", - help='Disable web interface and API.' + help='Disable both web interface and API.' ) parser.add_argument( "--nocraft", dest='nocraft', default=False, action="store_true", @@ -90,7 +94,8 @@ if __name__ == "__main__": anchors = alst, sizelimit = sizelimit, noweb = args.noweb, - nocraft = args.nocraft + nocraft = args.nocraft, + noapi = args.noapi ) except pathod.PathodError, v: parser.error(str(v)) -- cgit v1.2.3 From 2dd2137d44d0bc7e3e25d4ce14610acfe88cb4f2 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Tue, 24 Jul 2012 00:00:55 +1200 Subject: Better handling of binary data output by pathoc. --- libpathod/pathoc.py | 10 +++++----- libpathod/utils.py | 4 ++++ 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/libpathod/pathoc.py b/libpathod/pathoc.py index 3e5204c2..8fe7b7f6 100644 --- a/libpathod/pathoc.py +++ b/libpathod/pathoc.py @@ -1,18 +1,18 @@ import sys, os from netlib import tcp, http -import rparse +import rparse, utils class PathocError(Exception): pass def print_short(fp, httpversion, code, msg, headers, content): - print >> fp, "<< %s %s: %s bytes"%(code, msg, len(content)) + print >> fp, "<< %s %s: %s bytes"%(code, utils.xrepr(msg), len(content)) def print_full(fp, httpversion, code, msg, headers, content): - print >> fp, "<< HTTP%s/%s %s %s"%(httpversion[0], httpversion[1], code, msg) - print >> fp, headers - print >> fp, content + print >> fp, "<< HTTP%s/%s %s %s"%(httpversion[0], httpversion[1], code, utils.xrepr(msg)) + print >> fp, utils.escape_unprintables(str(headers)) + print >> fp, utils.escape_unprintables(content) class Pathoc(tcp.TCPClient): diff --git a/libpathod/utils.py b/libpathod/utils.py index 40f37cab..1ee50b83 100644 --- a/libpathod/utils.py +++ b/libpathod/utils.py @@ -41,6 +41,10 @@ def parse_anchor_spec(s): return tuple(s.split("=", 1)) +def xrepr(s): + return repr(s)[1:-1] + + def escape_unprintables(s): s = s.replace("\r\n", "PATHOD_MARKER_RN") s = s.replace("\n", "PATHOD_MARKER_N") -- cgit v1.2.3 From dbed251fb9e514c8d244a03eb1c84aed9ddb0988 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Tue, 24 Jul 2012 12:18:14 +1200 Subject: Add a raw modifier to requests and responses, which turn off automatic additions. For now, this just turns off adding a Content-Length header when a body is specified. --- libpathod/rparse.py | 36 +++++++++++++++++++++++++----------- test/test_rparse.py | 17 ++++++++++++++++- 2 files changed, 41 insertions(+), 12 deletions(-) diff --git a/libpathod/rparse.py b/libpathod/rparse.py index 5e435628..38b38232 100644 --- a/libpathod/rparse.py +++ b/libpathod/rparse.py @@ -8,6 +8,7 @@ TRUNCATE = 1024 class FileAccessDenied(Exception): pass + class ParseException(Exception): def __init__(self, msg, s, col): Exception.__init__(self) @@ -22,7 +23,6 @@ class ParseException(Exception): return "%s at offset %s of %s"%(self.msg, self.col, repr(self.s)) - def actions_log(lst): ret = [] for i in lst: @@ -344,6 +344,16 @@ class Body: return e.setParseAction(lambda x: klass(*x)) +class Raw: + def accept(self, settings, r): + r.raw = True + + @classmethod + def expr(klass): + e = pp.Literal("r").suppress() + return e.setParseAction(lambda x: klass(*x)) + + class Path: def __init__(self, value): if isinstance(value, basestring): @@ -496,6 +506,12 @@ class Code: class Message: version = "HTTP/1.1" + def __init__(self): + self.body = LiteralGenerator("") + self.headers = [] + self.actions = [] + self.raw = False + def length(self): """ Calculate the length of the base message without any applied actions. @@ -528,12 +544,12 @@ class Message: fp: The file pointer to write to. check: A function called with the effective actions (after random - values have been calculated). If it returns False service - proceeds, otherwise the return is treated as an error message to be - sent to the client, and service stops. + values have been calculated). If it returns False service proceeds, + otherwise the return is treated as an error message to be sent to + the client, and service stops. """ started = time.time() - if self.body and not utils.get_header("Content-Length", self.headers): + if self.body and not self.raw and not utils.get_header("Content-Length", self.headers): self.headers.append( ( LiteralGenerator("Content-Length"), @@ -596,14 +612,13 @@ class Response(Message): InjectAt, ShortcutContentType, ShortcutLocation, + Raw ) logattrs = ["code", "version"] def __init__(self): - self.headers = [] - self.actions = [] + Message.__init__(self) self.code = 200 self.msg = LiteralGenerator(http_status.RESPONSES[self.code]) - self.body = LiteralGenerator("") def preamble(self): return [self.version, " ", str(self.code), " ", self.msg] @@ -635,14 +650,13 @@ class Request(Message): DisconnectAt, InjectAt, ShortcutContentType, + Raw ) logattrs = ["method", "path"] def __init__(self): + Message.__init__(self) self.method = None self.path = None - self.body = LiteralGenerator("") - self.headers = [] - self.actions = [] def preamble(self): return [self.method, " ", self.path, " ", self.version] diff --git a/test/test_rparse.py b/test/test_rparse.py index 70395bdc..24ca3653 100644 --- a/test/test_rparse.py +++ b/test/test_rparse.py @@ -109,13 +109,17 @@ class TestMisc: assert e.parseString('"/foo"')[0].value.val == "/foo" e = rparse.Path("/foo") assert e.value.val == "/foo" - + def test_method(self): e = rparse.Method.expr() assert e.parseString("get")[0].value.val == "GET" assert e.parseString("'foo'")[0].value.val == "foo" assert e.parseString("'get'")[0].value.val == "get" + def test_raw(self): + e = rparse.Raw.expr() + assert e.parseString("r")[0] + def test_body(self): e = rparse.Body.expr() v = e.parseString("b'foo'")[0] @@ -436,6 +440,17 @@ class TestResponse: r = rparse.parse_response({}, "400'msg'") assert r.serve(s) + def test_raw(self): + s = cStringIO.StringIO() + r = rparse.parse_response({}, "400:b'foo'") + r.serve(s) + assert "Content-Length" in s.getvalue() + + s = cStringIO.StringIO() + r = rparse.parse_response({}, "400:b'foo':r") + r.serve(s) + assert not "Content-Length" in s.getvalue() + def test_length(self): def testlen(x): s = cStringIO.StringIO() -- cgit v1.2.3 From a9e60fa3e66d9de28233aab89b426ac74d1f8ad6 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Tue, 24 Jul 2012 12:46:14 +1200 Subject: Add a Date header to server responses, unless raw is set. --- libpathod/rparse.py | 40 ++++++++++++++++++++++++++++------------ test/test_rparse.py | 8 ++++++-- 2 files changed, 34 insertions(+), 14 deletions(-) diff --git a/libpathod/rparse.py b/libpathod/rparse.py index 38b38232..767cb861 100644 --- a/libpathod/rparse.py +++ b/libpathod/rparse.py @@ -1,6 +1,8 @@ import operator, string, random, mmap, os, time +from email.utils import formatdate import contrib.pyparsing as pp from netlib import http_status, tcp + import utils BLOCKSIZE = 1024 @@ -539,7 +541,7 @@ class Message: l += len(i[2]) return l - def serve(self, fp, check): + def serve(self, fp, check, is_request): """ fp: The file pointer to write to. @@ -547,15 +549,30 @@ class Message: values have been calculated). If it returns False service proceeds, otherwise the return is treated as an error message to be sent to the client, and service stops. + + is_request: Is this a request? If False, we assume it's a response. + Used to decide what standard modifications to make if raw is not + set. """ started = time.time() - if self.body and not self.raw and not utils.get_header("Content-Length", self.headers): - self.headers.append( - ( - LiteralGenerator("Content-Length"), - LiteralGenerator(str(len(self.body))), + if not self.raw: + if self.body and not utils.get_header("Content-Length", self.headers): + self.headers.append( + ( + LiteralGenerator("Content-Length"), + LiteralGenerator(str(len(self.body))), + ) ) - ) + if is_request: + pass + else: + if not utils.get_header("Date", self.headers): + self.headers.append( + ( + LiteralGenerator("Date"), + LiteralGenerator(formatdate(timeval=None, localtime=False, usegmt=True)) + ) + ) hdrs = [] for k, v in self.headers: @@ -690,7 +707,7 @@ class CraftedRequest(Request): i.accept(settings, self) def serve(self, fp, check=None): - d = Request.serve(self, fp, check) + d = Request.serve(self, fp, check, True) d["spec"] = self.spec return d @@ -703,7 +720,7 @@ class CraftedResponse(Response): i.accept(settings, self) def serve(self, fp, check=None): - d = Response.serve(self, fp, check) + d = Response.serve(self, fp, check, False) d["spec"] = self.spec return d @@ -716,13 +733,12 @@ class PathodErrorResponse(Response): self.body = LiteralGenerator("pathod error: " + (body or msg)) self.headers = [ ( - LiteralGenerator("Content-Type"), - LiteralGenerator("text/plain") + LiteralGenerator("Content-Type"), LiteralGenerator("text/plain") ), ] def serve(self, fp, check=None): - d = Response.serve(self, fp, check) + d = Response.serve(self, fp, check, False) d["internal"] = True return d diff --git a/test/test_rparse.py b/test/test_rparse.py index 24ca3653..ef724d68 100644 --- a/test/test_rparse.py +++ b/test/test_rparse.py @@ -444,12 +444,16 @@ class TestResponse: s = cStringIO.StringIO() r = rparse.parse_response({}, "400:b'foo'") r.serve(s) - assert "Content-Length" in s.getvalue() + v = s.getvalue() + assert "Content-Length" in v + assert "Date" in v s = cStringIO.StringIO() r = rparse.parse_response({}, "400:b'foo':r") r.serve(s) - assert not "Content-Length" in s.getvalue() + v = s.getvalue() + assert not "Content-Length" in v + assert not "Date" in v def test_length(self): def testlen(x): -- cgit v1.2.3 From 9502eeadaa8463019059137584a75597e20a5544 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Tue, 24 Jul 2012 12:52:53 +1200 Subject: Document raw flag. --- libpathod/templates/docs_lang.html | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/libpathod/templates/docs_lang.html b/libpathod/templates/docs_lang.html index 6f5f4748..8f78adbe 100644 --- a/libpathod/templates/docs_lang.html +++ b/libpathod/templates/docs_lang.html @@ -86,6 +86,17 @@ be an integer or "f" to pause forever. + + + + r + + + Set the "raw" flag on this response. Pathod will not + calculate a Content-Length header if a body is set, or add + a Date header to the response. + + @@ -238,7 +249,7 @@ octdigits - 0-8 + 0-7 punctuation -- cgit v1.2.3 From 94b491bb277054af106f63e2e2b9cec6b9ca6d3c Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Tue, 24 Jul 2012 21:38:28 +1200 Subject: Add a Host header to pathoc requests by default. --- libpathod/pathoc.py | 6 +++--- libpathod/rparse.py | 31 ++++++++++++++++++++----------- test/test_rparse.py | 14 +++++++------- 3 files changed, 30 insertions(+), 21 deletions(-) diff --git a/libpathod/pathoc.py b/libpathod/pathoc.py index 8fe7b7f6..b78d986f 100644 --- a/libpathod/pathoc.py +++ b/libpathod/pathoc.py @@ -20,7 +20,7 @@ class Pathoc(tcp.TCPClient): tcp.TCPClient.__init__(self, host, port) self.settings = dict( staticdir = os.getcwd(), - unconstrained_file_access = True + unconstrained_file_access = True, ) def request(self, spec): @@ -31,7 +31,7 @@ class Pathoc(tcp.TCPClient): rparse.FileAccessDenied. """ r = rparse.parse_request(self.settings, spec) - ret = r.serve(self.wfile) + ret = r.serve(self.wfile, None, self.host) self.wfile.flush() return http.read_response(self.rfile, r.method, None) @@ -43,7 +43,7 @@ class Pathoc(tcp.TCPClient): for i in reqs: try: r = rparse.parse_request(self.settings, i) - req = r.serve(self.wfile) + req = r.serve(self.wfile, None, self.host) if reqdump: print >> fp, "\n>>", req["method"], repr(req["path"]) for a in req["actions"]: diff --git a/libpathod/rparse.py b/libpathod/rparse.py index 767cb861..e56135ea 100644 --- a/libpathod/rparse.py +++ b/libpathod/rparse.py @@ -541,7 +541,7 @@ class Message: l += len(i[2]) return l - def serve(self, fp, check, is_request): + def serve(self, fp, check, request_host): """ fp: The file pointer to write to. @@ -550,9 +550,11 @@ class Message: otherwise the return is treated as an error message to be sent to the client, and service stops. - is_request: Is this a request? If False, we assume it's a response. - Used to decide what standard modifications to make if raw is not - set. + 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. """ started = time.time() if not self.raw: @@ -563,8 +565,15 @@ class Message: LiteralGenerator(str(len(self.body))), ) ) - if is_request: - pass + if request_host: + if not utils.get_header("Host", self.headers): + self.headers.append( + ( + LiteralGenerator("Host"), + LiteralGenerator(request_host) + ) + ) + else: if not utils.get_header("Date", self.headers): self.headers.append( @@ -706,8 +715,8 @@ class CraftedRequest(Request): for i in tokens: i.accept(settings, self) - def serve(self, fp, check=None): - d = Request.serve(self, fp, check, True) + def serve(self, fp, check, host): + d = Request.serve(self, fp, check, host) d["spec"] = self.spec return d @@ -719,8 +728,8 @@ class CraftedResponse(Response): for i in tokens: i.accept(settings, self) - def serve(self, fp, check=None): - d = Response.serve(self, fp, check, False) + def serve(self, fp, check): + d = Response.serve(self, fp, check, None) d["spec"] = self.spec return d @@ -738,7 +747,7 @@ class PathodErrorResponse(Response): ] def serve(self, fp, check=None): - d = Response.serve(self, fp, check, False) + d = Response.serve(self, fp, check, None) d["internal"] = True return d diff --git a/test/test_rparse.py b/test/test_rparse.py index ef724d68..922c8fc6 100644 --- a/test/test_rparse.py +++ b/test/test_rparse.py @@ -210,7 +210,7 @@ class TestInject: def test_serve(self): s = cStringIO.StringIO() r = rparse.parse_response({}, "400:i0,'foo'") - assert r.serve(s) + assert r.serve(s, None) class TestShortcuts: @@ -262,7 +262,7 @@ class TestParseRequest: def test_render(self): s = cStringIO.StringIO() r = rparse.parse_request({}, "GET:'/foo'") - assert r.serve(s) + assert r.serve(s, None, "foo.com") def test_str(self): r = rparse.parse_request({}, 'GET:"/foo"') @@ -438,19 +438,19 @@ class TestResponse: def test_render(self): s = cStringIO.StringIO() r = rparse.parse_response({}, "400'msg'") - assert r.serve(s) + assert r.serve(s, None) def test_raw(self): s = cStringIO.StringIO() r = rparse.parse_response({}, "400:b'foo'") - r.serve(s) + r.serve(s, None) v = s.getvalue() assert "Content-Length" in v assert "Date" in v s = cStringIO.StringIO() r = rparse.parse_response({}, "400:b'foo':r") - r.serve(s) + r.serve(s, None) v = s.getvalue() assert not "Content-Length" in v assert not "Date" in v @@ -458,7 +458,7 @@ class TestResponse: def test_length(self): def testlen(x): s = cStringIO.StringIO() - x.serve(s) + x.serve(s, None) assert x.length() == len(s.getvalue()) testlen(rparse.parse_response({}, "400'msg'")) testlen(rparse.parse_response({}, "400'msg':h'foo'='bar'")) @@ -467,7 +467,7 @@ class TestResponse: def test_effective_length(self): def testlen(x, actions): s = cStringIO.StringIO() - x.serve(s) + x.serve(s, None) assert x.effective_length(actions) == len(s.getvalue()) actions = [ -- cgit v1.2.3 From 97fe026c3230e1be12536e7390ef374447ffe9e8 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Tue, 24 Jul 2012 21:45:05 +1200 Subject: Add basic docs for request spec language. --- libpathod/templates/docs_lang.html | 95 ++++++++++++++++++++++++++++---------- 1 file changed, 71 insertions(+), 24 deletions(-) diff --git a/libpathod/templates/docs_lang.html b/libpathod/templates/docs_lang.html index 8f78adbe..4325ef39 100644 --- a/libpathod/templates/docs_lang.html +++ b/libpathod/templates/docs_lang.html @@ -21,18 +21,14 @@ - + - + - + - + - + - + - + - + - + - + - + + + + + - + -- cgit v1.2.3 From 95968ad558b9f0d6f0455298b3c7e245e39bbd73 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Fri, 17 Aug 2012 10:08:02 +1200 Subject: Don't allow negative integer offsets for now. --- libpathod/rparse.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libpathod/rparse.py b/libpathod/rparse.py index 1a615950..3eaf2569 100644 --- a/libpathod/rparse.py +++ b/libpathod/rparse.py @@ -113,7 +113,8 @@ DATATYPES = dict( ) -v_integer = pp.Regex(r"[+-]?\d+")\ +#v_integer = pp.Regex(r"[+-]?\d+")\ +v_integer = pp.Regex(r"\d+")\ .setName("integer")\ .setParseAction(lambda toks: int(toks[0])) -- cgit v1.2.3 From 39cf10588631e4f3709f4ae521da214c5c7cadef Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Wed, 22 Aug 2012 13:59:24 +1200 Subject: Add a changelog. --- CHANGELOG | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 CHANGELOG diff --git a/CHANGELOG b/CHANGELOG new file mode 100644 index 00000000..38053ffe --- /dev/null +++ b/CHANGELOG @@ -0,0 +1,21 @@ +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. -- cgit v1.2.3 From d758409b76454db983b28b023653aad977181717 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Thu, 23 Aug 2012 12:00:16 +1200 Subject: Make sure date stamps are added to all logs. --- libpathod/pathod.py | 8 +++++--- pathod | 21 +++++++++++++-------- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/libpathod/pathod.py b/libpathod/pathod.py index 78ce3b95..1c53d7d7 100644 --- a/libpathod/pathod.py +++ b/libpathod/pathod.py @@ -2,6 +2,8 @@ import urllib, threading, re, logging, socket, sys from netlib import tcp, http, odict, wsgi import version, app, rparse +logger = logging.getLogger('pathod') + class PathodError(Exception): pass @@ -10,7 +12,7 @@ class PathodHandler(tcp.BaseHandler): wbufsize = 0 sni = None def info(self, s): - logging.info("%s:%s: %s"%(self.client_address[0], self.client_address[1], str(s))) + logger.info("%s:%s: %s"%(self.client_address[0], self.client_address[1], str(s))) def handle_sni(self, connection): self.sni = connection.get_servername() @@ -89,13 +91,13 @@ class PathodHandler(tcp.BaseHandler): for i in self.server.anchors: if i[0].match(path): - self.info("Serving anchor: %s"%path) + self.info("crafting anchor: %s"%path) aresp = rparse.parse_response(self.server.request_settings, i[1]) return self.serve_crafted(aresp, request_log) if not self.server.nocraft and path.startswith(self.server.craftanchor): spec = urllib.unquote(path)[len(self.server.craftanchor):] - self.info("Serving spec: %s"%spec) + self.info("crafting spec: %s"%spec) try: crafted = rparse.parse_response(self.server.request_settings, spec) except rparse.ParseException, v: diff --git a/pathod b/pathod index 9d5d64bf..ba7662ea 100755 --- a/pathod +++ b/pathod @@ -3,6 +3,7 @@ import argparse, sys, logging, logging.handlers import os from libpathod import pathod, utils, version, rparse + def daemonize (stdin='/dev/null', stdout='/dev/null', stderr='/dev/null'): try: pid = os.fork() @@ -53,17 +54,21 @@ def main(parser, args): if root.handlers: for handler in root.handlers: root.removeHandler(handler) - logging.basicConfig( - format='%(asctime)s: %(message)s', + + log = logging.getLogger('pathod') + log.setLevel(logging.DEBUG) + fmt = logging.Formatter( + '%(asctime)s: %(message)s', datefmt='%d-%m-%y %I:%M:%S', - level=logging.DEBUG ) - if not args.debug: - logging.disable(logging.DEBUG) if args.logfile: - ch = logging.handlers.WatchedFileHandler(args.logfile) - root.addHandler(ch) - + 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) sizelimit = None if args.sizelimit: -- cgit v1.2.3 From b78a3817cdddd4bc43a7d3159382283f25a22fa7 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Thu, 23 Aug 2012 12:37:02 +1200 Subject: Adjust examples and docs. --- .sources/examples_context.py | 29 ++++++++++++++--------------- libpathod/templates/about.html | 22 ++++++++++++++++------ libpathod/templates/examples_context.html | 29 ++++++++++++++--------------- 3 files changed, 44 insertions(+), 36 deletions(-) diff --git a/.sources/examples_context.py b/.sources/examples_context.py index 80278e76..f3da83cb 100644 --- a/.sources/examples_context.py +++ b/.sources/examples_context.py @@ -1,23 +1,22 @@ import requests from libpathod import test -class Test: +def test_simple(): """ - Testing the requests module with + Testing the requests module with a pathod context manager. """ - def test_simple(self): - # 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) + # 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 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" + # Check pathod's internal log + log = d.last_log()["request"] + assert log["method"] == "PUT" diff --git a/libpathod/templates/about.html b/libpathod/templates/about.html index 4d74c126..3c05ad2d 100644 --- a/libpathod/templates/about.html +++ b/libpathod/templates/about.html @@ -4,22 +4,32 @@
-

git clone git://github.com/cortesi/pathod.git

+

The easiest way to install pathod is to use pip:

+ +
pip install pathod
+ +

You can find the project source on GitHub:

+ + + +

Please also use the github issue tracker to + report bugs.

-

Please use the github - issue tracker to report bugs.

diff --git a/libpathod/templates/examples_context.html b/libpathod/templates/examples_context.html index 3d33e76d..39146d37 100644 --- a/libpathod/templates/examples_context.html +++ b/libpathod/templates/examples_context.html @@ -1,24 +1,23 @@
import requests
 from libpathod import test
 
-class Test:
+def test_simple():
     """
-        Testing the requests module with 
+        Testing the requests module with
         a pathod context manager.
     """
-    def test_simple(self):
-        # 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)
+    # 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 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"
+        # Check pathod's internal log
+        log = d.last_log()["request"]
+        assert log["method"] == "PUT"
 
-- cgit v1.2.3 From 2f7188164cea54451728c03eed40153fd844224b Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Thu, 23 Aug 2012 12:54:58 +1200 Subject: Download page, MANIFEST.in --- MANIFEST.in | 9 +++++++ libpathod/app.py | 6 +++++ libpathod/templates/about.html | 24 ------------------ libpathod/templates/download.html | 53 +++++++++++++++++++++++++++++++++++++++ libpathod/templates/frame.html | 1 + test/.pry | 5 ---- 6 files changed, 69 insertions(+), 29 deletions(-) create mode 100644 MANIFEST.in create mode 100644 libpathod/templates/download.html delete mode 100644 test/.pry diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 00000000..1cd3f043 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,9 @@ +include LICENSE +include CHANGELOG +include README.txt +exclude README.mkd +recursive-include test * +recursive-include libpathod/resources * +recursive-include libpathod/templates * +recursive-include libpathod/static * +recursive-exclude test *.swo *.swp *.pyc diff --git a/libpathod/app.py b/libpathod/app.py index 7d9636e1..c7d0e7b2 100644 --- a/libpathod/app.py +++ b/libpathod/app.py @@ -42,6 +42,12 @@ def index(): return render("index.html", True, section="main") +@app.route('/download') +@app.route('/download.html') +def download(): + return render("download.html", True, section="download") + + @app.route('/about') @app.route('/about.html') def about(): diff --git a/libpathod/templates/about.html b/libpathod/templates/about.html index 3c05ad2d..4bf473da 100644 --- a/libpathod/templates/about.html +++ b/libpathod/templates/about.html @@ -1,30 +1,6 @@ {% extends "frame.html" %} {% block body %} -
- - -

The easiest way to install pathod is to use pip:

- -
pip install pathod
- -

You can find the project source on GitHub:

- - - -

Please also use the github issue tracker to - report bugs.

- -
-
-- cgit v1.2.3 From 0dd250d4f886cfa7cf91e3690b4196acfe3740ce Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Mon, 24 Sep 2012 10:08:18 +1200 Subject: Fix utils.get_header case handling. --- libpathod/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libpathod/utils.py b/libpathod/utils.py index 7a1e533f..ac0c0e4c 100644 --- a/libpathod/utils.py +++ b/libpathod/utils.py @@ -27,7 +27,7 @@ def get_header(val, headers): Header keys may be Values, so we have to "generate" them as we try the match. """ for k, v in headers: - if len(k) == len(val) and k[:].lower() == val: + if len(k) == len(val) and k[:].lower() == val.lower(): return v return None -- cgit v1.2.3 From 0a5d4fbbbb06a4e6c5e6172b353aa878aaa6e0c6 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Wed, 26 Sep 2012 10:12:30 +1200 Subject: Stub out new output argument structure. --- libpathod/pathoc.py | 19 +++++++++++++------ pathoc | 33 ++++++++++++++++++++++++--------- test/test_pathoc.py | 23 +++++++++++++++++------ 3 files changed, 54 insertions(+), 21 deletions(-) diff --git a/libpathod/pathoc.py b/libpathod/pathoc.py index 0483a328..57bff46b 100644 --- a/libpathod/pathoc.py +++ b/libpathod/pathoc.py @@ -35,22 +35,29 @@ class Pathoc(tcp.TCPClient): self.wfile.flush() return http.read_response(self.rfile, r.method, None) - def print_requests(self, reqs, respdump, reqdump, fp=sys.stdout): + def print_requests(self, reqs, showreq, showresp, explain, hexdump, fp=sys.stdout): """ Performs a series of requests, and prints results to the specified - file pointer. + file descriptor. + + reqs: A sequence of request specifications + showreq: Print requests + showresp: Print responses + explain: Print request explanation + hexdump: When printing requests or responses, use hex dump output """ for i in reqs: try: r = rparse.parse_request(self.settings, i) req = r.serve(self.wfile, None, self.host) - if reqdump: - print >> fp, "\n>>", req["method"], repr(req["path"]) + if explain: + print >> fp, ">>", req["method"], repr(req["path"]) for a in req["actions"]: print >> fp, "\t", for x in a: print >> fp, x, print >> fp + print req self.wfile.flush() resp = http.read_response(self.rfile, r.method, None) except rparse.ParseException, v: @@ -61,7 +68,7 @@ class Pathoc(tcp.TCPClient): print >> fp, "File access error: %s"%v return except http.HttpError, v: - print >> fp, "<<", v.msg + print >> fp, "<< HTTP Error:", v.msg return except tcp.NetLibTimeout: print >> fp, "<<", "Timeout" @@ -70,7 +77,7 @@ class Pathoc(tcp.TCPClient): print >> fp, "<<", "Disconnect" return else: - if respdump: + if showresp: print_full(fp, *resp) else: print_short(fp, *resp) diff --git a/pathoc b/pathoc index 70105ced..19f70d4e 100755 --- a/pathoc +++ b/pathoc @@ -17,10 +17,6 @@ if __name__ == "__main__": "-p", dest="port", type=int, default=None, help="Port. Defaults to 80, or 443 if SSL is active." ) - parser.add_argument( - "-d", dest="reqdump", action="store_true", default=False, - help="Print request record before each response." - ) parser.add_argument( "-s", dest="ssl", action="store_true", default=False, help="Connect with SSL." @@ -29,10 +25,6 @@ if __name__ == "__main__": "-t", dest="timeout", type=int, default=None, help="Connection timeout." ) - parser.add_argument( - "-v", dest="verbose", action='count', - help="Increase verbosity." - ) parser.add_argument( 'host', type=str, help='Host to connect to' @@ -41,6 +33,23 @@ if __name__ == "__main__": 'request', type=str, nargs="+", help='Request specification' ) + group = parser.add_argument_group('Controlling Output') + group.add_argument( + "-e", dest="explain", action="store_true", default=False, + help="Explain requests." + ) + group.add_argument( + "-q", dest="showreq", action="store_true", default=False, + help="Print full request." + ) + group.add_argument( + "-r", dest="showresp", action="store_true", default=False, + help="Print full response." + ) + group.add_argument( + "-x", dest="hexdump", action="store_true", default=False, + help="Output in hexdump format." + ) args = parser.parse_args() @@ -65,7 +74,13 @@ if __name__ == "__main__": continue if args.timeout: p.settimeout(args.timeout) - p.print_requests(args.request, args.verbose, args.reqdump) + p.print_requests( + args.request, + showreq=args.showreq, + showresp=args.showresp, + explain=args.explain, + hexdump=args.hexdump, + ) sys.stdout.flush() except KeyboardInterrupt: pass diff --git a/test/test_pathoc.py b/test/test_pathoc.py index 397a7f0e..8eb180af 100644 --- a/test/test_pathoc.py +++ b/test/test_pathoc.py @@ -31,21 +31,28 @@ class TestDaemon: s = cStringIO.StringIO() c.print_requests( - ["get:'/p/200:p0,10'"], True, True, s + ["get:'/p/200:p0,10'"], True, True, True, True, s ) assert "Timeout" in s.getvalue() - def tval(self, requests, verbose=False): + def tval(self, requests, showreq=False, showresp=False, explain=False, hexdump=False): c = pathoc.Pathoc("127.0.0.1", self.d.port) c.connect() s = cStringIO.StringIO() - c.print_requests(requests, verbose, True, s) + c.print_requests( + requests, + showreq = showreq, + showresp = showresp, + explain = explain, + hexdump = hexdump, + fp = s + ) return s.getvalue() def test_print_requests(self): reqs = [ "get:/api/info:p0,0", "get:/api/info:p0,0" ] - assert self.tval(reqs, False).count("200") == 2 - assert self.tval(reqs, True).count("Date") == 2 + assert self.tval(reqs).count("200") == 2 + assert self.tval(reqs, showresp=True).count("Date") == 2 def test_parse_err(self): assert "Error parsing" in self.tval(["foo"]) @@ -53,8 +60,12 @@ class TestDaemon: def test_conn_err(self): assert "Invalid server response" in self.tval(["get:'/p/200:d2'"]) + def test_explain(self): + reqs = [ "get:/api/info:ir,'x'"] + assert "inject" in self.tval(reqs, explain=True) + def test_fileread(self): d = tutils.test_data.path("data/request") - assert "foo" in self.tval(["+%s"%d]) + assert "foo" in self.tval(["+%s"%d], explain=True) assert "File" in self.tval(["+/nonexistent"]) -- cgit v1.2.3 From a69d60208717db775ca68bbd1cd8a1af76082495 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Wed, 26 Sep 2012 10:38:47 +1200 Subject: Hex and verbatim output of requests. This works by sniffing traffic through the socket, so always gives us the exact traffic sent upstream. --- libpathod/pathoc.py | 15 +++++++++++++-- libpathod/rparse.py | 4 ++-- test/test_pathoc.py | 7 ++++++- 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/libpathod/pathoc.py b/libpathod/pathoc.py index 57bff46b..b283c94e 100644 --- a/libpathod/pathoc.py +++ b/libpathod/pathoc.py @@ -1,5 +1,6 @@ import sys, os from netlib import tcp, http +import netlib.utils import rparse, utils class PathocError(Exception): pass @@ -49,15 +50,25 @@ class Pathoc(tcp.TCPClient): for i in reqs: try: r = rparse.parse_request(self.settings, i) + if showreq: + self.wfile.start_log() req = r.serve(self.wfile, None, self.host) if explain: - print >> fp, ">>", req["method"], repr(req["path"]) + print >> fp, ">> ", req["method"], repr(req["path"]) for a in req["actions"]: print >> fp, "\t", for x in a: print >> fp, x, print >> fp - print req + if showreq: + data = self.wfile.get_log() + if hexdump: + print >> fp, ">> Request (hex dump):" + for line in netlib.utils.hexdump(data): + print >> fp, "\t%s %s %s"%line + else: + print >> fp, ">> Request (unprintables escaped):" + print >> fp, netlib.utils.cleanBin(data) self.wfile.flush() resp = http.read_response(self.rfile, r.method, None) except rparse.ParseException, v: diff --git a/libpathod/rparse.py b/libpathod/rparse.py index 3eaf2569..53878b97 100644 --- a/libpathod/rparse.py +++ b/libpathod/rparse.py @@ -653,7 +653,7 @@ class Response(Message): ShortcutLocation, Raw ) - logattrs = ["code", "version"] + logattrs = ["code", "version", "body"] def __init__(self): Message.__init__(self) self.code = 200 @@ -691,7 +691,7 @@ class Request(Message): ShortcutContentType, Raw ) - logattrs = ["method", "path"] + logattrs = ["method", "path", "body"] def __init__(self): Message.__init__(self) self.method = None diff --git a/test/test_pathoc.py b/test/test_pathoc.py index 8eb180af..9485f84d 100644 --- a/test/test_pathoc.py +++ b/test/test_pathoc.py @@ -49,11 +49,16 @@ class TestDaemon: ) return s.getvalue() - def test_print_requests(self): + def test_showresp(self): reqs = [ "get:/api/info:p0,0", "get:/api/info:p0,0" ] assert self.tval(reqs).count("200") == 2 assert self.tval(reqs, showresp=True).count("Date") == 2 + def test_showreq(self): + reqs = [ "get:/api/info:p0,0", "get:/api/info:p0,0" ] + assert self.tval(reqs, showreq=True).count("unprintables escaped") == 2 + assert self.tval(reqs, showreq=True, hexdump=True).count("hex dump") == 2 + def test_parse_err(self): assert "Error parsing" in self.tval(["foo"]) -- cgit v1.2.3 From b67d99638a8e57b4558f094c62efc12189137a80 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Wed, 26 Sep 2012 11:07:22 +1200 Subject: Response printing Options to output full response, as sniffed from the socket. --- libpathod/pathoc.py | 41 +++++++++++++++++++++-------------------- test/test_pathoc.py | 27 ++++++++++++++------------- 2 files changed, 35 insertions(+), 33 deletions(-) diff --git a/libpathod/pathoc.py b/libpathod/pathoc.py index b283c94e..18c86c5d 100644 --- a/libpathod/pathoc.py +++ b/libpathod/pathoc.py @@ -6,16 +6,6 @@ import rparse, utils class PathocError(Exception): pass -def print_short(fp, httpversion, code, msg, headers, content): - print >> fp, "<< %s %s: %s bytes"%(code, utils.xrepr(msg), len(content)) - - -def print_full(fp, httpversion, code, msg, headers, content): - print >> fp, "<< HTTP%s/%s %s %s"%(httpversion[0], httpversion[1], code, utils.xrepr(msg)) - print >> fp, utils.escape_unprintables(str(headers)) - print >> fp, utils.escape_unprintables(content) - - class Pathoc(tcp.TCPClient): def __init__(self, host, port): tcp.TCPClient.__init__(self, host, port) @@ -36,6 +26,18 @@ class Pathoc(tcp.TCPClient): self.wfile.flush() return http.read_response(self.rfile, r.method, None) + def _show_summary(self, fp, httpversion, code, msg, headers, content): + print >> fp, "<< %s %s: %s bytes"%(code, utils.xrepr(msg), len(content)) + + def _show(self, fp, header, data, hexdump): + if hexdump: + print >> fp, "%s (hex dump):"%header + for line in netlib.utils.hexdump(data): + print >> fp, "\t%s %s %s"%line + else: + print >> fp, "%s (unprintables escaped):"%header + print >> fp, netlib.utils.cleanBin(data) + def print_requests(self, reqs, showreq, showresp, explain, hexdump, fp=sys.stdout): """ Performs a series of requests, and prints results to the specified @@ -61,15 +63,10 @@ class Pathoc(tcp.TCPClient): print >> fp, x, print >> fp if showreq: - data = self.wfile.get_log() - if hexdump: - print >> fp, ">> Request (hex dump):" - for line in netlib.utils.hexdump(data): - print >> fp, "\t%s %s %s"%line - else: - print >> fp, ">> Request (unprintables escaped):" - print >> fp, netlib.utils.cleanBin(data) + self._show(fp, ">> Request", self.wfile.get_log(), hexdump) self.wfile.flush() + if showresp: + self.rfile.start_log() resp = http.read_response(self.rfile, r.method, None) except rparse.ParseException, v: print >> fp, "Error parsing request spec: %s"%v.msg @@ -80,15 +77,19 @@ class Pathoc(tcp.TCPClient): return except http.HttpError, v: print >> fp, "<< HTTP Error:", v.msg + if showresp: + self._show(fp, "<< Response", self.rfile.get_log(), hexdump) return except tcp.NetLibTimeout: print >> fp, "<<", "Timeout" + if showresp: + self._show(fp, "<< Response", self.rfile.get_log(), hexdump) return except tcp.NetLibDisconnect: # pragma: nocover print >> fp, "<<", "Disconnect" return else: if showresp: - print_full(fp, *resp) + self._show(fp, "<< Response", self.rfile.get_log(), hexdump) else: - print_short(fp, *resp) + self._show_summary(fp, *resp) diff --git a/test/test_pathoc.py b/test/test_pathoc.py index 9485f84d..ce1476a6 100644 --- a/test/test_pathoc.py +++ b/test/test_pathoc.py @@ -24,20 +24,11 @@ class TestDaemon: _, _, _, _, content = c.request("get:/api/info") assert tuple(json.loads(content)["version"]) == version.IVERSION - def test_timeout(self): - c = pathoc.Pathoc("127.0.0.1", self.d.port) - c.connect() - c.settimeout(0.01) - - s = cStringIO.StringIO() - c.print_requests( - ["get:'/p/200:p0,10'"], True, True, True, True, s - ) - assert "Timeout" in s.getvalue() - - def tval(self, requests, showreq=False, showresp=False, explain=False, hexdump=False): + def tval(self, requests, showreq=False, showresp=False, explain=False, hexdump=False, timeout=None): c = pathoc.Pathoc("127.0.0.1", self.d.port) c.connect() + if timeout: + c.settimeout(timeout) s = cStringIO.StringIO() c.print_requests( requests, @@ -49,10 +40,20 @@ class TestDaemon: ) return s.getvalue() + def test_timeout(self): + assert "Timeout" in self.tval(["get:'/p/200:p0,10'"], timeout=0.01) + assert "HTTP" in self.tval(["get:'/p/200:p5,10'"], showresp=True, timeout=0.01) + def test_showresp(self): reqs = [ "get:/api/info:p0,0", "get:/api/info:p0,0" ] assert self.tval(reqs).count("200") == 2 - assert self.tval(reqs, showresp=True).count("Date") == 2 + assert self.tval(reqs, showresp=True).count("unprintables escaped") == 2 + assert self.tval(reqs, showresp=True, hexdump=True).count("hex dump") == 2 + + def test_showresp_httperr(self): + v = self.tval(["get:'/p/200:d20'"], showresp=True) + assert "Invalid headers" in v + assert "HTTP/" in v def test_showreq(self): reqs = [ "get:/api/info:p0,0", "get:/api/info:p0,0" ] -- cgit v1.2.3 From 424d15c28ba46d2d42b96d2247166eb13b4c7b8e Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Wed, 26 Sep 2012 11:21:40 +1200 Subject: Expand the docs to explain pathoc multiple requests. --- libpathod/templates/docs_pathoc.html | 37 ++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/libpathod/templates/docs_pathoc.html b/libpathod/templates/docs_pathoc.html index e1bef4d5..5c07aee3 100644 --- a/libpathod/templates/docs_pathoc.html +++ b/libpathod/templates/docs_pathoc.html @@ -43,6 +43,43 @@ the command-line help:

+
+ + +

There are two ways to tell pathoc to issue multiple requests. The first + is to specify them on the command-line, like so:

+ +
> pathoc google.com get:/ get:/
+<< 301 Moved Permanently: 219 bytes
+<< 301 Moved Permanently: 219 bytes
+ +

In this case, pathoc issues the specified requests over the same TCP + connection - so in the above example only one connection is made to + google.com

+ +

The other way to issue multiple requets is to use the -n flag:

+ +
> pathoc -n 2 google.com get:/
+<< 301 Moved Permanently: 219 bytes
+<< 301 Moved Permanently: 219 bytes
+ +

The output is identical, but two separate TCP connections are made to + the upstream server. These two specification styles can be combined:

+ +
> pathoc -n 2 google.com get:/ get:/
+<< 301 Moved Permanently: 219 bytes
+<< 301 Moved Permanently: 219 bytes
+<< 301 Moved Permanently: 219 bytes
+<< 301 Moved Permanently: 219 bytes
+ +

Here, two distinct TCP connections are made, with two requests issued + over each.

+ +
+ +
- -
-
-

pathoc

-

A perverse HTTP client.

+
- {% include "request_previewform.html" %} - -
-
- - - -
-
-
-

pathod server

- -

A live, public instance of pathod

+

There is also a live, public instance of pathod here:

http://public.pathod.net/ + +

+
+
+
+

pathoc

+ +

A perverse HTTP client.

+ + {% include "request_previewform.html" %} + +
> pathoc -n 3 google.com "get:/:ir,@1"
+<< 200 OK: 44177 bytes
+<< 405 Method Not Allowed: 959 bytes
+<< 200 OK: 44107 bytes
+ +
+
+
+ + +
+
+
+

libpathod.test

+ +

Using pathod in your unit tests.

+
-- cgit v1.2.3 From f5d5cc49887d3a54bc5edc7905b90e5912ae9e8a Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Fri, 5 Oct 2012 10:30:32 +1300 Subject: rparse.py -> language.py --- libpathod/app.py | 10 +- libpathod/language.py | 806 ++++++++++++++++++++++++++++++++++++++++++++++++++ libpathod/pathoc.py | 18 +- libpathod/pathod.py | 22 +- libpathod/rparse.py | 806 -------------------------------------------------- pathoc | 2 +- pathod | 4 +- test/test_language.py | 529 +++++++++++++++++++++++++++++++++ test/test_rparse.py | 529 --------------------------------- 9 files changed, 1363 insertions(+), 1363 deletions(-) create mode 100644 libpathod/language.py delete mode 100644 libpathod/rparse.py create mode 100644 test/test_language.py delete mode 100644 test/test_rparse.py diff --git a/libpathod/app.py b/libpathod/app.py index 0db31ae5..396e45c2 100644 --- a/libpathod/app.py +++ b/libpathod/app.py @@ -1,6 +1,6 @@ import logging, pprint, cStringIO from flask import Flask, jsonify, render_template, request, abort, make_response -import version, rparse, utils +import version, language, utils logging.basicConfig(level="DEBUG") app = Flask(__name__) @@ -116,14 +116,14 @@ def _preview(is_request): try: if is_request: - r = rparse.parse_request(app.config["pathod"].request_settings, spec) + r = language.parse_request(app.config["pathod"].request_settings, spec) else: - r = rparse.parse_response(app.config["pathod"].request_settings, spec) - except rparse.ParseException, v: + r = language.parse_response(app.config["pathod"].request_settings, spec) + except language.ParseException, v: args["syntaxerror"] = str(v) args["marked"] = v.marked() return render(template, False, **args) - except rparse.FileAccessDenied: + except language.FileAccessDenied: args["error"] = "File access is disabled." return render(template, False, **args) diff --git a/libpathod/language.py b/libpathod/language.py new file mode 100644 index 00000000..53878b97 --- /dev/null +++ b/libpathod/language.py @@ -0,0 +1,806 @@ +import operator, string, random, mmap, os, time +from email.utils import formatdate +import contrib.pyparsing as pp +from netlib import http_status, tcp + +import utils + +BLOCKSIZE = 1024 +TRUNCATE = 1024 + +class FileAccessDenied(Exception): 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) + + +def actions_log(lst): + ret = [] + for i in lst: + if i[1] == "inject": + ret.append( + [i[0], i[1], repr(i[2])] + ) + else: + ret.append(i) + return ret + + +def ready_actions(length, lst): + ret = [] + for i in lst: + itms = list(i) + if i[0] == "r": + itms[0] = random.randrange(length) + elif i[0] == "a": + itms[0] = length+1 + ret.append(tuple(itms)) + ret.sort() + return ret + + +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, skip=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(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 tcp.NetLibDisconnect: # pragma: no cover + return True + + +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)) +) + + +#v_integer = pp.Regex(r"[+-]?\d+")\ +v_integer = pp.Regex(r"\d+")\ + .setName("integer")\ + .setParseAction(lambda toks: int(toks[0])) + + +v_literal = pp.MatchFirst( + [ + pp.QuotedString("\"", escChar="\\", unquoteResults=True, multiline=True), + pp.QuotedString("'", escChar="\\", 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 LiteralGenerator: + def __init__(self, s): + self.s = s + + def __eq__(self, other): + return self[:] == other + + def __len__(self): + return len(self.s) + + def __getitem__(self, x): + return self.s.__getitem__(x) + + def __getslice__(self, a, b): + return self.s.__getslice__(a, b) + + def __repr__(self): + return '"%s"'%self.s + + +class RandomGenerator: + 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: + def __init__(self, path): + self.path = path + self.fp = file(path, "r") + self.map = mmap.mmap(self.fp.fileno(), 0, prot=mmap.PROT_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 + + +class _Value: + def __init__(self, val): + self.val = val.decode("string_escape") + + def get_generator(self, settings): + return LiteralGenerator(self.val) + + def __repr__(self): + return self.val + + +class ValueLiteral(_Value): + @classmethod + def expr(klass): + e = v_literal.copy() + return e.setParseAction(lambda x: klass(*x)) + + +class ValueNakedLiteral(_Value): + @classmethod + def expr(klass): + e = v_naked_literal.copy() + return e.setParseAction(lambda x: klass(*x)) + + +class ValueGenerate: + 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 RandomGenerator(self.datatype, self.bytes()) + + @classmethod + def expr(klass): + e = pp.Literal("@").suppress() + v_integer + + u = reduce(operator.or_, [pp.Literal(i) for i in utils.SIZE_UNITS.keys()]) + e = e + pp.Optional(u, default=None) + + s = pp.Literal(",").suppress() + s += reduce(operator.or_, [pp.Literal(i) for i in DATATYPES.keys()]) + e += pp.Optional(s, default="bytes") + return e.setParseAction(lambda x: klass(*x)) + + def __str__(self): + return "@%s%s,%s"%(self.usize, self.unit, self.datatype) + + +class ValueFile: + def __init__(self, path): + self.path = path + + @classmethod + def expr(klass): + e = pp.Literal("<").suppress() + e = e + v_naked_literal + return e.setParseAction(lambda x: klass(*x)) + + def get_generator(self, settings): + uf = settings.get("unconstrained_file_access") + sd = settings.get("staticdir") + if not sd: + raise FileAccessDenied("File access disabled.") + sd = os.path.normpath(os.path.abspath(sd)) + + s = os.path.expanduser(self.path) + s = os.path.normpath(os.path.abspath(os.path.join(sd, s))) + if not uf and not s.startswith(sd): + raise FileAccessDenied("File access outside of configured directory") + if not os.path.isfile(s): + raise FileAccessDenied("File not readable") + return FileGenerator(s) + + def __str__(self): + return "<%s"%(self.path) + + +Value = pp.MatchFirst( + [ + ValueGenerate.expr(), + ValueFile.expr(), + ValueLiteral.expr() + ] +) + + +NakedValue = pp.MatchFirst( + [ + ValueGenerate.expr(), + ValueFile.expr(), + ValueLiteral.expr(), + ValueNakedLiteral.expr(), + ] +) + + +Offset = pp.MatchFirst( + [ + v_integer, + pp.Literal("r"), + pp.Literal("a") + ] + ) + + +class ShortcutContentType: + def __init__(self, value): + self.value = value + + def accept(self, settings, r): + r.headers.append( + ( + LiteralGenerator("Content-Type"), + self.value.get_generator(settings) + ) + ) + + @classmethod + def expr(klass): + e = pp.Literal("c").suppress() + e = e + Value + return e.setParseAction(lambda x: klass(*x)) + + + +class ShortcutLocation: + def __init__(self, value): + self.value = value + + def accept(self, settings, r): + r.headers.append( + ( + LiteralGenerator("Location"), + self.value.get_generator(settings) + ) + ) + + @classmethod + def expr(klass): + e = pp.Literal("l").suppress() + e = e + Value + return e.setParseAction(lambda x: klass(*x)) + + +class Body: + def __init__(self, value): + self.value = value + + def accept(self, settings, r): + r.body = self.value.get_generator(settings) + + @classmethod + def expr(klass): + e = pp.Literal("b").suppress() + e = e + Value + return e.setParseAction(lambda x: klass(*x)) + + +class Raw: + def accept(self, settings, r): + r.raw = True + + @classmethod + def expr(klass): + e = pp.Literal("r").suppress() + return e.setParseAction(lambda x: klass(*x)) + + +class Path: + def __init__(self, value): + if isinstance(value, basestring): + value = ValueLiteral(value) + self.value = value + + def accept(self, settings, r): + r.path = self.value.get_generator(settings) + + @classmethod + def expr(klass): + e = NakedValue.copy() + return e.setParseAction(lambda x: klass(*x)) + + + +class Method: + methods = [ + "get", + "head", + "post", + "put", + "delete", + "options", + "trace", + "connect", + ] + def __init__(self, value): + # If it's a string, we were passed one of the methods, so we upper-case + # it to be canonical. The user can specify a different case by using a + # string value literal. + if isinstance(value, basestring): + value = ValueLiteral(value.upper()) + self.value = value + + def accept(self, settings, r): + r.method = self.value.get_generator(settings) + + @classmethod + def expr(klass): + parts = [pp.CaselessLiteral(i) for i in klass.methods] + m = pp.MatchFirst(parts) + spec = m | Value.copy() + spec = spec.setParseAction(lambda x: klass(*x)) + return spec + + +class PauseAt: + def __init__(self, offset, seconds): + self.offset, self.seconds = offset, seconds + + @classmethod + def expr(klass): + e = pp.Literal("p").suppress() + e += Offset + e += pp.Literal(",").suppress() + e += pp.MatchFirst( + [ + v_integer, + pp.Literal("f") + ] + ) + return e.setParseAction(lambda x: klass(*x)) + + def accept(self, settings, r): + r.actions.append((self.offset, "pause", self.seconds)) + + +class DisconnectAt: + def __init__(self, value): + self.value = value + + def accept(self, settings, r): + r.actions.append((self.value, "disconnect")) + + @classmethod + def expr(klass): + e = pp.Literal("d").suppress() + e += Offset + return e.setParseAction(lambda x: klass(*x)) + + +class InjectAt: + def __init__(self, offset, value): + self.offset, self.value = offset, value + + @classmethod + def expr(klass): + e = pp.Literal("i").suppress() + e += Offset + e += pp.Literal(",").suppress() + e += Value + return e.setParseAction(lambda x: klass(*x)) + + def accept(self, settings, r): + r.actions.append( + ( + self.offset, + "inject", + self.value.get_generator(settings) + ) + ) + + +class Header: + def __init__(self, key, value): + self.key, self.value = key, value + + def accept(self, settings, r): + r.headers.append( + ( + self.key.get_generator(settings), + self.value.get_generator(settings) + ) + ) + + @classmethod + def expr(klass): + e = pp.Literal("h").suppress() + e += Value + e += pp.Literal("=").suppress() + e += Value + return e.setParseAction(lambda x: klass(*x)) + + +class Code: + def __init__(self, code, msg=None): + self.code, self.msg = code, msg + if msg is None: + self.msg = ValueLiteral(http_status.RESPONSES.get(self.code, "Unknown code")) + + def accept(self, settings, r): + r.code = self.code + r.msg = self.msg.get_generator(settings) + + @classmethod + def expr(klass): + e = v_integer + e = e + pp.Optional( + Value + ) + return e.setParseAction(lambda x: klass(*x)) + + + +class Message: + version = "HTTP/1.1" + def __init__(self): + self.body = LiteralGenerator("") + self.headers = [] + self.actions = [] + self.raw = False + + def length(self): + """ + Calculate the length of the base message without any applied actions. + """ + l = sum(len(x) for x in self.preamble()) + l += 2 + for i in self.headers: + l += len(i[0]) + len(i[1]) + l += 4 + l += 2 + l += len(self.body) + return l + + def preview_safe(self): + """ + Modify this message to be safe for previews. Returns a list of elided actions. + """ + pauses = [i for i in self.actions if i[1] == "pause"] + self.actions = [i for i in self.actions if i[1] != "pause"] + return pauses + + def effective_length(self, actions): + """ + Calculate the length of the base message with all applied actions. + """ + # Order matters here, and must match the order of application in + # write_values. + l = self.length() + for i in reversed(actions): + if i[1] == "disconnect": + return i[0] + elif i[1] == "inject": + l += len(i[2]) + return l + + def serve(self, fp, check, request_host): + """ + fp: The file pointer to write to. + + check: A function called with the effective actions (after random + values have been calculated). If it returns False service proceeds, + otherwise the return is treated as an error message to be sent to + the client, and service stops. + + 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. + """ + started = time.time() + if not self.raw: + if self.body and not utils.get_header("Content-Length", self.headers): + self.headers.append( + ( + LiteralGenerator("Content-Length"), + LiteralGenerator(str(len(self.body))), + ) + ) + if request_host: + if not utils.get_header("Host", self.headers): + self.headers.append( + ( + LiteralGenerator("Host"), + LiteralGenerator(request_host) + ) + ) + + else: + if not utils.get_header("Date", self.headers): + self.headers.append( + ( + LiteralGenerator("Date"), + LiteralGenerator(formatdate(timeval=None, localtime=False, usegmt=True)) + ) + ) + + hdrs = [] + for k, v in self.headers: + hdrs.extend([ + k, + ": ", + v, + "\r\n", + ]) + vals = self.preamble() + vals.append("\r\n") + vals.extend(hdrs) + vals.append("\r\n") + if self.body: + vals.append(self.body) + vals.reverse() + actions = ready_actions(self.length(), self.actions) + actions.reverse() + if check: + ret = check(self, actions) + if ret: + err = PathodErrorResponse(ret) + err.serve(fp) + return dict( + disconnect = True, + actions = actions_log(actions), + error = ret + ) + disconnect = write_values(fp, vals, actions[:]) + duration = time.time() - started + ret = dict( + disconnect = disconnect, + started = started, + duration = duration, + actions = actions_log(actions), + ) + 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, "__len__"): + v = v[:TRUNCATE] + ret[i] = v + return ret + + +Sep = pp.Optional(pp.Literal(":")).suppress() + +class Response(Message): + comps = ( + Body, + Header, + PauseAt, + DisconnectAt, + InjectAt, + ShortcutContentType, + ShortcutLocation, + Raw + ) + logattrs = ["code", "version", "body"] + def __init__(self): + Message.__init__(self) + self.code = 200 + self.msg = LiteralGenerator(http_status.RESPONSES[self.code]) + + def preamble(self): + return [self.version, " ", str(self.code), " ", self.msg] + + @classmethod + def expr(klass): + parts = [i.expr() for i in klass.comps] + atom = pp.MatchFirst(parts) + resp = pp.And( + [ + Code.expr(), + pp.ZeroOrMore(Sep + atom) + ] + ) + return resp + + def __str__(self): + parts = [ + "%s %s"%(self.code, self.msg[:]) + ] + return "\n".join(parts) + + +class Request(Message): + comps = ( + Body, + Header, + PauseAt, + DisconnectAt, + InjectAt, + ShortcutContentType, + Raw + ) + logattrs = ["method", "path", "body"] + def __init__(self): + Message.__init__(self) + self.method = None + self.path = None + + def preamble(self): + return [self.method, " ", self.path, " ", self.version] + + @classmethod + def expr(klass): + parts = [i.expr() for i in klass.comps] + atom = pp.MatchFirst(parts) + resp = pp.And( + [ + Method.expr(), + Sep, + Path.expr(), + pp.ZeroOrMore(Sep + atom) + ] + ) + return resp + + def __str__(self): + parts = [ + "%s %s"%(self.method[:], self.path[:]) + ] + return "\n".join(parts) + + +class CraftedRequest(Request): + def __init__(self, settings, spec, tokens): + Request.__init__(self) + self.spec, self.tokens = spec, tokens + for i in tokens: + i.accept(settings, self) + + def serve(self, fp, check, host): + d = Request.serve(self, fp, check, host) + d["spec"] = self.spec + return d + + +class CraftedResponse(Response): + def __init__(self, settings, spec, tokens): + Response.__init__(self) + self.spec, self.tokens = spec, tokens + for i in tokens: + i.accept(settings, self) + + def serve(self, fp, check): + d = Response.serve(self, fp, check, None) + d["spec"] = self.spec + return d + + +class PathodErrorResponse(Response): + def __init__(self, msg, body=None): + Response.__init__(self) + self.code = 800 + self.msg = LiteralGenerator(msg) + self.body = LiteralGenerator("pathod error: " + (body or msg)) + self.headers = [ + ( + LiteralGenerator("Content-Type"), LiteralGenerator("text/plain") + ), + ] + + def serve(self, fp, check=None): + d = Response.serve(self, fp, check, None) + d["internal"] = True + return d + + +FILESTART = "+" +def read_file(settings, s): + uf = settings.get("unconstrained_file_access") + sd = settings.get("staticdir") + if not sd: + raise FileAccessDenied("File access disabled.") + sd = os.path.normpath(os.path.abspath(sd)) + s = s[1:] + s = os.path.expanduser(s) + s = os.path.normpath(os.path.abspath(os.path.join(sd, s))) + if not uf and not s.startswith(sd): + raise FileAccessDenied("File access outside of configured directory") + if not os.path.isfile(s): + raise FileAccessDenied("File not readable") + return file(s, "r").read() + + +def parse_response(settings, s): + """ + May raise ParseException or FileAccessDenied + """ + if s.startswith(FILESTART): + s = read_file(settings, s) + try: + return CraftedResponse(settings, s, Response.expr().parseString(s, parseAll=True)) + except pp.ParseException, v: + raise ParseException(v.msg, v.line, v.col) + + +def parse_request(settings, s): + """ + May raise ParseException or FileAccessDenied + """ + if s.startswith(FILESTART): + s = read_file(settings, s) + try: + return CraftedRequest(settings, s, Request.expr().parseString(s, parseAll=True)) + except pp.ParseException, v: + raise ParseException(v.msg, v.line, v.col) diff --git a/libpathod/pathoc.py b/libpathod/pathoc.py index 7551c589..bab568ca 100644 --- a/libpathod/pathoc.py +++ b/libpathod/pathoc.py @@ -1,7 +1,7 @@ import sys, os from netlib import tcp, http import netlib.utils -import rparse, utils +import language, utils class PathocError(Exception): pass @@ -18,10 +18,10 @@ class Pathoc(tcp.TCPClient): """ Return an (httpversion, code, msg, headers, content) tuple. - May raise rparse.ParseException, netlib.http.HttpError or - rparse.FileAccessDenied. + May raise language.ParseException, netlib.http.HttpError or + language.FileAccessDenied. """ - r = rparse.parse_request(self.settings, spec) + r = language.parse_request(self.settings, spec) ret = r.serve(self.wfile, None, self.host) self.wfile.flush() return http.read_response(self.rfile, r.method, None) @@ -53,23 +53,23 @@ class Pathoc(tcp.TCPClient): Returns True if we have a non-ignored response. """ try: - r = rparse.parse_request(self.settings, spec) - except rparse.ParseException, v: + r = language.parse_request(self.settings, spec) + except language.ParseException, v: print >> fp, "Error parsing request spec: %s"%v.msg print >> fp, v.marked() return - except rparse.FileAccessDenied, v: + except language.FileAccessDenied, v: print >> fp, "File access error: %s"%v return resp, req = None, None if showreq: self.wfile.start_log() + if showresp: + self.rfile.start_log() try: req = r.serve(self.wfile, None, self.host) self.wfile.flush() - if showresp: - self.rfile.start_log() resp = http.read_response(self.rfile, r.method, None) except http.HttpError, v: print >> fp, "<< HTTP Error:", v.msg diff --git a/libpathod/pathod.py b/libpathod/pathod.py index 6afcf4bf..4ce268fa 100644 --- a/libpathod/pathod.py +++ b/libpathod/pathod.py @@ -1,7 +1,7 @@ import urllib, threading, re, logging, socket, sys from netlib import tcp, http, odict, wsgi import netlib.utils -import version, app, rparse +import version, app, language logger = logging.getLogger('pathod') @@ -30,7 +30,7 @@ class PathodHandler(tcp.BaseHandler): def handle_request(self): """ - Returns a (again, log) tuple. + Returns a (again, log) tuple. again: True if request handling should continue. log: A dictionary, or None @@ -76,26 +76,26 @@ class PathodHandler(tcp.BaseHandler): for i in self.server.anchors: if i[0].match(path): self.info("crafting anchor: %s"%path) - aresp = rparse.parse_response(self.server.request_settings, i[1]) + aresp = language.parse_response(self.server.request_settings, i[1]) return self.serve_crafted(aresp, request_log) if not self.server.nocraft and path.startswith(self.server.craftanchor): spec = urllib.unquote(path)[len(self.server.craftanchor):] self.info("crafting spec: %s"%spec) try: - crafted = rparse.parse_response(self.server.request_settings, spec) - except rparse.ParseException, v: + crafted = language.parse_response(self.server.request_settings, spec) + except language.ParseException, v: self.info("Parse error: %s"%v.msg) - crafted = rparse.PathodErrorResponse( + crafted = language.PathodErrorResponse( "Parse Error", "Error parsing response spec: %s\n"%v.msg + v.marked() ) - except rparse.FileAccessDenied: + except language.FileAccessDenied: self.info("File access denied") - crafted = rparse.PathodErrorResponse("Access Denied") + crafted = language.PathodErrorResponse("Access Denied") return self.serve_crafted(crafted, request_log) elif self.server.noweb: - crafted = rparse.PathodErrorResponse("Access Denied") + crafted = language.PathodErrorResponse("Access Denied") crafted.serve(self.wfile, self.server.check_policy) return False, dict(type = "error", msg="Access denied: web interface disabled") else: @@ -200,8 +200,8 @@ class Pathod(tcp.TCPServer): except re.error: raise PathodError("Invalid regex in anchor: %s"%i[0]) try: - aresp = rparse.parse_response(self.request_settings, i[1]) - except rparse.ParseException, v: + aresp = language.parse_response(self.request_settings, i[1]) + except language.ParseException, v: raise PathodError("Invalid page spec in anchor: '%s', %s"%(i[1], str(v))) self.anchors.append((arex, i[1])) diff --git a/libpathod/rparse.py b/libpathod/rparse.py deleted file mode 100644 index 53878b97..00000000 --- a/libpathod/rparse.py +++ /dev/null @@ -1,806 +0,0 @@ -import operator, string, random, mmap, os, time -from email.utils import formatdate -import contrib.pyparsing as pp -from netlib import http_status, tcp - -import utils - -BLOCKSIZE = 1024 -TRUNCATE = 1024 - -class FileAccessDenied(Exception): 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) - - -def actions_log(lst): - ret = [] - for i in lst: - if i[1] == "inject": - ret.append( - [i[0], i[1], repr(i[2])] - ) - else: - ret.append(i) - return ret - - -def ready_actions(length, lst): - ret = [] - for i in lst: - itms = list(i) - if i[0] == "r": - itms[0] = random.randrange(length) - elif i[0] == "a": - itms[0] = length+1 - ret.append(tuple(itms)) - ret.sort() - return ret - - -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, skip=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(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 tcp.NetLibDisconnect: # pragma: no cover - return True - - -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)) -) - - -#v_integer = pp.Regex(r"[+-]?\d+")\ -v_integer = pp.Regex(r"\d+")\ - .setName("integer")\ - .setParseAction(lambda toks: int(toks[0])) - - -v_literal = pp.MatchFirst( - [ - pp.QuotedString("\"", escChar="\\", unquoteResults=True, multiline=True), - pp.QuotedString("'", escChar="\\", 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 LiteralGenerator: - def __init__(self, s): - self.s = s - - def __eq__(self, other): - return self[:] == other - - def __len__(self): - return len(self.s) - - def __getitem__(self, x): - return self.s.__getitem__(x) - - def __getslice__(self, a, b): - return self.s.__getslice__(a, b) - - def __repr__(self): - return '"%s"'%self.s - - -class RandomGenerator: - 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: - def __init__(self, path): - self.path = path - self.fp = file(path, "r") - self.map = mmap.mmap(self.fp.fileno(), 0, prot=mmap.PROT_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 - - -class _Value: - def __init__(self, val): - self.val = val.decode("string_escape") - - def get_generator(self, settings): - return LiteralGenerator(self.val) - - def __repr__(self): - return self.val - - -class ValueLiteral(_Value): - @classmethod - def expr(klass): - e = v_literal.copy() - return e.setParseAction(lambda x: klass(*x)) - - -class ValueNakedLiteral(_Value): - @classmethod - def expr(klass): - e = v_naked_literal.copy() - return e.setParseAction(lambda x: klass(*x)) - - -class ValueGenerate: - 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 RandomGenerator(self.datatype, self.bytes()) - - @classmethod - def expr(klass): - e = pp.Literal("@").suppress() + v_integer - - u = reduce(operator.or_, [pp.Literal(i) for i in utils.SIZE_UNITS.keys()]) - e = e + pp.Optional(u, default=None) - - s = pp.Literal(",").suppress() - s += reduce(operator.or_, [pp.Literal(i) for i in DATATYPES.keys()]) - e += pp.Optional(s, default="bytes") - return e.setParseAction(lambda x: klass(*x)) - - def __str__(self): - return "@%s%s,%s"%(self.usize, self.unit, self.datatype) - - -class ValueFile: - def __init__(self, path): - self.path = path - - @classmethod - def expr(klass): - e = pp.Literal("<").suppress() - e = e + v_naked_literal - return e.setParseAction(lambda x: klass(*x)) - - def get_generator(self, settings): - uf = settings.get("unconstrained_file_access") - sd = settings.get("staticdir") - if not sd: - raise FileAccessDenied("File access disabled.") - sd = os.path.normpath(os.path.abspath(sd)) - - s = os.path.expanduser(self.path) - s = os.path.normpath(os.path.abspath(os.path.join(sd, s))) - if not uf and not s.startswith(sd): - raise FileAccessDenied("File access outside of configured directory") - if not os.path.isfile(s): - raise FileAccessDenied("File not readable") - return FileGenerator(s) - - def __str__(self): - return "<%s"%(self.path) - - -Value = pp.MatchFirst( - [ - ValueGenerate.expr(), - ValueFile.expr(), - ValueLiteral.expr() - ] -) - - -NakedValue = pp.MatchFirst( - [ - ValueGenerate.expr(), - ValueFile.expr(), - ValueLiteral.expr(), - ValueNakedLiteral.expr(), - ] -) - - -Offset = pp.MatchFirst( - [ - v_integer, - pp.Literal("r"), - pp.Literal("a") - ] - ) - - -class ShortcutContentType: - def __init__(self, value): - self.value = value - - def accept(self, settings, r): - r.headers.append( - ( - LiteralGenerator("Content-Type"), - self.value.get_generator(settings) - ) - ) - - @classmethod - def expr(klass): - e = pp.Literal("c").suppress() - e = e + Value - return e.setParseAction(lambda x: klass(*x)) - - - -class ShortcutLocation: - def __init__(self, value): - self.value = value - - def accept(self, settings, r): - r.headers.append( - ( - LiteralGenerator("Location"), - self.value.get_generator(settings) - ) - ) - - @classmethod - def expr(klass): - e = pp.Literal("l").suppress() - e = e + Value - return e.setParseAction(lambda x: klass(*x)) - - -class Body: - def __init__(self, value): - self.value = value - - def accept(self, settings, r): - r.body = self.value.get_generator(settings) - - @classmethod - def expr(klass): - e = pp.Literal("b").suppress() - e = e + Value - return e.setParseAction(lambda x: klass(*x)) - - -class Raw: - def accept(self, settings, r): - r.raw = True - - @classmethod - def expr(klass): - e = pp.Literal("r").suppress() - return e.setParseAction(lambda x: klass(*x)) - - -class Path: - def __init__(self, value): - if isinstance(value, basestring): - value = ValueLiteral(value) - self.value = value - - def accept(self, settings, r): - r.path = self.value.get_generator(settings) - - @classmethod - def expr(klass): - e = NakedValue.copy() - return e.setParseAction(lambda x: klass(*x)) - - - -class Method: - methods = [ - "get", - "head", - "post", - "put", - "delete", - "options", - "trace", - "connect", - ] - def __init__(self, value): - # If it's a string, we were passed one of the methods, so we upper-case - # it to be canonical. The user can specify a different case by using a - # string value literal. - if isinstance(value, basestring): - value = ValueLiteral(value.upper()) - self.value = value - - def accept(self, settings, r): - r.method = self.value.get_generator(settings) - - @classmethod - def expr(klass): - parts = [pp.CaselessLiteral(i) for i in klass.methods] - m = pp.MatchFirst(parts) - spec = m | Value.copy() - spec = spec.setParseAction(lambda x: klass(*x)) - return spec - - -class PauseAt: - def __init__(self, offset, seconds): - self.offset, self.seconds = offset, seconds - - @classmethod - def expr(klass): - e = pp.Literal("p").suppress() - e += Offset - e += pp.Literal(",").suppress() - e += pp.MatchFirst( - [ - v_integer, - pp.Literal("f") - ] - ) - return e.setParseAction(lambda x: klass(*x)) - - def accept(self, settings, r): - r.actions.append((self.offset, "pause", self.seconds)) - - -class DisconnectAt: - def __init__(self, value): - self.value = value - - def accept(self, settings, r): - r.actions.append((self.value, "disconnect")) - - @classmethod - def expr(klass): - e = pp.Literal("d").suppress() - e += Offset - return e.setParseAction(lambda x: klass(*x)) - - -class InjectAt: - def __init__(self, offset, value): - self.offset, self.value = offset, value - - @classmethod - def expr(klass): - e = pp.Literal("i").suppress() - e += Offset - e += pp.Literal(",").suppress() - e += Value - return e.setParseAction(lambda x: klass(*x)) - - def accept(self, settings, r): - r.actions.append( - ( - self.offset, - "inject", - self.value.get_generator(settings) - ) - ) - - -class Header: - def __init__(self, key, value): - self.key, self.value = key, value - - def accept(self, settings, r): - r.headers.append( - ( - self.key.get_generator(settings), - self.value.get_generator(settings) - ) - ) - - @classmethod - def expr(klass): - e = pp.Literal("h").suppress() - e += Value - e += pp.Literal("=").suppress() - e += Value - return e.setParseAction(lambda x: klass(*x)) - - -class Code: - def __init__(self, code, msg=None): - self.code, self.msg = code, msg - if msg is None: - self.msg = ValueLiteral(http_status.RESPONSES.get(self.code, "Unknown code")) - - def accept(self, settings, r): - r.code = self.code - r.msg = self.msg.get_generator(settings) - - @classmethod - def expr(klass): - e = v_integer - e = e + pp.Optional( - Value - ) - return e.setParseAction(lambda x: klass(*x)) - - - -class Message: - version = "HTTP/1.1" - def __init__(self): - self.body = LiteralGenerator("") - self.headers = [] - self.actions = [] - self.raw = False - - def length(self): - """ - Calculate the length of the base message without any applied actions. - """ - l = sum(len(x) for x in self.preamble()) - l += 2 - for i in self.headers: - l += len(i[0]) + len(i[1]) - l += 4 - l += 2 - l += len(self.body) - return l - - def preview_safe(self): - """ - Modify this message to be safe for previews. Returns a list of elided actions. - """ - pauses = [i for i in self.actions if i[1] == "pause"] - self.actions = [i for i in self.actions if i[1] != "pause"] - return pauses - - def effective_length(self, actions): - """ - Calculate the length of the base message with all applied actions. - """ - # Order matters here, and must match the order of application in - # write_values. - l = self.length() - for i in reversed(actions): - if i[1] == "disconnect": - return i[0] - elif i[1] == "inject": - l += len(i[2]) - return l - - def serve(self, fp, check, request_host): - """ - fp: The file pointer to write to. - - check: A function called with the effective actions (after random - values have been calculated). If it returns False service proceeds, - otherwise the return is treated as an error message to be sent to - the client, and service stops. - - 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. - """ - started = time.time() - if not self.raw: - if self.body and not utils.get_header("Content-Length", self.headers): - self.headers.append( - ( - LiteralGenerator("Content-Length"), - LiteralGenerator(str(len(self.body))), - ) - ) - if request_host: - if not utils.get_header("Host", self.headers): - self.headers.append( - ( - LiteralGenerator("Host"), - LiteralGenerator(request_host) - ) - ) - - else: - if not utils.get_header("Date", self.headers): - self.headers.append( - ( - LiteralGenerator("Date"), - LiteralGenerator(formatdate(timeval=None, localtime=False, usegmt=True)) - ) - ) - - hdrs = [] - for k, v in self.headers: - hdrs.extend([ - k, - ": ", - v, - "\r\n", - ]) - vals = self.preamble() - vals.append("\r\n") - vals.extend(hdrs) - vals.append("\r\n") - if self.body: - vals.append(self.body) - vals.reverse() - actions = ready_actions(self.length(), self.actions) - actions.reverse() - if check: - ret = check(self, actions) - if ret: - err = PathodErrorResponse(ret) - err.serve(fp) - return dict( - disconnect = True, - actions = actions_log(actions), - error = ret - ) - disconnect = write_values(fp, vals, actions[:]) - duration = time.time() - started - ret = dict( - disconnect = disconnect, - started = started, - duration = duration, - actions = actions_log(actions), - ) - 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, "__len__"): - v = v[:TRUNCATE] - ret[i] = v - return ret - - -Sep = pp.Optional(pp.Literal(":")).suppress() - -class Response(Message): - comps = ( - Body, - Header, - PauseAt, - DisconnectAt, - InjectAt, - ShortcutContentType, - ShortcutLocation, - Raw - ) - logattrs = ["code", "version", "body"] - def __init__(self): - Message.__init__(self) - self.code = 200 - self.msg = LiteralGenerator(http_status.RESPONSES[self.code]) - - def preamble(self): - return [self.version, " ", str(self.code), " ", self.msg] - - @classmethod - def expr(klass): - parts = [i.expr() for i in klass.comps] - atom = pp.MatchFirst(parts) - resp = pp.And( - [ - Code.expr(), - pp.ZeroOrMore(Sep + atom) - ] - ) - return resp - - def __str__(self): - parts = [ - "%s %s"%(self.code, self.msg[:]) - ] - return "\n".join(parts) - - -class Request(Message): - comps = ( - Body, - Header, - PauseAt, - DisconnectAt, - InjectAt, - ShortcutContentType, - Raw - ) - logattrs = ["method", "path", "body"] - def __init__(self): - Message.__init__(self) - self.method = None - self.path = None - - def preamble(self): - return [self.method, " ", self.path, " ", self.version] - - @classmethod - def expr(klass): - parts = [i.expr() for i in klass.comps] - atom = pp.MatchFirst(parts) - resp = pp.And( - [ - Method.expr(), - Sep, - Path.expr(), - pp.ZeroOrMore(Sep + atom) - ] - ) - return resp - - def __str__(self): - parts = [ - "%s %s"%(self.method[:], self.path[:]) - ] - return "\n".join(parts) - - -class CraftedRequest(Request): - def __init__(self, settings, spec, tokens): - Request.__init__(self) - self.spec, self.tokens = spec, tokens - for i in tokens: - i.accept(settings, self) - - def serve(self, fp, check, host): - d = Request.serve(self, fp, check, host) - d["spec"] = self.spec - return d - - -class CraftedResponse(Response): - def __init__(self, settings, spec, tokens): - Response.__init__(self) - self.spec, self.tokens = spec, tokens - for i in tokens: - i.accept(settings, self) - - def serve(self, fp, check): - d = Response.serve(self, fp, check, None) - d["spec"] = self.spec - return d - - -class PathodErrorResponse(Response): - def __init__(self, msg, body=None): - Response.__init__(self) - self.code = 800 - self.msg = LiteralGenerator(msg) - self.body = LiteralGenerator("pathod error: " + (body or msg)) - self.headers = [ - ( - LiteralGenerator("Content-Type"), LiteralGenerator("text/plain") - ), - ] - - def serve(self, fp, check=None): - d = Response.serve(self, fp, check, None) - d["internal"] = True - return d - - -FILESTART = "+" -def read_file(settings, s): - uf = settings.get("unconstrained_file_access") - sd = settings.get("staticdir") - if not sd: - raise FileAccessDenied("File access disabled.") - sd = os.path.normpath(os.path.abspath(sd)) - s = s[1:] - s = os.path.expanduser(s) - s = os.path.normpath(os.path.abspath(os.path.join(sd, s))) - if not uf and not s.startswith(sd): - raise FileAccessDenied("File access outside of configured directory") - if not os.path.isfile(s): - raise FileAccessDenied("File not readable") - return file(s, "r").read() - - -def parse_response(settings, s): - """ - May raise ParseException or FileAccessDenied - """ - if s.startswith(FILESTART): - s = read_file(settings, s) - try: - return CraftedResponse(settings, s, Response.expr().parseString(s, parseAll=True)) - except pp.ParseException, v: - raise ParseException(v.msg, v.line, v.col) - - -def parse_request(settings, s): - """ - May raise ParseException or FileAccessDenied - """ - if s.startswith(FILESTART): - s = read_file(settings, s) - try: - return CraftedRequest(settings, s, Request.expr().parseString(s, parseAll=True)) - except pp.ParseException, v: - raise ParseException(v.msg, v.line, v.col) diff --git a/pathoc b/pathoc index 5932d4a4..c9290587 100755 --- a/pathoc +++ b/pathoc @@ -1,6 +1,6 @@ #!/usr/bin/env python import argparse, sys -from libpathod import pathoc, version, rparse +from libpathod import pathoc, version, language from netlib import tcp if __name__ == "__main__": diff --git a/pathod b/pathod index e692a935..41b7578d 100755 --- a/pathod +++ b/pathod @@ -1,7 +1,7 @@ #!/usr/bin/env python import argparse, sys, logging, logging.handlers import os -from libpathod import pathod, utils, version, rparse +from libpathod import pathod, utils, version, language def daemonize (stdin='/dev/null', stdout='/dev/null', stderr='/dev/null'): @@ -96,7 +96,7 @@ def main(parser, args): ) except pathod.PathodError, v: parser.error(str(v)) - except rparse.FileAccessDenied, v: + except language.FileAccessDenied, v: parser.error("%s You probably want to a -d argument."%str(v)) try: diff --git a/test/test_language.py b/test/test_language.py new file mode 100644 index 00000000..0668fba2 --- /dev/null +++ b/test/test_language.py @@ -0,0 +1,529 @@ +import os, cStringIO +from libpathod import language, utils +import tutils + +language.TESTING = True + + +class TestMisc: + def test_generators(self): + v = language.Value.parseString("'val'")[0] + g = v.get_generator({}) + assert g[:] == "val" + + def test_randomgenerator(self): + g = language.RandomGenerator("bytes", 100) + assert repr(g) + assert len(g[:10]) == 10 + assert len(g[1:10]) == 9 + assert len(g[:1000]) == 100 + assert len(g[1000:1001]) == 0 + assert g[0] + + def test_literalgenerator(self): + g = language.LiteralGenerator("one") + assert repr(g) + assert g == "one" + assert g[:] == "one" + assert g[1] == "n" + + def test_filegenerator(self): + with tutils.tmpdir() as t: + path = os.path.join(t, "foo") + f = open(path, "w") + f.write("x"*10000) + f.close() + g = language.FileGenerator(path) + assert len(g) == 10000 + assert g[0] == "x" + assert g[-1] == "x" + assert g[0:5] == "xxxxx" + assert repr(g) + + def test_valueliteral(self): + v = language.ValueLiteral("foo") + assert v.expr() + assert v.val == "foo" + + v = language.ValueLiteral(r"foo\n") + assert v.expr() + assert v.val == "foo\n" + assert repr(v) + + def test_valuenakedliteral(self): + v = language.ValueNakedLiteral("foo") + assert v.expr() + assert repr(v) + + def test_file_value(self): + v = language.Value.parseString("<'one two'")[0] + assert str(v) + assert v.path == "one two" + + v = language.Value.parseString(" 100 + + x = [(1, 5), (0, 5)] + assert language.ready_actions(100, x) == sorted(x) + + +class TestResponse: + def dummy_response(self): + return language.parse_response({}, "400'msg'") + + def test_file(self): + p = tutils.test_data.path("data") + d = dict(staticdir=p) + r = language.parse_response(d, "+response") + assert r.code == 202 + + def test_response(self): + r = language.parse_response({}, "400'msg'") + assert r.code == 400 + assert r.msg == "msg" + + r = language.parse_response({}, "400'msg':b@100b") + assert r.msg == "msg" + assert r.body[:] + assert str(r) + + def test_checkfunc(self): + s = cStringIO.StringIO() + r = language.parse_response({}, "400:b@100k") + def check(req, acts): + return "errmsg" + assert r.serve(s, check=check)["error"] == "errmsg" + + def test_render(self): + s = cStringIO.StringIO() + r = language.parse_response({}, "400'msg'") + assert r.serve(s, None) + + def test_raw(self): + s = cStringIO.StringIO() + r = language.parse_response({}, "400:b'foo'") + r.serve(s, None) + v = s.getvalue() + assert "Content-Length" in v + assert "Date" in v + + s = cStringIO.StringIO() + r = language.parse_response({}, "400:b'foo':r") + r.serve(s, None) + v = s.getvalue() + assert not "Content-Length" in v + assert not "Date" in v + + def test_length(self): + def testlen(x): + s = cStringIO.StringIO() + x.serve(s, None) + assert x.length() == len(s.getvalue()) + testlen(language.parse_response({}, "400'msg'")) + testlen(language.parse_response({}, "400'msg':h'foo'='bar'")) + testlen(language.parse_response({}, "400'msg':h'foo'='bar':b@100b")) + + def test_effective_length(self): + def testlen(x, actions): + s = cStringIO.StringIO() + x.serve(s, None) + assert x.effective_length(actions) == len(s.getvalue()) + actions = [ + + ] + r = language.parse_response({}, "400'msg':b@100") + + actions = [ + (0, "disconnect"), + ] + r.actions = actions + testlen(r, actions) + + actions = [ + (0, "disconnect"), + (0, "inject", "foo") + ] + r.actions = actions + testlen(r, actions) + + actions = [ + (0, "inject", "foo") + ] + r.actions = actions + testlen(r, actions) + + def test_render(self): + r = language.parse_response({}, "400:p0,100:dr") + assert r.actions[0][1] == "pause" + assert len(r.preview_safe()) == 1 + assert not r.actions[0][1] == "pause" + + + +def test_read_file(): + tutils.raises(language.FileAccessDenied, language.read_file, {}, "=/foo") + p = tutils.test_data.path("data") + d = dict(staticdir=p) + assert language.read_file(d, "+./file").strip() == "testfile" + assert language.read_file(d, "+file").strip() == "testfile" + tutils.raises(language.FileAccessDenied, language.read_file, d, "+./nonexistent") + tutils.raises(language.FileAccessDenied, language.read_file, d, "+/nonexistent") + + tutils.raises(language.FileAccessDenied, language.read_file, d, "+../test_language.py") + d["unconstrained_file_access"] = True + assert language.read_file(d, "+../test_language.py") diff --git a/test/test_rparse.py b/test/test_rparse.py deleted file mode 100644 index 3cbec335..00000000 --- a/test/test_rparse.py +++ /dev/null @@ -1,529 +0,0 @@ -import os, cStringIO -from libpathod import rparse, utils -import tutils - -rparse.TESTING = True - - -class TestMisc: - def test_generators(self): - v = rparse.Value.parseString("'val'")[0] - g = v.get_generator({}) - assert g[:] == "val" - - def test_randomgenerator(self): - g = rparse.RandomGenerator("bytes", 100) - assert repr(g) - assert len(g[:10]) == 10 - assert len(g[1:10]) == 9 - assert len(g[:1000]) == 100 - assert len(g[1000:1001]) == 0 - assert g[0] - - def test_literalgenerator(self): - g = rparse.LiteralGenerator("one") - assert repr(g) - assert g == "one" - assert g[:] == "one" - assert g[1] == "n" - - def test_filegenerator(self): - with tutils.tmpdir() as t: - path = os.path.join(t, "foo") - f = open(path, "w") - f.write("x"*10000) - f.close() - g = rparse.FileGenerator(path) - assert len(g) == 10000 - assert g[0] == "x" - assert g[-1] == "x" - assert g[0:5] == "xxxxx" - assert repr(g) - - def test_valueliteral(self): - v = rparse.ValueLiteral("foo") - assert v.expr() - assert v.val == "foo" - - v = rparse.ValueLiteral(r"foo\n") - assert v.expr() - assert v.val == "foo\n" - assert repr(v) - - def test_valuenakedliteral(self): - v = rparse.ValueNakedLiteral("foo") - assert v.expr() - assert repr(v) - - def test_file_value(self): - v = rparse.Value.parseString("<'one two'")[0] - assert str(v) - assert v.path == "one two" - - v = rparse.Value.parseString(" 100 - - x = [(1, 5), (0, 5)] - assert rparse.ready_actions(100, x) == sorted(x) - - -class TestResponse: - def dummy_response(self): - return rparse.parse_response({}, "400'msg'") - - def test_file(self): - p = tutils.test_data.path("data") - d = dict(staticdir=p) - r = rparse.parse_response(d, "+response") - assert r.code == 202 - - def test_response(self): - r = rparse.parse_response({}, "400'msg'") - assert r.code == 400 - assert r.msg == "msg" - - r = rparse.parse_response({}, "400'msg':b@100b") - assert r.msg == "msg" - assert r.body[:] - assert str(r) - - def test_checkfunc(self): - s = cStringIO.StringIO() - r = rparse.parse_response({}, "400:b@100k") - def check(req, acts): - return "errmsg" - assert r.serve(s, check=check)["error"] == "errmsg" - - def test_render(self): - s = cStringIO.StringIO() - r = rparse.parse_response({}, "400'msg'") - assert r.serve(s, None) - - def test_raw(self): - s = cStringIO.StringIO() - r = rparse.parse_response({}, "400:b'foo'") - r.serve(s, None) - v = s.getvalue() - assert "Content-Length" in v - assert "Date" in v - - s = cStringIO.StringIO() - r = rparse.parse_response({}, "400:b'foo':r") - r.serve(s, None) - v = s.getvalue() - assert not "Content-Length" in v - assert not "Date" in v - - def test_length(self): - def testlen(x): - s = cStringIO.StringIO() - x.serve(s, None) - assert x.length() == len(s.getvalue()) - testlen(rparse.parse_response({}, "400'msg'")) - testlen(rparse.parse_response({}, "400'msg':h'foo'='bar'")) - testlen(rparse.parse_response({}, "400'msg':h'foo'='bar':b@100b")) - - def test_effective_length(self): - def testlen(x, actions): - s = cStringIO.StringIO() - x.serve(s, None) - assert x.effective_length(actions) == len(s.getvalue()) - actions = [ - - ] - r = rparse.parse_response({}, "400'msg':b@100") - - actions = [ - (0, "disconnect"), - ] - r.actions = actions - testlen(r, actions) - - actions = [ - (0, "disconnect"), - (0, "inject", "foo") - ] - r.actions = actions - testlen(r, actions) - - actions = [ - (0, "inject", "foo") - ] - r.actions = actions - testlen(r, actions) - - def test_render(self): - r = rparse.parse_response({}, "400:p0,100:dr") - assert r.actions[0][1] == "pause" - assert len(r.preview_safe()) == 1 - assert not r.actions[0][1] == "pause" - - - -def test_read_file(): - tutils.raises(rparse.FileAccessDenied, rparse.read_file, {}, "=/foo") - p = tutils.test_data.path("data") - d = dict(staticdir=p) - assert rparse.read_file(d, "+./file").strip() == "testfile" - assert rparse.read_file(d, "+file").strip() == "testfile" - tutils.raises(rparse.FileAccessDenied, rparse.read_file, d, "+./nonexistent") - tutils.raises(rparse.FileAccessDenied, rparse.read_file, d, "+/nonexistent") - - tutils.raises(rparse.FileAccessDenied, rparse.read_file, d, "+../test_rparse.py") - d["unconstrained_file_access"] = True - assert rparse.read_file(d, "+../test_rparse.py") -- cgit v1.2.3 From 2bdb5d15b935f345e769bacafe60f0f594929f31 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Fri, 5 Oct 2012 10:46:09 +1300 Subject: Add a simple libpathod.pathoc example --- examples/pathoc.py | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100755 examples/pathoc.py diff --git a/examples/pathoc.py b/examples/pathoc.py new file mode 100755 index 00000000..4c3fae52 --- /dev/null +++ b/examples/pathoc.py @@ -0,0 +1,6 @@ +#!/usr/bin/env python +from libpathod import pathoc + +p = pathoc.Pathoc("google.com", 80) +p.connect() +print p.request("get:/") -- cgit v1.2.3 From 495daf2b641d14bbe8d7cab1482e12e8c8d7ea1b Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Fri, 5 Oct 2012 11:14:17 +1300 Subject: Remove actions log and explain, preparing for a more sophisticated take on this. --- libpathod/language.py | 18 ++---------------- libpathod/pathoc.py | 7 ------- test/test_pathoc.py | 6 +----- 3 files changed, 3 insertions(+), 28 deletions(-) diff --git a/libpathod/language.py b/libpathod/language.py index 53878b97..960e9d17 100644 --- a/libpathod/language.py +++ b/libpathod/language.py @@ -25,18 +25,6 @@ class ParseException(Exception): return "%s at char %s"%(self.msg, self.col) -def actions_log(lst): - ret = [] - for i in lst: - if i[1] == "inject": - ret.append( - [i[0], i[1], repr(i[2])] - ) - else: - ret.append(i) - return ret - - def ready_actions(length, lst): ret = [] for i in lst: @@ -620,7 +608,6 @@ class Message: err.serve(fp) return dict( disconnect = True, - actions = actions_log(actions), error = ret ) disconnect = write_values(fp, vals, actions[:]) @@ -629,7 +616,6 @@ class Message: disconnect = disconnect, started = started, duration = duration, - actions = actions_log(actions), ) for i in self.logattrs: v = getattr(self, i) @@ -656,8 +642,8 @@ class Response(Message): logattrs = ["code", "version", "body"] def __init__(self): Message.__init__(self) - self.code = 200 - self.msg = LiteralGenerator(http_status.RESPONSES[self.code]) + self.code = None + self.msg = None def preamble(self): return [self.version, " ", str(self.code), " ", self.msg] diff --git a/libpathod/pathoc.py b/libpathod/pathoc.py index bab568ca..3ed09190 100644 --- a/libpathod/pathoc.py +++ b/libpathod/pathoc.py @@ -83,13 +83,6 @@ class Pathoc(tcp.TCPClient): if req: if ignorecodes and resp and resp[1] in ignorecodes: return - if explain: - print >> fp, ">>", req["method"], repr(req["path"]) - for a in req["actions"]: - print >> fp, "\t", - for x in a: - print >> fp, x, - print >> fp if showreq: self._show(fp, ">> Request", self.wfile.get_log(), hexdump) diff --git a/test/test_pathoc.py b/test/test_pathoc.py index 1c64f076..bec339cb 100644 --- a/test/test_pathoc.py +++ b/test/test_pathoc.py @@ -75,12 +75,8 @@ class TestDaemon: def test_conn_err(self): assert "Invalid server response" in self.tval(["get:'/p/200:d2'"]) - def test_explain(self): - reqs = [ "get:/api/info:ir,'x'"] - assert "inject" in self.tval(reqs, explain=True) - def test_fileread(self): d = tutils.test_data.path("data/request") - assert "foo" in self.tval(["+%s"%d], explain=True) + assert "foo" in self.tval(["+%s"%d], showreq=True) assert "File" in self.tval(["+/nonexistent"]) -- cgit v1.2.3 From e83392bfc8e44323c326e0a677210b9c1e6a3268 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Fri, 5 Oct 2012 11:23:30 +1300 Subject: Start making Action objects more sophisticated. --- libpathod/language.py | 31 +++++++++++++++++++++++-------- test/test_language.py | 18 +++++++++++++++--- 2 files changed, 38 insertions(+), 11 deletions(-) diff --git a/libpathod/language.py b/libpathod/language.py index 960e9d17..1445ca82 100644 --- a/libpathod/language.py +++ b/libpathod/language.py @@ -401,9 +401,23 @@ class Method: return spec -class PauseAt: +class _Action: + """ + 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 __cmp__(self, other): + return cmp(self.offset, other.offset) + + +class PauseAt(_Action): def __init__(self, offset, seconds): - self.offset, self.seconds = offset, seconds + _Action.__init__(self, offset) + self.seconds = seconds @classmethod def expr(klass): @@ -422,12 +436,12 @@ class PauseAt: r.actions.append((self.offset, "pause", self.seconds)) -class DisconnectAt: - def __init__(self, value): - self.value = value +class DisconnectAt(_Action): + def __init__(self, offset): + _Action.__init__(self, offset) def accept(self, settings, r): - r.actions.append((self.value, "disconnect")) + r.actions.append((self.offset, "disconnect")) @classmethod def expr(klass): @@ -436,9 +450,10 @@ class DisconnectAt: return e.setParseAction(lambda x: klass(*x)) -class InjectAt: +class InjectAt(_Action): def __init__(self, offset, value): - self.offset, self.value = offset, value + _Action.__init__(self, offset) + self.value = value @classmethod def expr(klass): diff --git a/test/test_language.py b/test/test_language.py index 0668fba2..7680492f 100644 --- a/test/test_language.py +++ b/test/test_language.py @@ -168,6 +168,18 @@ class TestMisc: s.serve(d) +class Test_Action: + def test_cmp(self): + a = language._Action(0) + b = language._Action(1) + c = language._Action(0) + assert a < b + assert a == c + l = [b, a] + l.sort() + assert l[0].offset == 0 + + class TestDisconnects: def test_parse_response(self): assert (0, "disconnect") in language.parse_response({}, "400:d0").actions @@ -177,14 +189,14 @@ class TestDisconnects: e = language.DisconnectAt.expr() v = e.parseString("d0")[0] assert isinstance(v, language.DisconnectAt) - assert v.value == 0 + assert v.offset == 0 v = e.parseString("d100")[0] - assert v.value == 100 + assert v.offset == 100 e = language.DisconnectAt.expr() v = e.parseString("dr")[0] - assert v.value == "r" + assert v.offset == "r" class TestInject: -- cgit v1.2.3 From c684f7417d75660048351470990818505bfb1d53 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Wed, 24 Oct 2012 11:32:53 +1300 Subject: Extend Action and Value classes - Values now know how to print their own specs - Actions now know how to print their own specs - Actions have a resolve_offset method that resolves relative and random offsets. --- libpathod/language.py | 50 ++++++++++++-- test/test_language.py | 180 +++++++++++++++++++++++++++++++++----------------- 2 files changed, 165 insertions(+), 65 deletions(-) diff --git a/libpathod/language.py b/libpathod/language.py index 1445ca82..a5c53677 100644 --- a/libpathod/language.py +++ b/libpathod/language.py @@ -1,4 +1,4 @@ -import operator, string, random, mmap, os, time +import operator, string, random, mmap, os, time, copy from email.utils import formatdate import contrib.pyparsing as pp from netlib import http_status, tcp @@ -189,7 +189,7 @@ class _Value: return LiteralGenerator(self.val) def __repr__(self): - return self.val + return self.spec() class ValueLiteral(_Value): @@ -198,6 +198,9 @@ class ValueLiteral(_Value): e = v_literal.copy() return e.setParseAction(lambda x: klass(*x)) + def spec(self): + return '"%s"'%self.val.encode("string_escape") + class ValueNakedLiteral(_Value): @classmethod @@ -205,6 +208,9 @@ class ValueNakedLiteral(_Value): e = v_naked_literal.copy() return e.setParseAction(lambda x: klass(*x)) + def spec(self): + return self.val.encode("string_escape") + class ValueGenerate: def __init__(self, usize, unit, datatype): @@ -230,8 +236,16 @@ class ValueGenerate: e += pp.Optional(s, default="bytes") return e.setParseAction(lambda x: klass(*x)) - def __str__(self): - return "@%s%s,%s"%(self.usize, self.unit, self.datatype) + def spec(self): + s = "@%s"%self.usize + if self.unit != "b": + s += self.unit + if self.datatype != "bytes": + s += ",%s"%self.datatype + return s + + def __repr__(self): + return self.spec() class ValueFile: @@ -259,8 +273,8 @@ class ValueFile: raise FileAccessDenied("File not readable") return FileGenerator(s) - def __str__(self): - return "<%s"%(self.path) + def spec(self): + return '<"%s"'%self.path.encode("string_escape") Value = pp.MatchFirst( @@ -410,9 +424,24 @@ class _Action: def __init__(self, offset): self.offset = offset + def resolve_offset(self, msg): + """ + Resolves offset specifications to a numeric offset. Returns a copy + of the action object. + """ + c = copy.copy(self) + if c.offset == "r": + c.offset = random.randrange(msg.length()) + elif c.offset == "a": + c.offset = msg.length() + 1 + return c + def __cmp__(self, other): return cmp(self.offset, other.offset) + def __repr__(self): + return self.spec() + class PauseAt(_Action): def __init__(self, offset, seconds): @@ -432,6 +461,9 @@ class PauseAt(_Action): ) return e.setParseAction(lambda x: klass(*x)) + def spec(self): + return "p%s,%s"%(self.offset, self.seconds) + def accept(self, settings, r): r.actions.append((self.offset, "pause", self.seconds)) @@ -449,6 +481,9 @@ class DisconnectAt(_Action): e += Offset return e.setParseAction(lambda x: klass(*x)) + def spec(self): + return "d%s"%self.offset + class InjectAt(_Action): def __init__(self, offset, value): @@ -463,6 +498,9 @@ class InjectAt(_Action): e += Value return e.setParseAction(lambda x: klass(*x)) + def spec(self): + return "i%s,%s"%(self.offset, self.value.spec()) + def accept(self, settings, r): r.actions.append( ( diff --git a/test/test_language.py b/test/test_language.py index 7680492f..e1697f95 100644 --- a/test/test_language.py +++ b/test/test_language.py @@ -5,42 +5,21 @@ import tutils language.TESTING = True -class TestMisc: - def test_generators(self): - v = language.Value.parseString("'val'")[0] - g = v.get_generator({}) - assert g[:] == "val" +class TestValueNakedLiteral: + def test_expr(self): + v = language.ValueNakedLiteral("foo") + assert v.expr() - def test_randomgenerator(self): - g = language.RandomGenerator("bytes", 100) - assert repr(g) - assert len(g[:10]) == 10 - assert len(g[1:10]) == 9 - assert len(g[:1000]) == 100 - assert len(g[1000:1001]) == 0 - assert g[0] + def test_spec(self): + v = language.ValueNakedLiteral("foo") + assert v.spec() == repr(v) == "foo" - def test_literalgenerator(self): - g = language.LiteralGenerator("one") - assert repr(g) - assert g == "one" - assert g[:] == "one" - assert g[1] == "n" + v = language.ValueNakedLiteral("f\x00oo") + assert v.spec() == repr(v) == r"f\x00oo" - def test_filegenerator(self): - with tutils.tmpdir() as t: - path = os.path.join(t, "foo") - f = open(path, "w") - f.write("x"*10000) - f.close() - g = language.FileGenerator(path) - assert len(g) == 10000 - assert g[0] == "x" - assert g[-1] == "x" - assert g[0:5] == "xxxxx" - assert repr(g) - def test_valueliteral(self): +class TestValueLiteral: + def test_espr(self): v = language.ValueLiteral("foo") assert v.expr() assert v.val == "foo" @@ -50,11 +29,51 @@ class TestMisc: assert v.val == "foo\n" assert repr(v) - def test_valuenakedliteral(self): - v = language.ValueNakedLiteral("foo") - assert v.expr() - assert repr(v) + def test_spec(self): + v = language.ValueLiteral("foo") + assert v.spec() == r'"foo"' + + v = language.ValueLiteral("f\x00oo") + assert v.spec() == repr(v) == r'"f\x00oo"' + + +class TestValueGenerate: + def test_basic(self): + v = language.Value.parseString("@10b")[0] + assert v.usize == 10 + assert v.unit == "b" + assert v.bytes() == 10 + v = language.Value.parseString("@10")[0] + assert v.unit == "b" + v = language.Value.parseString("@10k")[0] + assert v.bytes() == 10240 + v = language.Value.parseString("@10g")[0] + assert v.bytes() == 1024**3 * 10 + v = language.Value.parseString("@10g,digits")[0] + assert v.datatype == "digits" + g = v.get_generator({}) + assert g[:100] + + v = language.Value.parseString("@10,digits")[0] + assert v.unit == "b" + assert v.datatype == "digits" + + def test_spec(self): + v = language.ValueGenerate(1, "b", "bytes") + assert v.spec() == repr(v) == "@1" + + v = language.ValueGenerate(1, "k", "bytes") + assert v.spec() == repr(v) == "@1k" + + v = language.ValueGenerate(1, "k", "ascii") + assert v.spec() == repr(v) == "@1k,ascii" + + v = language.ValueGenerate(1, "b", "ascii") + assert v.spec() == repr(v) == "@1,ascii" + + +class TestValueFile: def test_file_value(self): v = language.Value.parseString("<'one two'")[0] assert str(v) @@ -63,6 +82,8 @@ class TestMisc: v = language.Value.parseString(" Date: Thu, 25 Oct 2012 09:45:55 +1300 Subject: Start moving policy checks to service-time, rather than parse-time. --- libpathod/app.py | 4 +-- libpathod/language.py | 56 ++++++++++++++-------------------- libpathod/pathoc.py | 4 +-- libpathod/pathod.py | 4 +-- test/test_language.py | 83 ++++++++++++++++++++++----------------------------- 5 files changed, 64 insertions(+), 87 deletions(-) diff --git a/libpathod/app.py b/libpathod/app.py index 396e45c2..fc4e23ec 100644 --- a/libpathod/app.py +++ b/libpathod/app.py @@ -131,9 +131,9 @@ def _preview(is_request): args["pauses"] = r.preview_safe() if is_request: - r.serve(s, check=app.config["pathod"].check_policy, host="example.com") + r.serve(app.config["pathod"].request_settings, s, check=app.config["pathod"].check_policy, host="example.com") else: - r.serve(s, check=app.config["pathod"].check_policy) + r.serve(app.config["pathod"].request_settings, s, check=app.config["pathod"].check_policy) args["output"] = utils.escape_unprintables(s.getvalue()) return render(template, False, **args) diff --git a/libpathod/language.py b/libpathod/language.py index a5c53677..c9aa7f66 100644 --- a/libpathod/language.py +++ b/libpathod/language.py @@ -25,19 +25,6 @@ class ParseException(Exception): return "%s at char %s"%(self.msg, self.col) -def ready_actions(length, lst): - ret = [] - for i in lst: - itms = list(i) - if i[0] == "r": - itms[0] = random.randrange(length) - elif i[0] == "a": - itms[0] = length+1 - ret.append(tuple(itms)) - ret.sort() - return ret - - def send_chunk(fp, val, blocksize, start, end): """ (start, end): Inclusive lower bound, exclusive upper bound. @@ -441,6 +428,9 @@ class _Action: def __repr__(self): return self.spec() + + def accept(self, settings, r): + r.actions.append(self) class PauseAt(_Action): @@ -464,17 +454,14 @@ class PauseAt(_Action): def spec(self): return "p%s,%s"%(self.offset, self.seconds) - def accept(self, settings, r): - r.actions.append((self.offset, "pause", self.seconds)) + def intermediate(self, settings): + return (self.offset, "pause", self.seconds) class DisconnectAt(_Action): def __init__(self, offset): _Action.__init__(self, offset) - def accept(self, settings, r): - r.actions.append((self.offset, "disconnect")) - @classmethod def expr(klass): e = pp.Literal("d").suppress() @@ -484,6 +471,9 @@ class DisconnectAt(_Action): def spec(self): return "d%s"%self.offset + def intermediate(self, settings): + return (self.offset, "disconnect") + class InjectAt(_Action): def __init__(self, offset, value): @@ -501,14 +491,12 @@ class InjectAt(_Action): def spec(self): return "i%s,%s"%(self.offset, self.value.spec()) - def accept(self, settings, r): - r.actions.append( - ( + def intermediate(self, settings): + return ( self.offset, "inject", self.value.get_generator(settings) ) - ) class Header: @@ -577,8 +565,8 @@ class Message: """ Modify this message to be safe for previews. Returns a list of elided actions. """ - pauses = [i for i in self.actions if i[1] == "pause"] - self.actions = [i for i in self.actions if i[1] != "pause"] + pauses = [i for i in self.actions if isinstance(i, PauseAt)] + self.actions = [i for i in self.actions if not isinstance(i, PauseAt)] return pauses def effective_length(self, actions): @@ -595,7 +583,7 @@ class Message: l += len(i[2]) return l - def serve(self, fp, check, request_host): + def serve(self, settings, fp, check, request_host): """ fp: The file pointer to write to. @@ -652,13 +640,15 @@ class Message: if self.body: vals.append(self.body) vals.reverse() - actions = ready_actions(self.length(), self.actions) + actions = [i.resolve_offset(self) for i in self.actions] + actions.sort() actions.reverse() + actions = [i.intermediate(settings) for i in actions] if check: ret = check(self, actions) if ret: err = PathodErrorResponse(ret) - err.serve(fp) + err.serve(settings, fp) return dict( disconnect = True, error = ret @@ -767,8 +757,8 @@ class CraftedRequest(Request): for i in tokens: i.accept(settings, self) - def serve(self, fp, check, host): - d = Request.serve(self, fp, check, host) + def serve(self, settings, fp, check, host): + d = Request.serve(self, settings, fp, check, host) d["spec"] = self.spec return d @@ -780,8 +770,8 @@ class CraftedResponse(Response): for i in tokens: i.accept(settings, self) - def serve(self, fp, check): - d = Response.serve(self, fp, check, None) + def serve(self, settings, fp, check): + d = Response.serve(self, settings, fp, check, None) d["spec"] = self.spec return d @@ -798,8 +788,8 @@ class PathodErrorResponse(Response): ), ] - def serve(self, fp, check=None): - d = Response.serve(self, fp, check, None) + def serve(self, settings, fp, check=None): + d = Response.serve(self, settings, fp, check, None) d["internal"] = True return d diff --git a/libpathod/pathoc.py b/libpathod/pathoc.py index 3ed09190..873a989c 100644 --- a/libpathod/pathoc.py +++ b/libpathod/pathoc.py @@ -22,7 +22,7 @@ class Pathoc(tcp.TCPClient): language.FileAccessDenied. """ r = language.parse_request(self.settings, spec) - ret = r.serve(self.wfile, None, self.host) + ret = r.serve(self.settings, self.wfile, None, self.host) self.wfile.flush() return http.read_response(self.rfile, r.method, None) @@ -68,7 +68,7 @@ class Pathoc(tcp.TCPClient): if showresp: self.rfile.start_log() try: - req = r.serve(self.wfile, None, self.host) + req = r.serve(self.settings, self.wfile, None, self.host) self.wfile.flush() resp = http.read_response(self.rfile, r.method, None) except http.HttpError, v: diff --git a/libpathod/pathod.py b/libpathod/pathod.py index 4ce268fa..9d343a51 100644 --- a/libpathod/pathod.py +++ b/libpathod/pathod.py @@ -18,7 +18,7 @@ class PathodHandler(tcp.BaseHandler): self.sni = connection.get_servername() def serve_crafted(self, crafted, request_log): - response_log = crafted.serve(self.wfile, self.server.check_policy) + response_log = crafted.serve(self.server.request_settings, self.wfile, self.server.check_policy) log = dict( type = "crafted", request=request_log, @@ -96,7 +96,7 @@ class PathodHandler(tcp.BaseHandler): return self.serve_crafted(crafted, request_log) elif self.server.noweb: crafted = language.PathodErrorResponse("Access Denied") - crafted.serve(self.wfile, self.server.check_policy) + crafted.serve(self.server.request_settings, self.wfile, self.server.check_policy) return False, dict(type = "error", msg="Access denied: web interface disabled") else: self.info("app: %s %s"%(method, path)) diff --git a/test/test_language.py b/test/test_language.py index e1697f95..a0781327 100644 --- a/test/test_language.py +++ b/test/test_language.py @@ -206,7 +206,7 @@ class TestMisc: def test_internal_response(self): d = cStringIO.StringIO() s = language.PathodErrorResponse("foo") - s.serve(d) + s.serve({}, d) class Test_Action: @@ -229,8 +229,10 @@ class Test_Action: class TestDisconnects: def test_parse_response(self): - assert (0, "disconnect") in language.parse_response({}, "400:d0").actions - assert ("r", "disconnect") in language.parse_response({}, "400:dr").actions + a = language.parse_response({}, "400:d0").actions[0] + assert a.spec() == "d0" + a = language.parse_response({}, "400:dr").actions[0] + assert a.spec() == "dr" def test_at(self): e = language.DisconnectAt.expr() @@ -253,12 +255,12 @@ class TestDisconnects: class TestInject: def test_parse_response(self): a = language.parse_response({}, "400:ir,@100").actions[0] - assert a[0] == "r" - assert a[1] == "inject" + assert a.offset == "r" + assert a.value.datatype == "bytes" + assert a.value.usize == 100 a = language.parse_response({}, "400:ia,@100").actions[0] - assert a[0] == "a" - assert a[1] == "inject" + assert a.offset == "a" def test_at(self): e = language.InjectAt.expr() @@ -273,7 +275,7 @@ class TestInject: def test_serve(self): s = cStringIO.StringIO() r = language.parse_response({}, "400:i0,'foo'") - assert r.serve(s, None) + assert r.serve({}, s, None) def test_spec(self): e = language.InjectAt.expr() @@ -281,7 +283,6 @@ class TestInject: assert v.spec() == 'i0,"foo"' - class TestPauses: def test_parse_response(self): e = language.PauseAt.expr() @@ -300,7 +301,7 @@ class TestPauses: def test_request(self): r = language.parse_response({}, '400:p10,10') - assert r.actions[0] == (10, "pause", 10) + assert r.actions[0].spec() == "p10,10" def test_spec(self): assert language.PauseAt("r", 5).spec() == "pr,5" @@ -336,7 +337,7 @@ class TestParseRequest: def test_render(self): s = cStringIO.StringIO() r = language.parse_request({}, "GET:'/foo'") - assert r.serve(s, None, "foo.com") + assert r.serve({}, s, None, "foo.com") def test_str(self): r = language.parse_request({}, 'GET:"/foo"') @@ -386,15 +387,15 @@ class TestParseResponse: def test_parse_pause_before(self): r = language.parse_response({}, "400:p0,10") - assert (0, "pause", 10) in r.actions + assert r.actions[0].spec() == "p0,10" def test_parse_pause_after(self): r = language.parse_response({}, "400:pa,10") - assert ("a", "pause", 10) in r.actions + assert r.actions[0].spec() == "pa,10" def test_parse_pause_random(self): r = language.parse_response({}, "400:pr,10") - assert ("r", "pause", 10) in r.actions + assert r.actions[0].spec() == "pr,10" def test_parse_stress(self): r = language.parse_response({}, "400:b@100g") @@ -468,34 +469,18 @@ class TestWriteValues: def test_write_values_after(self): s = cStringIO.StringIO() r = language.parse_response({}, "400:da") - r.serve(s, None) + r.serve({}, s, None) s = cStringIO.StringIO() r = language.parse_response({}, "400:pa,0") - r.serve(s, None) + r.serve({}, s, None) s = cStringIO.StringIO() r = language.parse_response({}, "400:ia,'xx'") - r.serve(s, None) + r.serve({}, s, None) assert s.getvalue().endswith('xx') -def test_ready_actions(): - x = [(0, 5)] - assert language.ready_actions(100, x) == x - - x = [("r", 5)] - ret = language.ready_actions(100, x) - assert 0 <= ret[0][0] < 100 - - x = [("a", "pause", 5)] - ret = language.ready_actions(100, x) - assert ret[0][0] > 100 - - x = [(1, 5), (0, 5)] - assert language.ready_actions(100, x) == sorted(x) - - class TestResponse: def dummy_response(self): return language.parse_response({}, "400'msg'") @@ -521,24 +506,24 @@ class TestResponse: r = language.parse_response({}, "400:b@100k") def check(req, acts): return "errmsg" - assert r.serve(s, check=check)["error"] == "errmsg" + assert r.serve({}, s, check=check)["error"] == "errmsg" def test_render(self): s = cStringIO.StringIO() r = language.parse_response({}, "400'msg'") - assert r.serve(s, None) + assert r.serve({}, s, None) def test_raw(self): s = cStringIO.StringIO() r = language.parse_response({}, "400:b'foo'") - r.serve(s, None) + r.serve({}, s, None) v = s.getvalue() assert "Content-Length" in v assert "Date" in v s = cStringIO.StringIO() r = language.parse_response({}, "400:b'foo':r") - r.serve(s, None) + r.serve({}, s, None) v = s.getvalue() assert not "Content-Length" in v assert not "Date" in v @@ -546,46 +531,48 @@ class TestResponse: def test_length(self): def testlen(x): s = cStringIO.StringIO() - x.serve(s, None) + x.serve({}, s, None) assert x.length() == len(s.getvalue()) testlen(language.parse_response({}, "400'msg'")) testlen(language.parse_response({}, "400'msg':h'foo'='bar'")) testlen(language.parse_response({}, "400'msg':h'foo'='bar':b@100b")) def test_effective_length(self): + l = [None] + def check(req, actions): + l[0] = req.effective_length(actions) + def testlen(x, actions): s = cStringIO.StringIO() - x.serve(s, None) - assert x.effective_length(actions) == len(s.getvalue()) - actions = [ + x.serve({}, s, check) + assert l[0] == len(s.getvalue()) - ] r = language.parse_response({}, "400'msg':b@100") actions = [ - (0, "disconnect"), + language.DisconnectAt(0) ] r.actions = actions testlen(r, actions) actions = [ - (0, "disconnect"), - (0, "inject", "foo") + language.DisconnectAt(0), + language.InjectAt(0, language.ValueLiteral("foo")) ] r.actions = actions testlen(r, actions) actions = [ - (0, "inject", "foo") + language.InjectAt(0, language.ValueLiteral("foo")) ] r.actions = actions testlen(r, actions) def test_render(self): r = language.parse_response({}, "400:p0,100:dr") - assert r.actions[0][1] == "pause" + assert r.actions[0].spec() == "p0,100" assert len(r.preview_safe()) == 1 - assert not r.actions[0][1] == "pause" + assert not r.actions[0].spec().startswith("p") -- cgit v1.2.3 From 6174e46023e798517ac206b7681dd9c7d36b1283 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Thu, 25 Oct 2012 10:59:18 +1300 Subject: Unit test suite love: 100% coverage Also start figuring out how to sanitize binary data in the JSON API. --- libpathod/app.py | 18 +++++++++--------- libpathod/language.py | 11 ++++++++++- libpathod/pathod.py | 6 +++--- test/test_app.py | 5 +++++ test/test_language.py | 12 +++++++++++- test/test_pathoc.py | 1 + test/test_pathod.py | 11 +++++++++++ test/tutils.py | 2 ++ 8 files changed, 52 insertions(+), 14 deletions(-) diff --git a/libpathod/app.py b/libpathod/app.py index fc4e23ec..1ebe9901 100644 --- a/libpathod/app.py +++ b/libpathod/app.py @@ -13,17 +13,17 @@ def api(): ) - @app.route('/api/log') - def api_log(): - return jsonify( - log = app.config["pathod"].get_log() - ) +@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" +@app.route('/api/clear_log') +def api_clear_log(): + app.config["pathod"].clear_log() + return "OK" def render(s, cacheable, **kwargs): diff --git a/libpathod/language.py b/libpathod/language.py index c9aa7f66..fa80360b 100644 --- a/libpathod/language.py +++ b/libpathod/language.py @@ -428,7 +428,7 @@ class _Action: def __repr__(self): return self.spec() - + def accept(self, settings, r): r.actions.append(self) @@ -665,6 +665,7 @@ class Message: # Careful not to log any VALUE specs without sanitizing them first. We truncate at 1k. if hasattr(v, "__len__"): v = v[:TRUNCATE] + v = v.encode("string_escape") ret[i] = v return ret @@ -815,6 +816,10 @@ def parse_response(settings, s): """ May raise ParseException or FileAccessDenied """ + try: + s.decode("ascii") + except UnicodeError: + raise ParseException("Spec must be valid ASCII.", 0, 0) if s.startswith(FILESTART): s = read_file(settings, s) try: @@ -827,6 +832,10 @@ def parse_request(settings, s): """ May raise ParseException or FileAccessDenied """ + try: + s.decode("ascii") + except UnicodeError: + raise ParseException("Spec must be valid ASCII.", 0, 0) if s.startswith(FILESTART): s = read_file(settings, s) try: diff --git a/libpathod/pathod.py b/libpathod/pathod.py index 9d343a51..d4535d03 100644 --- a/libpathod/pathod.py +++ b/libpathod/pathod.py @@ -1,4 +1,4 @@ -import urllib, threading, re, logging, socket, sys +import urllib, threading, re, logging, socket, sys, base64 from netlib import tcp, http, odict, wsgi import netlib.utils import version, app, language @@ -149,10 +149,10 @@ class PathodHandler(tcp.BaseHandler): again, log = self.handle_request() if log: if self.server.logreq: - log["request_bytes"] = self.rfile.get_log() + log["request_bytes"] = self.rfile.get_log().encode("string_escape") self._log_bytes("Request", log["request_bytes"], self.server.hexdump) if self.server.logresp: - log["response_bytes"] = self.wfile.get_log() + log["response_bytes"] = self.wfile.get_log().encode("string_escape") self._log_bytes("Response", log["response_bytes"], self.server.hexdump) self.server.add_log(log) if not again: diff --git a/test/test_app.py b/test/test_app.py index e7c1c085..7b2451d6 100644 --- a/test/test_app.py +++ b/test/test_app.py @@ -11,10 +11,15 @@ class TestApp(tutils.DaemonTests): r = self.getpath("/about") assert r.ok + def test_download(self): + r = self.getpath("/download") + assert r.ok + def test_docs(self): assert self.getpath("/docs/pathod").status_code == 200 assert self.getpath("/docs/pathoc").status_code == 200 assert self.getpath("/docs/language").status_code == 200 + assert self.getpath("/docs/libpathod").status_code == 200 assert self.getpath("/docs/test").status_code == 200 def test_log(self): diff --git a/test/test_language.py b/test/test_language.py index a0781327..e4da6d4d 100644 --- a/test/test_language.py +++ b/test/test_language.py @@ -226,6 +226,10 @@ class Test_Action: ret = e.resolve_offset(r) assert isinstance(ret.offset, int) + def test_repr(self): + e = language.DisconnectAt("r") + assert repr(e) + class TestDisconnects: def test_parse_response(self): @@ -281,7 +285,7 @@ class TestInject: e = language.InjectAt.expr() v = e.parseString("i0,'foo'")[0] assert v.spec() == 'i0,"foo"' - + class TestPauses: def test_parse_response(self): @@ -322,6 +326,9 @@ class TestParseRequest: r = language.parse_request(d, "+request") assert r.path == "/foo" + def test_nonascii(self): + tutils.raises("ascii", language.parse_request, {}, "get:\xf0") + def test_err(self): tutils.raises(language.ParseException, language.parse_request, {}, 'GET') @@ -381,6 +388,9 @@ class TestParseResponse: assert v.marked() assert str(v) + def test_nonascii(self): + tutils.raises("ascii", language.parse_response, {}, "foo:b\xf0") + def test_parse_header(self): r = language.parse_response({}, '400:h"foo"="bar"') assert utils.get_header("foo", r.headers) diff --git a/test/test_pathoc.py b/test/test_pathoc.py index bec339cb..c22fd4f8 100644 --- a/test/test_pathoc.py +++ b/test/test_pathoc.py @@ -52,6 +52,7 @@ class TestDaemon: def test_timeout(self): assert "Timeout" in self.tval(["get:'/p/200:p0,10'"], timeout=0.01) assert "HTTP" in self.tval(["get:'/p/200:p5,10'"], showresp=True, timeout=0.01) + assert not "HTTP" in self.tval(["get:'/p/200:p5,10'"], showresp=True, timeout=0.01, ignoretimeout=True) def test_showresp(self): reqs = [ "get:/api/info:p0,0", "get:/api/info:p0,0" ] diff --git a/test/test_pathod.py b/test/test_pathod.py index 429c2ef9..7bbb5545 100644 --- a/test/test_pathod.py +++ b/test/test_pathod.py @@ -61,7 +61,18 @@ class TestNohang(tutils.DaemonTests): assert "Pauses have been disabled" in l["response"]["error"] +class TestHexdump(tutils.DaemonTests): + hexdump = True + def test_hexdump(self): + r = self.get(r"200:b'\xf0'") + + class CommonTests(tutils.DaemonTests): + def test_binarydata(self): + r = self.get(r"200:b'\xf0'") + l = self.d.last_log() + # FIXME: Other binary data elements + def test_sizelimit(self): r = self.get("200:b@1g") assert r.status_code == 800 diff --git a/test/tutils.py b/test/tutils.py index 71a6034f..a63ed7eb 100644 --- a/test/tutils.py +++ b/test/tutils.py @@ -9,6 +9,7 @@ class DaemonTests: nohang = False ssl = False timeout = None + hexdump = False @classmethod def setUpAll(self): self.d = test.Daemon( @@ -20,6 +21,7 @@ class DaemonTests: noapi = self.noapi, nohang = self.nohang, timeout = self.timeout, + hexdump = self.hexdump, logreq = True, logresp = True ) -- cgit v1.2.3 From 06864e5a1b61eaa6684dc8b1a6bdc11ae7987720 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Sat, 27 Oct 2012 14:00:50 +1300 Subject: Major refactoring towars separate representation of expressions and request/response service. --- libpathod/language.py | 171 ++++++++++++++++++++++++-------------------------- libpathod/pathod.py | 2 +- libpathod/utils.py | 5 +- test/test_language.py | 12 ++-- 4 files changed, 91 insertions(+), 99 deletions(-) diff --git a/libpathod/language.py b/libpathod/language.py index fa80360b..a9670c32 100644 --- a/libpathod/language.py +++ b/libpathod/language.py @@ -292,17 +292,35 @@ Offset = pp.MatchFirst( ) -class ShortcutContentType: - def __init__(self, value): - self.value = value +class _Header: + def __init__(self, key, value): + self.key, self.value = key, value + + def values(self, settings): + return [ + self.key.get_generator(settings), + ": ", + self.value.get_generator(settings), + "\r\n", + ] def accept(self, settings, r): - r.headers.append( - ( - LiteralGenerator("Content-Type"), - self.value.get_generator(settings) - ) - ) + r.headers.append(self) + + +class Header(_Header): + @classmethod + def expr(klass): + e = pp.Literal("h").suppress() + e += Value + e += pp.Literal("=").suppress() + e += Value + return e.setParseAction(lambda x: klass(*x)) + + +class ShortcutContentType(_Header): + def __init__(self, value): + _Header.__init__(self, ValueLiteral("Content-Type"), value) @classmethod def expr(klass): @@ -311,18 +329,9 @@ class ShortcutContentType: return e.setParseAction(lambda x: klass(*x)) - -class ShortcutLocation: +class ShortcutLocation(_Header): def __init__(self, value): - self.value = value - - def accept(self, settings, r): - r.headers.append( - ( - LiteralGenerator("Location"), - self.value.get_generator(settings) - ) - ) + _Header.__init__(self, ValueLiteral("Location"), value) @classmethod def expr(klass): @@ -411,16 +420,17 @@ class _Action: def __init__(self, offset): self.offset = offset - def resolve_offset(self, msg): + def resolve_offset(self, msg, settings, request_host): """ Resolves offset specifications to a numeric offset. Returns a copy of the action object. """ c = copy.copy(self) + l = msg.length(settings, request_host) if c.offset == "r": - c.offset = random.randrange(msg.length()) + c.offset = random.randrange(l) elif c.offset == "a": - c.offset = msg.length() + 1 + c.offset = l + 1 return c def __cmp__(self, other): @@ -499,27 +509,6 @@ class InjectAt(_Action): ) -class Header: - def __init__(self, key, value): - self.key, self.value = key, value - - def accept(self, settings, r): - r.headers.append( - ( - self.key.get_generator(settings), - self.value.get_generator(settings) - ) - ) - - @classmethod - def expr(klass): - e = pp.Literal("h").suppress() - e += Value - e += pp.Literal("=").suppress() - e += Value - return e.setParseAction(lambda x: klass(*x)) - - class Code: def __init__(self, code, msg=None): self.code, self.msg = code, msg @@ -548,15 +537,14 @@ class Message: self.actions = [] self.raw = False - def length(self): + def length(self, settings, request_host): """ Calculate the length of the base message without any applied actions. """ l = sum(len(x) for x in self.preamble()) l += 2 - for i in self.headers: - l += len(i[0]) + len(i[1]) - l += 4 + for h in self.headervals(settings, request_host): + l += len(h) l += 2 l += len(self.body) return l @@ -569,20 +557,58 @@ class Message: self.actions = [i for i in self.actions if not isinstance(i, PauseAt)] return pauses - def effective_length(self, actions): + def effective_length(self, settings, request_host): """ Calculate the length of the base message with all applied actions. """ # Order matters here, and must match the order of application in # write_values. - l = self.length() - for i in reversed(actions): + l = self.length(settings, request_host) + for i in reversed(self.ready_actions(settings, request_host)): if i[1] == "disconnect": return i[0] elif i[1] == "inject": l += len(i[2]) return l + def headervals(self, settings, request_host): + hdrs = self.headers[:] + if not self.raw: + if self.body and not utils.get_header("Content-Length", self.headers): + hdrs.append( + Header( + ValueLiteral("Content-Length"), + ValueLiteral(str(len(self.body))), + ) + ) + if request_host: + if not utils.get_header("Host", self.headers): + hdrs.append( + Header( + ValueLiteral("Host"), + ValueLiteral(request_host) + ) + ) + + else: + if not utils.get_header("Date", self.headers): + hdrs.append( + Header( + ValueLiteral("Date"), + ValueLiteral(formatdate(timeval=None, localtime=False, usegmt=True)) + ) + ) + values = [] + for h in hdrs: + values.extend(h.values(settings)) + return values + + def ready_actions(self, settings, request_host): + actions = [i.resolve_offset(self, settings, request_host) for i in self.actions] + actions.sort() + actions.reverse() + return [i.intermediate(settings) for i in actions] + def serve(self, settings, fp, check, request_host): """ fp: The file pointer to write to. @@ -599,40 +625,9 @@ class Message: Calling this function may modify the object. """ started = time.time() - if not self.raw: - if self.body and not utils.get_header("Content-Length", self.headers): - self.headers.append( - ( - LiteralGenerator("Content-Length"), - LiteralGenerator(str(len(self.body))), - ) - ) - if request_host: - if not utils.get_header("Host", self.headers): - self.headers.append( - ( - LiteralGenerator("Host"), - LiteralGenerator(request_host) - ) - ) - else: - if not utils.get_header("Date", self.headers): - self.headers.append( - ( - LiteralGenerator("Date"), - LiteralGenerator(formatdate(timeval=None, localtime=False, usegmt=True)) - ) - ) + hdrs = self.headervals(settings, request_host) - hdrs = [] - for k, v in self.headers: - hdrs.extend([ - k, - ": ", - v, - "\r\n", - ]) vals = self.preamble() vals.append("\r\n") vals.extend(hdrs) @@ -640,10 +635,7 @@ class Message: if self.body: vals.append(self.body) vals.reverse() - actions = [i.resolve_offset(self) for i in self.actions] - actions.sort() - actions.reverse() - actions = [i.intermediate(settings) for i in actions] + actions = self.ready_actions(settings, request_host) if check: ret = check(self, actions) if ret: @@ -653,6 +645,7 @@ class Message: disconnect = True, error = ret ) + disconnect = write_values(fp, vals, actions[:]) duration = time.time() - started ret = dict( @@ -784,9 +777,7 @@ class PathodErrorResponse(Response): self.msg = LiteralGenerator(msg) self.body = LiteralGenerator("pathod error: " + (body or msg)) self.headers = [ - ( - LiteralGenerator("Content-Type"), LiteralGenerator("text/plain") - ), + Header(ValueLiteral("Content-Type"), ValueLiteral("text/plain")), ] def serve(self, settings, fp, check=None): diff --git a/libpathod/pathod.py b/libpathod/pathod.py index d4535d03..e0e30d17 100644 --- a/libpathod/pathod.py +++ b/libpathod/pathod.py @@ -209,7 +209,7 @@ class Pathod(tcp.TCPServer): """ A policy check that verifies the request size is withing limits. """ - if self.sizelimit and req.effective_length(actions) > self.sizelimit: + if self.sizelimit and req.effective_length({}, None) > self.sizelimit: return "Response too large." if self.nohang and any([i[1] == "pause" for i in actions]): return "Pauses have been disabled." diff --git a/libpathod/utils.py b/libpathod/utils.py index ac0c0e4c..70a97cff 100644 --- a/libpathod/utils.py +++ b/libpathod/utils.py @@ -26,9 +26,10 @@ def get_header(val, headers): """ Header keys may be Values, so we have to "generate" them as we try the match. """ - for k, v in headers: + for h in headers: + k = h.key.get_generator({}) if len(k) == len(val) and k[:].lower() == val.lower(): - return v + return h return None diff --git a/test/test_language.py b/test/test_language.py index e4da6d4d..d3124c5a 100644 --- a/test/test_language.py +++ b/test/test_language.py @@ -223,7 +223,7 @@ class Test_Action: def test_resolve_offset(self): r = language.parse_request({}, 'GET:"/foo"') e = language.DisconnectAt("r") - ret = e.resolve_offset(r) + ret = e.resolve_offset(r, {}, None) assert isinstance(ret.offset, int) def test_repr(self): @@ -315,8 +315,8 @@ class TestPauses: class TestShortcuts: def test_parse_response(self): - assert language.parse_response({}, "400:c'foo'").headers[0][0][:] == "Content-Type" - assert language.parse_response({}, "400:l'foo'").headers[0][0][:] == "Location" + assert language.parse_response({}, "400:c'foo'").headers[0].key.val == "Content-Type" + assert language.parse_response({}, "400:l'foo'").headers[0].key.val == "Location" class TestParseRequest: @@ -409,7 +409,7 @@ class TestParseResponse: def test_parse_stress(self): r = language.parse_response({}, "400:b@100g") - assert r.length() + assert r.length({}, None) class TestWriteValues: @@ -542,7 +542,7 @@ class TestResponse: def testlen(x): s = cStringIO.StringIO() x.serve({}, s, None) - assert x.length() == len(s.getvalue()) + assert x.length({}, None) == len(s.getvalue()) testlen(language.parse_response({}, "400'msg'")) testlen(language.parse_response({}, "400'msg':h'foo'='bar'")) testlen(language.parse_response({}, "400'msg':h'foo'='bar':b@100b")) @@ -550,7 +550,7 @@ class TestResponse: def test_effective_length(self): l = [None] def check(req, actions): - l[0] = req.effective_length(actions) + l[0] = req.effective_length({}, None) def testlen(x, actions): s = cStringIO.StringIO() -- cgit v1.2.3 From ac5aacce443f619bce2f31a1c016904da1930510 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Sat, 27 Oct 2012 17:40:22 +1300 Subject: Remove check argument to serve() methods. Refactoring means we can now do this without a callback. Also introduce the maximum_length method that estimates the max possible message length. --- libpathod/app.py | 9 +++++++-- libpathod/language.py | 42 ++++++++++++------------------------------ libpathod/pathoc.py | 4 ++-- libpathod/pathod.py | 20 +++++++++++++++----- test/test_language.py | 36 +++++++++++++----------------------- test/test_pathod.py | 4 ++-- 6 files changed, 51 insertions(+), 64 deletions(-) diff --git a/libpathod/app.py b/libpathod/app.py index 1ebe9901..6cde801a 100644 --- a/libpathod/app.py +++ b/libpathod/app.py @@ -129,11 +129,16 @@ def _preview(is_request): s = cStringIO.StringIO() args["pauses"] = r.preview_safe() + + c = app.config["pathod"].check_policy(r) + if c: + args["error"] = c + return render(template, False, **args) if is_request: - r.serve(app.config["pathod"].request_settings, s, check=app.config["pathod"].check_policy, host="example.com") + r.serve(app.config["pathod"].request_settings, s, host="example.com") else: - r.serve(app.config["pathod"].request_settings, s, check=app.config["pathod"].check_policy) + r.serve(app.config["pathod"].request_settings, s) args["output"] = utils.escape_unprintables(s.getvalue()) return render(template, False, **args) diff --git a/libpathod/language.py b/libpathod/language.py index a9670c32..311d51e5 100644 --- a/libpathod/language.py +++ b/libpathod/language.py @@ -557,18 +557,14 @@ class Message: self.actions = [i for i in self.actions if not isinstance(i, PauseAt)] return pauses - def effective_length(self, settings, request_host): + def maximum_length(self, settings, request_host): """ - Calculate the length of the base message with all applied actions. + Calculate the maximum length of the base message with all applied actions. """ - # Order matters here, and must match the order of application in - # write_values. l = self.length(settings, request_host) - for i in reversed(self.ready_actions(settings, request_host)): - if i[1] == "disconnect": - return i[0] - elif i[1] == "inject": - l += len(i[2]) + for i in self.actions: + if isinstance(i, InjectAt): + l += len(i.value.get_generator(settings)) return l def headervals(self, settings, request_host): @@ -609,15 +605,10 @@ class Message: actions.reverse() return [i.intermediate(settings) for i in actions] - def serve(self, settings, fp, check, request_host): + def serve(self, settings, fp, request_host): """ fp: The file pointer to write to. - check: A function called with the effective actions (after random - values have been calculated). If it returns False service proceeds, - otherwise the return is treated as an error message to be sent to - the client, and service stops. - 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. @@ -636,15 +627,6 @@ class Message: vals.append(self.body) vals.reverse() actions = self.ready_actions(settings, request_host) - if check: - ret = check(self, actions) - if ret: - err = PathodErrorResponse(ret) - err.serve(settings, fp) - return dict( - disconnect = True, - error = ret - ) disconnect = write_values(fp, vals, actions[:]) duration = time.time() - started @@ -751,8 +733,8 @@ class CraftedRequest(Request): for i in tokens: i.accept(settings, self) - def serve(self, settings, fp, check, host): - d = Request.serve(self, settings, fp, check, host) + def serve(self, settings, fp, host): + d = Request.serve(self, settings, fp, host) d["spec"] = self.spec return d @@ -764,8 +746,8 @@ class CraftedResponse(Response): for i in tokens: i.accept(settings, self) - def serve(self, settings, fp, check): - d = Response.serve(self, settings, fp, check, None) + def serve(self, settings, fp): + d = Response.serve(self, settings, fp, None) d["spec"] = self.spec return d @@ -780,8 +762,8 @@ class PathodErrorResponse(Response): Header(ValueLiteral("Content-Type"), ValueLiteral("text/plain")), ] - def serve(self, settings, fp, check=None): - d = Response.serve(self, settings, fp, check, None) + def serve(self, settings, fp): + d = Response.serve(self, settings, fp, None) d["internal"] = True return d diff --git a/libpathod/pathoc.py b/libpathod/pathoc.py index 873a989c..df291c59 100644 --- a/libpathod/pathoc.py +++ b/libpathod/pathoc.py @@ -22,7 +22,7 @@ class Pathoc(tcp.TCPClient): language.FileAccessDenied. """ r = language.parse_request(self.settings, spec) - ret = r.serve(self.settings, self.wfile, None, self.host) + ret = r.serve(self.settings, self.wfile, self.host) self.wfile.flush() return http.read_response(self.rfile, r.method, None) @@ -68,7 +68,7 @@ class Pathoc(tcp.TCPClient): if showresp: self.rfile.start_log() try: - req = r.serve(self.settings, self.wfile, None, self.host) + req = r.serve(self.settings, self.wfile, self.host) self.wfile.flush() resp = http.read_response(self.rfile, r.method, None) except http.HttpError, v: diff --git a/libpathod/pathod.py b/libpathod/pathod.py index e0e30d17..5d787c55 100644 --- a/libpathod/pathod.py +++ b/libpathod/pathod.py @@ -18,7 +18,17 @@ class PathodHandler(tcp.BaseHandler): self.sni = connection.get_servername() def serve_crafted(self, crafted, request_log): - response_log = crafted.serve(self.server.request_settings, self.wfile, self.server.check_policy) + c = self.server.check_policy(crafted) + if c: + err = language.PathodErrorResponse(c) + err.serve(self.server.request_settings, self.wfile) + log = dict( + type = "error", + msg = c + ) + return False, log + + response_log = crafted.serve(self.server.request_settings, self.wfile) log = dict( type = "crafted", request=request_log, @@ -96,7 +106,7 @@ class PathodHandler(tcp.BaseHandler): return self.serve_crafted(crafted, request_log) elif self.server.noweb: crafted = language.PathodErrorResponse("Access Denied") - crafted.serve(self.server.request_settings, self.wfile, self.server.check_policy) + crafted.serve(self.server.request_settings, self.wfile) return False, dict(type = "error", msg="Access denied: web interface disabled") else: self.info("app: %s %s"%(method, path)) @@ -205,13 +215,13 @@ class Pathod(tcp.TCPServer): raise PathodError("Invalid page spec in anchor: '%s', %s"%(i[1], str(v))) self.anchors.append((arex, i[1])) - def check_policy(self, req, actions): + def check_policy(self, req): """ A policy check that verifies the request size is withing limits. """ - if self.sizelimit and req.effective_length({}, None) > self.sizelimit: + if self.sizelimit and req.maximum_length({}, None) > self.sizelimit: return "Response too large." - if self.nohang and any([i[1] == "pause" for i in actions]): + if self.nohang and any([isinstance(i, language.PauseAt) for i in req.actions]): return "Pauses have been disabled." return False diff --git a/test/test_language.py b/test/test_language.py index d3124c5a..289f180c 100644 --- a/test/test_language.py +++ b/test/test_language.py @@ -279,7 +279,7 @@ class TestInject: def test_serve(self): s = cStringIO.StringIO() r = language.parse_response({}, "400:i0,'foo'") - assert r.serve({}, s, None) + assert r.serve({}, s) def test_spec(self): e = language.InjectAt.expr() @@ -344,7 +344,7 @@ class TestParseRequest: def test_render(self): s = cStringIO.StringIO() r = language.parse_request({}, "GET:'/foo'") - assert r.serve({}, s, None, "foo.com") + assert r.serve({}, s, "foo.com") def test_str(self): r = language.parse_request({}, 'GET:"/foo"') @@ -479,15 +479,15 @@ class TestWriteValues: def test_write_values_after(self): s = cStringIO.StringIO() r = language.parse_response({}, "400:da") - r.serve({}, s, None) + r.serve({}, s) s = cStringIO.StringIO() r = language.parse_response({}, "400:pa,0") - r.serve({}, s, None) + r.serve({}, s) s = cStringIO.StringIO() r = language.parse_response({}, "400:ia,'xx'") - r.serve({}, s, None) + r.serve({}, s) assert s.getvalue().endswith('xx') @@ -511,29 +511,22 @@ class TestResponse: assert r.body[:] assert str(r) - def test_checkfunc(self): - s = cStringIO.StringIO() - r = language.parse_response({}, "400:b@100k") - def check(req, acts): - return "errmsg" - assert r.serve({}, s, check=check)["error"] == "errmsg" - def test_render(self): s = cStringIO.StringIO() r = language.parse_response({}, "400'msg'") - assert r.serve({}, s, None) + assert r.serve({}, s) def test_raw(self): s = cStringIO.StringIO() r = language.parse_response({}, "400:b'foo'") - r.serve({}, s, None) + r.serve({}, s) v = s.getvalue() assert "Content-Length" in v assert "Date" in v s = cStringIO.StringIO() r = language.parse_response({}, "400:b'foo':r") - r.serve({}, s, None) + r.serve({}, s) v = s.getvalue() assert not "Content-Length" in v assert not "Date" in v @@ -541,21 +534,18 @@ class TestResponse: def test_length(self): def testlen(x): s = cStringIO.StringIO() - x.serve({}, s, None) + x.serve({}, s) assert x.length({}, None) == len(s.getvalue()) testlen(language.parse_response({}, "400'msg'")) testlen(language.parse_response({}, "400'msg':h'foo'='bar'")) testlen(language.parse_response({}, "400'msg':h'foo'='bar':b@100b")) - def test_effective_length(self): - l = [None] - def check(req, actions): - l[0] = req.effective_length({}, None) - + def test_maximum_length(self): def testlen(x, actions): s = cStringIO.StringIO() - x.serve({}, s, check) - assert l[0] == len(s.getvalue()) + m = x.maximum_length({}, None) + x.serve({}, s) + assert m >= len(s.getvalue()) r = language.parse_response({}, "400'msg':b@100") diff --git a/test/test_pathod.py b/test/test_pathod.py index 7bbb5545..195c7333 100644 --- a/test/test_pathod.py +++ b/test/test_pathod.py @@ -58,7 +58,7 @@ class TestNohang(tutils.DaemonTests): r = self.get("200:p0,0") assert r.status_code == 800 l = self.d.last_log() - assert "Pauses have been disabled" in l["response"]["error"] + assert "Pauses have been disabled" in l["msg"] class TestHexdump(tutils.DaemonTests): @@ -77,7 +77,7 @@ class CommonTests(tutils.DaemonTests): r = self.get("200:b@1g") assert r.status_code == 800 l = self.d.last_log() - assert "too large" in l["response"]["error"] + assert "too large" in l["msg"] def test_preline(self): v = self.pathoc(r"get:'/p/200':i0,'\r\n'") -- cgit v1.2.3 From f54ed69a358d2dd059bd844c752f96e29e90e269 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Sun, 28 Oct 2012 09:06:55 +1300 Subject: Adjust serve() API. --- libpathod/app.py | 6 +++--- libpathod/language.py | 16 ++++++++-------- libpathod/pathoc.py | 4 ++-- libpathod/pathod.py | 6 +++--- test/test_language.py | 22 +++++++++++----------- 5 files changed, 27 insertions(+), 27 deletions(-) diff --git a/libpathod/app.py b/libpathod/app.py index 6cde801a..6f12c0a2 100644 --- a/libpathod/app.py +++ b/libpathod/app.py @@ -129,16 +129,16 @@ def _preview(is_request): s = cStringIO.StringIO() args["pauses"] = r.preview_safe() - + c = app.config["pathod"].check_policy(r) if c: args["error"] = c return render(template, False, **args) if is_request: - r.serve(app.config["pathod"].request_settings, s, host="example.com") + r.serve(s, app.config["pathod"].request_settings, host="example.com") else: - r.serve(app.config["pathod"].request_settings, s) + r.serve(s, app.config["pathod"].request_settings) args["output"] = utils.escape_unprintables(s.getvalue()) return render(template, False, **args) diff --git a/libpathod/language.py b/libpathod/language.py index 311d51e5..31239b74 100644 --- a/libpathod/language.py +++ b/libpathod/language.py @@ -605,7 +605,7 @@ class Message: actions.reverse() return [i.intermediate(settings) for i in actions] - def serve(self, settings, fp, request_host): + def serve(self, fp, settings, request_host): """ fp: The file pointer to write to. @@ -733,8 +733,8 @@ class CraftedRequest(Request): for i in tokens: i.accept(settings, self) - def serve(self, settings, fp, host): - d = Request.serve(self, settings, fp, host) + def serve(self, fp, settings, host): + d = Request.serve(self, fp, settings, host) d["spec"] = self.spec return d @@ -746,8 +746,8 @@ class CraftedResponse(Response): for i in tokens: i.accept(settings, self) - def serve(self, settings, fp): - d = Response.serve(self, settings, fp, None) + def serve(self, fp, settings): + d = Response.serve(self, fp, settings, None) d["spec"] = self.spec return d @@ -759,11 +759,11 @@ class PathodErrorResponse(Response): self.msg = LiteralGenerator(msg) self.body = LiteralGenerator("pathod error: " + (body or msg)) self.headers = [ - Header(ValueLiteral("Content-Type"), ValueLiteral("text/plain")), + Header(ValueLiteral("Content-Type"), ValueLiteral("text/plain")), ] - def serve(self, settings, fp): - d = Response.serve(self, settings, fp, None) + def serve(self, fp, settings): + d = Response.serve(self, fp, settings, None) d["internal"] = True return d diff --git a/libpathod/pathoc.py b/libpathod/pathoc.py index df291c59..4e592a06 100644 --- a/libpathod/pathoc.py +++ b/libpathod/pathoc.py @@ -22,7 +22,7 @@ class Pathoc(tcp.TCPClient): language.FileAccessDenied. """ r = language.parse_request(self.settings, spec) - ret = r.serve(self.settings, self.wfile, self.host) + ret = r.serve(self.wfile, self.settings, self.host) self.wfile.flush() return http.read_response(self.rfile, r.method, None) @@ -68,7 +68,7 @@ class Pathoc(tcp.TCPClient): if showresp: self.rfile.start_log() try: - req = r.serve(self.settings, self.wfile, self.host) + req = r.serve(self.wfile, self.settings, self.host) self.wfile.flush() resp = http.read_response(self.rfile, r.method, None) except http.HttpError, v: diff --git a/libpathod/pathod.py b/libpathod/pathod.py index 5d787c55..d80a9018 100644 --- a/libpathod/pathod.py +++ b/libpathod/pathod.py @@ -21,14 +21,14 @@ class PathodHandler(tcp.BaseHandler): c = self.server.check_policy(crafted) if c: err = language.PathodErrorResponse(c) - err.serve(self.server.request_settings, self.wfile) + err.serve(self.wfile, self.server.request_settings) log = dict( type = "error", msg = c ) return False, log - response_log = crafted.serve(self.server.request_settings, self.wfile) + response_log = crafted.serve(self.wfile, self.server.request_settings) log = dict( type = "crafted", request=request_log, @@ -106,7 +106,7 @@ class PathodHandler(tcp.BaseHandler): return self.serve_crafted(crafted, request_log) elif self.server.noweb: crafted = language.PathodErrorResponse("Access Denied") - crafted.serve(self.server.request_settings, self.wfile) + crafted.serve(self.wfile, self.server.request_settings) return False, dict(type = "error", msg="Access denied: web interface disabled") else: self.info("app: %s %s"%(method, path)) diff --git a/test/test_language.py b/test/test_language.py index 289f180c..e5ec51d2 100644 --- a/test/test_language.py +++ b/test/test_language.py @@ -206,7 +206,7 @@ class TestMisc: def test_internal_response(self): d = cStringIO.StringIO() s = language.PathodErrorResponse("foo") - s.serve({}, d) + s.serve(d, {}) class Test_Action: @@ -279,7 +279,7 @@ class TestInject: def test_serve(self): s = cStringIO.StringIO() r = language.parse_response({}, "400:i0,'foo'") - assert r.serve({}, s) + assert r.serve(s, {}) def test_spec(self): e = language.InjectAt.expr() @@ -344,7 +344,7 @@ class TestParseRequest: def test_render(self): s = cStringIO.StringIO() r = language.parse_request({}, "GET:'/foo'") - assert r.serve({}, s, "foo.com") + assert r.serve(s, {}, "foo.com") def test_str(self): r = language.parse_request({}, 'GET:"/foo"') @@ -479,15 +479,15 @@ class TestWriteValues: def test_write_values_after(self): s = cStringIO.StringIO() r = language.parse_response({}, "400:da") - r.serve({}, s) + r.serve(s, {}) s = cStringIO.StringIO() r = language.parse_response({}, "400:pa,0") - r.serve({}, s) + r.serve(s, {}) s = cStringIO.StringIO() r = language.parse_response({}, "400:ia,'xx'") - r.serve({}, s) + r.serve(s, {}) assert s.getvalue().endswith('xx') @@ -514,19 +514,19 @@ class TestResponse: def test_render(self): s = cStringIO.StringIO() r = language.parse_response({}, "400'msg'") - assert r.serve({}, s) + assert r.serve(s, {}) def test_raw(self): s = cStringIO.StringIO() r = language.parse_response({}, "400:b'foo'") - r.serve({}, s) + r.serve(s, {}) v = s.getvalue() assert "Content-Length" in v assert "Date" in v s = cStringIO.StringIO() r = language.parse_response({}, "400:b'foo':r") - r.serve({}, s) + r.serve(s, {}) v = s.getvalue() assert not "Content-Length" in v assert not "Date" in v @@ -534,7 +534,7 @@ class TestResponse: def test_length(self): def testlen(x): s = cStringIO.StringIO() - x.serve({}, s) + x.serve(s, {}) assert x.length({}, None) == len(s.getvalue()) testlen(language.parse_response({}, "400'msg'")) testlen(language.parse_response({}, "400'msg':h'foo'='bar'")) @@ -544,7 +544,7 @@ class TestResponse: def testlen(x, actions): s = cStringIO.StringIO() m = x.maximum_length({}, None) - x.serve({}, s) + x.serve(s, {}) assert m >= len(s.getvalue()) r = language.parse_response({}, "400'msg':b@100") -- cgit v1.2.3 From 9d42a06c92f0fdc7ca986b738086d361d9b0599b Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Sun, 28 Oct 2012 12:56:08 +1300 Subject: Move message body to new lazy-generator scheme. --- .gitignore | 2 ++ libpathod/app.py | 6 +----- libpathod/language.py | 21 +++++++++++++++------ libpathod/pathod.py | 13 +++++++------ test/test_app.py | 8 ++++++-- test/test_language.py | 2 +- test/test_pathod.py | 2 +- 7 files changed, 33 insertions(+), 21 deletions(-) diff --git a/.gitignore b/.gitignore index 674722aa..2c7c0b9a 100644 --- a/.gitignore +++ b/.gitignore @@ -7,5 +7,7 @@ MANIFEST *.swp /doc .coverage +.noseids netlib venv + diff --git a/libpathod/app.py b/libpathod/app.py index 6f12c0a2..38d0be33 100644 --- a/libpathod/app.py +++ b/libpathod/app.py @@ -123,18 +123,14 @@ def _preview(is_request): args["syntaxerror"] = str(v) args["marked"] = v.marked() return render(template, False, **args) - except language.FileAccessDenied: - args["error"] = "File access is disabled." - return render(template, False, **args) s = cStringIO.StringIO() args["pauses"] = r.preview_safe() - c = app.config["pathod"].check_policy(r) + c = app.config["pathod"].check_policy(r, app.config["pathod"].request_settings) if c: args["error"] = c return render(template, False, **args) - if is_request: r.serve(s, app.config["pathod"].request_settings, host="example.com") else: diff --git a/libpathod/language.py b/libpathod/language.py index 31239b74..5d6471e6 100644 --- a/libpathod/language.py +++ b/libpathod/language.py @@ -345,7 +345,7 @@ class Body: self.value = value def accept(self, settings, r): - r.body = self.value.get_generator(settings) + r.body = self @classmethod def expr(klass): @@ -353,6 +353,11 @@ class Body: e = e + Value return e.setParseAction(lambda x: klass(*x)) + def values(self, settings): + return [ + self.value.get_generator(settings), + ] + class Raw: def accept(self, settings, r): @@ -532,7 +537,7 @@ class Code: class Message: version = "HTTP/1.1" def __init__(self): - self.body = LiteralGenerator("") + self.body = None self.headers = [] self.actions = [] self.raw = False @@ -546,7 +551,8 @@ class Message: for h in self.headervals(settings, request_host): l += len(h) l += 2 - l += len(self.body) + if self.body: + l += len(self.body.value.get_generator(settings)) return l def preview_safe(self): @@ -574,7 +580,7 @@ class Message: hdrs.append( Header( ValueLiteral("Content-Length"), - ValueLiteral(str(len(self.body))), + ValueLiteral(str(len(self.body.value.get_generator(settings)))), ) ) if request_host: @@ -624,7 +630,7 @@ class Message: vals.extend(hdrs) vals.append("\r\n") if self.body: - vals.append(self.body) + vals.append(self.body.value.get_generator(settings)) vals.reverse() actions = self.ready_actions(settings, request_host) @@ -638,6 +644,9 @@ class Message: 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[:TRUNCATE] for x in v.values(settings)] + v = "".join(v) if hasattr(v, "__len__"): v = v[:TRUNCATE] v = v.encode("string_escape") @@ -757,7 +766,7 @@ class PathodErrorResponse(Response): Response.__init__(self) self.code = 800 self.msg = LiteralGenerator(msg) - self.body = LiteralGenerator("pathod error: " + (body or msg)) + self.body = Body(ValueLiteral("pathod error: " + (body or msg))) self.headers = [ Header(ValueLiteral("Content-Type"), ValueLiteral("text/plain")), ] diff --git a/libpathod/pathod.py b/libpathod/pathod.py index d80a9018..131dbc3c 100644 --- a/libpathod/pathod.py +++ b/libpathod/pathod.py @@ -18,7 +18,7 @@ class PathodHandler(tcp.BaseHandler): self.sni = connection.get_servername() def serve_crafted(self, crafted, request_log): - c = self.server.check_policy(crafted) + c = self.server.check_policy(crafted, self.server.request_settings) if c: err = language.PathodErrorResponse(c) err.serve(self.wfile, self.server.request_settings) @@ -100,9 +100,6 @@ class PathodHandler(tcp.BaseHandler): "Parse Error", "Error parsing response spec: %s\n"%v.msg + v.marked() ) - except language.FileAccessDenied: - self.info("File access denied") - crafted = language.PathodErrorResponse("Access Denied") return self.serve_crafted(crafted, request_log) elif self.server.noweb: crafted = language.PathodErrorResponse("Access Denied") @@ -215,11 +212,15 @@ class Pathod(tcp.TCPServer): raise PathodError("Invalid page spec in anchor: '%s', %s"%(i[1], str(v))) self.anchors.append((arex, i[1])) - def check_policy(self, req): + def check_policy(self, req, settings): """ A policy check that verifies the request size is withing limits. """ - if self.sizelimit and req.maximum_length({}, None) > self.sizelimit: + try: + l = req.maximum_length(settings, None) + except language.FileAccessDenied, v: + return "File access denied." + if self.sizelimit and l > self.sizelimit: return "Response too large." if self.nohang and any([isinstance(i, language.PauseAt) for i in req.actions]): return "Pauses have been disabled." diff --git a/test/test_app.py b/test/test_app.py index 7b2451d6..b3194052 100644 --- a/test/test_app.py +++ b/test/test_app.py @@ -47,9 +47,13 @@ class TestApp(tutils.DaemonTests): assert r.status_code == 200 assert 'Response' in r.content - r = self.getpath("/response_preview", params=dict(spec="200:b Date: Sun, 28 Oct 2012 14:15:29 +1300 Subject: .body and .method to lazy generator instantiation. Also introduce a _Component ABC. --- libpathod/language.py | 58 +++++++++++++++++++++++++++++++++++++++------------ test/test_language.py | 22 +++++++++---------- 2 files changed, 56 insertions(+), 24 deletions(-) diff --git a/libpathod/language.py b/libpathod/language.py index 5d6471e6..446c1823 100644 --- a/libpathod/language.py +++ b/libpathod/language.py @@ -1,4 +1,5 @@ import operator, string, random, mmap, os, time, copy +import abc from email.utils import formatdate import contrib.pyparsing as pp from netlib import http_status, tcp @@ -292,7 +293,22 @@ Offset = pp.MatchFirst( ) -class _Header: +class _Component(object): + __metaclass__ = abc.ABCMeta + @abc.abstractmethod + def values(self, settings): return None # pragma: no cover + + @abc.abstractmethod + def expr(klass): return None # pragma: no cover + + @abc.abstractmethod + def accept(self, settings): return None # pragma: no cover + + def string(self, settings=None): + return "".join(i[:] for i in self.values(settings or {})) + + +class _Header(_Component): def __init__(self, key, value): self.key, self.value = key, value @@ -340,7 +356,7 @@ class ShortcutLocation(_Header): return e.setParseAction(lambda x: klass(*x)) -class Body: +class Body(_Component): def __init__(self, value): self.value = value @@ -369,23 +385,29 @@ class Raw: return e.setParseAction(lambda x: klass(*x)) -class Path: +class Path(_Component): def __init__(self, value): if isinstance(value, basestring): value = ValueLiteral(value) self.value = value def accept(self, settings, r): - r.path = self.value.get_generator(settings) + r.path = self @classmethod def expr(klass): e = NakedValue.copy() return e.setParseAction(lambda x: klass(*x)) + def values(self, settings): + return [ + self.value.get_generator(settings), + ] + -class Method: + +class Method(_Component): methods = [ "get", "head", @@ -405,7 +427,7 @@ class Method: self.value = value def accept(self, settings, r): - r.method = self.value.get_generator(settings) + r.method = self @classmethod def expr(klass): @@ -415,6 +437,11 @@ class Method: spec = spec.setParseAction(lambda x: klass(*x)) return spec + def values(self, settings): + return [ + self.value.get_generator(settings) + ] + class _Action: """ @@ -546,7 +573,7 @@ class Message: """ Calculate the length of the base message without any applied actions. """ - l = sum(len(x) for x in self.preamble()) + l = sum(len(x) for x in self.preamble(settings)) l += 2 for h in self.headervals(settings, request_host): l += len(h) @@ -625,7 +652,7 @@ class Message: hdrs = self.headervals(settings, request_host) - vals = self.preamble() + vals = self.preamble(settings) vals.append("\r\n") vals.extend(hdrs) vals.append("\r\n") @@ -673,7 +700,7 @@ class Response(Message): self.code = None self.msg = None - def preamble(self): + def preamble(self, settings): return [self.version, " ", str(self.code), " ", self.msg] @classmethod @@ -711,8 +738,13 @@ class Request(Message): self.method = None self.path = None - def preamble(self): - return [self.method, " ", self.path, " ", self.version] + def preamble(self, settings): + v = self.method.values(settings) + v.append(" ") + v.extend(self.path.values(settings)) + v.append(" ") + v.append(self.version) + return v @classmethod def expr(klass): @@ -728,9 +760,9 @@ class Request(Message): ) return resp - def __str__(self): + def string(self, values=None): parts = [ - "%s %s"%(self.method[:], self.path[:]) + "%s %s"%(self.method.string(values), self.path.string(values)) ] return "\n".join(parts) diff --git a/test/test_language.py b/test/test_language.py index 54f96d51..aba67274 100644 --- a/test/test_language.py +++ b/test/test_language.py @@ -324,7 +324,7 @@ class TestParseRequest: p = tutils.test_data.path("data") d = dict(staticdir=p) r = language.parse_request(d, "+request") - assert r.path == "/foo" + assert r.path.values({})[0][:] == "/foo" def test_nonascii(self): tutils.raises("ascii", language.parse_request, {}, "get:\xf0") @@ -334,21 +334,21 @@ class TestParseRequest: def test_simple(self): r = language.parse_request({}, 'GET:"/foo"') - assert r.method == "GET" - assert r.path == "/foo" + assert r.method.string() == "GET" + assert r.path.string() == "/foo" r = language.parse_request({}, 'GET:/foo') - assert r.path == "/foo" + assert r.path.string() == "/foo" r = language.parse_request({}, 'GET:@1k') - assert len(r.path) == 1024 + assert len(r.path.string()) == 1024 def test_render(self): s = cStringIO.StringIO() r = language.parse_request({}, "GET:'/foo'") assert r.serve(s, {}, "foo.com") - def test_str(self): + def test_string(self): r = language.parse_request({}, 'GET:"/foo"') - assert str(r) + assert r.string() def test_multiline(self): l = """ @@ -357,8 +357,8 @@ class TestParseRequest: ir,@1 """ r = language.parse_request({}, l) - assert r.method == "GET" - assert r.path == "/foo" + assert r.method.string() == "GET" + assert r.path.string() == "/foo" assert r.actions @@ -374,8 +374,8 @@ class TestParseRequest: ir,@1 """ r = language.parse_request({}, l) - assert r.method == "GET" - assert r.path.s.endswith("bar") + assert r.method.string() == "GET" + assert r.path.string().endswith("bar") assert r.actions -- cgit v1.2.3 From 7d74f75f6d422189b26c2977368aca47ac329cfa Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Sun, 28 Oct 2012 17:07:16 +1300 Subject: Split Code and Reason into separate _Component objects. --- libpathod/language.py | 62 ++++++++++++++++++++++++++++++++------------------- test/test_language.py | 20 +++++++++++------ 2 files changed, 52 insertions(+), 30 deletions(-) diff --git a/libpathod/language.py b/libpathod/language.py index 446c1823..671a6ec7 100644 --- a/libpathod/language.py +++ b/libpathod/language.py @@ -89,7 +89,6 @@ DATATYPES = dict( ) -#v_integer = pp.Regex(r"[+-]?\d+")\ v_integer = pp.Regex(r"\d+")\ .setName("integer")\ .setParseAction(lambda toks: int(toks[0])) @@ -541,24 +540,36 @@ class InjectAt(_Action): ) -class Code: - def __init__(self, code, msg=None): - self.code, self.msg = code, msg - if msg is None: - self.msg = ValueLiteral(http_status.RESPONSES.get(self.code, "Unknown code")) +class Code(_Component): + def __init__(self, code): + self.code = str(code) def accept(self, settings, r): - r.code = self.code - r.msg = self.msg.get_generator(settings) + r.code = self @classmethod def expr(klass): - e = v_integer - e = e + pp.Optional( - Value - ) + e = v_integer.copy() return e.setParseAction(lambda x: klass(*x)) + def values(self, settings): + return [LiteralGenerator(self.code)] + + +class Reason(_Component): + def __init__(self, value): + self.value = value + + def accept(self, settings, r): + r.reason = self + + @classmethod + def expr(klass): + e = Value.copy() + return e.setParseAction(lambda x: klass(*x)) + + def values(self, settings): + return [self.value.get_generator(settings)] class Message: @@ -673,8 +684,8 @@ class Message: # Careful not to log any VALUE specs without sanitizing them first. We truncate at 1k. if hasattr(v, "values"): v = [x[:TRUNCATE] for x in v.values(settings)] - v = "".join(v) - if hasattr(v, "__len__"): + v = "".join(v).encode("string_escape") + elif hasattr(v, "__len__"): v = v[:TRUNCATE] v = v.encode("string_escape") ret[i] = v @@ -694,14 +705,21 @@ class Response(Message): ShortcutLocation, Raw ) - logattrs = ["code", "version", "body"] + logattrs = ["code", "reason", "version", "body"] def __init__(self): Message.__init__(self) self.code = None - self.msg = None + self.reason = None def preamble(self, settings): - return [self.version, " ", str(self.code), " ", self.msg] + l = [self.version, " "] + l.extend(self.code.values(settings)) + l.append(" ") + if self.reason: + l.extend(self.reason.values(settings)) + else: + l.append(LiteralGenerator(http_status.RESPONSES.get(int(self.code.code), "Unknown code"))) + return l @classmethod def expr(klass): @@ -710,16 +728,14 @@ class Response(Message): resp = pp.And( [ Code.expr(), + pp.Optional(Reason.expr()), pp.ZeroOrMore(Sep + atom) ] ) return resp - def __str__(self): - parts = [ - "%s %s"%(self.code, self.msg[:]) - ] - return "\n".join(parts) + def string(self, settings): + return "%s"%self.code class Request(Message): @@ -796,7 +812,7 @@ class CraftedResponse(Response): class PathodErrorResponse(Response): def __init__(self, msg, body=None): Response.__init__(self) - self.code = 800 + self.code = Code("800") self.msg = LiteralGenerator(msg) self.body = Body(ValueLiteral("pathod error: " + (body or msg))) self.headers = [ diff --git a/test/test_language.py b/test/test_language.py index aba67274..9391ddda 100644 --- a/test/test_language.py +++ b/test/test_language.py @@ -182,11 +182,12 @@ class TestMisc: def test_code(self): e = language.Code.expr() v = e.parseString("200")[0] - assert v.code == 200 + assert v.string() == "200" + def _test_reason(self): v = e.parseString("404'msg'")[0] - assert v.code == 404 - assert v.msg.val == "msg" + assert v.code.string() == "404" + assert v.reason == "msg" r = e.parseString("200'foo'")[0] assert r.msg.val == "foo" @@ -499,18 +500,23 @@ class TestResponse: p = tutils.test_data.path("data") d = dict(staticdir=p) r = language.parse_response(d, "+response") - assert r.code == 202 + assert r.code.string() == "202" def test_response(self): r = language.parse_response({}, "400'msg'") - assert r.code == 400 - assert r.msg == "msg" + assert r.code.string() == "400" + assert r.reason.string() == "msg" r = language.parse_response({}, "400'msg':b@100b") - assert r.msg == "msg" + assert r.reason.string() == "msg" assert r.body.values({}) assert str(r) + r = language.parse_response({}, "200") + assert r.code.string() == "200" + assert not r.reason + assert "OK" in [i[:] for i in r.preamble({})] + def test_render(self): s = cStringIO.StringIO() r = language.parse_response({}, "400'msg'") -- cgit v1.2.3 From 35f37626a97c8dd4bcbf24f3330c1b795d7310fe Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Sun, 28 Oct 2012 17:39:58 +1300 Subject: Clean up .accept() signature. --- libpathod/language.py | 52 ++++++++++++++++++++++++++++++--------------------- test/test_language.py | 4 ---- 2 files changed, 31 insertions(+), 25 deletions(-) diff --git a/libpathod/language.py b/libpathod/language.py index 671a6ec7..5a00d17e 100644 --- a/libpathod/language.py +++ b/libpathod/language.py @@ -293,17 +293,35 @@ Offset = pp.MatchFirst( class _Component(object): + """ + A component of the specification of an HTTP message. + """ __metaclass__ = abc.ABCMeta @abc.abstractmethod - def values(self, settings): return None # pragma: no cover + def values(self, settings): # pragma: no cover + """ + A sequence of value objects. + """ + return None @abc.abstractmethod - def expr(klass): return None # pragma: no cover + def expr(klass): # pragma: no cover + """ + A parse expression. + """ + return None @abc.abstractmethod - def accept(self, settings): return None # pragma: no cover + def accept(self, r): # pragma: no cover + """ + Notifies the component to register itself with message r. + """ + return None def string(self, settings=None): + """ + A string representation of the object. + """ return "".join(i[:] for i in self.values(settings or {})) @@ -319,7 +337,7 @@ class _Header(_Component): "\r\n", ] - def accept(self, settings, r): + def accept(self, r): r.headers.append(self) @@ -359,7 +377,7 @@ class Body(_Component): def __init__(self, value): self.value = value - def accept(self, settings, r): + def accept(self, r): r.body = self @classmethod @@ -375,7 +393,7 @@ class Body(_Component): class Raw: - def accept(self, settings, r): + def accept(self, r): r.raw = True @classmethod @@ -390,7 +408,7 @@ class Path(_Component): value = ValueLiteral(value) self.value = value - def accept(self, settings, r): + def accept(self, r): r.path = self @classmethod @@ -425,7 +443,7 @@ class Method(_Component): value = ValueLiteral(value.upper()) self.value = value - def accept(self, settings, r): + def accept(self, r): r.method = self @classmethod @@ -470,7 +488,7 @@ class _Action: def __repr__(self): return self.spec() - def accept(self, settings, r): + def accept(self, r): r.actions.append(self) @@ -544,7 +562,7 @@ class Code(_Component): def __init__(self, code): self.code = str(code) - def accept(self, settings, r): + def accept(self, r): r.code = self @classmethod @@ -560,7 +578,7 @@ class Reason(_Component): def __init__(self, value): self.value = value - def accept(self, settings, r): + def accept(self, r): r.reason = self @classmethod @@ -734,9 +752,6 @@ class Response(Message): ) return resp - def string(self, settings): - return "%s"%self.code - class Request(Message): comps = ( @@ -776,11 +791,6 @@ class Request(Message): ) return resp - def string(self, values=None): - parts = [ - "%s %s"%(self.method.string(values), self.path.string(values)) - ] - return "\n".join(parts) class CraftedRequest(Request): @@ -788,7 +798,7 @@ class CraftedRequest(Request): Request.__init__(self) self.spec, self.tokens = spec, tokens for i in tokens: - i.accept(settings, self) + i.accept(self) def serve(self, fp, settings, host): d = Request.serve(self, fp, settings, host) @@ -801,7 +811,7 @@ class CraftedResponse(Response): Response.__init__(self) self.spec, self.tokens = spec, tokens for i in tokens: - i.accept(settings, self) + i.accept(self) def serve(self, fp, settings): d = Response.serve(self, fp, settings, None) diff --git a/test/test_language.py b/test/test_language.py index 9391ddda..749f1928 100644 --- a/test/test_language.py +++ b/test/test_language.py @@ -347,10 +347,6 @@ class TestParseRequest: r = language.parse_request({}, "GET:'/foo'") assert r.serve(s, {}, "foo.com") - def test_string(self): - r = language.parse_request({}, 'GET:"/foo"') - assert r.string() - def test_multiline(self): l = """ GET -- cgit v1.2.3 From 07560ffe307f13ffb5a8fa4ef593902cc69807b9 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Sun, 28 Oct 2012 22:00:19 +1300 Subject: Cleaup, ABC for Value classes. --- libpathod/language.py | 42 ++++++++++++++++++++++-------------------- test/test_language.py | 1 - test/test_pathod.py | 3 ++- 3 files changed, 24 insertions(+), 22 deletions(-) diff --git a/libpathod/language.py b/libpathod/language.py index 5a00d17e..7f1d8cec 100644 --- a/libpathod/language.py +++ b/libpathod/language.py @@ -113,9 +113,6 @@ class LiteralGenerator: def __init__(self, s): self.s = s - def __eq__(self, other): - return self[:] == other - def __len__(self): return len(self.s) @@ -168,18 +165,29 @@ class FileGenerator: return "<%s"%self.path -class _Value: +class _Value(object): + __metaclass__ = abc.ABCMeta + def __repr__(self): + return self.spec() + + @abc.abstractmethod + def spec(self): # pragma: no cover + return None + + @abc.abstractmethod + def expr(self): # pragma: no cover + return None + + +class _ValueLiteral(_Value): def __init__(self, val): self.val = val.decode("string_escape") def get_generator(self, settings): return LiteralGenerator(self.val) - def __repr__(self): - return self.spec() - -class ValueLiteral(_Value): +class ValueLiteral(_ValueLiteral): @classmethod def expr(klass): e = v_literal.copy() @@ -189,7 +197,7 @@ class ValueLiteral(_Value): return '"%s"'%self.val.encode("string_escape") -class ValueNakedLiteral(_Value): +class ValueNakedLiteral(_ValueLiteral): @classmethod def expr(klass): e = v_naked_literal.copy() @@ -199,7 +207,7 @@ class ValueNakedLiteral(_Value): return self.val.encode("string_escape") -class ValueGenerate: +class ValueGenerate(_Value): def __init__(self, usize, unit, datatype): if not unit: unit = "b" @@ -231,11 +239,8 @@ class ValueGenerate: s += ",%s"%self.datatype return s - def __repr__(self): - return self.spec() - -class ValueFile: +class ValueFile(_Value): def __init__(self, path): self.path = path @@ -294,7 +299,7 @@ Offset = pp.MatchFirst( class _Component(object): """ - A component of the specification of an HTTP message. + A component of the specification of an HTTP message. """ __metaclass__ = abc.ABCMeta @abc.abstractmethod @@ -316,11 +321,11 @@ class _Component(object): """ Notifies the component to register itself with message r. """ - return None + return None def string(self, settings=None): """ - A string representation of the object. + A string representation of the object. """ return "".join(i[:] for i in self.values(settings or {})) @@ -422,8 +427,6 @@ class Path(_Component): ] - - class Method(_Component): methods = [ "get", @@ -792,7 +795,6 @@ class Request(Message): return resp - class CraftedRequest(Request): def __init__(self, settings, spec, tokens): Request.__init__(self) diff --git a/test/test_language.py b/test/test_language.py index 749f1928..0d7c60da 100644 --- a/test/test_language.py +++ b/test/test_language.py @@ -123,7 +123,6 @@ class TestMisc: def test_literalgenerator(self): g = language.LiteralGenerator("one") assert repr(g) - assert g == "one" assert g[:] == "one" assert g[1] == "n" diff --git a/test/test_pathod.py b/test/test_pathod.py index 83f57727..3fe7e848 100644 --- a/test/test_pathod.py +++ b/test/test_pathod.py @@ -35,11 +35,12 @@ class TestNoWeb(tutils.DaemonTests): class TestTimeout(tutils.DaemonTests): - timeout = 0.1 + timeout = 0.01 def test_noweb(self): # FIXME: Add float values to spec language, reduce test timeout to # increase test performance assert self.get("200:p1,1").status_code == 200 + print self.d.last_log() assert self.d.last_log()["type"] == "timeout" -- cgit v1.2.3 From 61f8992fbffac936ac058b64ed7ac00f21127df9 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Sun, 28 Oct 2012 22:18:06 +1300 Subject: Change response spec format to code[:features] Reason message is now specified as just another feature with the "m" mnemonic. --- libpathod/language.py | 7 ++-- libpathod/templates/docs_lang.html | 51 ++++++++++++++++----------- libpathod/templates/index.html | 2 +- libpathod/templates/response_previewform.html | 2 +- test/test_language.py | 14 ++++---- test/test_pathod.py | 1 - 6 files changed, 43 insertions(+), 34 deletions(-) diff --git a/libpathod/language.py b/libpathod/language.py index 7f1d8cec..76676517 100644 --- a/libpathod/language.py +++ b/libpathod/language.py @@ -586,7 +586,8 @@ class Reason(_Component): @classmethod def expr(klass): - e = Value.copy() + e = pp.Literal("m").suppress() + e = e + Value return e.setParseAction(lambda x: klass(*x)) def values(self, settings): @@ -724,7 +725,8 @@ class Response(Message): InjectAt, ShortcutContentType, ShortcutLocation, - Raw + Raw, + Reason ) logattrs = ["code", "reason", "version", "body"] def __init__(self): @@ -749,7 +751,6 @@ class Response(Message): resp = pp.And( [ Code.expr(), - pp.Optional(Reason.expr()), pp.ZeroOrMore(Sep + atom) ] ) diff --git a/libpathod/templates/docs_lang.html b/libpathod/templates/docs_lang.html index ebed7388..f01d61fb 100644 --- a/libpathod/templates/docs_lang.html +++ b/libpathod/templates/docs_lang.html @@ -17,17 +17,10 @@

The general form of a response is as follows:

-
code[MESSAGE]:[colon-separated list of features]

+
code:[colon-separated list of features]

- hVALUE=VALUE - hVALUE=VALUE Set a header.
- bVALUE - bVALUE Set the body. When the body is set, pathod will automatically set the appropriate Content-Length header. @@ -40,9 +36,7 @@
- cVALUE - cVALUE A shortcut for setting the Content-Type header. Equivalent to h"Content-Type"=VALUE @@ -50,18 +44,14 @@
- iOFFSET,VALUE - iOFFSET,VALUE Inject the specified value at the offset.
- lVALUE - lVALUE A shortcut for setting the Location header. Equivalent to h"Location"=VALUE @@ -69,18 +59,14 @@
- dOFFSET - dOFFSET Disconnect after OFFSET bytes.
- pSECONDS,OFFSET - pSECONDS,OFFSET Pause for SECONDS seconds after OFFSET bytes. SECONDS can be an integer or "f" to pause forever. @@ -88,9 +74,7 @@
- r - r Set the "raw" flag on this response. Pathod will not calculate a Content-Length header if a body is set, or add @@ -106,6 +90,69 @@ + +

The general form of a request is as follows:

+ +
method:path:[colon-separated list of features]

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
hVALUE=VALUE + Set a header. +
bVALUE + Set the body. When the body is set, pathod will + automatically set the appropriate Content-Length header. +
cVALUE + A shortcut for setting the Content-Type header. Equivalent to + h"Content-Type"=VALUE +
iOFFSET,VALUE + Inject the specified value at the offset. +
dOFFSET + Disconnect after OFFSET bytes. +
pSECONDS,OFFSET + Pause for SECONDS seconds after OFFSET bytes. SECONDS can + be an integer or "f" to pause forever. +
r + Set the "raw" flag on this response. Pathod will not + calculate a Content-Length header if a body is set, or add + a Date header to the response. +
+ -- cgit v1.2.3 From eb1f2c3fc40ccfc0db60776412add6a35af93bf9 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Tue, 24 Jul 2012 21:51:43 +1200 Subject: Add option to specify craft anchor point. --- libpathod/pathod.py | 10 +++++----- pathod | 5 +++++ 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/libpathod/pathod.py b/libpathod/pathod.py index 90064581..0ab7e915 100644 --- a/libpathod/pathod.py +++ b/libpathod/pathod.py @@ -83,8 +83,8 @@ class PathodHandler(tcp.BaseHandler): if i[0].match(path): return self.serve_crafted(i[1], request_log) - if not self.server.nocraft and path.startswith(self.server.prefix): - spec = urllib.unquote(path)[len(self.server.prefix):] + if not self.server.nocraft and path.startswith(self.server.craftanchor): + spec = urllib.unquote(path)[len(self.server.craftanchor):] try: crafted = rparse.parse_response(self.server.request_settings, spec) except rparse.ParseException, v: @@ -149,14 +149,14 @@ class PathodHandler(tcp.BaseHandler): class Pathod(tcp.TCPServer): LOGBUF = 500 def __init__( self, - addr, ssloptions=None, prefix="/p/", staticdir=None, anchors=None, + addr, ssloptions=None, craftanchor="/p/", staticdir=None, anchors=None, sizelimit=None, noweb=False, nocraft=False, noapi=False ): """ addr: (address, port) tuple. If port is 0, a free port will be automatically chosen. ssloptions: a dictionary containing certfile and keyfile specifications. - prefix: string specifying the prefix at which to anchor response generation. + craftanchor: string specifying the path under which to anchor response generation. staticdir: path to a directory of static resources, or None. anchors: A list of (regex, spec) tuples, or None. sizelimit: Limit size of served data. @@ -164,7 +164,7 @@ class Pathod(tcp.TCPServer): tcp.TCPServer.__init__(self, addr) self.ssloptions = ssloptions self.staticdir = staticdir - self.prefix = prefix + self.craftanchor = craftanchor self.sizelimit = sizelimit self.noweb, self.nocraft, self.noapi = noweb, nocraft, noapi if not noapi: diff --git a/pathod b/pathod index a5cc02ca..df044ae7 100755 --- a/pathod +++ b/pathod @@ -10,6 +10,10 @@ if __name__ == "__main__": "-a", dest='anchors', default=[], type=str, action="append", metavar="ANCHOR", help='Add an anchor. Specified as a string with the form pattern=pagespec' ) + parser.add_argument( + "-c", dest='craftanchor', default="/p/", type=str, + help='Anchorpoint for URL crafting commands.' + ) parser.add_argument( "-d", dest='staticdir', default=None, type=str, help='Directory for static files.' @@ -89,6 +93,7 @@ if __name__ == "__main__": try: pd = pathod.Pathod( (args.address, args.port), + craftanchor = args.craftanchor, ssloptions = ssl, staticdir = args.staticdir, anchors = alst, -- cgit v1.2.3 From 11896d21182d3afe14d7fa7a5c6ecc64d7fb636c Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Tue, 24 Jul 2012 22:27:04 +1200 Subject: Turn off /log when noapi is set. --- libpathod/app.py | 25 ++++++++++++++++--------- libpathod/templates/frame.html | 2 ++ 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/libpathod/app.py b/libpathod/app.py index 2c4cea23..5de69337 100644 --- a/libpathod/app.py +++ b/libpathod/app.py @@ -26,35 +26,42 @@ def api(): return "OK" +def render(s, **kwargs): + kwargs["noapi"] = app.config["pathod"].noapi + return render_template(s, **kwargs) + + @app.route('/') @app.route('/index.html') def index(): - return render_template("index.html", section="main") + return render("index.html", section="main") @app.route('/docs/pathod') def docs_pathod(): - return render_template("docs_pathod.html", section="docs") + return render("docs_pathod.html", section="docs") @app.route('/docs/language') def docs_language(): - return render_template("docs_lang.html", section="docs") + return render("docs_lang.html", section="docs") @app.route('/docs/pathoc') def docs_pathoc(): - return render_template("docs_pathoc.html", section="docs") + return render("docs_pathoc.html", section="docs") @app.route('/docs/test') def docs_test(): - return render_template("docs_test.html", section="docs") + return render("docs_test.html", section="docs") @app.route('/log') def log(): - return render_template("log.html", section="log", log=app.config["pathod"].get_log()) + if app.config["pathod"].noapi: + abort(404) + return render("log.html", section="log", log=app.config["pathod"].get_log()) @app.route('/log/') @@ -63,7 +70,7 @@ def onelog(lid): if not item: abort(404) l = pprint.pformat(item) - return render_template("onelog.html", section="log", alog=l, lid=lid) + return render("onelog.html", section="log", alog=l, lid=lid) @app.route('/preview') @@ -80,9 +87,9 @@ def preview(): except rparse.ParseException, v: args["syntaxerror"] = str(v) args["marked"] = v.marked() - return render_template("preview.html", **args) + return render("preview.html", **args) s = cStringIO.StringIO() r.serve(s, check=app.config["pathod"].check_size) args["output"] = utils.escape_unprintables(s.getvalue()) - return render_template("preview.html", **args) + return render("preview.html", **args) diff --git a/libpathod/templates/frame.html b/libpathod/templates/frame.html index f176b15d..416f3eca 100644 --- a/libpathod/templates/frame.html +++ b/libpathod/templates/frame.html @@ -38,7 +38,9 @@
pSECONDS,OFFSET pOFFSET,SECONDS Pause for SECONDS seconds after OFFSET bytes. SECONDS can be an integer or "f" to pause forever. @@ -135,7 +135,7 @@
pSECONDS,OFFSET pOFFSET,SECONDS Pause for SECONDS seconds after OFFSET bytes. SECONDS can be an integer or "f" to pause forever. diff --git a/libpathod/templates/docs_pathod.html b/libpathod/templates/docs_pathod.html index 977642c6..1c8f01c7 100644 --- a/libpathod/templates/docs_pathod.html +++ b/libpathod/templates/docs_pathod.html @@ -92,19 +92,19 @@ various other goodies. Try it by visiting the server root:

for 120 seconds after sending 50 bytes (counted from the first byte of the HTTP response):

-
200:b@1m:p120,50
+
200:b@1m:p50,120

If that's not long enough, we can tell pathod to hang forever:

-
200:b@1m:p120,f
+
200:b@1m:pf,120

Or to send all data, and then hang without disconnecting:

-
200:b@1m:p120,a
+
200:b@1m:pa,120

We can also ask pathod to hang randomly:

-
200:b@1m:pr,a
+
200:b@1m:pr,10

There is a similar mechanism for dropping connections mid-response. So, we can tell pathod to disconnect after sending 50 bytes:

diff --git a/test/test_pathoc.py b/test/test_pathoc.py index 15493e96..3cd07649 100644 --- a/test/test_pathoc.py +++ b/test/test_pathoc.py @@ -31,7 +31,7 @@ class TestDaemon: s = cStringIO.StringIO() c.print_requests( - ["get:'/p/200:p10,0'"], True, True, s + ["get:'/p/200:p0,10'"], True, True, s ) assert "Timeout" in s.getvalue() diff --git a/test/test_rparse.py b/test/test_rparse.py index 634eb6a7..57520176 100644 --- a/test/test_rparse.py +++ b/test/test_rparse.py @@ -226,13 +226,13 @@ class TestPauses: assert v.seconds == 10 assert v.offset == 10 - v = e.parseString("pf,10")[0] + v = e.parseString("p10,f")[0] assert v.seconds == "f" - v = e.parseString("pf,r")[0] + v = e.parseString("pr,f")[0] assert v.offset == "r" - v = e.parseString("pf,a")[0] + v = e.parseString("pa,f")[0] assert v.offset == "a" def test_request(self): @@ -311,15 +311,15 @@ class TestParseResponse: assert utils.get_header("foo", r.headers) def test_parse_pause_before(self): - r = rparse.parse_response({}, "400:p10,0") + r = rparse.parse_response({}, "400:p0,10") assert (0, "pause", 10) in r.actions def test_parse_pause_after(self): - r = rparse.parse_response({}, "400:p10,a") + r = rparse.parse_response({}, "400:pa,10") assert ("a", "pause", 10) in r.actions def test_parse_pause_random(self): - r = rparse.parse_response({}, "400:p10,r") + r = rparse.parse_response({}, "400:pr,10") assert ("r", "pause", 10) in r.actions def test_parse_stress(self): @@ -397,7 +397,7 @@ class TestWriteValues: r.serve(s, None) s = cStringIO.StringIO() - r = rparse.parse_response({}, "400:p0,a") + r = rparse.parse_response({}, "400:pa,0") r.serve(s, None) s = cStringIO.StringIO() -- cgit v1.2.3 From 7dab85e8b1de9101cadcc75768672a7ca26da3b8 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Wed, 25 Jul 2012 00:16:24 +1200 Subject: Handle file access denied errors in previews. --- libpathod/app.py | 51 ++++++++++++++++++++++-------------------- libpathod/templates/frame.html | 2 +- test/test_app.py | 4 ++++ 3 files changed, 32 insertions(+), 25 deletions(-) diff --git a/libpathod/app.py b/libpathod/app.py index 79c0fc71..cf66f8d4 100644 --- a/libpathod/app.py +++ b/libpathod/app.py @@ -75,8 +75,12 @@ def onelog(lid): return render("onelog.html", section="log", alog=l, lid=lid) -@app.route('/response_preview') -def response_preview(): +def _preview(is_request): + if is_request: + template = "request_preview.html" + else: + template = "response_preview.html" + spec = request.args["spec"] args = dict( spec = spec, @@ -85,37 +89,36 @@ def response_preview(): error = None ) try: - r = rparse.parse_response(app.config["pathod"].request_settings, spec) + if is_request: + r = rparse.parse_request(app.config["pathod"].request_settings, spec) + else: + r = rparse.parse_response(app.config["pathod"].request_settings, spec) except rparse.ParseException, v: args["syntaxerror"] = str(v) args["marked"] = v.marked() - return render("response_preview.html", **args) + return render(template, **args) + except rparse.FileAccessDenied: + args["error"] = "File access is disabled." + return render(template, **args) s = cStringIO.StringIO() r.preview_safe() - r.serve(s, check=app.config["pathod"].check_size) + + if is_request: + r.serve(s, check=app.config["pathod"].check_size, host="example.com") + else: + r.serve(s, check=app.config["pathod"].check_size) + args["output"] = utils.escape_unprintables(s.getvalue()) - return render("response_preview.html", **args) + return render(template, **args) +@app.route('/response_preview') +def response_preview(): + return _preview(False) + + @app.route('/request_preview') def request_preview(): - spec = request.args["spec"] - args = dict( - spec = spec, - section = "main", - syntaxerror = None, - error = None - ) - try: - r = rparse.parse_request(app.config["pathod"].request_settings, spec) - except rparse.ParseException, v: - args["syntaxerror"] = str(v) - args["marked"] = v.marked() - return render("request_preview.html", **args) + return _preview(True) - s = cStringIO.StringIO() - r.preview_safe() - r.serve(s, check=app.config["pathod"].check_size, host="example.com") - args["output"] = utils.escape_unprintables(s.getvalue()) - return render("request_preview.html", **args) diff --git a/libpathod/templates/frame.html b/libpathod/templates/frame.html index 416f3eca..d91004e4 100644 --- a/libpathod/templates/frame.html +++ b/libpathod/templates/frame.html @@ -77,7 +77,7 @@ $.localScroll( { duration: 300, - offset: {top: -100} + offset: {top: -45} } ); }); diff --git a/test/test_app.py b/test/test_app.py index b0d730af..8070996f 100644 --- a/test/test_app.py +++ b/test/test_app.py @@ -38,6 +38,10 @@ class TestApp(tutils.DaemonTests): assert r.status_code == 200 assert 'Response' in r.content + r = self.getpath("/response_preview", params=dict(spec="200:b Date: Wed, 25 Jul 2012 10:24:09 +1200 Subject: Sizing and placeholder for preview forms. --- libpathod/templates/request_previewform.html | 9 ++++++++- libpathod/templates/response_previewform.html | 9 ++++++++- test/test_app.py | 3 --- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/libpathod/templates/request_previewform.html b/libpathod/templates/request_previewform.html index accb1631..a7130da0 100644 --- a/libpathod/templates/request_previewform.html +++ b/libpathod/templates/request_previewform.html @@ -1,4 +1,11 @@
- +
diff --git a/libpathod/templates/response_previewform.html b/libpathod/templates/response_previewform.html index d98a72f7..a82535e4 100644 --- a/libpathod/templates/response_previewform.html +++ b/libpathod/templates/response_previewform.html @@ -1,5 +1,12 @@
- + {% if not nocraft %} go to diff --git a/test/test_app.py b/test/test_app.py index 8070996f..699c9c41 100644 --- a/test/test_app.py +++ b/test/test_app.py @@ -59,6 +59,3 @@ class TestApp(tutils.DaemonTests): assert r.status_code == 200 assert 'Request' in r.content - - - -- cgit v1.2.3 From 8cfbc2f80eb186e0958b7fbf7b9f70dca37389f7 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Wed, 25 Jul 2012 10:34:57 +1200 Subject: Refactor test.py, add unit tests for app.py corner case. --- libpathod/app.py | 2 +- libpathod/test.py | 15 ++++++--------- test/test_pathod.py | 13 +++++++++---- test/tutils.py | 7 +++++-- 4 files changed, 21 insertions(+), 16 deletions(-) diff --git a/libpathod/app.py b/libpathod/app.py index cf66f8d4..17be448d 100644 --- a/libpathod/app.py +++ b/libpathod/app.py @@ -116,7 +116,7 @@ def _preview(is_request): @app.route('/response_preview') def response_preview(): return _preview(False) - + @app.route('/request_preview') def request_preview(): diff --git a/libpathod/test.py b/libpathod/test.py index e8ca536a..5bcebcb6 100644 --- a/libpathod/test.py +++ b/libpathod/test.py @@ -5,9 +5,9 @@ import pathod, utils IFACE = "127.0.0.1" class Daemon: - def __init__(self, staticdir=None, anchors=(), ssl=None, sizelimit=None, noweb=False): + def __init__(self, ssl=None, **daemonargs): self.q = Queue.Queue() - self.thread = PaThread(self.q, staticdir, anchors, ssl, sizelimit, noweb) + self.thread = PaThread(self.q, ssl, daemonargs) self.thread.start() self.port = self.q.get(True, 5) self.urlbase = "%s://%s:%s"%("https" if ssl else "http", IFACE, self.port) @@ -42,10 +42,10 @@ class Daemon: class PaThread(threading.Thread): - def __init__(self, q, staticdir, anchors, ssl, sizelimit, noweb): + def __init__(self, q, ssl, daemonargs): threading.Thread.__init__(self) - self.q, self.staticdir, self.anchors, self.ssl, self.sizelimit = q, staticdir, anchors, ssl, sizelimit - self.noweb = noweb + self.q, self.ssl = q, ssl + self.daemonargs = daemonargs def run(self): if self.ssl is True: @@ -58,10 +58,7 @@ class PaThread(threading.Thread): self.server = pathod.Pathod( (IFACE, 0), ssloptions = ssloptions, - anchors = self.anchors, - staticdir = self.staticdir, - sizelimit = self.sizelimit, - noweb = self.noweb + **self.daemonargs ) self.q.put(self.server.port) self.server.serve_forever() diff --git a/test/test_pathod.py b/test/test_pathod.py index 58477620..036fbf0b 100644 --- a/test/test_pathod.py +++ b/test/test_pathod.py @@ -29,15 +29,20 @@ class TestPathod: class TestNoWeb(tutils.DaemonTests): noweb = True - def setUp(self): - # Over ride log clearing - pass - def test_noweb(self): assert self.get("200").status_code == 200 assert self.getpath("/").status_code == 800 +class TestNoApi(tutils.DaemonTests): + noapi = True + def test_noapi(self): + assert self.getpath("/log").status_code == 404 + r = self.getpath("/") + assert r.status_code == 200 + assert not "Log" in r.content + + class CommonTests(tutils.DaemonTests): def test_sizelimit(self): r = self.get("200:b@1g") diff --git a/test/tutils.py b/test/tutils.py index b1e277e7..1eb78980 100644 --- a/test/tutils.py +++ b/test/tutils.py @@ -5,6 +5,7 @@ import requests class DaemonTests: noweb = False + noapi = False ssl = False @classmethod def setUpAll(self): @@ -13,7 +14,8 @@ class DaemonTests: anchors=[("/anchor/.*", "202")], ssl = self.ssl, sizelimit=1*1024*1024, - noweb = self.noweb + noweb = self.noweb, + noapi = self.noapi ) @classmethod @@ -21,7 +23,8 @@ class DaemonTests: self.d.shutdown() def setUp(self): - self.d.clear_log() + if not (self.noweb or self.noapi): + self.d.clear_log() def getpath(self, path, params=None): scheme = "https" if self.ssl else "http" -- cgit v1.2.3 From 59f408dcf437b0099edc0b54b73c006752c7dc7e Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Wed, 25 Jul 2012 10:44:21 +1200 Subject: Refine parse error message a bit. --- libpathod/rparse.py | 2 +- libpathod/utils.py | 16 +++++++++++++++- test/test_utils.py | 5 +++++ 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/libpathod/rparse.py b/libpathod/rparse.py index ab657974..ea12185b 100644 --- a/libpathod/rparse.py +++ b/libpathod/rparse.py @@ -22,7 +22,7 @@ class ParseException(Exception): return "%s\n%s"%(self.s, " "*(self.col-1) + "^") def __str__(self): - return "%s at offset %s of %s"%(self.msg, self.col, repr(self.s)) + return "%s at char %s"%(self.msg, self.col) def actions_log(lst): diff --git a/libpathod/utils.py b/libpathod/utils.py index 1ee50b83..7a1e533f 100644 --- a/libpathod/utils.py +++ b/libpathod/utils.py @@ -45,10 +45,24 @@ def xrepr(s): return repr(s)[1:-1] +def inner_repr(s): + """ + Returns the inner portion of a string or unicode repr (i.e. without the + quotes) + """ + if isinstance(s, unicode): + return repr(s)[2:-1] + else: + return repr(s)[1:-1] + + def escape_unprintables(s): + """ + Like inner_repr, but preserves line breaks. + """ s = s.replace("\r\n", "PATHOD_MARKER_RN") s = s.replace("\n", "PATHOD_MARKER_N") - s = repr(s)[1:-1] + s = inner_repr(s) s = s.replace("PATHOD_MARKER_RN", "\n") s = s.replace("PATHOD_MARKER_N", "\n") return s diff --git a/test/test_utils.py b/test/test_utils.py index a8f513f8..0a48d87d 100644 --- a/test/test_utils.py +++ b/test/test_utils.py @@ -17,6 +17,11 @@ def test_data_path(): tutils.raises(ValueError, utils.data.path, "nonexistent") +def test_inner_repr(): + assert utils.inner_repr("\x66") == "\x66" + assert utils.inner_repr(u"foo") == "foo" + + def test_escape_unprintables(): s = "".join([chr(i) for i in range(255)]) e = utils.escape_unprintables(s) -- cgit v1.2.3 From e7e6a60246c28e1399f0fd626c73b4f16b04a995 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Wed, 25 Jul 2012 10:53:29 +1200 Subject: Nicer error for empty preview spec submission. --- libpathod/app.py | 5 +++++ libpathod/templates/request_preview.html | 1 - libpathod/templates/response_preview.html | 1 - test/test_app.py | 4 ++++ 4 files changed, 9 insertions(+), 2 deletions(-) diff --git a/libpathod/app.py b/libpathod/app.py index 17be448d..e93a898f 100644 --- a/libpathod/app.py +++ b/libpathod/app.py @@ -82,12 +82,17 @@ def _preview(is_request): 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, **args) + try: if is_request: r = rparse.parse_request(app.config["pathod"].request_settings, spec) diff --git a/libpathod/templates/request_preview.html b/libpathod/templates/request_preview.html index fe8a03e9..eaf78898 100644 --- a/libpathod/templates/request_preview.html +++ b/libpathod/templates/request_preview.html @@ -7,7 +7,6 @@

Error: {{ syntaxerror }}

{{ marked }}
{% elif error %} -

Error

{{ error }}

{% else %}

Spec:

diff --git a/libpathod/templates/response_preview.html b/libpathod/templates/response_preview.html index 1b44e480..2e1abcf4 100644 --- a/libpathod/templates/response_preview.html +++ b/libpathod/templates/response_preview.html @@ -7,7 +7,6 @@

Error: {{ syntaxerror }}

{{ marked }}
{% elif error %} -

Error

{{ error }}

{% else %}

Spec:

diff --git a/test/test_app.py b/test/test_app.py index 699c9c41..da6431c9 100644 --- a/test/test_app.py +++ b/test/test_app.py @@ -59,3 +59,7 @@ class TestApp(tutils.DaemonTests): assert r.status_code == 200 assert 'Request' in r.content + r = self.getpath("/request_preview", params=dict(spec="")) + assert r.status_code == 200 + assert 'empty spec' in r.content + -- cgit v1.2.3 From 410144c579bcb7abfc5efd0e3a2ed6a75a4bd61c Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Wed, 25 Jul 2012 11:36:19 +1200 Subject: Add examples for each preview form. --- libpathod/templates/request_previewform.html | 36 ++++++++++++++++++++++++++ libpathod/templates/response_previewform.html | 37 +++++++++++++++++++++++++++ 2 files changed, 73 insertions(+) diff --git a/libpathod/templates/request_previewform.html b/libpathod/templates/request_previewform.html index a7130da0..c09ae018 100644 --- a/libpathod/templates/request_previewform.html +++ b/libpathod/templates/request_previewform.html @@ -9,3 +9,39 @@ > + +more examples... +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
get:/Get path /
get:/:b@100100 random bytes as the body
get:/:h"User-Agent"="';drop table browsers;"Add a User-Agent header
get:/:b@100:drDrop the connection randomly
get:/:b@100,ascii:ir,@1100 ASCII bytes as the body, and randomly inject a random byte
+
diff --git a/libpathod/templates/response_previewform.html b/libpathod/templates/response_previewform.html index a82535e4..93d5960a 100644 --- a/libpathod/templates/response_previewform.html +++ b/libpathod/templates/response_previewform.html @@ -12,6 +12,43 @@ go to {% endif %} + +more examples... +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
200A basic HTTP 200 response
200:b@100100 random bytes as the body
200:b@100:h"Etag"="';drop table servers;"Add a Server header
200:b@100:drDrop the connection randomly
200:b@100,ascii:ir,@1100 ASCII bytes as the body, and randomly inject a random byte
+
+
@@ -22,6 +46,7 @@ A perverse HTTP client. {% include "request_previewform.html" %} +
-- cgit v1.2.3 From 3e158211a830bbcba2dd463189a79ec3ad17c8d4 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Thu, 26 Jul 2012 20:01:51 +1200 Subject: Add a --nohang flag that turns off pauses in response generation. --- libpathod/app.py | 4 ++-- libpathod/pathod.py | 15 ++++++++++----- pathod | 7 ++++++- test/test_pathod.py | 9 +++++++++ test/tutils.py | 4 +++- 5 files changed, 30 insertions(+), 9 deletions(-) diff --git a/libpathod/app.py b/libpathod/app.py index ebcf0369..29b56711 100644 --- a/libpathod/app.py +++ b/libpathod/app.py @@ -117,9 +117,9 @@ def _preview(is_request): args["pauses"] = r.preview_safe() if is_request: - r.serve(s, check=app.config["pathod"].check_size, host="example.com") + r.serve(s, check=app.config["pathod"].check_policy, host="example.com") else: - r.serve(s, check=app.config["pathod"].check_size) + r.serve(s, check=app.config["pathod"].check_policy) args["output"] = utils.escape_unprintables(s.getvalue()) return render(template, **args) diff --git a/libpathod/pathod.py b/libpathod/pathod.py index 8ee7f9ae..b1343ea2 100644 --- a/libpathod/pathod.py +++ b/libpathod/pathod.py @@ -19,7 +19,7 @@ class PathodHandler(tcp.BaseHandler): self.sni = connection.get_servername() def serve_crafted(self, crafted, request_log): - response_log = crafted.serve(self.wfile, self.server.check_size) + response_log = crafted.serve(self.wfile, self.server.check_policy) self.server.add_log( dict( type = "crafted", @@ -97,7 +97,7 @@ class PathodHandler(tcp.BaseHandler): return self.serve_crafted(crafted, request_log) elif self.server.noweb: crafted = rparse.PathodErrorResponse("Access Denied") - crafted.serve(self.wfile, self.server.check_size) + crafted.serve(self.wfile, self.server.check_policy) return False else: cc = wsgi.ClientConn(self.client_address) @@ -150,7 +150,7 @@ class Pathod(tcp.TCPServer): LOGBUF = 500 def __init__( self, addr, ssloptions=None, craftanchor="/p/", staticdir=None, anchors=None, - sizelimit=None, noweb=False, nocraft=False, noapi=False + sizelimit=None, noweb=False, nocraft=False, noapi=False, nohang=False ): """ addr: (address, port) tuple. If port is 0, a free port will be @@ -160,13 +160,16 @@ class Pathod(tcp.TCPServer): staticdir: path to a directory of static resources, or None. anchors: A list of (regex, spec) 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.ssloptions = ssloptions self.staticdir = staticdir self.craftanchor = craftanchor self.sizelimit = sizelimit - self.noweb, self.nocraft, self.noapi = noweb, nocraft, noapi + self.noweb, self.nocraft, self.noapi, self.nohang = noweb, nocraft, noapi, nohang if not noapi: app.api() self.app = app.app @@ -186,12 +189,14 @@ class Pathod(tcp.TCPServer): raise PathodError("Invalid page spec in anchor: '%s', %s"%(i[1], str(v))) self.anchors.append((arex, aresp)) - def check_size(self, req, actions): + def check_policy(self, req, actions): """ A policy check that verifies the request size is withing limits. """ if self.sizelimit and req.effective_length(actions) > self.sizelimit: return "Response too large." + if self.nohang and any([i[1] == "pause" for i in actions]): + return "Pauses have been disabled." return False @property diff --git a/pathod b/pathod index df044ae7..56f6e3fe 100755 --- a/pathod +++ b/pathod @@ -34,6 +34,10 @@ if __name__ == "__main__": "--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.' @@ -100,7 +104,8 @@ if __name__ == "__main__": sizelimit = sizelimit, noweb = args.noweb, nocraft = args.nocraft, - noapi = args.noapi + noapi = args.noapi, + nohang = args.nohang ) except pathod.PathodError, v: parser.error(str(v)) diff --git a/test/test_pathod.py b/test/test_pathod.py index 036fbf0b..d6e2e886 100644 --- a/test/test_pathod.py +++ b/test/test_pathod.py @@ -43,6 +43,15 @@ class TestNoApi(tutils.DaemonTests): assert not "Log" in r.content +class TestNohang(tutils.DaemonTests): + nohang = True + def test_nohang(self): + r = self.get("200:p0,0") + assert r.status_code == 800 + l = self.d.log()[0] + assert "Pauses have been disabled" in l["response"]["error"] + + class CommonTests(tutils.DaemonTests): def test_sizelimit(self): r = self.get("200:b@1g") diff --git a/test/tutils.py b/test/tutils.py index 1eb78980..d9e543a1 100644 --- a/test/tutils.py +++ b/test/tutils.py @@ -6,6 +6,7 @@ import requests class DaemonTests: noweb = False noapi = False + nohang = False ssl = False @classmethod def setUpAll(self): @@ -15,7 +16,8 @@ class DaemonTests: ssl = self.ssl, sizelimit=1*1024*1024, noweb = self.noweb, - noapi = self.noapi + noapi = self.noapi, + nohang = self.nohang ) @classmethod -- cgit v1.2.3 From d8c53cbc57574bfce0be617d36e7ccf6f1b55885 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Fri, 27 Jul 2012 14:03:15 +1200 Subject: Add an option to specify a log file to pathod. --- libpathod/pathod.py | 6 +++++- pathod | 12 ++++++++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/libpathod/pathod.py b/libpathod/pathod.py index b1343ea2..0ae32c47 100644 --- a/libpathod/pathod.py +++ b/libpathod/pathod.py @@ -81,18 +81,22 @@ class PathodHandler(tcp.BaseHandler): for i in self.server.anchors: if i[0].match(path): + self.info("Serving anchor: %s"%path) return self.serve_crafted(i[1], request_log) if not self.server.nocraft and path.startswith(self.server.craftanchor): spec = urllib.unquote(path)[len(self.server.craftanchor):] + self.info("Serving spec: %s"%spec) try: crafted = rparse.parse_response(self.server.request_settings, spec) except rparse.ParseException, v: + self.info("Parse error: %s"%v.msg) crafted = rparse.PathodErrorResponse( "Parse Error", "Error parsing response spec: %s\n"%v.msg + v.marked() ) except rparse.FileAccessDenied: + self.info("File access denied") crafted = rparse.PathodErrorResponse("Access Denied") return self.serve_crafted(crafted, request_log) elif self.server.noweb: @@ -100,6 +104,7 @@ class PathodHandler(tcp.BaseHandler): crafted.serve(self.wfile, self.server.check_policy) return False else: + self.info("app: %s %s"%(method, path)) cc = wsgi.ClientConn(self.client_address) req = wsgi.Request(cc, "http", method, path, headers, content) sn = self.connection.getsockname() @@ -110,7 +115,6 @@ class PathodHandler(tcp.BaseHandler): version.NAMEVERSION ) app.serve(req, self.wfile) - self.debug("%s %s"%(method, path)) return True def handle(self): diff --git a/pathod b/pathod index 56f6e3fe..144e01b5 100755 --- a/pathod +++ b/pathod @@ -1,5 +1,5 @@ #!/usr/bin/env python -import argparse, sys, logging +import argparse, sys, logging, logging.handlers from libpathod import pathod, utils, version if __name__ == "__main__": @@ -18,6 +18,10 @@ if __name__ == "__main__": "-d", dest='staticdir', default=None, type=str, help='Directory for static files.' ) + parser.add_argument( + "-f", dest='logfile', default=None, type=str, + help='Log file.' + ) parser.add_argument( "--debug", dest='debug', default=False, action="store_true", help='Enable debug output.' @@ -86,6 +90,10 @@ if __name__ == "__main__": ) if not args.debug: logging.disable(logging.DEBUG) + if args.logfile: + ch = logging.handlers.WatchedFileHandler(args.logfile) + root.addHandler(ch) + sizelimit = None if args.sizelimit: @@ -104,7 +112,7 @@ if __name__ == "__main__": sizelimit = sizelimit, noweb = args.noweb, nocraft = args.nocraft, - noapi = args.noapi, + noapi = args.noapi, nohang = args.nohang ) except pathod.PathodError, v: -- cgit v1.2.3 From 483e8182ff07359900dada138ec309a9a589e42a Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Sun, 29 Jul 2012 13:55:59 +1200 Subject: Add new styling (thanks to bootswatch.com) --- .less/bootswatch.less | 171 ++++++++++++++++++++++++++++++ .less/variables.less | 208 +++++++++++++++++++++++++++++++++++++ libpathod/static/bootstrap.min.css | 4 +- libpathod/static/pathod.css | 17 +++ libpathod/static/torture.png | Bin 0 -> 108327 bytes libpathod/templates/frame.html | 5 +- libpathod/templates/index.html | 46 +++++--- 7 files changed, 430 insertions(+), 21 deletions(-) create mode 100644 .less/bootswatch.less create mode 100644 .less/variables.less create mode 100644 libpathod/static/torture.png diff --git a/.less/bootswatch.less b/.less/bootswatch.less new file mode 100644 index 00000000..f9e4b827 --- /dev/null +++ b/.less/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/.less/variables.less b/.less/variables.less new file mode 100644 index 00000000..75ff5be6 --- /dev/null +++ b/.less/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/libpathod/static/bootstrap.min.css b/libpathod/static/bootstrap.min.css index b74b4546..23f25a89 100644 --- a/libpathod/static/bootstrap.min.css +++ b/libpathod/static/bootstrap.min.css @@ -1,4 +1,4 @@ -/*! +@import url('https://fonts.googleapis.com/css?family=Open+Sans:400,700');/*! * Bootstrap v2.0.4 * * Copyright 2012 Twitter, Inc @@ -6,4 +6,4 @@ * http://www.apache.org/licenses/LICENSE-2.0 * * Designed and built with all the love in the world @twitter by @mdo and @fat. - */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{max-width:100%;vertical-align:middle;border:0;-ms-interpolation-mode:bicubic}#map_canvas 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,input[type="button"],input[type="reset"],input[type="submit"]{cursor:pointer;-webkit-appearance:button}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}.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;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:28px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;box-sizing:border-box}body{margin:0;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:13px;line-height:18px;color:#333;background-color:#fff}a{color:#08c;text-decoration:none}a:hover{color:#005580;text-decoration:underline}.row{margin-left:-20px;*zoom:1}.row:before,.row:after{display:table;content:""}.row:after{clear:both}[class*="span"]{float:left;margin-left:20px}.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;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:28px;margin-left:2.127659574%;*margin-left:2.0744680846382977%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .span12{width:99.99999998999999%;*width:99.94680850063828%}.row-fluid .span11{width:91.489361693%;*width:91.4361702036383%}.row-fluid .span10{width:82.97872339599999%;*width:82.92553190663828%}.row-fluid .span9{width:74.468085099%;*width:74.4148936096383%}.row-fluid .span8{width:65.95744680199999%;*width:65.90425531263828%}.row-fluid .span7{width:57.446808505%;*width:57.3936170156383%}.row-fluid .span6{width:48.93617020799999%;*width:48.88297871863829%}.row-fluid .span5{width:40.425531911%;*width:40.3723404216383%}.row-fluid .span4{width:31.914893614%;*width:31.8617021246383%}.row-fluid .span3{width:23.404255317%;*width:23.3510638276383%}.row-fluid .span2{width:14.89361702%;*width:14.8404255306383%}.row-fluid .span1{width:6.382978723%;*width:6.329787233638298%}.container{margin-right:auto;margin-left:auto;*zoom:1}.container:before,.container:after{display:table;content:""}.container:after{clear:both}.container-fluid{padding-right:20px;padding-left:20px;*zoom:1}.container-fluid:before,.container-fluid:after{display:table;content:""}.container-fluid:after{clear:both}p{margin:0 0 9px}p small{font-size:11px;color:#999}.lead{margin-bottom:18px;font-size:20px;font-weight:200;line-height:27px}h1,h2,h3,h4,h5,h6{margin:0;font-family:inherit;font-weight:bold;color:inherit;text-rendering:optimizelegibility}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small{font-weight:normal;color:#999}h1{font-size:30px;line-height:36px}h1 small{font-size:18px}h2{font-size:24px;line-height:36px}h2 small{font-size:18px}h3{font-size:18px;line-height:27px}h3 small{font-size:14px}h4,h5,h6{line-height:18px}h4{font-size:14px}h4 small{font-size:12px}h5{font-size:12px}h6{font-size:11px;color:#999;text-transform:uppercase}.page-header{padding-bottom:17px;margin:18px 0;border-bottom:1px solid #eee}.page-header h1{line-height:1}ul,ol{padding:0;margin:0 0 9px 25px}ul ul,ul ol,ol ol,ol ul{margin-bottom:0}ul{list-style:disc}ol{list-style:decimal}li{line-height:18px}ul.unstyled,ol.unstyled{margin-left:0;list-style:none}dl{margin-bottom:18px}dt,dd{line-height:18px}dt{font-weight:bold;line-height:17px}dd{margin-left:9px}.dl-horizontal dt{float:left;width:120px;overflow:hidden;clear:left;text-align:right;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:130px}hr{margin:18px 0;border:0;border-top:1px solid #eee;border-bottom:1px solid #fff}strong{font-weight:bold}em{font-style:italic}.muted{color:#999}abbr[title]{cursor:help;border-bottom:1px dotted #999}abbr.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:0 0 0 15px;margin:0 0 18px;border-left:5px solid #eee}blockquote p{margin-bottom:0;font-size:16px;font-weight:300;line-height:22.5px}blockquote small{display:block;line-height:18px;color:#999}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}q:before,q:after,blockquote:before,blockquote:after{content:""}address{display:block;margin-bottom:18px;font-style:normal;line-height:18px}small{font-size:100%}cite{font-style:normal}code,pre{padding:0 3px 2px;font-family:Menlo,Monaco,Consolas,"Courier New",monospace;font-size:12px;color:#333;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}code{padding:2px 4px;color:#d14;background-color:#f7f7f9;border:1px solid #e1e1e8}pre{display:block;padding:8.5px;margin:0 0 9px;font-size:12.025px;line-height:18px;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:4px;-moz-border-radius:4px;border-radius:4px}pre.prettyprint{margin-bottom:18px}pre code{padding:0;color:inherit;background-color:transparent;border:0}.pre-scrollable{max-height:340px;overflow-y:scroll}form{margin:0 0 18px}fieldset{padding:0;margin:0;border:0}legend{display:block;width:100%;padding:0;margin-bottom:27px;font-size:19.5px;line-height:36px;color:#333;border:0;border-bottom:1px solid #e5e5e5}legend small{font-size:13.5px;color:#999}label,input,button,select,textarea{font-size:13px;font-weight:normal;line-height:18px}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:18px;padding:4px;margin-bottom:9px;font-size:13px;line-height:18px;color:#555}input,textarea{width:210px}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 #ccc;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;-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;-ms-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:3px 0;*margin-top:0;line-height:normal;cursor:pointer}input[type="submit"],input[type="reset"],input[type="button"],input[type="radio"],input[type="checkbox"]{width:auto}.uneditable-textarea{width:auto;height:auto}select,input[type="file"]{height:28px;*margin-top:4px;line-height:28px}select{width:220px;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}.radio,.checkbox{min-height:18px;padding-left:18px}.radio input[type="radio"],.checkbox input[type="checkbox"]{float:left;margin-left:-18px}.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-prepend [class*="span"],.row-fluid .input-append [class*="span"]{display:inline-block}input,textarea,.uneditable-input{margin-left:0}input.span12,textarea.span12,.uneditable-input.span12{width:930px}input.span11,textarea.span11,.uneditable-input.span11{width:850px}input.span10,textarea.span10,.uneditable-input.span10{width:770px}input.span9,textarea.span9,.uneditable-input.span9{width:690px}input.span8,textarea.span8,.uneditable-input.span8{width:610px}input.span7,textarea.span7,.uneditable-input.span7{width:530px}input.span6,textarea.span6,.uneditable-input.span6{width:450px}input.span5,textarea.span5,.uneditable-input.span5{width:370px}input.span4,textarea.span4,.uneditable-input.span4{width:290px}input.span3,textarea.span3,.uneditable-input.span3{width:210px}input.span2,textarea.span2,.uneditable-input.span2{width:130px}input.span1,textarea.span1,.uneditable-input.span1{width:50px}input[disabled],select[disabled],textarea[disabled],input[readonly],select[readonly],textarea[readonly]{cursor:not-allowed;background-color:#eee;border-color:#ddd}input[type="radio"][disabled],input[type="checkbox"][disabled],input[type="radio"][readonly],input[type="checkbox"][readonly]{background-color:transparent}.control-group.warning>label,.control-group.warning .help-block,.control-group.warning .help-inline{color:#c09853}.control-group.warning .checkbox,.control-group.warning .radio,.control-group.warning input,.control-group.warning select,.control-group.warning textarea{color:#c09853;border-color:#c09853}.control-group.warning .checkbox:focus,.control-group.warning .radio:focus,.control-group.warning input:focus,.control-group.warning select:focus,.control-group.warning textarea:focus{border-color:#a47e3c;-webkit-box-shadow:0 0 6px #dbc59e;-moz-box-shadow:0 0 6px #dbc59e;box-shadow:0 0 6px #dbc59e}.control-group.warning .input-prepend .add-on,.control-group.warning .input-append .add-on{color:#c09853;background-color:#fcf8e3;border-color:#c09853}.control-group.error>label,.control-group.error .help-block,.control-group.error .help-inline{color:#b94a48}.control-group.error .checkbox,.control-group.error .radio,.control-group.error input,.control-group.error select,.control-group.error textarea{color:#b94a48;border-color:#b94a48}.control-group.error .checkbox:focus,.control-group.error .radio:focus,.control-group.error input:focus,.control-group.error select:focus,.control-group.error textarea:focus{border-color:#953b39;-webkit-box-shadow:0 0 6px #d59392;-moz-box-shadow:0 0 6px #d59392;box-shadow:0 0 6px #d59392}.control-group.error .input-prepend .add-on,.control-group.error .input-append .add-on{color:#b94a48;background-color:#f2dede;border-color:#b94a48}.control-group.success>label,.control-group.success .help-block,.control-group.success .help-inline{color:#468847}.control-group.success .checkbox,.control-group.success .radio,.control-group.success input,.control-group.success select,.control-group.success textarea{color:#468847;border-color:#468847}.control-group.success .checkbox:focus,.control-group.success .radio:focus,.control-group.success input:focus,.control-group.success select:focus,.control-group.success textarea:focus{border-color:#356635;-webkit-box-shadow:0 0 6px #7aba7b;-moz-box-shadow:0 0 6px #7aba7b;box-shadow:0 0 6px #7aba7b}.control-group.success .input-prepend .add-on,.control-group.success .input-append .add-on{color:#468847;background-color:#dff0d8;border-color:#468847}input:focus:required:invalid,textarea:focus:required:invalid,select:focus:required:invalid{color:#b94a48;border-color:#ee5f5b}input:focus:required:invalid:focus,textarea:focus:required:invalid:focus,select:focus:required: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:17px 20px 18px;margin-top:18px;margin-bottom:18px;background-color:#f5f5f5;border-top:1px solid #e5e5e5;*zoom:1}.form-actions:before,.form-actions:after{display:table;content:""}.form-actions:after{clear:both}.uneditable-input{overflow:hidden;white-space:nowrap;cursor:not-allowed;background-color:#fff;border-color:#eee;-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)}:-moz-placeholder{color:#999}:-ms-input-placeholder{color:#999}::-webkit-input-placeholder{color:#999}.help-block,.help-inline{color:#555}.help-block{display:block;margin-bottom:9px}.help-inline{display:inline-block;*display:inline;padding-left:5px;vertical-align:middle;*zoom:1}.input-prepend,.input-append{margin-bottom:5px}.input-prepend input,.input-append input,.input-prepend select,.input-append select,.input-prepend .uneditable-input,.input-append .uneditable-input{position:relative;margin-bottom:0;*margin-left:0;vertical-align:middle;-webkit-border-radius:0 3px 3px 0;-moz-border-radius:0 3px 3px 0;border-radius:0 3px 3px 0}.input-prepend input:focus,.input-append input:focus,.input-prepend select:focus,.input-append select:focus,.input-prepend .uneditable-input:focus,.input-append .uneditable-input:focus{z-index:2}.input-prepend .uneditable-input,.input-append .uneditable-input{border-left-color:#ccc}.input-prepend .add-on,.input-append .add-on{display:inline-block;width:auto;height:18px;min-width:16px;padding:4px 5px;font-weight:normal;line-height:18px;text-align:center;text-shadow:0 1px 0 #fff;vertical-align:middle;background-color:#eee;border:1px solid #ccc}.input-prepend .add-on,.input-append .add-on,.input-prepend .btn,.input-append .btn{margin-left:-1px;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.input-prepend .active,.input-append .active{background-color:#a9dba9;border-color:#46a546}.input-prepend .add-on,.input-prepend .btn{margin-right:-1px}.input-prepend .add-on:first-child,.input-prepend .btn:first-child{-webkit-border-radius:3px 0 0 3px;-moz-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px}.input-append input,.input-append select,.input-append .uneditable-input{-webkit-border-radius:3px 0 0 3px;-moz-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px}.input-append .uneditable-input{border-right-color:#ccc;border-left-color:#eee}.input-append .add-on:last-child,.input-append .btn:last-child{-webkit-border-radius:0 3px 3px 0;-moz-border-radius:0 3px 3px 0;border-radius:0 3px 3px 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 .add-on:first-child,.input-prepend.input-append .btn:first-child{margin-right:-1px;-webkit-border-radius:3px 0 0 3px;-moz-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px}.input-prepend.input-append .add-on:last-child,.input-prepend.input-append .btn:last-child{margin-left:-1px;-webkit-border-radius:0 3px 3px 0;-moz-border-radius:0 3px 3px 0;border-radius:0 3px 3px 0}.search-query{padding-right:14px;padding-right:4px \9;padding-left:14px;padding-left:4px \9;margin-bottom:0;-webkit-border-radius:14px;-moz-border-radius:14px;border-radius: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;*zoom:1}.form-search .hide,.form-inline .hide,.form-horizontal .hide{display:none}.form-search label,.form-inline label{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:9px}legend+.control-group{margin-top:18px;-webkit-margin-top-collapse:separate}.form-horizontal .control-group{margin-bottom:18px;*zoom:1}.form-horizontal .control-group:before,.form-horizontal .control-group:after{display:table;content:""}.form-horizontal .control-group:after{clear:both}.form-horizontal .control-label{float:left;width:140px;padding-top:5px;text-align:right}.form-horizontal .controls{*display:inline-block;*padding-left:20px;margin-left:160px;*margin-left:0}.form-horizontal .controls:first-child{*padding-left:160px}.form-horizontal .help-block{margin-top:9px;margin-bottom:0}.form-horizontal .form-actions{padding-left:160px}table{max-width:100%;background-color:transparent;border-collapse:collapse;border-spacing:0}.table{width:100%;margin-bottom:18px}.table th,.table td{padding:8px;line-height:18px;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-condensed th,.table-condensed td{padding:4px 5px}.table-bordered{border:1px solid #ddd;border-collapse:separate;*border-collapse:collapsed;border-left:0;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.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{-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-topleft:4px}.table-bordered thead:first-child tr:first-child th:last-child,.table-bordered tbody:first-child tr:first-child td:last-child{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-moz-border-radius-topright:4px}.table-bordered thead:last-child tr:last-child th:first-child,.table-bordered tbody:last-child tr:last-child td:first-child{-webkit-border-radius:0 0 0 4px;-moz-border-radius:0 0 0 4px;border-radius:0 0 0 4px;-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px;-moz-border-radius-bottomleft:4px}.table-bordered thead:last-child tr:last-child th:last-child,.table-bordered tbody:last-child tr:last-child td:last-child{-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px;-moz-border-radius-bottomright:4px}.table-striped tbody tr:nth-child(odd) td,.table-striped tbody tr:nth-child(odd) th{background-color:#f9f9f9}.table tbody tr:hover td,.table tbody tr:hover th{background-color:#f5f5f5}table .span1{float:none;width:44px;margin-left:0}table .span2{float:none;width:124px;margin-left:0}table .span3{float:none;width:204px;margin-left:0}table .span4{float:none;width:284px;margin-left:0}table .span5{float:none;width:364px;margin-left:0}table .span6{float:none;width:444px;margin-left:0}table .span7{float:none;width:524px;margin-left:0}table .span8{float:none;width:604px;margin-left:0}table .span9{float:none;width:684px;margin-left:0}table .span10{float:none;width:764px;margin-left:0}table .span11{float:none;width:844px;margin-left:0}table .span12{float:none;width:924px;margin-left:0}table .span13{float:none;width:1004px;margin-left:0}table .span14{float:none;width:1084px;margin-left:0}table .span15{float:none;width:1164px;margin-left:0}table .span16{float:none;width:1244px;margin-left:0}table .span17{float:none;width:1324px;margin-left:0}table .span18{float:none;width:1404px;margin-left:0}table .span19{float:none;width:1484px;margin-left:0}table .span20{float:none;width:1564px;margin-left:0}table .span21{float:none;width:1644px;margin-left:0}table .span22{float:none;width:1724px;margin-left:0}table .span23{float:none;width:1804px;margin-left:0}table .span24{float:none;width:1884px;margin-left:0}[class^="icon-"],[class*=" icon-"]{display:inline-block;width:14px;height:14px;*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}[class^="icon-"]:last-child,[class*=" icon-"]:last-child{*margin-left:0}.icon-white{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{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{background-position:-384px -120px}.icon-folder-open{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:"";opacity:.3;filter:alpha(opacity=30)}.dropdown .caret{margin-top:8px;margin-left:2px}.dropdown:hover .caret,.open .caret{opacity:1;filter:alpha(opacity=100)}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:4px 0;margin:1px 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:5px;-moz-border-radius:5px;border-radius:5px;-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:8px 1px;*margin:-5px 0 5px;overflow:hidden;background-color:#e5e5e5;border-bottom:1px solid #fff}.dropdown-menu a{display:block;padding:3px 15px;clear:both;font-weight:normal;line-height:18px;color:#333;white-space:nowrap}.dropdown-menu li>a:hover,.dropdown-menu .active>a,.dropdown-menu .active>a:hover{color:#fff;text-decoration:none;background-color:#08c}.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:"\2191"}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:1px}.typeahead{margin-top:2px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #eee;border:1px solid rgba(0,0,0,0.05);-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-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:6px;-moz-border-radius:6px;border-radius:6px}.well-small{padding:9px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.fade{opacity:0;-webkit-transition:opacity .15s linear;-moz-transition:opacity .15s linear;-ms-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;-ms-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:18px;color:#000;text-shadow:0 1px 0 #fff;opacity:.2;filter:alpha(opacity=20)}.close:hover{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 10px 4px;margin-bottom:0;*margin-left:.3em;font-size:13px;line-height:18px;*line-height:20px;color:#333;text-align:center;text-shadow:0 1px 1px rgba(255,255,255,0.75);vertical-align:middle;cursor:pointer;background-color:#f5f5f5;*background-color:#e6e6e6;background-image:-ms-linear-gradient(top,#fff,#e6e6e6);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fff),to(#e6e6e6));background-image:-webkit-linear-gradient(top,#fff,#e6e6e6);background-image:-o-linear-gradient(top,#fff,#e6e6e6);background-image:linear-gradient(top,#fff,#e6e6e6);background-image:-moz-linear-gradient(top,#fff,#e6e6e6);background-repeat:repeat-x;border:1px solid #ccc;*border:0;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);border-color:#e6e6e6 #e6e6e6 #bfbfbf;border-bottom-color:#b3b3b3;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;filter:progid:dximagetransform.microsoft.gradient(startColorstr='#ffffff',endColorstr='#e6e6e6',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:active,.btn.active,.btn.disabled,.btn[disabled]{background-color:#e6e6e6;*background-color:#d9d9d9}.btn:active,.btn.active{background-color:#ccc \9}.btn:first-child{*margin-left:0}.btn:hover{color:#333;text-decoration:none;background-color:#e6e6e6;*background-color:#d9d9d9;background-position:0 -15px;-webkit-transition:background-position .1s linear;-moz-transition:background-position .1s linear;-ms-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-color:#e6e6e6;background-color:#d9d9d9 \9;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-color:#e6e6e6;background-image:none;opacity:.65;filter:alpha(opacity=65);-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.btn-large{padding:9px 14px;font-size:15px;line-height:normal;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px}.btn-large [class^="icon-"]{margin-top:1px}.btn-small{padding:5px 9px;font-size:11px;line-height:16px}.btn-small [class^="icon-"]{margin-top:-1px}.btn-mini{padding:2px 6px;font-size:11px;line-height:14px}.btn-primary,.btn-primary:hover,.btn-warning,.btn-warning:hover,.btn-danger,.btn-danger:hover,.btn-success,.btn-success:hover,.btn-info,.btn-info:hover,.btn-inverse,.btn-inverse:hover{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25)}.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{border-color:#ccc;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25)}.btn-primary{background-color:#0074cc;*background-color:#05c;background-image:-ms-linear-gradient(top,#08c,#05c);background-image:-webkit-gradient(linear,0 0,0 100%,from(#08c),to(#05c));background-image:-webkit-linear-gradient(top,#08c,#05c);background-image:-o-linear-gradient(top,#08c,#05c);background-image:-moz-linear-gradient(top,#08c,#05c);background-image:linear-gradient(top,#08c,#05c);background-repeat:repeat-x;border-color:#05c #05c #003580;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='#0088cc',endColorstr='#0055cc',GradientType=0);filter:progid:dximagetransform.microsoft.gradient(enabled=false)}.btn-primary:hover,.btn-primary:active,.btn-primary.active,.btn-primary.disabled,.btn-primary[disabled]{background-color:#05c;*background-color:#004ab3}.btn-primary:active,.btn-primary.active{background-color:#004099 \9}.btn-warning{background-color:#faa732;*background-color:#f89406;background-image:-ms-linear-gradient(top,#fbb450,#f89406);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fbb450),to(#f89406));background-image:-webkit-linear-gradient(top,#fbb450,#f89406);background-image:-o-linear-gradient(top,#fbb450,#f89406);background-image:-moz-linear-gradient(top,#fbb450,#f89406);background-image:linear-gradient(top,#fbb450,#f89406);background-repeat:repeat-x;border-color:#f89406 #f89406 #ad6704;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='#fbb450',endColorstr='#f89406',GradientType=0);filter:progid:dximagetransform.microsoft.gradient(enabled=false)}.btn-warning:hover,.btn-warning:active,.btn-warning.active,.btn-warning.disabled,.btn-warning[disabled]{background-color:#f89406;*background-color:#df8505}.btn-warning:active,.btn-warning.active{background-color:#c67605 \9}.btn-danger{background-color:#da4f49;*background-color:#bd362f;background-image:-ms-linear-gradient(top,#ee5f5b,#bd362f);background-image:-webkit-gradient(linear,0 0,0 100%,from(#ee5f5b),to(#bd362f));background-image:-webkit-linear-gradient(top,#ee5f5b,#bd362f);background-image:-o-linear-gradient(top,#ee5f5b,#bd362f);background-image:-moz-linear-gradient(top,#ee5f5b,#bd362f);background-image:linear-gradient(top,#ee5f5b,#bd362f);background-repeat:repeat-x;border-color:#bd362f #bd362f #802420;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='#ee5f5b',endColorstr='#bd362f',GradientType=0);filter:progid:dximagetransform.microsoft.gradient(enabled=false)}.btn-danger:hover,.btn-danger:active,.btn-danger.active,.btn-danger.disabled,.btn-danger[disabled]{background-color:#bd362f;*background-color:#a9302a}.btn-danger:active,.btn-danger.active{background-color:#942a25 \9}.btn-success{background-color:#5bb75b;*background-color:#51a351;background-image:-ms-linear-gradient(top,#62c462,#51a351);background-image:-webkit-gradient(linear,0 0,0 100%,from(#62c462),to(#51a351));background-image:-webkit-linear-gradient(top,#62c462,#51a351);background-image:-o-linear-gradient(top,#62c462,#51a351);background-image:-moz-linear-gradient(top,#62c462,#51a351);background-image:linear-gradient(top,#62c462,#51a351);background-repeat:repeat-x;border-color:#51a351 #51a351 #387038;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='#62c462',endColorstr='#51a351',GradientType=0);filter:progid:dximagetransform.microsoft.gradient(enabled=false)}.btn-success:hover,.btn-success:active,.btn-success.active,.btn-success.disabled,.btn-success[disabled]{background-color:#51a351;*background-color:#499249}.btn-success:active,.btn-success.active{background-color:#408140 \9}.btn-info{background-color:#49afcd;*background-color:#2f96b4;background-image:-ms-linear-gradient(top,#5bc0de,#2f96b4);background-image:-webkit-gradient(linear,0 0,0 100%,from(#5bc0de),to(#2f96b4));background-image:-webkit-linear-gradient(top,#5bc0de,#2f96b4);background-image:-o-linear-gradient(top,#5bc0de,#2f96b4);background-image:-moz-linear-gradient(top,#5bc0de,#2f96b4);background-image:linear-gradient(top,#5bc0de,#2f96b4);background-repeat:repeat-x;border-color:#2f96b4 #2f96b4 #1f6377;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='#5bc0de',endColorstr='#2f96b4',GradientType=0);filter:progid:dximagetransform.microsoft.gradient(enabled=false)}.btn-info:hover,.btn-info:active,.btn-info.active,.btn-info.disabled,.btn-info[disabled]{background-color:#2f96b4;*background-color:#2a85a0}.btn-info:active,.btn-info.active{background-color:#24748c \9}.btn-inverse{background-color:#414141;*background-color:#222;background-image:-ms-linear-gradient(top,#555,#222);background-image:-webkit-gradient(linear,0 0,0 100%,from(#555),to(#222));background-image:-webkit-linear-gradient(top,#555,#222);background-image:-o-linear-gradient(top,#555,#222);background-image:-moz-linear-gradient(top,#555,#222);background-image:linear-gradient(top,#555,#222);background-repeat:repeat-x;border-color:#222 #222 #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='#555555',endColorstr='#222222',GradientType=0);filter:progid:dximagetransform.microsoft.gradient(enabled=false)}.btn-inverse:hover,.btn-inverse:active,.btn-inverse.active,.btn-inverse.disabled,.btn-inverse[disabled]{background-color:#222;*background-color:#151515}.btn-inverse:active,.btn-inverse.active{background-color:#080808 \9}button.btn,input[type="submit"].btn{*padding-top:2px;*padding-bottom:2px}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-group{position:relative;*margin-left:.3em;*zoom:1}.btn-group:before,.btn-group:after{display:table;content:""}.btn-group:after{clear:both}.btn-group:first-child{*margin-left:0}.btn-group+.btn-group{margin-left:5px}.btn-toolbar{margin-top:9px;margin-bottom:9px}.btn-toolbar .btn-group{display:inline-block;*display:inline;*zoom:1}.btn-group>.btn{position:relative;float:left;margin-left:-1px;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.btn-group>.btn:first-child{margin-left:0;-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px;-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-bottomleft:4px;-moz-border-radius-topleft:4px}.btn-group>.btn:last-child,.btn-group>.dropdown-toggle{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px;-moz-border-radius-topright:4px;-moz-border-radius-bottomright:4px}.btn-group>.btn.large:first-child{margin-left:0;-webkit-border-bottom-left-radius:6px;border-bottom-left-radius:6px;-webkit-border-top-left-radius:6px;border-top-left-radius:6px;-moz-border-radius-bottomleft:6px;-moz-border-radius-topleft:6px}.btn-group>.btn.large:last-child,.btn-group>.large.dropdown-toggle{-webkit-border-top-right-radius:6px;border-top-right-radius:6px;-webkit-border-bottom-right-radius:6px;border-bottom-right-radius:6px;-moz-border-radius-topright:6px;-moz-border-radius-bottomright:6px}.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>.dropdown-toggle{*padding-top:4px;padding-right:8px;*padding-bottom:4px;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-right:5px;padding-left:5px}.btn-group>.btn-small.dropdown-toggle{*padding-top:4px;*padding-bottom:4px}.btn-group>.btn-large.dropdown-toggle{padding-right:12px;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:#e6e6e6}.btn-group.open .btn-primary.dropdown-toggle{background-color:#05c}.btn-group.open .btn-warning.dropdown-toggle{background-color:#f89406}.btn-group.open .btn-danger.dropdown-toggle{background-color:#bd362f}.btn-group.open .btn-success.dropdown-toggle{background-color:#51a351}.btn-group.open .btn-info.dropdown-toggle{background-color:#2f96b4}.btn-group.open .btn-inverse.dropdown-toggle{background-color:#222}.btn .caret{margin-top:7px;margin-left:0}.btn:hover .caret,.open.btn-group .caret{opacity:1;filter:alpha(opacity=100)}.btn-mini .caret{margin-top:5px}.btn-small .caret{margin-top:6px}.btn-large .caret{margin-top:6px;border-top-width:5px;border-right-width:5px;border-left-width:5px}.dropup .btn-large .caret{border-top:0;border-bottom:5px solid #000}.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;opacity:.75;filter:alpha(opacity=75)}.alert{padding:8px 35px 8px 14px;margin-bottom:18px;color:#c09853;text-shadow:0 1px 0 rgba(255,255,255,0.5);background-color:#fcf8e3;border:1px solid #fbeed5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.alert-heading{color:inherit}.alert .close{position:relative;top:-2px;right:-21px;line-height:18px}.alert-success{color:#468847;background-color:#dff0d8;border-color:#d6e9c6}.alert-danger,.alert-error{color:#b94a48;background-color:#f2dede;border-color:#eed3d7}.alert-info{color:#3a87ad;background-color:#d9edf7;border-color:#bce8f1}.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:18px;margin-left:0;list-style:none}.nav>li>a{display:block}.nav>li>a:hover{text-decoration:none;background-color:#eee}.nav>.pull-right{float:right}.nav .nav-header{display:block;padding:3px 15px;font-size:11px;font-weight:bold;line-height:18px;color:#999;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{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.2);background-color:#08c}.nav-list [class^="icon-"]{margin-right:2px}.nav-list .divider{*width:100%;height:1px;margin:8px 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;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:18px;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{border-color:#eee #eee #ddd}.nav-tabs>.active>a,.nav-tabs>.active>a:hover{color:#555;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{color:#fff;background-color:#08c}.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-radius:4px 4px 0 0;-moz-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0}.nav-tabs.nav-stacked>li:last-child>a{-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px}.nav-tabs.nav-stacked>li>a:hover{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 5px 5px;-moz-border-radius:0 0 5px 5px;border-radius:0 0 5px 5px}.nav-pills .dropdown-menu{-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.nav-tabs .dropdown-toggle .caret,.nav-pills .dropdown-toggle .caret{margin-top:6px;border-top-color:#08c;border-bottom-color:#08c}.nav-tabs .dropdown-toggle:hover .caret,.nav-pills .dropdown-toggle:hover .caret{border-top-color:#005580;border-bottom-color:#005580}.nav-tabs .active .dropdown-toggle .caret,.nav-pills .active .dropdown-toggle .caret{border-top-color:#333;border-bottom-color:#333}.nav>.dropdown.active>a:hover{color:#000;cursor:pointer}.nav-tabs .open .dropdown-toggle,.nav-pills .open .dropdown-toggle,.nav>li.dropdown.open.active>a:hover{color:#fff;background-color:#999;border-color:#999}.nav li.dropdown.open .caret,.nav li.dropdown.open.active .caret,.nav li.dropdown.open a:hover .caret{border-top-color:#fff;border-bottom-color:#fff;opacity:1;filter:alpha(opacity=100)}.tabs-stacked .open>a:hover{border-color:#999}.tabbable{*zoom:1}.tabbable:before,.tabbable:after{display:table;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{border-top-color:#ddd;border-bottom-color:transparent}.tabs-below>.nav-tabs>.active>a,.tabs-below>.nav-tabs>.active>a:hover{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{border-color:#eee #ddd #eee #eee}.tabs-left>.nav-tabs .active>a,.tabs-left>.nav-tabs .active>a:hover{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{border-color:#eee #eee #eee #ddd}.tabs-right>.nav-tabs .active>a,.tabs-right>.nav-tabs .active>a:hover{border-color:#ddd #ddd #ddd transparent;*border-left-color:#fff}.navbar{*position:relative;*z-index:2;margin-bottom:18px;overflow:visible}.navbar-inner{min-height:40px;padding-right:20px;padding-left:20px;background-color:#2c2c2c;background-image:-moz-linear-gradient(top,#333,#222);background-image:-ms-linear-gradient(top,#333,#222);background-image:-webkit-gradient(linear,0 0,0 100%,from(#333),to(#222));background-image:-webkit-linear-gradient(top,#333,#222);background-image:-o-linear-gradient(top,#333,#222);background-image:linear-gradient(top,#333,#222);background-repeat:repeat-x;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;filter:progid:dximagetransform.microsoft.gradient(startColorstr='#333333',endColorstr='#222222',GradientType=0);-webkit-box-shadow:0 1px 3px rgba(0,0,0,0.25),inset 0 -1px 0 rgba(0,0,0,0.1);-moz-box-shadow:0 1px 3px rgba(0,0,0,0.25),inset 0 -1px 0 rgba(0,0,0,0.1);box-shadow:0 1px 3px rgba(0,0,0,0.25),inset 0 -1px 0 rgba(0,0,0,0.1)}.navbar .container{width:auto}.nav-collapse.collapse{height:auto}.navbar{color:#999}.navbar .brand:hover{text-decoration:none}.navbar .brand{display:block;float:left;padding:8px 20px 12px;margin-left:-20px;font-size:20px;font-weight:200;line-height:1;color:#999}.navbar .navbar-text{margin-bottom:0;line-height:40px}.navbar .navbar-link{color:#999}.navbar .navbar-link:hover{color:#fff}.navbar .btn,.navbar .btn-group{margin-top:5px}.navbar .btn-group .btn{margin:0}.navbar-form{margin-bottom:0;*zoom:1}.navbar-form:before,.navbar-form:after{display:table;content:""}.navbar-form:after{clear:both}.navbar-form input,.navbar-form select,.navbar-form .radio,.navbar-form .checkbox{margin-top:5px}.navbar-form input,.navbar-form select{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:6px;white-space:nowrap}.navbar-form .input-append input,.navbar-form .input-prepend input{margin-top:0}.navbar-search{position:relative;float:left;margin-top:6px;margin-bottom:0}.navbar-search .search-query{padding:4px 9px;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:13px;font-weight:normal;line-height:1;color:#fff;background-color:#626262;border:1px solid #151515;-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;-ms-transition:none;-o-transition:none;transition:none}.navbar-search .search-query:-moz-placeholder{color:#ccc}.navbar-search .search-query:-ms-input-placeholder{color:#ccc}.navbar-search .search-query::-webkit-input-placeholder{color:#ccc}.navbar-search .search-query:focus,.navbar-search .search-query.focused{padding:5px 10px;color:#333;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-fixed-top,.navbar-fixed-bottom{position:fixed;right:0;left:0;z-index:1030;margin-bottom: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-fixed-top .container,.navbar-fixed-bottom .container{width:940px}.navbar-fixed-top{top:0}.navbar-fixed-bottom{bottom:0}.navbar .nav{position:relative;left:0;display:block;float:left;margin:0 10px 0 0}.navbar .nav.pull-right{float:right}.navbar .nav>li{display:block;float:left}.navbar .nav>li>a{float:none;padding:9px 10px 11px;line-height:19px;color:#999;text-decoration:none;text-shadow:0 -1px 0 rgba(0,0,0,0.25)}.navbar .btn{display:inline-block;padding:4px 10px 4px;margin:5px 5px 6px;line-height:18px}.navbar .btn-group{padding:5px 5px 6px;margin:0}.navbar .nav>li>a:hover{color:#fff;text-decoration:none;background-color:transparent}.navbar .nav .active>a,.navbar .nav .active>a:hover{color:#fff;text-decoration:none;background-color:#222}.navbar .divider-vertical{width:1px;height:40px;margin:0 9px;overflow:hidden;background-color:#222;border-right:1px solid #333}.navbar .nav.pull-right{margin-right:0;margin-left:10px}.navbar .btn-navbar{display:none;float:right;padding:7px 10px;margin-right:5px;margin-left:5px;background-color:#2c2c2c;*background-color:#222;background-image:-ms-linear-gradient(top,#333,#222);background-image:-webkit-gradient(linear,0 0,0 100%,from(#333),to(#222));background-image:-webkit-linear-gradient(top,#333,#222);background-image:-o-linear-gradient(top,#333,#222);background-image:linear-gradient(top,#333,#222);background-image:-moz-linear-gradient(top,#333,#222);background-repeat:repeat-x;border-color:#222 #222 #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='#333333',endColorstr='#222222',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:active,.navbar .btn-navbar.active,.navbar .btn-navbar.disabled,.navbar .btn-navbar[disabled]{background-color:#222;*background-color:#151515}.navbar .btn-navbar:active,.navbar .btn-navbar.active{background-color:#080808 \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 .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 .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 .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 .dropdown-menu:after{top:auto;bottom:-6px;border-top:6px solid #fff;border-bottom:0}.navbar .nav li.dropdown .dropdown-toggle .caret,.navbar .nav li.dropdown.open .caret{border-top-color:#fff;border-bottom-color:#fff}.navbar .nav li.dropdown.active .caret{opacity:1;filter:alpha(opacity=100)}.navbar .nav li.dropdown.open>.dropdown-toggle,.navbar .nav li.dropdown.active>.dropdown-toggle,.navbar .nav li.dropdown.open.active>.dropdown-toggle{background-color:transparent}.navbar .nav li.dropdown.active>.dropdown-toggle:hover{color:#fff}.navbar .pull-right .dropdown-menu,.navbar .dropdown-menu.pull-right{right:0;left:auto}.navbar .pull-right .dropdown-menu:before,.navbar .dropdown-menu.pull-right:before{right:12px;left:auto}.navbar .pull-right .dropdown-menu:after,.navbar .dropdown-menu.pull-right:after{right:13px;left:auto}.breadcrumb{padding:7px 14px;margin:0 0 18px;list-style:none;background-color:#fbfbfb;background-image:-moz-linear-gradient(top,#fff,#f5f5f5);background-image:-ms-linear-gradient(top,#fff,#f5f5f5);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fff),to(#f5f5f5));background-image:-webkit-linear-gradient(top,#fff,#f5f5f5);background-image:-o-linear-gradient(top,#fff,#f5f5f5);background-image:linear-gradient(top,#fff,#f5f5f5);background-repeat:repeat-x;border:1px solid #ddd;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;filter:progid:dximagetransform.microsoft.gradient(startColorstr='#ffffff',endColorstr='#f5f5f5',GradientType=0);-webkit-box-shadow:inset 0 1px 0 #fff;-moz-box-shadow:inset 0 1px 0 #fff;box-shadow:inset 0 1px 0 #fff}.breadcrumb li{display:inline-block;*display:inline;text-shadow:0 1px 0 #fff;*zoom:1}.breadcrumb .divider{padding:0 5px;color:#999}.breadcrumb .active a{color:#333}.pagination{height:36px;margin:18px 0}.pagination ul{display:inline-block;*display:inline;margin-bottom:0;margin-left:0;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;*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 li{display:inline}.pagination a{float:left;padding:0 14px;line-height:34px;text-decoration:none;border:1px solid #ddd;border-left-width:0}.pagination a:hover,.pagination .active a{background-color:#f5f5f5}.pagination .active a{color:#999;cursor:default}.pagination .disabled span,.pagination .disabled a,.pagination .disabled a:hover{color:#999;cursor:default;background-color:transparent}.pagination li:first-child a{border-left-width:1px;-webkit-border-radius:3px 0 0 3px;-moz-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px}.pagination li:last-child a{-webkit-border-radius:0 3px 3px 0;-moz-border-radius:0 3px 3px 0;border-radius:0 3px 3px 0}.pagination-centered{text-align:center}.pagination-right{text-align:right}.pager{margin-bottom:18px;margin-left:0;text-align:center;list-style:none;*zoom:1}.pager:before,.pager:after{display:table;content:""}.pager:after{clear:both}.pager li{display:inline}.pager a{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 a:hover{text-decoration:none;background-color:#f5f5f5}.pager .next a{float:right}.pager .previous a{float:left}.pager .disabled a,.pager .disabled a:hover{color:#999;cursor:default;background-color:#fff}.modal-open .dropdown-menu{z-index:2050}.modal-open .dropdown.open{*z-index:2050}.modal-open .popover{z-index:2060}.modal-open .tooltip{z-index:2070}.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:50%;left:50%;z-index:1050;width:560px;margin:-250px 0 0 -280px;overflow:auto;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;-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;-ms-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:50%}.modal-header{padding:9px 15px;border-bottom:1px solid #eee}.modal-header .close{margin-top:2px}.modal-body{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;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}.tooltip{position:absolute;z-index:1020;display:block;padding:5px;font-size:11px;opacity:0;filter:alpha(opacity=0);visibility:visible}.tooltip.in{opacity:.8;filter:alpha(opacity=80)}.tooltip.top{margin-top:-2px}.tooltip.right{margin-left:2px}.tooltip.bottom{margin-top:2px}.tooltip.left{margin-left:-2px}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-top:5px solid #000;border-right:5px solid transparent;border-left:5px solid transparent}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-top:5px solid transparent;border-bottom:5px solid transparent;border-left:5px solid #000}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-right:5px solid transparent;border-bottom:5px solid #000;border-left:5px solid transparent}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-top:5px solid transparent;border-right:5px solid #000;border-bottom:5px solid transparent}.tooltip-inner{max-width:200px;padding:3px 8px;color:#fff;text-align:center;text-decoration:none;background-color:#000;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0}.popover{position:absolute;top:0;left:0;z-index:1010;display:none;padding:5px}.popover.top{margin-top:-5px}.popover.right{margin-left:5px}.popover.bottom{margin-top:5px}.popover.left{margin-left:-5px}.popover.top .arrow{bottom:0;left:50%;margin-left:-5px;border-top:5px solid #000;border-right:5px solid transparent;border-left:5px solid transparent}.popover.right .arrow{top:50%;left:0;margin-top:-5px;border-top:5px solid transparent;border-right:5px solid #000;border-bottom:5px solid transparent}.popover.bottom .arrow{top:0;left:50%;margin-left:-5px;border-right:5px solid transparent;border-bottom:5px solid #000;border-left:5px solid transparent}.popover.left .arrow{top:50%;right:0;margin-top:-5px;border-top:5px solid transparent;border-bottom:5px solid transparent;border-left:5px solid #000}.popover .arrow{position:absolute;width:0;height:0}.popover-inner{width:280px;padding:3px;overflow:hidden;background:#000;background:rgba(0,0,0,0.8);-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-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)}.popover-title{padding:9px 15px;line-height:1;background-color:#f5f5f5;border-bottom:1px solid #eee;-webkit-border-radius:3px 3px 0 0;-moz-border-radius:3px 3px 0 0;border-radius:3px 3px 0 0}.popover-content{padding:14px;background-color:#fff;-webkit-border-radius:0 0 3px 3px;-moz-border-radius:0 0 3px 3px;border-radius:0 0 3px 3px;-webkit-background-clip:padding-box;-moz-background-clip:padding-box;background-clip:padding-box}.popover-content p,.popover-content ul,.popover-content ol{margin-bottom:0}.thumbnails{margin-left:-20px;list-style:none;*zoom:1}.thumbnails:before,.thumbnails:after{display:table;content:""}.thumbnails:after{clear:both}.row-fluid .thumbnails{margin-left:0}.thumbnails>li{float:left;margin-bottom:18px;margin-left:20px}.thumbnail{display:block;padding:4px;line-height:1;border:1px solid #ddd;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:0 1px 1px rgba(0,0,0,0.075);box-shadow:0 1px 1px rgba(0,0,0,0.075)}a.thumbnail:hover{border-color:#08c;-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}.label,.badge{font-size:10.998px;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:#999}.label{padding:1px 4px 2px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.badge{padding:1px 9px 2px;-webkit-border-radius:9px;-moz-border-radius:9px;border-radius:9px}a.label:hover,a.badge:hover{color:#fff;text-decoration:none;cursor:pointer}.label-important,.badge-important{background-color:#b94a48}.label-important[href],.badge-important[href]{background-color:#953b39}.label-warning,.badge-warning{background-color:#f89406}.label-warning[href],.badge-warning[href]{background-color:#c67605}.label-success,.badge-success{background-color:#468847}.label-success[href],.badge-success[href]{background-color:#356635}.label-info,.badge-info{background-color:#3a87ad}.label-info[href],.badge-info[href]{background-color:#2d6987}.label-inverse,.badge-inverse{background-color:#333}.label-inverse[href],.badge-inverse[href]{background-color:#1a1a1a}@-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:18px;margin-bottom:18px;overflow:hidden;background-color:#f7f7f7;background-image:-moz-linear-gradient(top,#f5f5f5,#f9f9f9);background-image:-ms-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(top,#f5f5f5,#f9f9f9);background-repeat:repeat-x;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;filter:progid:dximagetransform.microsoft.gradient(startColorstr='#f5f5f5',endColorstr='#f9f9f9',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{width:0;height:18px;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(top,#149bdf,#0480be);background-image:-ms-linear-gradient(top,#149bdf,#0480be);background-repeat:repeat-x;filter:progid:dximagetransform.microsoft.gradient(startColorstr='#149bdf',endColorstr='#0480be',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;-ms-box-sizing:border-box;box-sizing:border-box;-webkit-transition:width .6s ease;-moz-transition:width .6s ease;-ms-transition:width .6s ease;-o-transition:width .6s ease;transition:width .6s ease}.progress-striped .bar{background-color:#149bdf;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:-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:-ms-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:-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: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{background-color:#dd514c;background-image:-moz-linear-gradient(top,#ee5f5b,#c43c35);background-image:-ms-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(top,#ee5f5b,#c43c35);background-repeat:repeat-x;filter:progid:dximagetransform.microsoft.gradient(startColorstr='#ee5f5b',endColorstr='#c43c35',GradientType=0)}.progress-danger.progress-striped .bar{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:-ms-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{background-color:#5eb95e;background-image:-moz-linear-gradient(top,#62c462,#57a957);background-image:-ms-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(top,#62c462,#57a957);background-repeat:repeat-x;filter:progid:dximagetransform.microsoft.gradient(startColorstr='#62c462',endColorstr='#57a957',GradientType=0)}.progress-success.progress-striped .bar{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:-ms-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{background-color:#4bb1cf;background-image:-moz-linear-gradient(top,#5bc0de,#339bb9);background-image:-ms-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(top,#5bc0de,#339bb9);background-repeat:repeat-x;filter:progid:dximagetransform.microsoft.gradient(startColorstr='#5bc0de',endColorstr='#339bb9',GradientType=0)}.progress-info.progress-striped .bar{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:-ms-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{background-color:#faa732;background-image:-moz-linear-gradient(top,#fbb450,#f89406);background-image:-ms-linear-gradient(top,#fbb450,#f89406);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fbb450),to(#f89406));background-image:-webkit-linear-gradient(top,#fbb450,#f89406);background-image:-o-linear-gradient(top,#fbb450,#f89406);background-image:linear-gradient(top,#fbb450,#f89406);background-repeat:repeat-x;filter:progid:dximagetransform.microsoft.gradient(startColorstr='#fbb450',endColorstr='#f89406',GradientType=0)}.progress-warning.progress-striped .bar{background-color:#fbb450;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:-ms-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:18px}.accordion-group{margin-bottom:2px;border:1px solid #e5e5e5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.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:18px;line-height:1}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel .item{position:relative;display:none;-webkit-transition:.6s ease-in-out left;-moz-transition:.6s ease-in-out left;-ms-transition:.6s ease-in-out left;-o-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel .item>img{display:block;line-height:1}.carousel .active,.carousel .next,.carousel .prev{display:block}.carousel .active{left:0}.carousel .next,.carousel .prev{position:absolute;top:0;width:100%}.carousel .next{left:100%}.carousel .prev{left:-100%}.carousel .next.left,.carousel .prev.right{left:0}.carousel .active.left{left:-100%}.carousel .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:#222;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{color:#fff;text-decoration:none;opacity:.9;filter:alpha(opacity=90)}.carousel-caption{position:absolute;right:0;bottom:0;left:0;padding:10px 15px 5px;background:#333;background:rgba(0,0,0,0.75)}.carousel-caption h4,.carousel-caption p{color:#fff}.hero-unit{padding:60px;margin-bottom:30px;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 p{font-size:18px;font-weight:200;line-height:27px;color:inherit}.pull-right{float:right}.pull-left{float:left}.hide{display:none}.show{display:block}.invisible{visibility:hidden} + */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{max-width:100%;vertical-align:middle;border:0;-ms-interpolation-mode:bicubic}#map_canvas 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,input[type="button"],input[type="reset"],input[type="submit"]{cursor:pointer;-webkit-appearance:button}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}.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;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:28px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;box-sizing:border-box}body{margin:0;font-family:'Open Sans',"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:18px;color:#222;background-color:#fcfbfd}a{color:#4380d3;text-decoration:none}a:hover{color:#c00;text-decoration:underline}.row{margin-left:-20px;*zoom:1}.row:before,.row:after{display:table;content:""}.row:after{clear:both}[class*="span"]{float:left;margin-left:20px}.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;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:28px;margin-left:2.127659574%;*margin-left:2.0744680846382977%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .span12{width:99.99999998999999%;*width:99.94680850063828%}.row-fluid .span11{width:91.489361693%;*width:91.4361702036383%}.row-fluid .span10{width:82.97872339599999%;*width:82.92553190663828%}.row-fluid .span9{width:74.468085099%;*width:74.4148936096383%}.row-fluid .span8{width:65.95744680199999%;*width:65.90425531263828%}.row-fluid .span7{width:57.446808505%;*width:57.3936170156383%}.row-fluid .span6{width:48.93617020799999%;*width:48.88297871863829%}.row-fluid .span5{width:40.425531911%;*width:40.3723404216383%}.row-fluid .span4{width:31.914893614%;*width:31.8617021246383%}.row-fluid .span3{width:23.404255317%;*width:23.3510638276383%}.row-fluid .span2{width:14.89361702%;*width:14.8404255306383%}.row-fluid .span1{width:6.382978723%;*width:6.329787233638298%}.container{margin-right:auto;margin-left:auto;*zoom:1}.container:before,.container:after{display:table;content:""}.container:after{clear:both}.container-fluid{padding-right:20px;padding-left:20px;*zoom:1}.container-fluid:before,.container-fluid:after{display:table;content:""}.container-fluid:after{clear:both}p{margin:0 0 9px}p small{font-size:12px;color:#999}.lead{margin-bottom:18px;font-size:20px;font-weight:200;line-height:27px}h1,h2,h3,h4,h5,h6{margin:0;font-family:inherit;font-weight:bold;color:inherit;text-rendering:optimizelegibility}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small{font-weight:normal;color:#999}h1{font-size:30px;line-height:36px}h1 small{font-size:18px}h2{font-size:24px;line-height:36px}h2 small{font-size:18px}h3{font-size:18px;line-height:27px}h3 small{font-size:14px}h4,h5,h6{line-height:18px}h4{font-size:14px}h4 small{font-size:12px}h5{font-size:12px}h6{font-size:11px;color:#999;text-transform:uppercase}.page-header{padding-bottom:17px;margin:18px 0;border-bottom:1px solid #eee}.page-header h1{line-height:1}ul,ol{padding:0;margin:0 0 9px 25px}ul ul,ul ol,ol ol,ol ul{margin-bottom:0}ul{list-style:disc}ol{list-style:decimal}li{line-height:18px}ul.unstyled,ol.unstyled{margin-left:0;list-style:none}dl{margin-bottom:18px}dt,dd{line-height:18px}dt{font-weight:bold;line-height:17px}dd{margin-left:9px}.dl-horizontal dt{float:left;width:120px;overflow:hidden;clear:left;text-align:right;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:130px}hr{margin:18px 0;border:0;border-top:1px solid #eee;border-bottom:1px solid #fff}strong{font-weight:bold}em{font-style:italic}.muted{color:#999}abbr[title]{cursor:help;border-bottom:1px dotted #999}abbr.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:0 0 0 15px;margin:0 0 18px;border-left:5px solid #eee}blockquote p{margin-bottom:0;font-size:16px;font-weight:300;line-height:22.5px}blockquote small{display:block;line-height:18px;color:#999}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}q:before,q:after,blockquote:before,blockquote:after{content:""}address{display:block;margin-bottom:18px;font-style:normal;line-height:18px}small{font-size:100%}cite{font-style:normal}code,pre{padding:0 3px 2px;font-family:Menlo,Monaco,Consolas,"Courier New",monospace;font-size:13px;color:#333;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}code{padding:2px 4px;color:#d14;background-color:#f7f7f9;border:1px solid #e1e1e8}pre{display:block;padding:8.5px;margin:0 0 9px;font-size:12.950000000000001px;line-height:18px;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:4px;-moz-border-radius:4px;border-radius:4px}pre.prettyprint{margin-bottom:18px}pre code{padding:0;color:inherit;background-color:transparent;border:0}.pre-scrollable{max-height:340px;overflow-y:scroll}form{margin:0 0 18px}fieldset{padding:0;margin:0;border:0}legend{display:block;width:100%;padding:0;margin-bottom:27px;font-size:21px;line-height:36px;color:#333;border:0;border-bottom:1px solid #e5e5e5}legend small{font-size:13.5px;color:#999}label,input,button,select,textarea{font-size:14px;font-weight:normal;line-height:18px}input,button,select,textarea{font-family:'Open Sans',"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:18px;padding:4px;margin-bottom:9px;font-size:14px;line-height:18px;color:#888}input,textarea{width:210px}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 #ccc;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;-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;-ms-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:3px 0;*margin-top:0;line-height:normal;cursor:pointer}input[type="submit"],input[type="reset"],input[type="button"],input[type="radio"],input[type="checkbox"]{width:auto}.uneditable-textarea{width:auto;height:auto}select,input[type="file"]{height:28px;*margin-top:4px;line-height:28px}select{width:220px;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}.radio,.checkbox{min-height:18px;padding-left:18px}.radio input[type="radio"],.checkbox input[type="checkbox"]{float:left;margin-left:-18px}.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-prepend [class*="span"],.row-fluid .input-append [class*="span"]{display:inline-block}input,textarea,.uneditable-input{margin-left:0}input.span12,textarea.span12,.uneditable-input.span12{width:930px}input.span11,textarea.span11,.uneditable-input.span11{width:850px}input.span10,textarea.span10,.uneditable-input.span10{width:770px}input.span9,textarea.span9,.uneditable-input.span9{width:690px}input.span8,textarea.span8,.uneditable-input.span8{width:610px}input.span7,textarea.span7,.uneditable-input.span7{width:530px}input.span6,textarea.span6,.uneditable-input.span6{width:450px}input.span5,textarea.span5,.uneditable-input.span5{width:370px}input.span4,textarea.span4,.uneditable-input.span4{width:290px}input.span3,textarea.span3,.uneditable-input.span3{width:210px}input.span2,textarea.span2,.uneditable-input.span2{width:130px}input.span1,textarea.span1,.uneditable-input.span1{width:50px}input[disabled],select[disabled],textarea[disabled],input[readonly],select[readonly],textarea[readonly]{cursor:not-allowed;background-color:#eee;border-color:#ddd}input[type="radio"][disabled],input[type="checkbox"][disabled],input[type="radio"][readonly],input[type="checkbox"][readonly]{background-color:transparent}.control-group.warning>label,.control-group.warning .help-block,.control-group.warning .help-inline{color:#c09853}.control-group.warning .checkbox,.control-group.warning .radio,.control-group.warning input,.control-group.warning select,.control-group.warning textarea{color:#c09853;border-color:#c09853}.control-group.warning .checkbox:focus,.control-group.warning .radio:focus,.control-group.warning input:focus,.control-group.warning select:focus,.control-group.warning textarea:focus{border-color:#a47e3c;-webkit-box-shadow:0 0 6px #dbc59e;-moz-box-shadow:0 0 6px #dbc59e;box-shadow:0 0 6px #dbc59e}.control-group.warning .input-prepend .add-on,.control-group.warning .input-append .add-on{color:#c09853;background-color:#fcf8e3;border-color:#c09853}.control-group.error>label,.control-group.error .help-block,.control-group.error .help-inline{color:#b94a48}.control-group.error .checkbox,.control-group.error .radio,.control-group.error input,.control-group.error select,.control-group.error textarea{color:#b94a48;border-color:#b94a48}.control-group.error .checkbox:focus,.control-group.error .radio:focus,.control-group.error input:focus,.control-group.error select:focus,.control-group.error textarea:focus{border-color:#953b39;-webkit-box-shadow:0 0 6px #d59392;-moz-box-shadow:0 0 6px #d59392;box-shadow:0 0 6px #d59392}.control-group.error .input-prepend .add-on,.control-group.error .input-append .add-on{color:#b94a48;background-color:#f2dede;border-color:#b94a48}.control-group.success>label,.control-group.success .help-block,.control-group.success .help-inline{color:#468847}.control-group.success .checkbox,.control-group.success .radio,.control-group.success input,.control-group.success select,.control-group.success textarea{color:#468847;border-color:#468847}.control-group.success .checkbox:focus,.control-group.success .radio:focus,.control-group.success input:focus,.control-group.success select:focus,.control-group.success textarea:focus{border-color:#356635;-webkit-box-shadow:0 0 6px #7aba7b;-moz-box-shadow:0 0 6px #7aba7b;box-shadow:0 0 6px #7aba7b}.control-group.success .input-prepend .add-on,.control-group.success .input-append .add-on{color:#468847;background-color:#dff0d8;border-color:#468847}input:focus:required:invalid,textarea:focus:required:invalid,select:focus:required:invalid{color:#b94a48;border-color:#ee5f5b}input:focus:required:invalid:focus,textarea:focus:required:invalid:focus,select:focus:required: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:17px 20px 18px;margin-top:18px;margin-bottom:18px;background-color:#eee;border-top:1px solid #e5e5e5;*zoom:1}.form-actions:before,.form-actions:after{display:table;content:""}.form-actions:after{clear:both}.uneditable-input{overflow:hidden;white-space:nowrap;cursor:not-allowed;background-color:#fff;border-color:#eee;-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)}:-moz-placeholder{color:#999}:-ms-input-placeholder{color:#999}::-webkit-input-placeholder{color:#999}.help-block,.help-inline{color:#888}.help-block{display:block;margin-bottom:9px}.help-inline{display:inline-block;*display:inline;padding-left:5px;vertical-align:middle;*zoom:1}.input-prepend,.input-append{margin-bottom:5px}.input-prepend input,.input-append input,.input-prepend select,.input-append select,.input-prepend .uneditable-input,.input-append .uneditable-input{position:relative;margin-bottom:0;*margin-left:0;vertical-align:middle;-webkit-border-radius:0 3px 3px 0;-moz-border-radius:0 3px 3px 0;border-radius:0 3px 3px 0}.input-prepend input:focus,.input-append input:focus,.input-prepend select:focus,.input-append select:focus,.input-prepend .uneditable-input:focus,.input-append .uneditable-input:focus{z-index:2}.input-prepend .uneditable-input,.input-append .uneditable-input{border-left-color:#ccc}.input-prepend .add-on,.input-append .add-on{display:inline-block;width:auto;height:18px;min-width:16px;padding:4px 5px;font-weight:normal;line-height:18px;text-align:center;text-shadow:0 1px 0 #fff;vertical-align:middle;background-color:#eee;border:1px solid #ccc}.input-prepend .add-on,.input-append .add-on,.input-prepend .btn,.input-append .btn{margin-left:-1px;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.input-prepend .active,.input-append .active{background-color:#85e8a2;border-color:#22b24c}.input-prepend .add-on,.input-prepend .btn{margin-right:-1px}.input-prepend .add-on:first-child,.input-prepend .btn:first-child{-webkit-border-radius:3px 0 0 3px;-moz-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px}.input-append input,.input-append select,.input-append .uneditable-input{-webkit-border-radius:3px 0 0 3px;-moz-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px}.input-append .uneditable-input{border-right-color:#ccc;border-left-color:#eee}.input-append .add-on:last-child,.input-append .btn:last-child{-webkit-border-radius:0 3px 3px 0;-moz-border-radius:0 3px 3px 0;border-radius:0 3px 3px 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 .add-on:first-child,.input-prepend.input-append .btn:first-child{margin-right:-1px;-webkit-border-radius:3px 0 0 3px;-moz-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px}.input-prepend.input-append .add-on:last-child,.input-prepend.input-append .btn:last-child{margin-left:-1px;-webkit-border-radius:0 3px 3px 0;-moz-border-radius:0 3px 3px 0;border-radius:0 3px 3px 0}.search-query{padding-right:14px;padding-right:4px \9;padding-left:14px;padding-left:4px \9;margin-bottom:0;-webkit-border-radius:14px;-moz-border-radius:14px;border-radius: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;*zoom:1}.form-search .hide,.form-inline .hide,.form-horizontal .hide{display:none}.form-search label,.form-inline label{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:9px}legend+.control-group{margin-top:18px;-webkit-margin-top-collapse:separate}.form-horizontal .control-group{margin-bottom:18px;*zoom:1}.form-horizontal .control-group:before,.form-horizontal .control-group:after{display:table;content:""}.form-horizontal .control-group:after{clear:both}.form-horizontal .control-label{float:left;width:140px;padding-top:5px;text-align:right}.form-horizontal .controls{*display:inline-block;*padding-left:20px;margin-left:160px;*margin-left:0}.form-horizontal .controls:first-child{*padding-left:160px}.form-horizontal .help-block{margin-top:9px;margin-bottom:0}.form-horizontal .form-actions{padding-left:160px}table{max-width:100%;background-color:transparent;border-collapse:collapse;border-spacing:0}.table{width:100%;margin-bottom:18px}.table th,.table td{padding:8px;line-height:18px;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-condensed th,.table-condensed td{padding:4px 5px}.table-bordered{border:1px solid #ddd;border-collapse:separate;*border-collapse:collapsed;border-left:0;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.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{-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-topleft:4px}.table-bordered thead:first-child tr:first-child th:last-child,.table-bordered tbody:first-child tr:first-child td:last-child{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-moz-border-radius-topright:4px}.table-bordered thead:last-child tr:last-child th:first-child,.table-bordered tbody:last-child tr:last-child td:first-child{-webkit-border-radius:0 0 0 4px;-moz-border-radius:0 0 0 4px;border-radius:0 0 0 4px;-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px;-moz-border-radius-bottomleft:4px}.table-bordered thead:last-child tr:last-child th:last-child,.table-bordered tbody:last-child tr:last-child td:last-child{-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px;-moz-border-radius-bottomright:4px}.table-striped tbody tr:nth-child(odd) td,.table-striped tbody tr:nth-child(odd) th{background-color:#eee}.table tbody tr:hover td,.table tbody tr:hover th{background-color:#f5f5f5}table .span1{float:none;width:44px;margin-left:0}table .span2{float:none;width:124px;margin-left:0}table .span3{float:none;width:204px;margin-left:0}table .span4{float:none;width:284px;margin-left:0}table .span5{float:none;width:364px;margin-left:0}table .span6{float:none;width:444px;margin-left:0}table .span7{float:none;width:524px;margin-left:0}table .span8{float:none;width:604px;margin-left:0}table .span9{float:none;width:684px;margin-left:0}table .span10{float:none;width:764px;margin-left:0}table .span11{float:none;width:844px;margin-left:0}table .span12{float:none;width:924px;margin-left:0}table .span13{float:none;width:1004px;margin-left:0}table .span14{float:none;width:1084px;margin-left:0}table .span15{float:none;width:1164px;margin-left:0}table .span16{float:none;width:1244px;margin-left:0}table .span17{float:none;width:1324px;margin-left:0}table .span18{float:none;width:1404px;margin-left:0}table .span19{float:none;width:1484px;margin-left:0}table .span20{float:none;width:1564px;margin-left:0}table .span21{float:none;width:1644px;margin-left:0}table .span22{float:none;width:1724px;margin-left:0}table .span23{float:none;width:1804px;margin-left:0}table .span24{float:none;width:1884px;margin-left:0}[class^="icon-"],[class*=" icon-"]{display:inline-block;width:14px;height:14px;*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}[class^="icon-"]:last-child,[class*=" icon-"]:last-child{*margin-left:0}.icon-white{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{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{background-position:-384px -120px}.icon-folder-open{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:"";opacity:.3;filter:alpha(opacity=30)}.dropdown .caret{margin-top:8px;margin-left:2px}.dropdown:hover .caret,.open .caret{opacity:1;filter:alpha(opacity=100)}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:4px 0;margin:1px 0 0;list-style:none;background-color:#fcfbfd;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);*border-right-width:2px;*border-bottom-width:2px;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px;-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:8px 1px;*margin:-5px 0 5px;overflow:hidden;background-color:#e5e5e5;border-bottom:1px solid #fff}.dropdown-menu a{display:block;padding:3px 15px;clear:both;font-weight:normal;line-height:18px;color:#222;white-space:nowrap}.dropdown-menu li>a:hover,.dropdown-menu .active>a,.dropdown-menu .active>a:hover{color:#222;text-decoration:none;background-color:#eee}.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:"\2191"}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:1px}.typeahead{margin-top:2px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #eee;border:1px solid rgba(0,0,0,0.05);-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-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:6px;-moz-border-radius:6px;border-radius:6px}.well-small{padding:9px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.fade{opacity:0;-webkit-transition:opacity .15s linear;-moz-transition:opacity .15s linear;-ms-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;-ms-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:18px;color:#000;text-shadow:0 1px 0 #fff;opacity:.2;filter:alpha(opacity=20)}.close:hover{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 10px 4px;margin-bottom:0;*margin-left:.3em;font-size:14px;line-height:18px;*line-height:20px;color:#333;text-align:center;text-shadow:0 1px 1px rgba(255,255,255,0.75);vertical-align:middle;cursor:pointer;background-color:#f5f5f5;*background-color:#e6e6e6;background-image:-ms-linear-gradient(top,#fff,#e6e6e6);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fff),to(#e6e6e6));background-image:-webkit-linear-gradient(top,#fff,#e6e6e6);background-image:-o-linear-gradient(top,#fff,#e6e6e6);background-image:linear-gradient(top,#fff,#e6e6e6);background-image:-moz-linear-gradient(top,#fff,#e6e6e6);background-repeat:repeat-x;border:1px solid #ccc;*border:0;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);border-color:#e6e6e6 #e6e6e6 #bfbfbf;border-bottom-color:#b3b3b3;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;filter:progid:dximagetransform.microsoft.gradient(startColorstr='#ffffff',endColorstr='#e6e6e6',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:active,.btn.active,.btn.disabled,.btn[disabled]{background-color:#e6e6e6;*background-color:#d9d9d9}.btn:active,.btn.active{background-color:#ccc \9}.btn:first-child{*margin-left:0}.btn:hover{color:#333;text-decoration:none;background-color:#e6e6e6;*background-color:#d9d9d9;background-position:0 -15px;-webkit-transition:background-position .1s linear;-moz-transition:background-position .1s linear;-ms-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-color:#e6e6e6;background-color:#d9d9d9 \9;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-color:#e6e6e6;background-image:none;opacity:.65;filter:alpha(opacity=65);-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.btn-large{padding:9px 14px;font-size:16px;line-height:normal;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px}.btn-large [class^="icon-"]{margin-top:1px}.btn-small{padding:5px 9px;font-size:12px;line-height:16px}.btn-small [class^="icon-"]{margin-top:-1px}.btn-mini{padding:2px 6px;font-size:12px;line-height:14px}.btn-primary,.btn-primary:hover,.btn-warning,.btn-warning:hover,.btn-danger,.btn-danger:hover,.btn-success,.btn-success:hover,.btn-info,.btn-info:hover,.btn-inverse,.btn-inverse:hover{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25)}.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{border-color:#ccc;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25)}.btn-primary{background-color:#4372d3;*background-color:#435cd3;background-image:-ms-linear-gradient(top,#4380d3,#435cd3);background-image:-webkit-gradient(linear,0 0,0 100%,from(#4380d3),to(#435cd3));background-image:-webkit-linear-gradient(top,#4380d3,#435cd3);background-image:-o-linear-gradient(top,#4380d3,#435cd3);background-image:-moz-linear-gradient(top,#4380d3,#435cd3);background-image:linear-gradient(top,#4380d3,#435cd3);background-repeat:repeat-x;border-color:#435cd3 #435cd3 #263ca3;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='#4380d3',endColorstr='#435cd3',GradientType=0);filter:progid:dximagetransform.microsoft.gradient(enabled=false)}.btn-primary:hover,.btn-primary:active,.btn-primary.active,.btn-primary.disabled,.btn-primary[disabled]{background-color:#435cd3;*background-color:#304bcd}.btn-primary:active,.btn-primary.active{background-color:#2b44b8 \9}.btn-warning{background-color:#ff8e1f;*background-color:#ff7f00;background-image:-ms-linear-gradient(top,#f93,#ff7f00);background-image:-webkit-gradient(linear,0 0,0 100%,from(#f93),to(#ff7f00));background-image:-webkit-linear-gradient(top,#f93,#ff7f00);background-image:-o-linear-gradient(top,#f93,#ff7f00);background-image:-moz-linear-gradient(top,#f93,#ff7f00);background-image:linear-gradient(top,#f93,#ff7f00);background-repeat:repeat-x;border-color:#ff7f00 #ff7f00 #b35900;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='#ff9933',endColorstr='#ff7f00',GradientType=0);filter:progid:dximagetransform.microsoft.gradient(enabled=false)}.btn-warning:hover,.btn-warning:active,.btn-warning.active,.btn-warning.disabled,.btn-warning[disabled]{background-color:#ff7f00;*background-color:#e67200}.btn-warning:active,.btn-warning.active{background-color:#c60 \9}.btn-danger{background-color:#da4f49;*background-color:#bd362f;background-image:-ms-linear-gradient(top,#ee5f5b,#bd362f);background-image:-webkit-gradient(linear,0 0,0 100%,from(#ee5f5b),to(#bd362f));background-image:-webkit-linear-gradient(top,#ee5f5b,#bd362f);background-image:-o-linear-gradient(top,#ee5f5b,#bd362f);background-image:-moz-linear-gradient(top,#ee5f5b,#bd362f);background-image:linear-gradient(top,#ee5f5b,#bd362f);background-repeat:repeat-x;border-color:#bd362f #bd362f #802420;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='#ee5f5b',endColorstr='#bd362f',GradientType=0);filter:progid:dximagetransform.microsoft.gradient(enabled=false)}.btn-danger:hover,.btn-danger:active,.btn-danger.active,.btn-danger.disabled,.btn-danger[disabled]{background-color:#bd362f;*background-color:#a9302a}.btn-danger:active,.btn-danger.active{background-color:#942a25 \9}.btn-success{background-color:#5bb75b;*background-color:#51a351;background-image:-ms-linear-gradient(top,#62c462,#51a351);background-image:-webkit-gradient(linear,0 0,0 100%,from(#62c462),to(#51a351));background-image:-webkit-linear-gradient(top,#62c462,#51a351);background-image:-o-linear-gradient(top,#62c462,#51a351);background-image:-moz-linear-gradient(top,#62c462,#51a351);background-image:linear-gradient(top,#62c462,#51a351);background-repeat:repeat-x;border-color:#51a351 #51a351 #387038;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='#62c462',endColorstr='#51a351',GradientType=0);filter:progid:dximagetransform.microsoft.gradient(enabled=false)}.btn-success:hover,.btn-success:active,.btn-success.active,.btn-success.disabled,.btn-success[disabled]{background-color:#51a351;*background-color:#499249}.btn-success:active,.btn-success.active{background-color:#408140 \9}.btn-info{background-color:#49afcd;*background-color:#2f96b4;background-image:-ms-linear-gradient(top,#5bc0de,#2f96b4);background-image:-webkit-gradient(linear,0 0,0 100%,from(#5bc0de),to(#2f96b4));background-image:-webkit-linear-gradient(top,#5bc0de,#2f96b4);background-image:-o-linear-gradient(top,#5bc0de,#2f96b4);background-image:-moz-linear-gradient(top,#5bc0de,#2f96b4);background-image:linear-gradient(top,#5bc0de,#2f96b4);background-repeat:repeat-x;border-color:#2f96b4 #2f96b4 #1f6377;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='#5bc0de',endColorstr='#2f96b4',GradientType=0);filter:progid:dximagetransform.microsoft.gradient(enabled=false)}.btn-info:hover,.btn-info:active,.btn-info.active,.btn-info.disabled,.btn-info[disabled]{background-color:#2f96b4;*background-color:#2a85a0}.btn-info:active,.btn-info.active{background-color:#24748c \9}.btn-inverse{background-color:#3b7bd0;*background-color:#3072cd;background-image:-ms-linear-gradient(top,#4380d3,#3072cd);background-image:-webkit-gradient(linear,0 0,0 100%,from(#4380d3),to(#3072cd));background-image:-webkit-linear-gradient(top,#4380d3,#3072cd);background-image:-o-linear-gradient(top,#4380d3,#3072cd);background-image:-moz-linear-gradient(top,#4380d3,#3072cd);background-image:linear-gradient(top,#4380d3,#3072cd);background-repeat:repeat-x;border-color:#3072cd #3072cd #21508f;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='#4380d3',endColorstr='#3072cd',GradientType=0);filter:progid:dximagetransform.microsoft.gradient(enabled=false)}.btn-inverse:hover,.btn-inverse:active,.btn-inverse.active,.btn-inverse.disabled,.btn-inverse[disabled]{background-color:#3072cd;*background-color:#2b67b8}.btn-inverse:active,.btn-inverse.active{background-color:#265ba3 \9}button.btn,input[type="submit"].btn{*padding-top:2px;*padding-bottom:2px}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-group{position:relative;*margin-left:.3em;*zoom:1}.btn-group:before,.btn-group:after{display:table;content:""}.btn-group:after{clear:both}.btn-group:first-child{*margin-left:0}.btn-group+.btn-group{margin-left:5px}.btn-toolbar{margin-top:9px;margin-bottom:9px}.btn-toolbar .btn-group{display:inline-block;*display:inline;*zoom:1}.btn-group>.btn{position:relative;float:left;margin-left:-1px;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.btn-group>.btn:first-child{margin-left:0;-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px;-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-bottomleft:4px;-moz-border-radius-topleft:4px}.btn-group>.btn:last-child,.btn-group>.dropdown-toggle{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px;-moz-border-radius-topright:4px;-moz-border-radius-bottomright:4px}.btn-group>.btn.large:first-child{margin-left:0;-webkit-border-bottom-left-radius:6px;border-bottom-left-radius:6px;-webkit-border-top-left-radius:6px;border-top-left-radius:6px;-moz-border-radius-bottomleft:6px;-moz-border-radius-topleft:6px}.btn-group>.btn.large:last-child,.btn-group>.large.dropdown-toggle{-webkit-border-top-right-radius:6px;border-top-right-radius:6px;-webkit-border-bottom-right-radius:6px;border-bottom-right-radius:6px;-moz-border-radius-topright:6px;-moz-border-radius-bottomright:6px}.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>.dropdown-toggle{*padding-top:4px;padding-right:8px;*padding-bottom:4px;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-right:5px;padding-left:5px}.btn-group>.btn-small.dropdown-toggle{*padding-top:4px;*padding-bottom:4px}.btn-group>.btn-large.dropdown-toggle{padding-right:12px;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:#e6e6e6}.btn-group.open .btn-primary.dropdown-toggle{background-color:#435cd3}.btn-group.open .btn-warning.dropdown-toggle{background-color:#ff7f00}.btn-group.open .btn-danger.dropdown-toggle{background-color:#bd362f}.btn-group.open .btn-success.dropdown-toggle{background-color:#51a351}.btn-group.open .btn-info.dropdown-toggle{background-color:#2f96b4}.btn-group.open .btn-inverse.dropdown-toggle{background-color:#3072cd}.btn .caret{margin-top:7px;margin-left:0}.btn:hover .caret,.open.btn-group .caret{opacity:1;filter:alpha(opacity=100)}.btn-mini .caret{margin-top:5px}.btn-small .caret{margin-top:6px}.btn-large .caret{margin-top:6px;border-top-width:5px;border-right-width:5px;border-left-width:5px}.dropup .btn-large .caret{border-top:0;border-bottom:5px solid #000}.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;opacity:.75;filter:alpha(opacity=75)}.alert{padding:8px 35px 8px 14px;margin-bottom:18px;color:#c09853;text-shadow:0 1px 0 rgba(255,255,255,0.5);background-color:#fcf8e3;border:1px solid #fbeed5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.alert-heading{color:inherit}.alert .close{position:relative;top:-2px;right:-21px;line-height:18px}.alert-success{color:#468847;background-color:#dff0d8;border-color:#d6e9c6}.alert-danger,.alert-error{color:#b94a48;background-color:#f2dede;border-color:#eed3d7}.alert-info{color:#3a87ad;background-color:#d9edf7;border-color:#bce8f1}.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:18px;margin-left:0;list-style:none}.nav>li>a{display:block}.nav>li>a:hover{text-decoration:none;background-color:#eee}.nav>.pull-right{float:right}.nav .nav-header{display:block;padding:3px 15px;font-size:11px;font-weight:bold;line-height:18px;color:#999;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{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.2);background-color:#4380d3}.nav-list [class^="icon-"]{margin-right:2px}.nav-list .divider{*width:100%;height:1px;margin:8px 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;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:18px;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{border-color:#eee #eee #ddd}.nav-tabs>.active>a,.nav-tabs>.active>a:hover{color:#888;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{color:#fff;background-color:#4380d3}.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-radius:4px 4px 0 0;-moz-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0}.nav-tabs.nav-stacked>li:last-child>a{-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px}.nav-tabs.nav-stacked>li>a:hover{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 5px 5px;-moz-border-radius:0 0 5px 5px;border-radius:0 0 5px 5px}.nav-pills .dropdown-menu{-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.nav-tabs .dropdown-toggle .caret,.nav-pills .dropdown-toggle .caret{margin-top:6px;border-top-color:#4380d3;border-bottom-color:#4380d3}.nav-tabs .dropdown-toggle:hover .caret,.nav-pills .dropdown-toggle:hover .caret{border-top-color:#c00;border-bottom-color:#c00}.nav-tabs .active .dropdown-toggle .caret,.nav-pills .active .dropdown-toggle .caret{border-top-color:#333;border-bottom-color:#333}.nav>.dropdown.active>a:hover{color:#000;cursor:pointer}.nav-tabs .open .dropdown-toggle,.nav-pills .open .dropdown-toggle,.nav>li.dropdown.open.active>a:hover{color:#fff;background-color:#999;border-color:#999}.nav li.dropdown.open .caret,.nav li.dropdown.open.active .caret,.nav li.dropdown.open a:hover .caret{border-top-color:#fff;border-bottom-color:#fff;opacity:1;filter:alpha(opacity=100)}.tabs-stacked .open>a:hover{border-color:#999}.tabbable{*zoom:1}.tabbable:before,.tabbable:after{display:table;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{border-top-color:#ddd;border-bottom-color:transparent}.tabs-below>.nav-tabs>.active>a,.tabs-below>.nav-tabs>.active>a:hover{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{border-color:#eee #ddd #eee #eee}.tabs-left>.nav-tabs .active>a,.tabs-left>.nav-tabs .active>a:hover{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{border-color:#eee #eee #eee #ddd}.tabs-right>.nav-tabs .active>a,.tabs-right>.nav-tabs .active>a:hover{border-color:#ddd #ddd #ddd transparent;*border-left-color:#fff}.navbar{*position:relative;*z-index:2;margin-bottom:18px;overflow:visible}.navbar-inner{min-height:50px;padding-right:20px;padding-left:20px;background-color:#fcfbfd;background-image:-moz-linear-gradient(top,#fcfbfd,#fcfbfd);background-image:-ms-linear-gradient(top,#fcfbfd,#fcfbfd);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fcfbfd),to(#fcfbfd));background-image:-webkit-linear-gradient(top,#fcfbfd,#fcfbfd);background-image:-o-linear-gradient(top,#fcfbfd,#fcfbfd);background-image:linear-gradient(top,#fcfbfd,#fcfbfd);background-repeat:repeat-x;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;filter:progid:dximagetransform.microsoft.gradient(startColorstr='#fcfbfd',endColorstr='#fcfbfd',GradientType=0);-webkit-box-shadow:0 1px 3px rgba(0,0,0,0.25),inset 0 -1px 0 rgba(0,0,0,0.1);-moz-box-shadow:0 1px 3px rgba(0,0,0,0.25),inset 0 -1px 0 rgba(0,0,0,0.1);box-shadow:0 1px 3px rgba(0,0,0,0.25),inset 0 -1px 0 rgba(0,0,0,0.1)}.navbar .container{width:auto}.nav-collapse.collapse{height:auto}.navbar{color:#222}.navbar .brand:hover{text-decoration:none}.navbar .brand{display:block;float:left;padding:13px 20px 17px;margin-left:-20px;font-size:20px;font-weight:200;line-height:1;color:#4380d3}.navbar .navbar-text{margin-bottom:0;line-height:50px}.navbar .navbar-link{color:#4380d3}.navbar .navbar-link:hover{color:#4380d3}.navbar .btn,.navbar .btn-group{margin-top:10px}.navbar .btn-group .btn{margin:0}.navbar-form{margin-bottom:0;*zoom:1}.navbar-form:before,.navbar-form:after{display:table;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{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:6px;white-space:nowrap}.navbar-form .input-append input,.navbar-form .input-prepend input{margin-top:0}.navbar-search{position:relative;float:left;margin-top:11px;margin-bottom:0}.navbar-search .search-query{padding:4px 9px;font-family:'Open Sans',"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:13px;font-weight:normal;line-height:1;color:#fff;background-color:#fff;border:1px solid #b3b3b3;-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;-ms-transition:none;-o-transition:none;transition:none}.navbar-search .search-query:-moz-placeholder{color:#ccc}.navbar-search .search-query:-ms-input-placeholder{color:#ccc}.navbar-search .search-query::-webkit-input-placeholder{color:#ccc}.navbar-search .search-query:focus,.navbar-search .search-query.focused{padding:5px 10px;color:#333;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-fixed-top,.navbar-fixed-bottom{position:fixed;right:0;left:0;z-index:1030;margin-bottom: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-fixed-top .container,.navbar-fixed-bottom .container{width:940px}.navbar-fixed-top{top:0}.navbar-fixed-bottom{bottom:0}.navbar .nav{position:relative;left:0;display:block;float:left;margin:0 10px 0 0}.navbar .nav.pull-right{float:right}.navbar .nav>li{display:block;float:left}.navbar .nav>li>a{float:none;padding:14px 10px 16px;line-height:19px;color:#4380d3;text-decoration:none;text-shadow:0 -1px 0 rgba(0,0,0,0.25)}.navbar .btn{display:inline-block;padding:4px 10px 4px;margin:10px 5px 11px;line-height:18px}.navbar .btn-group{padding:10px 5px 11px;margin:0}.navbar .nav>li>a:hover{color:#4380d3;text-decoration:none;background-color:#eee}.navbar .nav .active>a,.navbar .nav .active>a:hover{color:#4380d3;text-decoration:none;background-color:#eee}.navbar .divider-vertical{width:1px;height:50px;margin:0 9px;overflow:hidden;background-color:#fcfbfd;border-right:1px solid #fcfbfd}.navbar .nav.pull-right{margin-right:0;margin-left:10px}.navbar .btn-navbar{display:none;float:right;padding:7px 10px;margin-right:5px;margin-left:5px;background-color:#fcfbfd;*background-color:#fcfbfd;background-image:-ms-linear-gradient(top,#fcfbfd,#fcfbfd);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fcfbfd),to(#fcfbfd));background-image:-webkit-linear-gradient(top,#fcfbfd,#fcfbfd);background-image:-o-linear-gradient(top,#fcfbfd,#fcfbfd);background-image:linear-gradient(top,#fcfbfd,#fcfbfd);background-image:-moz-linear-gradient(top,#fcfbfd,#fcfbfd);background-repeat:repeat-x;border-color:#fcfbfd #fcfbfd #d6c8e4;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='#fcfbfd',endColorstr='#fcfbfd',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:active,.navbar .btn-navbar.active,.navbar .btn-navbar.disabled,.navbar .btn-navbar[disabled]{background-color:#fcfbfd;*background-color:#efeaf5}.navbar .btn-navbar:active,.navbar .btn-navbar.active{background-color:#e3d9ec \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 .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 .dropdown-menu:after{position:absolute;top:-6px;left:10px;display:inline-block;border-right:6px solid transparent;border-bottom:6px solid #fcfbfd;border-left:6px solid transparent;content:''}.navbar-fixed-bottom .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 .dropdown-menu:after{top:auto;bottom:-6px;border-top:6px solid #fcfbfd;border-bottom:0}.navbar .nav li.dropdown .dropdown-toggle .caret,.navbar .nav li.dropdown.open .caret{border-top-color:#fff;border-bottom-color:#fff}.navbar .nav li.dropdown.active .caret{opacity:1;filter:alpha(opacity=100)}.navbar .nav li.dropdown.open>.dropdown-toggle,.navbar .nav li.dropdown.active>.dropdown-toggle,.navbar .nav li.dropdown.open.active>.dropdown-toggle{background-color:transparent}.navbar .nav li.dropdown.active>.dropdown-toggle:hover{color:#fff}.navbar .pull-right .dropdown-menu,.navbar .dropdown-menu.pull-right{right:0;left:auto}.navbar .pull-right .dropdown-menu:before,.navbar .dropdown-menu.pull-right:before{right:12px;left:auto}.navbar .pull-right .dropdown-menu:after,.navbar .dropdown-menu.pull-right:after{right:13px;left:auto}.breadcrumb{padding:7px 14px;margin:0 0 18px;list-style:none;background-color:#fbfbfb;background-image:-moz-linear-gradient(top,#fff,#f5f5f5);background-image:-ms-linear-gradient(top,#fff,#f5f5f5);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fff),to(#f5f5f5));background-image:-webkit-linear-gradient(top,#fff,#f5f5f5);background-image:-o-linear-gradient(top,#fff,#f5f5f5);background-image:linear-gradient(top,#fff,#f5f5f5);background-repeat:repeat-x;border:1px solid #ddd;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;filter:progid:dximagetransform.microsoft.gradient(startColorstr='#ffffff',endColorstr='#f5f5f5',GradientType=0);-webkit-box-shadow:inset 0 1px 0 #fff;-moz-box-shadow:inset 0 1px 0 #fff;box-shadow:inset 0 1px 0 #fff}.breadcrumb li{display:inline-block;*display:inline;text-shadow:0 1px 0 #fff;*zoom:1}.breadcrumb .divider{padding:0 5px;color:#999}.breadcrumb .active a{color:#333}.pagination{height:36px;margin:18px 0}.pagination ul{display:inline-block;*display:inline;margin-bottom:0;margin-left:0;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;*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 li{display:inline}.pagination a{float:left;padding:0 14px;line-height:34px;text-decoration:none;border:1px solid #ddd;border-left-width:0}.pagination a:hover,.pagination .active a{background-color:#f5f5f5}.pagination .active a{color:#999;cursor:default}.pagination .disabled span,.pagination .disabled a,.pagination .disabled a:hover{color:#999;cursor:default;background-color:transparent}.pagination li:first-child a{border-left-width:1px;-webkit-border-radius:3px 0 0 3px;-moz-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px}.pagination li:last-child a{-webkit-border-radius:0 3px 3px 0;-moz-border-radius:0 3px 3px 0;border-radius:0 3px 3px 0}.pagination-centered{text-align:center}.pagination-right{text-align:right}.pager{margin-bottom:18px;margin-left:0;text-align:center;list-style:none;*zoom:1}.pager:before,.pager:after{display:table;content:""}.pager:after{clear:both}.pager li{display:inline}.pager a{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 a:hover{text-decoration:none;background-color:#f5f5f5}.pager .next a{float:right}.pager .previous a{float:left}.pager .disabled a,.pager .disabled a:hover{color:#999;cursor:default;background-color:#fff}.modal-open .dropdown-menu{z-index:2050}.modal-open .dropdown.open{*z-index:2050}.modal-open .popover{z-index:2060}.modal-open .tooltip{z-index:2070}.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:50%;left:50%;z-index:1050;width:560px;margin:-250px 0 0 -280px;overflow:auto;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;-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;-ms-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:50%}.modal-header{padding:9px 15px;border-bottom:1px solid #eee}.modal-header .close{margin-top:2px}.modal-body{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;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}.tooltip{position:absolute;z-index:1020;display:block;padding:5px;font-size:11px;opacity:0;filter:alpha(opacity=0);visibility:visible}.tooltip.in{opacity:.8;filter:alpha(opacity=80)}.tooltip.top{margin-top:-2px}.tooltip.right{margin-left:2px}.tooltip.bottom{margin-top:2px}.tooltip.left{margin-left:-2px}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-top:5px solid #000;border-right:5px solid transparent;border-left:5px solid transparent}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-top:5px solid transparent;border-bottom:5px solid transparent;border-left:5px solid #000}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-right:5px solid transparent;border-bottom:5px solid #000;border-left:5px solid transparent}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-top:5px solid transparent;border-right:5px solid #000;border-bottom:5px solid transparent}.tooltip-inner{max-width:200px;padding:3px 8px;color:#fff;text-align:center;text-decoration:none;background-color:#000;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0}.popover{position:absolute;top:0;left:0;z-index:1010;display:none;padding:5px}.popover.top{margin-top:-5px}.popover.right{margin-left:5px}.popover.bottom{margin-top:5px}.popover.left{margin-left:-5px}.popover.top .arrow{bottom:0;left:50%;margin-left:-5px;border-top:5px solid #000;border-right:5px solid transparent;border-left:5px solid transparent}.popover.right .arrow{top:50%;left:0;margin-top:-5px;border-top:5px solid transparent;border-right:5px solid #000;border-bottom:5px solid transparent}.popover.bottom .arrow{top:0;left:50%;margin-left:-5px;border-right:5px solid transparent;border-bottom:5px solid #000;border-left:5px solid transparent}.popover.left .arrow{top:50%;right:0;margin-top:-5px;border-top:5px solid transparent;border-bottom:5px solid transparent;border-left:5px solid #000}.popover .arrow{position:absolute;width:0;height:0}.popover-inner{width:280px;padding:3px;overflow:hidden;background:#000;background:rgba(0,0,0,0.8);-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-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)}.popover-title{padding:9px 15px;line-height:1;background-color:#f5f5f5;border-bottom:1px solid #eee;-webkit-border-radius:3px 3px 0 0;-moz-border-radius:3px 3px 0 0;border-radius:3px 3px 0 0}.popover-content{padding:14px;background-color:#fff;-webkit-border-radius:0 0 3px 3px;-moz-border-radius:0 0 3px 3px;border-radius:0 0 3px 3px;-webkit-background-clip:padding-box;-moz-background-clip:padding-box;background-clip:padding-box}.popover-content p,.popover-content ul,.popover-content ol{margin-bottom:0}.thumbnails{margin-left:-20px;list-style:none;*zoom:1}.thumbnails:before,.thumbnails:after{display:table;content:""}.thumbnails:after{clear:both}.row-fluid .thumbnails{margin-left:0}.thumbnails>li{float:left;margin-bottom:18px;margin-left:20px}.thumbnail{display:block;padding:4px;line-height:1;border:1px solid #ddd;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:0 1px 1px rgba(0,0,0,0.075);box-shadow:0 1px 1px rgba(0,0,0,0.075)}a.thumbnail:hover{border-color:#4380d3;-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}.label,.badge{font-size:11.844px;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:#999}.label{padding:1px 4px 2px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.badge{padding:1px 9px 2px;-webkit-border-radius:9px;-moz-border-radius:9px;border-radius:9px}a.label:hover,a.badge:hover{color:#fff;text-decoration:none;cursor:pointer}.label-important,.badge-important{background-color:#b94a48}.label-important[href],.badge-important[href]{background-color:#953b39}.label-warning,.badge-warning{background-color:#ff7f00}.label-warning[href],.badge-warning[href]{background-color:#c60}.label-success,.badge-success{background-color:#468847}.label-success[href],.badge-success[href]{background-color:#356635}.label-info,.badge-info{background-color:#3a87ad}.label-info[href],.badge-info[href]{background-color:#2d6987}.label-inverse,.badge-inverse{background-color:#333}.label-inverse[href],.badge-inverse[href]{background-color:#1a1a1a}@-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:18px;margin-bottom:18px;overflow:hidden;background-color:#f7f7f7;background-image:-moz-linear-gradient(top,#f5f5f5,#f9f9f9);background-image:-ms-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(top,#f5f5f5,#f9f9f9);background-repeat:repeat-x;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;filter:progid:dximagetransform.microsoft.gradient(startColorstr='#f5f5f5',endColorstr='#f9f9f9',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{width:0;height:18px;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(top,#149bdf,#0480be);background-image:-ms-linear-gradient(top,#149bdf,#0480be);background-repeat:repeat-x;filter:progid:dximagetransform.microsoft.gradient(startColorstr='#149bdf',endColorstr='#0480be',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;-ms-box-sizing:border-box;box-sizing:border-box;-webkit-transition:width .6s ease;-moz-transition:width .6s ease;-ms-transition:width .6s ease;-o-transition:width .6s ease;transition:width .6s ease}.progress-striped .bar{background-color:#149bdf;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:-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:-ms-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:-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: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{background-color:#dd514c;background-image:-moz-linear-gradient(top,#ee5f5b,#c43c35);background-image:-ms-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(top,#ee5f5b,#c43c35);background-repeat:repeat-x;filter:progid:dximagetransform.microsoft.gradient(startColorstr='#ee5f5b',endColorstr='#c43c35',GradientType=0)}.progress-danger.progress-striped .bar{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:-ms-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{background-color:#5eb95e;background-image:-moz-linear-gradient(top,#62c462,#57a957);background-image:-ms-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(top,#62c462,#57a957);background-repeat:repeat-x;filter:progid:dximagetransform.microsoft.gradient(startColorstr='#62c462',endColorstr='#57a957',GradientType=0)}.progress-success.progress-striped .bar{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:-ms-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{background-color:#4bb1cf;background-image:-moz-linear-gradient(top,#5bc0de,#339bb9);background-image:-ms-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(top,#5bc0de,#339bb9);background-repeat:repeat-x;filter:progid:dximagetransform.microsoft.gradient(startColorstr='#5bc0de',endColorstr='#339bb9',GradientType=0)}.progress-info.progress-striped .bar{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:-ms-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{background-color:#ff962e;background-image:-moz-linear-gradient(top,#ffa54d,#ff7f00);background-image:-ms-linear-gradient(top,#ffa54d,#ff7f00);background-image:-webkit-gradient(linear,0 0,0 100%,from(#ffa54d),to(#ff7f00));background-image:-webkit-linear-gradient(top,#ffa54d,#ff7f00);background-image:-o-linear-gradient(top,#ffa54d,#ff7f00);background-image:linear-gradient(top,#ffa54d,#ff7f00);background-repeat:repeat-x;filter:progid:dximagetransform.microsoft.gradient(startColorstr='#ffa54d',endColorstr='#ff7f00',GradientType=0)}.progress-warning.progress-striped .bar{background-color:#ffa54d;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:-ms-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:18px}.accordion-group{margin-bottom:2px;border:1px solid #e5e5e5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.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:18px;line-height:1}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel .item{position:relative;display:none;-webkit-transition:.6s ease-in-out left;-moz-transition:.6s ease-in-out left;-ms-transition:.6s ease-in-out left;-o-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel .item>img{display:block;line-height:1}.carousel .active,.carousel .next,.carousel .prev{display:block}.carousel .active{left:0}.carousel .next,.carousel .prev{position:absolute;top:0;width:100%}.carousel .next{left:100%}.carousel .prev{left:-100%}.carousel .next.left,.carousel .prev.right{left:0}.carousel .active.left{left:-100%}.carousel .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:#222;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{color:#fff;text-decoration:none;opacity:.9;filter:alpha(opacity=90)}.carousel-caption{position:absolute;right:0;bottom:0;left:0;padding:10px 15px 5px;background:#333;background:rgba(0,0,0,0.75)}.carousel-caption h4,.carousel-caption p{color:#fff}.hero-unit{padding:60px;margin-bottom:30px;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 p{font-size:18px;font-weight:200;line-height:27px;color:inherit}.pull-right{float:right}.pull-left{float:left}.hide{display:none}.show{display:block}.invisible{visibility:hidden}h1,h2,h3,h4,h5,h6,.navbar .brand{font-weight:700}a{text-decoration:none}.nav a,.navbar .brand,.subnav a,a.btn,.dropdown-menu a{text-decoration:none}.navbar .navbar-inner{border-top:1px solid #e5e5e5;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:0 2px 4px rgba(0,0,0,0.25),inset 0 -1px 0 rgba(0,0,0,0.1);-moz-box-shadow:0 2px 4px rgba(0,0,0,0.25),inset 0 -1px 0 rgba(0,0,0,0.1);box-shadow:0 2px 4px rgba(0,0,0,0.25),inset 0 -1px 0 rgba(0,0,0,0.1)}.navbar .brand{text-shadow:none}.navbar .brand:hover{background-color:#eee}.navbar .navbar-text{line-height:68px}.navbar .nav>li>a{text-shadow:none}.navbar .dropdown-menu{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.navbar .nav li.dropdown.active>.dropdown-toggle,.navbar .nav li.dropdown.active>.dropdown-toggle:hover,.navbar .nav li.dropdown.open>.dropdown-toggle,.navbar .nav li.dropdown.active.open>.dropdown-toggle,.navbar .nav li.dropdown.active.open>.dropdown-toggle:hover{color:#4380d3;background-color:#eee}.navbar .nav li.dropdown .dropdown-toggle .caret,.navbar .nav .open .caret,.navbar .nav .open .dropdown-toggle:hover .caret{border-top-color:#000;opacity:1}.navbar .nav-collapse.in .nav li>a:hover{background-color:#eee}.navbar .nav-collapse .nav li>a{font-weight:normal;color:#222;text-decoration:none}.navbar .nav-collapse .navbar-form,.navbar .nav-collapse .navbar-search{border-color:transparent}.navbar .navbar-search .search-query,.navbar .navbar-search .search-query:hover{color:#222;border:1px solid #eee}.navbar .navbar-search .search-query:-moz-placeholder,.navbar .navbar-search .search-query:hover:-moz-placeholder{color:#888}.navbar .navbar-search .search-query:-ms-input-placeholder,.navbar .navbar-search .search-query:hover:-ms-input-placeholder{color:#888}.navbar .navbar-search .search-query::-webkit-input-placeholder,.navbar .navbar-search .search-query:hover::-webkit-input-placeholder{color:#888}div.subnav{background-color:#fcfbfd;background-image:none;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:0 1px 2px rgba(0,0,0,0.25);-moz-box-shadow:0 1px 2px rgba(0,0,0,0.25);box-shadow:0 1px 2px rgba(0,0,0,0.25)}div.subnav.subnav-fixed{top:50px}div.subnav .nav>li>a:hover,div.subnav .nav>.active>a,div.subnav .nav>.active>a:hover{font-weight:normal;color:#222;text-decoration:none}div.subnav .nav>li:first-child>a,div.subnav .nav>li:first-child>a:hover{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.btn-primary{background-color:#4f88d6;*background-color:#4380d3;background-image:-ms-linear-gradient(top,#588ed8,#4380d3);background-image:-webkit-gradient(linear,0 0,0 100%,from(#588ed8),to(#4380d3));background-image:-webkit-linear-gradient(top,#588ed8,#4380d3);background-image:-o-linear-gradient(top,#588ed8,#4380d3);background-image:-moz-linear-gradient(top,#588ed8,#4380d3);background-image:linear-gradient(top,#588ed8,#4380d3);background-repeat:repeat-x;border-color:#4380d3 #4380d3 #265ba3;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='#588ed8',endColorstr='#4380d3',GradientType=0);filter:progid:dximagetransform.microsoft.gradient(enabled=false)}.btn-primary:hover,.btn-primary:active,.btn-primary.active,.btn-primary.disabled,.btn-primary[disabled]{background-color:#4380d3;*background-color:#3072cd}.btn-primary:active,.btn-primary.active{background-color:#2b67b8 \9}[class^="icon-"],[class*=" icon-"]{vertical-align:-2px}.modal{background:#fcfbfd;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.modal-header{border-bottom:0}.modal-header .close{text-decoration:none}.modal-footer{background:transparent;border-top:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}code,pre,pre.prettyprint,.well{background-color:#eee}.hero-unit{border:1px solid rgba(0,0,0,0.05);-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)}.table-bordered,.well,.prettyprint{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0} diff --git a/libpathod/static/pathod.css b/libpathod/static/pathod.css index ad67179a..7ac8bc81 100644 --- a/libpathod/static/pathod.css +++ b/libpathod/static/pathod.css @@ -1,4 +1,21 @@ +.fronttable { +} + +.bigtitle { + font-weight: bold; + font-size: 50px; + line-height: 55px; + text-align: center; + display: table; + height: 300px; +} + +.bigtitle>div { + display: table-cell; + vertical-align: middle; +} + section { margin-top: 50px; } diff --git a/libpathod/static/torture.png b/libpathod/static/torture.png new file mode 100644 index 00000000..50e245ea Binary files /dev/null and b/libpathod/static/torture.png differ diff --git a/libpathod/templates/frame.html b/libpathod/templates/frame.html index 33c96d19..f16e6d47 100644 --- a/libpathod/templates/frame.html +++ b/libpathod/templates/frame.html @@ -26,6 +26,7 @@ +
200A basic HTTP 200 responseA basic HTTP 200 response (this will hang).
200:daServer-side disconnect after all content has been sent.
200:b@100100 random bytes as the body100 random bytes as the body. A Content-Lenght header is + added, so the disconnect is no longer needed.
200:b@100:h"Etag"="';drop table servers;"
- - - - - + + + + + + + + + + + - + @@ -94,13 +103,6 @@
hVALUE=VALUE - Set a header. -
bVALUE @@ -44,6 +37,21 @@
dOFFSET + Disconnect after OFFSET bytes. +
hVALUE=VALUE + Set a header. +
iOFFSET,VALUE @@ -60,9 +68,10 @@
dOFFSET mVALUE - Disconnect after OFFSET bytes. + HTTP Reason message. Automatically chosen according to + the response code if not specified.
- - - - - - + - + + + + + + diff --git a/libpathod/templates/index.html b/libpathod/templates/index.html index 3c1ba8ff..9561fee1 100644 --- a/libpathod/templates/index.html +++ b/libpathod/templates/index.html @@ -31,7 +31,7 @@ name="spec" class="input-medium search-query" value="{{spec}}" - placeholder="code[msg]:[features]" + placeholder="code:[features]" > go

diff --git a/libpathod/templates/response_previewform.html b/libpathod/templates/response_previewform.html index 74e21362..7f72aaeb 100644 --- a/libpathod/templates/response_previewform.html +++ b/libpathod/templates/response_previewform.html @@ -5,7 +5,7 @@ name="spec" class="input-medium search-query" value="{{spec}}" - placeholder="code[msg]:[features]" + placeholder="code:[features]" > {% if not nocraft %} diff --git a/test/test_language.py b/test/test_language.py index 0d7c60da..0d3f2685 100644 --- a/test/test_language.py +++ b/test/test_language.py @@ -498,11 +498,11 @@ class TestResponse: assert r.code.string() == "202" def test_response(self): - r = language.parse_response({}, "400'msg'") + r = language.parse_response({}, "400:m'msg'") assert r.code.string() == "400" assert r.reason.string() == "msg" - r = language.parse_response({}, "400'msg':b@100b") + r = language.parse_response({}, "400:m'msg':b@100b") assert r.reason.string() == "msg" assert r.body.values({}) assert str(r) @@ -514,7 +514,7 @@ class TestResponse: def test_render(self): s = cStringIO.StringIO() - r = language.parse_response({}, "400'msg'") + r = language.parse_response({}, "400:m'msg'") assert r.serve(s, {}) def test_raw(self): @@ -537,9 +537,9 @@ class TestResponse: s = cStringIO.StringIO() x.serve(s, {}) assert x.length({}, None) == len(s.getvalue()) - testlen(language.parse_response({}, "400'msg'")) - testlen(language.parse_response({}, "400'msg':h'foo'='bar'")) - testlen(language.parse_response({}, "400'msg':h'foo'='bar':b@100b")) + testlen(language.parse_response({}, "400:m'msg'")) + testlen(language.parse_response({}, "400:m'msg':h'foo'='bar'")) + testlen(language.parse_response({}, "400:m'msg':h'foo'='bar':b@100b")) def test_maximum_length(self): def testlen(x, actions): @@ -548,7 +548,7 @@ class TestResponse: x.serve(s, {}) assert m >= len(s.getvalue()) - r = language.parse_response({}, "400'msg':b@100") + r = language.parse_response({}, "400:m'msg':b@100") actions = [ language.DisconnectAt(0) diff --git a/test/test_pathod.py b/test/test_pathod.py index 3fe7e848..30498c3a 100644 --- a/test/test_pathod.py +++ b/test/test_pathod.py @@ -40,7 +40,6 @@ class TestTimeout(tutils.DaemonTests): # FIXME: Add float values to spec language, reduce test timeout to # increase test performance assert self.get("200:p1,1").status_code == 200 - print self.d.last_log() assert self.d.last_log()["type"] == "timeout" -- cgit v1.2.3 From 882969086cf3e1277360bcd49588592f99c575bf Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Mon, 29 Oct 2012 10:00:41 +1300 Subject: Cleaup, some more ABCs. --- libpathod/language.py | 136 +++++++++++++++++++++++++++++--------------------- test/test_language.py | 6 +-- 2 files changed, 82 insertions(+), 60 deletions(-) diff --git a/libpathod/language.py b/libpathod/language.py index 76676517..a74412b6 100644 --- a/libpathod/language.py +++ b/libpathod/language.py @@ -297,29 +297,44 @@ Offset = pp.MatchFirst( ) -class _Component(object): +class _Spec(object): """ - A component of the specification of an HTTP message. + A specification token. """ __metaclass__ = abc.ABCMeta @abc.abstractmethod - def values(self, settings): # pragma: no cover + def expr(klass): # pragma: no cover """ - A sequence of value objects. + A parse expression. """ return None @abc.abstractmethod - def expr(klass): # pragma: no cover + def accept(self, r): # pragma: no cover """ - A parse expression. + Notifies the component to register itself with message r. """ return None + +class Raw(_Spec): + def accept(self, r): + r.raw = True + + @classmethod + def expr(klass): + e = pp.Literal("r").suppress() + return e.setParseAction(lambda x: klass(*x)) + + +class _Component(_Spec): + """ + A value component of the primary specification of an HTTP message. + """ @abc.abstractmethod - def accept(self, r): # pragma: no cover + def values(self, settings): # pragma: no cover """ - Notifies the component to register itself with message r. + A sequence of value objects. """ return None @@ -397,16 +412,6 @@ class Body(_Component): ] -class Raw: - def accept(self, r): - r.raw = True - - @classmethod - def expr(klass): - e = pp.Literal("r").suppress() - return e.setParseAction(lambda x: klass(*x)) - - class Path(_Component): def __init__(self, value): if isinstance(value, basestring): @@ -463,7 +468,40 @@ class Method(_Component): ] -class _Action: +class Code(_Component): + def __init__(self, code): + self.code = str(code) + + def accept(self, r): + r.code = self + + @classmethod + def expr(klass): + e = v_integer.copy() + return e.setParseAction(lambda x: klass(*x)) + + def values(self, settings): + return [LiteralGenerator(self.code)] + + +class Reason(_Component): + def __init__(self, value): + self.value = value + + def accept(self, r): + r.reason = self + + @classmethod + def expr(klass): + e = pp.Literal("m").suppress() + e = e + Value + return e.setParseAction(lambda x: klass(*x)) + + def values(self, settings): + return [self.value.get_generator(settings)] + + +class _Action(_Spec): """ 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 @@ -494,6 +532,14 @@ class _Action: def accept(self, r): r.actions.append(self) + @abc.abstractmethod + def spec(self): # pragma: no cover + pass + + @abc.abstractmethod + def intermediate(self): # pragma: no cover + pass + class PauseAt(_Action): def __init__(self, offset, seconds): @@ -561,40 +607,8 @@ class InjectAt(_Action): ) -class Code(_Component): - def __init__(self, code): - self.code = str(code) - - def accept(self, r): - r.code = self - - @classmethod - def expr(klass): - e = v_integer.copy() - return e.setParseAction(lambda x: klass(*x)) - - def values(self, settings): - return [LiteralGenerator(self.code)] - - -class Reason(_Component): - def __init__(self, value): - self.value = value - - def accept(self, r): - r.reason = self - - @classmethod - def expr(klass): - e = pp.Literal("m").suppress() - e = e + Value - return e.setParseAction(lambda x: klass(*x)) - - def values(self, settings): - return [self.value.get_generator(settings)] - - -class Message: +class _Message(object): + __metaclass__ = abc.ABCMeta version = "HTTP/1.1" def __init__(self): self.body = None @@ -713,10 +727,18 @@ class Message: ret[i] = v return ret + @abc.abstractmethod + def preamble(self, settings): # pragma: no cover + pass + + @abc.abstractmethod + def expr(klass): # pragma: no cover + pass + Sep = pp.Optional(pp.Literal(":")).suppress() -class Response(Message): +class Response(_Message): comps = ( Body, Header, @@ -730,7 +752,7 @@ class Response(Message): ) logattrs = ["code", "reason", "version", "body"] def __init__(self): - Message.__init__(self) + _Message.__init__(self) self.code = None self.reason = None @@ -757,7 +779,7 @@ class Response(Message): return resp -class Request(Message): +class Request(_Message): comps = ( Body, Header, @@ -769,7 +791,7 @@ class Request(Message): ) logattrs = ["method", "path", "body"] def __init__(self): - Message.__init__(self) + _Message.__init__(self) self.method = None self.path = None diff --git a/test/test_language.py b/test/test_language.py index 0d3f2685..cb7d7d1b 100644 --- a/test/test_language.py +++ b/test/test_language.py @@ -211,9 +211,9 @@ class TestMisc: class Test_Action: def test_cmp(self): - a = language._Action(0) - b = language._Action(1) - c = language._Action(0) + a = language.DisconnectAt(0) + b = language.DisconnectAt(1) + c = language.DisconnectAt(0) assert a < b assert a == c l = [b, a] -- cgit v1.2.3 From 8741600ce770ac949550a3d7f3eac7cbbfd7456e Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Mon, 29 Oct 2012 15:16:44 +1300 Subject: Refactoring: canonical storage of a message is now the sequence of tokens that defines it. We use a set of accessor properties to ease access to tokens. We can now ditch the .accept() methods. --- libpathod/language.py | 129 ++++++++++++++++++++++++-------------------------- test/test_language.py | 30 ++++-------- 2 files changed, 70 insertions(+), 89 deletions(-) diff --git a/libpathod/language.py b/libpathod/language.py index a74412b6..6fa39f6a 100644 --- a/libpathod/language.py +++ b/libpathod/language.py @@ -309,18 +309,8 @@ class _Spec(object): """ return None - @abc.abstractmethod - def accept(self, r): # pragma: no cover - """ - Notifies the component to register itself with message r. - """ - return None - class Raw(_Spec): - def accept(self, r): - r.raw = True - @classmethod def expr(klass): e = pp.Literal("r").suppress() @@ -357,9 +347,6 @@ class _Header(_Component): "\r\n", ] - def accept(self, r): - r.headers.append(self) - class Header(_Header): @classmethod @@ -397,9 +384,6 @@ class Body(_Component): def __init__(self, value): self.value = value - def accept(self, r): - r.body = self - @classmethod def expr(klass): e = pp.Literal("b").suppress() @@ -418,9 +402,6 @@ class Path(_Component): value = ValueLiteral(value) self.value = value - def accept(self, r): - r.path = self - @classmethod def expr(klass): e = NakedValue.copy() @@ -451,9 +432,6 @@ class Method(_Component): value = ValueLiteral(value.upper()) self.value = value - def accept(self, r): - r.method = self - @classmethod def expr(klass): parts = [pp.CaselessLiteral(i) for i in klass.methods] @@ -472,9 +450,6 @@ class Code(_Component): def __init__(self, code): self.code = str(code) - def accept(self, r): - r.code = self - @classmethod def expr(klass): e = v_integer.copy() @@ -488,8 +463,6 @@ class Reason(_Component): def __init__(self, value): self.value = value - def accept(self, r): - r.reason = self @classmethod def expr(klass): @@ -529,9 +502,6 @@ class _Action(_Spec): def __repr__(self): return self.spec() - def accept(self, r): - r.actions.append(self) - @abc.abstractmethod def spec(self): # pragma: no cover pass @@ -610,11 +580,32 @@ class InjectAt(_Action): class _Message(object): __metaclass__ = abc.ABCMeta version = "HTTP/1.1" - def __init__(self): - self.body = None - self.headers = [] - self.actions = [] - self.raw = False + def __init__(self, tokens): + self.tokens = tokens + + def _get_tokens(self, klass): + return [i for i in self.tokens if isinstance(i, klass)] + + def _get_token(self, klass): + l = self._get_tokens(klass) + if l: + return l[0] + + @property + def raw(self): + return bool(self._get_token(Raw)) + + @property + def actions(self): + return self._get_tokens(_Action) + + @property + def body(self): + return self._get_token(Body) + + @property + def headers(self): + return self._get_tokens(_Header) def length(self, settings, request_host): """ @@ -634,7 +625,7 @@ class _Message(object): Modify this message to be safe for previews. Returns a list of elided actions. """ pauses = [i for i in self.actions if isinstance(i, PauseAt)] - self.actions = [i for i in self.actions if not isinstance(i, PauseAt)] + #self.actions = [i for i in self.actions if not isinstance(i, PauseAt)] return pauses def maximum_length(self, settings, request_host): @@ -738,7 +729,7 @@ class _Message(object): Sep = pp.Optional(pp.Literal(":")).suppress() -class Response(_Message): +class _Response(_Message): comps = ( Body, Header, @@ -751,10 +742,13 @@ class Response(_Message): Reason ) logattrs = ["code", "reason", "version", "body"] - def __init__(self): - _Message.__init__(self) - self.code = None - self.reason = None + @property + def code(self): + return self._get_token(Code) + + @property + def reason(self): + return self._get_token(Reason) def preamble(self, settings): l = [self.version, " "] @@ -779,7 +773,7 @@ class Response(_Message): return resp -class Request(_Message): +class _Request(_Message): comps = ( Body, Header, @@ -790,10 +784,13 @@ class Request(_Message): Raw ) logattrs = ["method", "path", "body"] - def __init__(self): - _Message.__init__(self) - self.method = None - self.path = None + @property + def method(self): + return self._get_token(Method) + + @property + def path(self): + return self._get_token(Path) def preamble(self, settings): v = self.method.values(settings) @@ -818,44 +815,40 @@ class Request(_Message): return resp -class CraftedRequest(Request): - def __init__(self, settings, spec, tokens): - Request.__init__(self) +class CraftedRequest(_Request): + def __init__(self, spec, tokens): + _Request.__init__(self, tokens) self.spec, self.tokens = spec, tokens - for i in tokens: - i.accept(self) def serve(self, fp, settings, host): - d = Request.serve(self, fp, settings, host) + d = _Request.serve(self, fp, settings, host) d["spec"] = self.spec return d -class CraftedResponse(Response): - def __init__(self, settings, spec, tokens): - Response.__init__(self) +class CraftedResponse(_Response): + def __init__(self, spec, tokens): + _Response.__init__(self, tokens) self.spec, self.tokens = spec, tokens - for i in tokens: - i.accept(self) def serve(self, fp, settings): - d = Response.serve(self, fp, settings, None) + d = _Response.serve(self, fp, settings, None) d["spec"] = self.spec return d -class PathodErrorResponse(Response): - def __init__(self, msg, body=None): - Response.__init__(self) - self.code = Code("800") - self.msg = LiteralGenerator(msg) - self.body = Body(ValueLiteral("pathod error: " + (body or msg))) - self.headers = [ +class PathodErrorResponse(_Response): + def __init__(self, reason, body=None): + tokens = [ + Code("800"), Header(ValueLiteral("Content-Type"), ValueLiteral("text/plain")), + Reason(ValueLiteral(reason)), + Body(ValueLiteral("pathod error: " + (body or reason))), ] + _Response.__init__(self, tokens) def serve(self, fp, settings): - d = Response.serve(self, fp, settings, None) + d = _Response.serve(self, fp, settings, None) d["internal"] = True return d @@ -888,7 +881,7 @@ def parse_response(settings, s): if s.startswith(FILESTART): s = read_file(settings, s) try: - return CraftedResponse(settings, s, Response.expr().parseString(s, parseAll=True)) + return CraftedResponse(s, _Response.expr().parseString(s, parseAll=True)) except pp.ParseException, v: raise ParseException(v.msg, v.line, v.col) @@ -904,6 +897,6 @@ def parse_request(settings, s): if s.startswith(FILESTART): s = read_file(settings, s) try: - return CraftedRequest(settings, s, Request.expr().parseString(s, parseAll=True)) + return CraftedRequest(s, _Request.expr().parseString(s, parseAll=True)) except pp.ParseException, v: raise ParseException(v.msg, v.line, v.col) diff --git a/test/test_language.py b/test/test_language.py index cb7d7d1b..009f4ddd 100644 --- a/test/test_language.py +++ b/test/test_language.py @@ -542,32 +542,20 @@ class TestResponse: testlen(language.parse_response({}, "400:m'msg':h'foo'='bar':b@100b")) def test_maximum_length(self): - def testlen(x, actions): + def testlen(x): s = cStringIO.StringIO() m = x.maximum_length({}, None) x.serve(s, {}) assert m >= len(s.getvalue()) - r = language.parse_response({}, "400:m'msg':b@100") - - actions = [ - language.DisconnectAt(0) - ] - r.actions = actions - testlen(r, actions) - - actions = [ - language.DisconnectAt(0), - language.InjectAt(0, language.ValueLiteral("foo")) - ] - r.actions = actions - testlen(r, actions) - - actions = [ - language.InjectAt(0, language.ValueLiteral("foo")) - ] - r.actions = actions - testlen(r, actions) + r = language.parse_response({}, "400:m'msg':b@100:d0") + testlen(r) + + r = language.parse_response({}, "400:m'msg':b@100:d0:i0,'foo'") + testlen(r) + + r = language.parse_response({}, "400:m'msg':b@100:d0:i0,'foo'") + testlen(r) def test_render(self): r = language.parse_response({}, "400:p0,100:dr") -- cgit v1.2.3 From 747eafd10703d5a93003e410469771f883df68b5 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Mon, 29 Oct 2012 16:31:35 +1300 Subject: Add a .spec method to the token ABC, and to all tokens. --- libpathod/language.py | 37 +++++++++++++++++++- test/test_language.py | 93 ++++++++++++++++++++++++++++++++++----------------- 2 files changed, 99 insertions(+), 31 deletions(-) diff --git a/libpathod/language.py b/libpathod/language.py index 6fa39f6a..2dceefa5 100644 --- a/libpathod/language.py +++ b/libpathod/language.py @@ -309,6 +309,12 @@ class _Spec(object): """ return None + def spec(self): # pragma: no cover + """ + A parseable specification for this token. + """ + return None + class Raw(_Spec): @classmethod @@ -316,6 +322,9 @@ class Raw(_Spec): e = pp.Literal("r").suppress() return e.setParseAction(lambda x: klass(*x)) + def spec(self): + return "r" + class _Component(_Spec): """ @@ -357,6 +366,9 @@ class Header(_Header): e += Value return e.setParseAction(lambda x: klass(*x)) + def spec(self): + return "h%s=%s"%(self.key.spec(), self.value.spec()) + class ShortcutContentType(_Header): def __init__(self, value): @@ -368,6 +380,9 @@ class ShortcutContentType(_Header): e = e + Value return e.setParseAction(lambda x: klass(*x)) + def spec(self): + return "c%s"%(self.value.spec()) + class ShortcutLocation(_Header): def __init__(self, value): @@ -379,6 +394,9 @@ class ShortcutLocation(_Header): e = e + Value return e.setParseAction(lambda x: klass(*x)) + def spec(self): + return "l%s"%(self.value.spec()) + class Body(_Component): def __init__(self, value): @@ -395,6 +413,9 @@ class Body(_Component): self.value.get_generator(settings), ] + def spec(self): + return "b%s"%(self.value.spec()) + class Path(_Component): def __init__(self, value): @@ -412,6 +433,9 @@ class Path(_Component): self.value.get_generator(settings), ] + def spec(self): + return "%s"%(self.value.spec()) + class Method(_Component): methods = [ @@ -445,6 +469,12 @@ class Method(_Component): self.value.get_generator(settings) ] + def spec(self): + s = self.value.spec() + if s[1:-1].lower() in self.methods: + s = s[1:-1].lower() + return "%s"%s + class Code(_Component): def __init__(self, code): @@ -458,12 +488,14 @@ class Code(_Component): def values(self, settings): return [LiteralGenerator(self.code)] + def spec(self): + return "%s"%(self.code) + class Reason(_Component): def __init__(self, value): self.value = value - @classmethod def expr(klass): e = pp.Literal("m").suppress() @@ -473,6 +505,9 @@ class Reason(_Component): def values(self, settings): return [self.value.get_generator(settings)] + def spec(self): + return "m%s"%(self.value.spec()) + class _Action(_Spec): """ diff --git a/test/test_language.py b/test/test_language.py index 009f4ddd..c453766c 100644 --- a/test/test_language.py +++ b/test/test_language.py @@ -150,15 +150,28 @@ class TestMisc: e = language.Path("/foo") assert e.value.val == "/foo" + s = e.spec() + assert s == e.expr().parseString(s)[0].spec() + def test_method(self): e = language.Method.expr() assert e.parseString("get")[0].value.val == "GET" assert e.parseString("'foo'")[0].value.val == "foo" assert e.parseString("'get'")[0].value.val == "get" + assert e.parseString("get")[0].spec() == "get" + assert e.parseString("'foo'")[0].spec() == '"foo"' + + s = e.parseString("get")[0].spec() + assert s == e.parseString(s)[0].spec() + + s = e.parseString("'foo'")[0].spec() + assert s == e.parseString(s)[0].spec() + def test_raw(self): - e = language.Raw.expr() - assert e.parseString("r")[0] + e = language.Raw.expr().parseString("r")[0] + assert e + assert e.spec() == "r" def test_body(self): e = language.Body.expr() @@ -172,41 +185,65 @@ class TestMisc: assert v.value.datatype == "digits" assert str(v.value) == "@100g,digits" + s = v.spec() + assert s == e.parseString(s)[0].spec() + + def test_code(self): + e = language.Code.expr() + v = e.parseString("200")[0] + assert v.string() == "200" + assert v.spec() == "200" + + def test_reason(self): + e = language.Reason.expr() + v = e.parseString("m'msg'")[0] + assert v.value.val == "msg" + + s = v.spec() + assert s == e.parseString(s)[0].spec() + + def test_internal_response(self): + d = cStringIO.StringIO() + s = language.PathodErrorResponse("foo") + s.serve(d, {}) + + +class TestHeaders: def test_header(self): e = language.Header.expr() v = e.parseString("h'foo'='bar'")[0] assert v.key.val == "foo" assert v.value.val == "bar" - def test_code(self): - e = language.Code.expr() - v = e.parseString("200")[0] - assert v.string() == "200" + v2 = e.parseString(v.spec())[0] + assert v2.key.val == v.key.val + assert v2.value.val == v.value.val - def _test_reason(self): - v = e.parseString("404'msg'")[0] - assert v.code.string() == "404" - assert v.reason == "msg" + s = v.spec() + assert s == e.parseString(s)[0].spec() - r = e.parseString("200'foo'")[0] - assert r.msg.val == "foo" + def test_ctype_shortcut(self): + e = language.ShortcutContentType.expr() + v = e.parseString("c'foo'")[0] + assert v.key.val == "Content-Type" + assert v.value.val == "foo" - r = e.parseString("200'\"foo\"'")[0] - assert r.msg.val == "\"foo\"" + s = v.spec() + assert s == e.parseString(s)[0].spec() - r = e.parseString('200"foo"')[0] - assert r.msg.val == "foo" + def test_location_shortcut(self): + e = language.ShortcutLocation.expr() + v = e.parseString("l'foo'")[0] + assert v.key.val == "Location" + assert v.value.val == "foo" - r = e.parseString('404')[0] - assert r.msg.val == "Not Found" + s = v.spec() + assert s == e.parseString(s)[0].spec() - r = e.parseString('10')[0] - assert r.msg.val == "Unknown code" + def test_shortcut_content_type(self): + assert language.parse_response({}, "400:c'foo'").headers[0].key.val == "Content-Type" + assert language.parse_response({}, "400:l'foo'").headers[0].key.val == "Location" - def test_internal_response(self): - d = cStringIO.StringIO() - s = language.PathodErrorResponse("foo") - s.serve(d, {}) class Test_Action: @@ -313,11 +350,6 @@ class TestPauses: assert language.PauseAt(0, "f").spec() == "p0,f" -class TestShortcuts: - def test_parse_response(self): - assert language.parse_response({}, "400:c'foo'").headers[0].key.val == "Content-Type" - assert language.parse_response({}, "400:l'foo'").headers[0].key.val == "Location" - class TestParseRequest: def test_file(self): @@ -557,7 +589,8 @@ class TestResponse: r = language.parse_response({}, "400:m'msg':b@100:d0:i0,'foo'") testlen(r) - def test_render(self): + # FIXME + def _test_render(self): r = language.parse_response({}, "400:p0,100:dr") assert r.actions[0].spec() == "p0,100" assert len(r.preview_safe()) == 1 -- cgit v1.2.3 From 3e0cd6442aa3dd5ecc08af4851e68545121737ab Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Mon, 29 Oct 2012 17:33:10 +1300 Subject: Add .spec methods for Request and Response objects. --- libpathod/language.py | 18 +++++++++++------- test/test_language.py | 15 +++++++++++++++ 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/libpathod/language.py b/libpathod/language.py index 2dceefa5..1c010c91 100644 --- a/libpathod/language.py +++ b/libpathod/language.py @@ -242,7 +242,7 @@ class ValueGenerate(_Value): class ValueFile(_Value): def __init__(self, path): - self.path = path + self.path = str(path) @classmethod def expr(klass): @@ -807,6 +807,9 @@ class _Response(_Message): ) return resp + def spec(self): + return ":".join([i.spec() for i in self.tokens]) + class _Request(_Message): comps = ( @@ -849,26 +852,27 @@ class _Request(_Message): ) return resp + def spec(self): + return ":".join([i.spec() for i in self.tokens]) + class CraftedRequest(_Request): def __init__(self, spec, tokens): _Request.__init__(self, tokens) - self.spec, self.tokens = spec, tokens def serve(self, fp, settings, host): d = _Request.serve(self, fp, settings, host) - d["spec"] = self.spec + d["spec"] = self.spec() return d class CraftedResponse(_Response): def __init__(self, spec, tokens): _Response.__init__(self, tokens) - self.spec, self.tokens = spec, tokens def serve(self, fp, settings): d = _Response.serve(self, fp, settings, None) - d["spec"] = self.spec + d["spec"] = self.spec() return d @@ -910,7 +914,7 @@ def parse_response(settings, s): May raise ParseException or FileAccessDenied """ try: - s.decode("ascii") + s = s.decode("ascii") except UnicodeError: raise ParseException("Spec must be valid ASCII.", 0, 0) if s.startswith(FILESTART): @@ -926,7 +930,7 @@ def parse_request(settings, s): May raise ParseException or FileAccessDenied """ try: - s.decode("ascii") + s = s.decode("ascii") except UnicodeError: raise ParseException("Spec must be valid ASCII.", 0, 0) if s.startswith(FILESTART): diff --git a/test/test_language.py b/test/test_language.py index c453766c..219b2909 100644 --- a/test/test_language.py +++ b/test/test_language.py @@ -406,6 +406,13 @@ class TestParseRequest: assert r.path.string().endswith("bar") assert r.actions + def test_spec(self): + def rt(s): + s = language.parse_request({}, s).spec() + assert language.parse_request({}, s).spec() == s + rt("get:/foo") + rt("get:/foo:da") + class TestParseResponse: def test_parse_err(self): @@ -439,6 +446,14 @@ class TestParseResponse: r = language.parse_response({}, "400:b@100g") assert r.length({}, None) + def test_spec(self): + def rt(s): + s = language.parse_response({}, s).spec() + assert language.parse_response({}, s).spec() == s + rt("400:b@100g") + rt("400") + rt("400:da") + class TestWriteValues: def test_send_chunk(self): -- cgit v1.2.3 From a1f782b543567ce2e88cd8b5defae64b3cbc89d3 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Tue, 30 Oct 2012 12:36:38 +1300 Subject: Simplify Request/Response class hierarchy. --- libpathod/language.py | 92 ++++++++++++++++++++------------------------------- test/test_language.py | 3 +- 2 files changed, 36 insertions(+), 59 deletions(-) diff --git a/libpathod/language.py b/libpathod/language.py index 1c010c91..f7909b81 100644 --- a/libpathod/language.py +++ b/libpathod/language.py @@ -165,21 +165,29 @@ class FileGenerator: return "<%s"%self.path -class _Value(object): +class _Token(object): + """ + A specification token. + """ __metaclass__ = abc.ABCMeta - def __repr__(self): - return self.spec() - @abc.abstractmethod - def spec(self): # pragma: no cover + def expr(klass): # pragma: no cover + """ + A parse expression. + """ return None - @abc.abstractmethod - def expr(self): # pragma: no cover + def spec(self): # pragma: no cover + """ + A parseable specification for this token. + """ return None + def __repr__(self): + return self.spec() + -class _ValueLiteral(_Value): +class _ValueLiteral(_Token): def __init__(self, val): self.val = val.decode("string_escape") @@ -207,7 +215,7 @@ class ValueNakedLiteral(_ValueLiteral): return self.val.encode("string_escape") -class ValueGenerate(_Value): +class ValueGenerate(_Token): def __init__(self, usize, unit, datatype): if not unit: unit = "b" @@ -240,7 +248,7 @@ class ValueGenerate(_Value): return s -class ValueFile(_Value): +class ValueFile(_Token): def __init__(self, path): self.path = str(path) @@ -297,26 +305,7 @@ Offset = pp.MatchFirst( ) -class _Spec(object): - """ - A specification token. - """ - __metaclass__ = abc.ABCMeta - @abc.abstractmethod - def expr(klass): # pragma: no cover - """ - A parse expression. - """ - return None - - def spec(self): # pragma: no cover - """ - A parseable specification for this token. - """ - return None - - -class Raw(_Spec): +class Raw(_Token): @classmethod def expr(klass): e = pp.Literal("r").suppress() @@ -326,7 +315,7 @@ class Raw(_Spec): return "r" -class _Component(_Spec): +class _Component(_Token): """ A value component of the primary specification of an HTTP message. """ @@ -509,7 +498,7 @@ class Reason(_Component): return "m%s"%(self.value.spec()) -class _Action(_Spec): +class _Action(_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 @@ -540,7 +529,7 @@ class _Action(_Spec): @abc.abstractmethod def spec(self): # pragma: no cover pass - + @abc.abstractmethod def intermediate(self): # pragma: no cover pass @@ -751,6 +740,7 @@ class _Message(object): v = v[:TRUNCATE] v = v.encode("string_escape") ret[i] = v + ret["spec"] = self.spec() return ret @abc.abstractmethod @@ -764,7 +754,7 @@ class _Message(object): Sep = pp.Optional(pp.Literal(":")).suppress() -class _Response(_Message): +class Response(_Message): comps = ( Body, Header, @@ -810,8 +800,12 @@ class _Response(_Message): def spec(self): return ":".join([i.spec() for i in self.tokens]) + def serve(self, fp, settings): + d = _Message.serve(self, fp, settings, None) + return d + -class _Request(_Message): +class Request(_Message): comps = ( Body, Header, @@ -855,28 +849,12 @@ class _Request(_Message): def spec(self): return ":".join([i.spec() for i in self.tokens]) - -class CraftedRequest(_Request): - def __init__(self, spec, tokens): - _Request.__init__(self, tokens) - def serve(self, fp, settings, host): - d = _Request.serve(self, fp, settings, host) - d["spec"] = self.spec() - return d - - -class CraftedResponse(_Response): - def __init__(self, spec, tokens): - _Response.__init__(self, tokens) - - def serve(self, fp, settings): - d = _Response.serve(self, fp, settings, None) - d["spec"] = self.spec() + d = _Message.serve(self, fp, settings, host) return d -class PathodErrorResponse(_Response): +class PathodErrorResponse(Response): def __init__(self, reason, body=None): tokens = [ Code("800"), @@ -884,10 +862,10 @@ class PathodErrorResponse(_Response): Reason(ValueLiteral(reason)), Body(ValueLiteral("pathod error: " + (body or reason))), ] - _Response.__init__(self, tokens) + Response.__init__(self, tokens) def serve(self, fp, settings): - d = _Response.serve(self, fp, settings, None) + d = Response.serve(self, fp, settings) d["internal"] = True return d @@ -920,7 +898,7 @@ def parse_response(settings, s): if s.startswith(FILESTART): s = read_file(settings, s) try: - return CraftedResponse(s, _Response.expr().parseString(s, parseAll=True)) + return Response(Response.expr().parseString(s, parseAll=True)) except pp.ParseException, v: raise ParseException(v.msg, v.line, v.col) @@ -936,6 +914,6 @@ def parse_request(settings, s): if s.startswith(FILESTART): s = read_file(settings, s) try: - return CraftedRequest(s, _Request.expr().parseString(s, parseAll=True)) + return Request(Request.expr().parseString(s, parseAll=True)) except pp.ParseException, v: raise ParseException(v.msg, v.line, v.col) diff --git a/test/test_language.py b/test/test_language.py index 219b2909..6d9fa31d 100644 --- a/test/test_language.py +++ b/test/test_language.py @@ -604,8 +604,7 @@ class TestResponse: r = language.parse_response({}, "400:m'msg':b@100:d0:i0,'foo'") testlen(r) - # FIXME - def _test_render(self): + def test_render(self): r = language.parse_response({}, "400:p0,100:dr") assert r.actions[0].spec() == "p0,100" assert len(r.preview_safe()) == 1 -- cgit v1.2.3 From 677f0e0580f92d1244285b9f0481c3ace3b14ee1 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Tue, 30 Oct 2012 13:36:32 +1300 Subject: Change preview_safe to return a safe copy of the current message. --- libpathod/app.py | 9 ++++----- libpathod/language.py | 9 ++++----- libpathod/templates/request_preview.html | 4 +--- libpathod/templates/response_preview.html | 4 +--- test/test_language.py | 6 +++--- 5 files changed, 13 insertions(+), 19 deletions(-) diff --git a/libpathod/app.py b/libpathod/app.py index 38d0be33..e073921c 100644 --- a/libpathod/app.py +++ b/libpathod/app.py @@ -108,7 +108,6 @@ def _preview(is_request): section = "main", syntaxerror = None, error = None, - pauses = None ) if not spec.strip(): args["error"] = "Can't parse an empty spec." @@ -125,16 +124,16 @@ def _preview(is_request): return render(template, False, **args) s = cStringIO.StringIO() - args["pauses"] = r.preview_safe() + safe = r.preview_safe() - c = app.config["pathod"].check_policy(r, app.config["pathod"].request_settings) + c = app.config["pathod"].check_policy(safe, app.config["pathod"].request_settings) if c: args["error"] = c return render(template, False, **args) if is_request: - r.serve(s, app.config["pathod"].request_settings, host="example.com") + safe.serve(s, app.config["pathod"].request_settings, host="example.com") else: - r.serve(s, app.config["pathod"].request_settings) + safe.serve(s, app.config["pathod"].request_settings) args["output"] = utils.escape_unprintables(s.getvalue()) return render(template, False, **args) diff --git a/libpathod/language.py b/libpathod/language.py index f7909b81..ba462abe 100644 --- a/libpathod/language.py +++ b/libpathod/language.py @@ -167,7 +167,7 @@ class FileGenerator: class _Token(object): """ - A specification token. + A specification token. Tokens are immutable. """ __metaclass__ = abc.ABCMeta @abc.abstractmethod @@ -646,11 +646,10 @@ class _Message(object): def preview_safe(self): """ - Modify this message to be safe for previews. Returns a list of elided actions. + Return a copy of this message that issafe for previews. """ - pauses = [i for i in self.actions if isinstance(i, PauseAt)] - #self.actions = [i for i in self.actions if not isinstance(i, PauseAt)] - return pauses + tokens = [i for i in self.tokens if not isinstance(i, PauseAt)] + return self.__class__(tokens) def maximum_length(self, settings, request_host): """ diff --git a/libpathod/templates/request_preview.html b/libpathod/templates/request_preview.html index e4efd9d0..db08580e 100644 --- a/libpathod/templates/request_preview.html +++ b/libpathod/templates/request_preview.html @@ -40,9 +40,7 @@
{{ output }}
- {% if pauses %} -

Note: pauses are skipped when generating previews!

- {% endif %} +

Note: pauses are skipped when generating previews!

{% endif %} diff --git a/libpathod/templates/response_preview.html b/libpathod/templates/response_preview.html index c725bcf8..bb72513d 100644 --- a/libpathod/templates/response_preview.html +++ b/libpathod/templates/response_preview.html @@ -40,9 +40,7 @@
{{ output }}
- {% if pauses %} -

Note: pauses are skipped when generating previews!

- {% endif %} +

Note: pauses are skipped when generating previews!

{% endif %} diff --git a/test/test_language.py b/test/test_language.py index 6d9fa31d..d0f43198 100644 --- a/test/test_language.py +++ b/test/test_language.py @@ -606,9 +606,9 @@ class TestResponse: def test_render(self): r = language.parse_response({}, "400:p0,100:dr") - assert r.actions[0].spec() == "p0,100" - assert len(r.preview_safe()) == 1 - assert not r.actions[0].spec().startswith("p") + assert "p0" in r.spec() + s = r.preview_safe() + assert not "p0" in s.spec() -- cgit v1.2.3 From b2deb470dea19358e2b391d459132d848d7fadde Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Tue, 30 Oct 2012 14:46:18 +1300 Subject: Extract .serve() method from message classes. --- libpathod/app.py | 4 +- libpathod/language.py | 112 +++++++++++++++++++++++++------------------------- libpathod/pathoc.py | 4 +- libpathod/pathod.py | 6 +-- test/test_language.py | 22 +++++----- 5 files changed, 73 insertions(+), 75 deletions(-) diff --git a/libpathod/app.py b/libpathod/app.py index e073921c..1fcfa078 100644 --- a/libpathod/app.py +++ b/libpathod/app.py @@ -131,9 +131,9 @@ def _preview(is_request): args["error"] = c return render(template, False, **args) if is_request: - safe.serve(s, app.config["pathod"].request_settings, host="example.com") + language.serve(safe, s, app.config["pathod"].request_settings, "example.com") else: - safe.serve(s, app.config["pathod"].request_settings) + language.serve(safe, s, app.config["pathod"].request_settings, None) args["output"] = utils.escape_unprintables(s.getvalue()) return render(template, False, **args) diff --git a/libpathod/language.py b/libpathod/language.py index ba462abe..a93d4dca 100644 --- a/libpathod/language.py +++ b/libpathod/language.py @@ -75,6 +75,51 @@ def write_values(fp, vals, actions, sofar=0, skip=0, blocksize=BLOCKSIZE): return True +def serve(msg, fp, settings, request_host=None): + """ + 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. + """ + started = time.time() + + hdrs = msg.headervals(settings, request_host) + + vals = msg.preamble(settings) + vals.append("\r\n") + vals.extend(hdrs) + vals.append("\r\n") + if msg.body: + vals.append(msg.body.value.get_generator(settings)) + vals.reverse() + actions = msg.ready_actions(settings, request_host) + + disconnect = write_values(fp, vals, actions[:]) + duration = time.time() - started + ret = dict( + disconnect = disconnect, + started = started, + duration = duration, + ) + for i in msg.logattrs: + v = getattr(msg, i) + # Careful not to log any VALUE specs without sanitizing them first. We truncate at 1k. + if hasattr(v, "values"): + v = [x[:TRUNCATE] for x in v.values(settings)] + v = "".join(v).encode("string_escape") + elif hasattr(v, "__len__"): + v = v[:TRUNCATE] + v = v.encode("string_escape") + ret[i] = v + ret["spec"] = msg.spec() + ret.update(msg.logflags) + return ret + + DATATYPES = dict( ascii_letters = string.ascii_letters, ascii_lowercase = string.ascii_lowercase, @@ -183,6 +228,13 @@ class _Token(object): """ return None + def resolve(self, msg): # pragma: no cover + """ + Resolves this token to ready it for transmission. This means that + the calculated offsets of actions are fixed. + """ + return self + def __repr__(self): return self.spec() @@ -679,7 +731,6 @@ class _Message(object): ValueLiteral(request_host) ) ) - else: if not utils.get_header("Date", self.headers): hdrs.append( @@ -699,49 +750,6 @@ class _Message(object): actions.reverse() return [i.intermediate(settings) for i in actions] - def serve(self, fp, settings, request_host): - """ - 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. - """ - started = time.time() - - hdrs = self.headervals(settings, request_host) - - vals = self.preamble(settings) - vals.append("\r\n") - vals.extend(hdrs) - vals.append("\r\n") - if self.body: - vals.append(self.body.value.get_generator(settings)) - vals.reverse() - actions = self.ready_actions(settings, request_host) - - disconnect = write_values(fp, vals, actions[:]) - duration = time.time() - started - ret = dict( - disconnect = disconnect, - started = started, - duration = duration, - ) - 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[:TRUNCATE] for x in v.values(settings)] - v = "".join(v).encode("string_escape") - elif hasattr(v, "__len__"): - v = v[:TRUNCATE] - v = v.encode("string_escape") - ret[i] = v - ret["spec"] = self.spec() - return ret - @abc.abstractmethod def preamble(self, settings): # pragma: no cover pass @@ -766,6 +774,7 @@ class Response(_Message): Reason ) logattrs = ["code", "reason", "version", "body"] + logflags = dict() @property def code(self): return self._get_token(Code) @@ -799,10 +808,6 @@ class Response(_Message): def spec(self): return ":".join([i.spec() for i in self.tokens]) - def serve(self, fp, settings): - d = _Message.serve(self, fp, settings, None) - return d - class Request(_Message): comps = ( @@ -815,6 +820,7 @@ class Request(_Message): Raw ) logattrs = ["method", "path", "body"] + logflags = dict() @property def method(self): return self._get_token(Method) @@ -848,12 +854,9 @@ class Request(_Message): def spec(self): return ":".join([i.spec() for i in self.tokens]) - def serve(self, fp, settings, host): - d = _Message.serve(self, fp, settings, host) - return d - class PathodErrorResponse(Response): + logflags = dict(internal=True) def __init__(self, reason, body=None): tokens = [ Code("800"), @@ -863,11 +866,6 @@ class PathodErrorResponse(Response): ] Response.__init__(self, tokens) - def serve(self, fp, settings): - d = Response.serve(self, fp, settings) - d["internal"] = True - return d - FILESTART = "+" def read_file(settings, s): diff --git a/libpathod/pathoc.py b/libpathod/pathoc.py index 4e592a06..b4020a3f 100644 --- a/libpathod/pathoc.py +++ b/libpathod/pathoc.py @@ -22,7 +22,7 @@ class Pathoc(tcp.TCPClient): language.FileAccessDenied. """ r = language.parse_request(self.settings, spec) - ret = r.serve(self.wfile, self.settings, self.host) + ret = language.serve(r, self.wfile, self.settings, self.host) self.wfile.flush() return http.read_response(self.rfile, r.method, None) @@ -68,7 +68,7 @@ class Pathoc(tcp.TCPClient): if showresp: self.rfile.start_log() try: - req = r.serve(self.wfile, self.settings, self.host) + req = language.serve(r, self.wfile, self.settings, self.host) self.wfile.flush() resp = http.read_response(self.rfile, r.method, None) except http.HttpError, v: diff --git a/libpathod/pathod.py b/libpathod/pathod.py index 131dbc3c..b3a32ef9 100644 --- a/libpathod/pathod.py +++ b/libpathod/pathod.py @@ -21,14 +21,14 @@ class PathodHandler(tcp.BaseHandler): c = self.server.check_policy(crafted, self.server.request_settings) if c: err = language.PathodErrorResponse(c) - err.serve(self.wfile, self.server.request_settings) + language.serve(err, self.wfile, self.server.request_settings) log = dict( type = "error", msg = c ) return False, log - response_log = crafted.serve(self.wfile, self.server.request_settings) + response_log = language.serve(crafted, self.wfile, self.server.request_settings, None) log = dict( type = "crafted", request=request_log, @@ -103,7 +103,7 @@ class PathodHandler(tcp.BaseHandler): return self.serve_crafted(crafted, request_log) elif self.server.noweb: crafted = language.PathodErrorResponse("Access Denied") - crafted.serve(self.wfile, self.server.request_settings) + language.serve(crafted, self.wfile, self.server.request_settings) return False, dict(type = "error", msg="Access denied: web interface disabled") else: self.info("app: %s %s"%(method, path)) diff --git a/test/test_language.py b/test/test_language.py index d0f43198..56c2249d 100644 --- a/test/test_language.py +++ b/test/test_language.py @@ -205,7 +205,7 @@ class TestMisc: def test_internal_response(self): d = cStringIO.StringIO() s = language.PathodErrorResponse("foo") - s.serve(d, {}) + language.serve(s, d, {}) class TestHeaders: @@ -316,7 +316,7 @@ class TestInject: def test_serve(self): s = cStringIO.StringIO() r = language.parse_response({}, "400:i0,'foo'") - assert r.serve(s, {}) + assert language.serve(r, s, {}) def test_spec(self): e = language.InjectAt.expr() @@ -376,7 +376,7 @@ class TestParseRequest: def test_render(self): s = cStringIO.StringIO() r = language.parse_request({}, "GET:'/foo'") - assert r.serve(s, {}, "foo.com") + assert language.serve(r, s, {}, "foo.com") def test_multiline(self): l = """ @@ -522,15 +522,15 @@ class TestWriteValues: def test_write_values_after(self): s = cStringIO.StringIO() r = language.parse_response({}, "400:da") - r.serve(s, {}) + language.serve(r, s, {}) s = cStringIO.StringIO() r = language.parse_response({}, "400:pa,0") - r.serve(s, {}) + language.serve(r, s, {}) s = cStringIO.StringIO() r = language.parse_response({}, "400:ia,'xx'") - r.serve(s, {}) + language.serve(r, s, {}) assert s.getvalue().endswith('xx') @@ -562,19 +562,19 @@ class TestResponse: def test_render(self): s = cStringIO.StringIO() r = language.parse_response({}, "400:m'msg'") - assert r.serve(s, {}) + assert language.serve(r, s, {}) def test_raw(self): s = cStringIO.StringIO() r = language.parse_response({}, "400:b'foo'") - r.serve(s, {}) + language.serve(r, s, {}) v = s.getvalue() assert "Content-Length" in v assert "Date" in v s = cStringIO.StringIO() r = language.parse_response({}, "400:b'foo':r") - r.serve(s, {}) + language.serve(r, s, {}) v = s.getvalue() assert not "Content-Length" in v assert not "Date" in v @@ -582,7 +582,7 @@ class TestResponse: def test_length(self): def testlen(x): s = cStringIO.StringIO() - x.serve(s, {}) + language.serve(x, s, {}) assert x.length({}, None) == len(s.getvalue()) testlen(language.parse_response({}, "400:m'msg'")) testlen(language.parse_response({}, "400:m'msg':h'foo'='bar'")) @@ -592,7 +592,7 @@ class TestResponse: def testlen(x): s = cStringIO.StringIO() m = x.maximum_length({}, None) - x.serve(s, {}) + language.serve(x, s, {}) assert m >= len(s.getvalue()) r = language.parse_response({}, "400:m'msg':b@100:d0") -- cgit v1.2.3 From a09584b9e65d42b4a0f63be7d4e55c113d5fbc89 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Tue, 30 Oct 2012 15:22:53 +1300 Subject: Make Message classes more self-contained. --- libpathod/language.py | 84 +++++++++++++++++++++++++++------------------------ test/test_language.py | 12 ++++---- 2 files changed, 51 insertions(+), 45 deletions(-) diff --git a/libpathod/language.py b/libpathod/language.py index a93d4dca..03cf66c7 100644 --- a/libpathod/language.py +++ b/libpathod/language.py @@ -85,6 +85,7 @@ def serve(msg, fp, settings, request_host=None): Calling this function may modify the object. """ + msg = msg.resolve(settings, request_host) started = time.time() hdrs = msg.headervals(settings, request_host) @@ -96,7 +97,11 @@ def serve(msg, fp, settings, request_host=None): if msg.body: vals.append(msg.body.value.get_generator(settings)) vals.reverse() - actions = msg.ready_actions(settings, request_host) + + actions = msg.actions[:] + actions.sort() + actions.reverse() + actions = [i.intermediate(settings) for i in actions] disconnect = write_values(fp, vals, actions[:]) duration = time.time() - started @@ -105,18 +110,7 @@ def serve(msg, fp, settings, request_host=None): started = started, duration = duration, ) - for i in msg.logattrs: - v = getattr(msg, i) - # Careful not to log any VALUE specs without sanitizing them first. We truncate at 1k. - if hasattr(v, "values"): - v = [x[:TRUNCATE] for x in v.values(settings)] - v = "".join(v).encode("string_escape") - elif hasattr(v, "__len__"): - v = v[:TRUNCATE] - v = v.encode("string_escape") - ret[i] = v - ret["spec"] = msg.spec() - ret.update(msg.logflags) + ret.update(msg.log(settings)) return ret @@ -228,7 +222,7 @@ class _Token(object): """ return None - def resolve(self, msg): # pragma: no cover + def resolve(self, msg, settings, request_host): # pragma: no cover """ Resolves this token to ready it for transmission. This means that the calculated offsets of actions are fixed. @@ -559,7 +553,7 @@ class _Action(_Token): def __init__(self, offset): self.offset = offset - def resolve_offset(self, msg, settings, request_host): + def resolve(self, msg, settings, request_host): """ Resolves offset specifications to a numeric offset. Returns a copy of the action object. @@ -713,11 +707,11 @@ class _Message(object): l += len(i.value.get_generator(settings)) return l - def headervals(self, settings, request_host): - hdrs = self.headers[:] + def resolve(self, settings, request_host): + tokens = self.tokens[:] if not self.raw: if self.body and not utils.get_header("Content-Length", self.headers): - hdrs.append( + tokens.append( Header( ValueLiteral("Content-Length"), ValueLiteral(str(len(self.body.value.get_generator(settings)))), @@ -725,7 +719,7 @@ class _Message(object): ) if request_host: if not utils.get_header("Host", self.headers): - hdrs.append( + tokens.append( Header( ValueLiteral("Host"), ValueLiteral(request_host) @@ -733,23 +727,21 @@ class _Message(object): ) else: if not utils.get_header("Date", self.headers): - hdrs.append( + tokens.append( Header( ValueLiteral("Date"), ValueLiteral(formatdate(timeval=None, localtime=False, usegmt=True)) ) ) + intermediate = self.__class__(tokens) + return self.__class__([i.resolve(intermediate, settings, request_host) for i in tokens]) + + def headervals(self, settings, request_host): values = [] - for h in hdrs: + for h in self.headers: values.extend(h.values(settings)) return values - def ready_actions(self, settings, request_host): - actions = [i.resolve_offset(self, settings, request_host) for i in self.actions] - actions.sort() - actions.reverse() - return [i.intermediate(settings) for i in actions] - @abc.abstractmethod def preamble(self, settings): # pragma: no cover pass @@ -758,6 +750,24 @@ class _Message(object): def expr(klass): # 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[:TRUNCATE] for x in v.values(settings)] + v = "".join(v).encode("string_escape") + elif hasattr(v, "__len__"): + v = v[:TRUNCATE] + v = v.encode("string_escape") + ret[i] = v + ret["spec"] = self.spec() + return ret + Sep = pp.Optional(pp.Literal(":")).suppress() @@ -774,7 +784,6 @@ class Response(_Message): Reason ) logattrs = ["code", "reason", "version", "body"] - logflags = dict() @property def code(self): return self._get_token(Code) @@ -820,7 +829,6 @@ class Request(_Message): Raw ) logattrs = ["method", "path", "body"] - logflags = dict() @property def method(self): return self._get_token(Method) @@ -855,16 +863,14 @@ class Request(_Message): return ":".join([i.spec() for i in self.tokens]) -class PathodErrorResponse(Response): - logflags = dict(internal=True) - def __init__(self, reason, body=None): - tokens = [ - Code("800"), - Header(ValueLiteral("Content-Type"), ValueLiteral("text/plain")), - Reason(ValueLiteral(reason)), - Body(ValueLiteral("pathod error: " + (body or reason))), - ] - Response.__init__(self, tokens) +def PathodErrorResponse(reason, body=None): + tokens = [ + Code("800"), + Header(ValueLiteral("Content-Type"), ValueLiteral("text/plain")), + Reason(ValueLiteral(reason)), + Body(ValueLiteral("pathod error: " + (body or reason))), + ] + return Response(tokens) FILESTART = "+" diff --git a/test/test_language.py b/test/test_language.py index 56c2249d..0711a02e 100644 --- a/test/test_language.py +++ b/test/test_language.py @@ -257,10 +257,10 @@ class Test_Action: l.sort() assert l[0].offset == 0 - def test_resolve_offset(self): + def test_resolve(self): r = language.parse_request({}, 'GET:"/foo"') e = language.DisconnectAt("r") - ret = e.resolve_offset(r, {}, None) + ret = e.resolve(r, {}, None) assert isinstance(ret.offset, int) def test_repr(self): @@ -530,7 +530,7 @@ class TestWriteValues: s = cStringIO.StringIO() r = language.parse_response({}, "400:ia,'xx'") - language.serve(r, s, {}) + language.serve(r, s, {}) assert s.getvalue().endswith('xx') @@ -584,9 +584,9 @@ class TestResponse: s = cStringIO.StringIO() language.serve(x, s, {}) assert x.length({}, None) == len(s.getvalue()) - testlen(language.parse_response({}, "400:m'msg'")) - testlen(language.parse_response({}, "400:m'msg':h'foo'='bar'")) - testlen(language.parse_response({}, "400:m'msg':h'foo'='bar':b@100b")) + testlen(language.parse_response({}, "400:m'msg':r")) + testlen(language.parse_response({}, "400:m'msg':h'foo'='bar':r")) + testlen(language.parse_response({}, "400:m'msg':h'foo'='bar':b@100b:r")) def test_maximum_length(self): def testlen(x): -- cgit v1.2.3 From f8df0a1e75422e5ffed1c7131d0c017afa227faa Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Tue, 30 Oct 2012 16:04:48 +1300 Subject: Add a .values method to messages, simplify a lot of stuff as a consequence. --- libpathod/language.py | 48 +++++++++++++++++++----------------------------- libpathod/pathod.py | 2 +- test/test_language.py | 8 ++++---- 3 files changed, 24 insertions(+), 34 deletions(-) diff --git a/libpathod/language.py b/libpathod/language.py index 03cf66c7..706dd6b7 100644 --- a/libpathod/language.py +++ b/libpathod/language.py @@ -88,14 +88,7 @@ def serve(msg, fp, settings, request_host=None): msg = msg.resolve(settings, request_host) started = time.time() - hdrs = msg.headervals(settings, request_host) - - vals = msg.preamble(settings) - vals.append("\r\n") - vals.extend(hdrs) - vals.append("\r\n") - if msg.body: - vals.append(msg.body.value.get_generator(settings)) + vals = msg.values(settings) vals.reverse() actions = msg.actions[:] @@ -222,7 +215,7 @@ class _Token(object): """ return None - def resolve(self, msg, settings, request_host): # pragma: no cover + def resolve(self, msg, settings): # pragma: no cover """ Resolves this token to ready it for transmission. This means that the calculated offsets of actions are fixed. @@ -553,13 +546,13 @@ class _Action(_Token): def __init__(self, offset): self.offset = offset - def resolve(self, msg, settings, request_host): + def resolve(self, msg, settings): """ Resolves offset specifications to a numeric offset. Returns a copy of the action object. """ c = copy.copy(self) - l = msg.length(settings, request_host) + l = msg.length(settings) if c.offset == "r": c.offset = random.randrange(l) elif c.offset == "a": @@ -677,18 +670,11 @@ class _Message(object): def headers(self): return self._get_tokens(_Header) - def length(self, settings, request_host): + def length(self, settings): """ Calculate the length of the base message without any applied actions. """ - l = sum(len(x) for x in self.preamble(settings)) - l += 2 - for h in self.headervals(settings, request_host): - l += len(h) - l += 2 - if self.body: - l += len(self.body.value.get_generator(settings)) - return l + return sum(len(x) for x in self.values(settings)) def preview_safe(self): """ @@ -697,11 +683,11 @@ class _Message(object): tokens = [i for i in self.tokens if not isinstance(i, PauseAt)] return self.__class__(tokens) - def maximum_length(self, settings, request_host): + def maximum_length(self, settings): """ Calculate the maximum length of the base message with all applied actions. """ - l = self.length(settings, request_host) + l = self.length(settings) for i in self.actions: if isinstance(i, InjectAt): l += len(i.value.get_generator(settings)) @@ -734,13 +720,7 @@ class _Message(object): ) ) intermediate = self.__class__(tokens) - return self.__class__([i.resolve(intermediate, settings, request_host) for i in tokens]) - - def headervals(self, settings, request_host): - values = [] - for h in self.headers: - values.extend(h.values(settings)) - return values + return self.__class__([i.resolve(intermediate, settings) for i in tokens]) @abc.abstractmethod def preamble(self, settings): # pragma: no cover @@ -768,6 +748,16 @@ class _Message(object): ret["spec"] = self.spec() return ret + 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.append(self.body.value.get_generator(settings)) + return vals + Sep = pp.Optional(pp.Literal(":")).suppress() diff --git a/libpathod/pathod.py b/libpathod/pathod.py index b3a32ef9..fb6a7725 100644 --- a/libpathod/pathod.py +++ b/libpathod/pathod.py @@ -217,7 +217,7 @@ class Pathod(tcp.TCPServer): A policy check that verifies the request size is withing limits. """ try: - l = req.maximum_length(settings, None) + l = req.maximum_length(settings) except language.FileAccessDenied, v: return "File access denied." if self.sizelimit and l > self.sizelimit: diff --git a/test/test_language.py b/test/test_language.py index 0711a02e..688dfc8c 100644 --- a/test/test_language.py +++ b/test/test_language.py @@ -260,7 +260,7 @@ class Test_Action: def test_resolve(self): r = language.parse_request({}, 'GET:"/foo"') e = language.DisconnectAt("r") - ret = e.resolve(r, {}, None) + ret = e.resolve(r, {}) assert isinstance(ret.offset, int) def test_repr(self): @@ -444,7 +444,7 @@ class TestParseResponse: def test_parse_stress(self): r = language.parse_response({}, "400:b@100g") - assert r.length({}, None) + assert r.length({}) def test_spec(self): def rt(s): @@ -583,7 +583,7 @@ class TestResponse: def testlen(x): s = cStringIO.StringIO() language.serve(x, s, {}) - assert x.length({}, None) == len(s.getvalue()) + assert x.length({}) == len(s.getvalue()) testlen(language.parse_response({}, "400:m'msg':r")) testlen(language.parse_response({}, "400:m'msg':h'foo'='bar':r")) testlen(language.parse_response({}, "400:m'msg':h'foo'='bar':b@100b:r")) @@ -591,7 +591,7 @@ class TestResponse: def test_maximum_length(self): def testlen(x): s = cStringIO.StringIO() - m = x.maximum_length({}, None) + m = x.maximum_length({}) language.serve(x, s, {}) assert m >= len(s.getvalue()) -- cgit v1.2.3 From 0c9cfb3f381e2c9df5f37dd451b871eac0bdfbd7 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Wed, 31 Oct 2012 09:32:21 +1300 Subject: Add a .freeze() method to all components. This expands and freezes all randomly generated values. The message returned can be queried for a precise spec to reproduce the message. --- libpathod/language.py | 53 ++++++++++++++++ test/test_language.py | 163 ++++++++++++++++++++++++++++++++++++-------------- 2 files changed, 171 insertions(+), 45 deletions(-) diff --git a/libpathod/language.py b/libpathod/language.py index 706dd6b7..0b49c2de 100644 --- a/libpathod/language.py +++ b/libpathod/language.py @@ -209,6 +209,7 @@ class _Token(object): """ return None + @abc.abstractmethod def spec(self): # pragma: no cover """ A parseable specification for this token. @@ -233,6 +234,9 @@ class _ValueLiteral(_Token): def get_generator(self, settings): return LiteralGenerator(self.val) + def freeze(self, settings): + return self + class ValueLiteral(_ValueLiteral): @classmethod @@ -266,6 +270,10 @@ class ValueGenerate(_Token): def get_generator(self, settings): return RandomGenerator(self.datatype, self.bytes()) + def freeze(self, settings): + g = self.get_generator(settings) + return ValueLiteral(g[:].encode("string_escape")) + @classmethod def expr(klass): e = pp.Literal("@").suppress() + v_integer @@ -297,6 +305,9 @@ class ValueFile(_Token): e = e + v_naked_literal return e.setParseAction(lambda x: klass(*x)) + def freeze(self, settings): + return self + def get_generator(self, settings): uf = settings.get("unconstrained_file_access") sd = settings.get("staticdir") @@ -353,6 +364,9 @@ class Raw(_Token): def spec(self): return "r" + def freeze(self, settings): + return self + class _Component(_Token): """ @@ -397,6 +411,9 @@ class Header(_Header): def spec(self): return "h%s=%s"%(self.key.spec(), self.value.spec()) + def freeze(self, settings): + return Header(self.key.freeze(settings), self.value.freeze(settings)) + class ShortcutContentType(_Header): def __init__(self, value): @@ -411,6 +428,9 @@ class ShortcutContentType(_Header): def spec(self): return "c%s"%(self.value.spec()) + def freeze(self, settings): + return ShortcutContentType(self.value.freeze(settings)) + class ShortcutLocation(_Header): def __init__(self, value): @@ -425,6 +445,9 @@ class ShortcutLocation(_Header): def spec(self): return "l%s"%(self.value.spec()) + def freeze(self, settings): + return ShortcutLocation(self.value.freeze(settings)) + class Body(_Component): def __init__(self, value): @@ -444,6 +467,9 @@ class Body(_Component): def spec(self): return "b%s"%(self.value.spec()) + def freeze(self, settings): + return Body(self.value.freeze(settings)) + class Path(_Component): def __init__(self, value): @@ -464,6 +490,9 @@ class Path(_Component): def spec(self): return "%s"%(self.value.spec()) + def freeze(self, settings): + return Path(self.value.freeze(settings)) + class Method(_Component): methods = [ @@ -503,6 +532,9 @@ class Method(_Component): s = s[1:-1].lower() return "%s"%s + def freeze(self, settings): + return Method(self.value.freeze(settings)) + class Code(_Component): def __init__(self, code): @@ -519,6 +551,9 @@ class Code(_Component): def spec(self): return "%s"%(self.code) + def freeze(self, settings): + return Code(self.code) + class Reason(_Component): def __init__(self, value): @@ -536,6 +571,9 @@ class Reason(_Component): def spec(self): return "m%s"%(self.value.spec()) + def freeze(self, settings): + return Reason(self.value.freeze(settings)) + class _Action(_Token): """ @@ -598,6 +636,9 @@ class PauseAt(_Action): def intermediate(self, settings): return (self.offset, "pause", self.seconds) + def freeze(self, settings): + return self + class DisconnectAt(_Action): def __init__(self, offset): @@ -615,6 +656,9 @@ class DisconnectAt(_Action): def intermediate(self, settings): return (self.offset, "disconnect") + def freeze(self, settings): + return self + class InjectAt(_Action): def __init__(self, offset, value): @@ -639,6 +683,9 @@ class InjectAt(_Action): self.value.get_generator(settings) ) + def freeze(self, settings): + return InjectAt(self.offset, self.value.freeze(settings)) + class _Message(object): __metaclass__ = abc.ABCMeta @@ -758,6 +805,12 @@ class _Message(object): vals.append(self.body.value.get_generator(settings)) return vals + def freeze(self, settings): + return self.__class__([i.freeze(settings) for i in self.tokens]) + + def __repr__(self): + return self.spec() + Sep = pp.Optional(pp.Literal(":")).suppress() diff --git a/test/test_language.py b/test/test_language.py index 688dfc8c..a6f735f1 100644 --- a/test/test_language.py +++ b/test/test_language.py @@ -36,6 +36,10 @@ class TestValueLiteral: v = language.ValueLiteral("f\x00oo") assert v.spec() == repr(v) == r'"f\x00oo"' + def test_freeze(self): + v = language.ValueLiteral("foo") + assert v.freeze({}).val == v.val + class TestValueGenerate: def test_basic(self): @@ -72,6 +76,11 @@ class TestValueGenerate: v = language.ValueGenerate(1, "b", "ascii") assert v.spec() == repr(v) == "@1,ascii" + def test_freeze(self): + v = language.ValueGenerate(100, "b", "ascii") + f = v.freeze({}) + assert len(f.val) == 100 + class TestValueFile: def test_file_value(self): @@ -104,6 +113,11 @@ class TestValueFile: v2 = language.Value.parseString(v.spec())[0] assert v2.path == "one two" + def test_freeze(self): + v = language.Value.parseString("<'one two'")[0] + v2 = v.freeze({}) + assert v2.path == v.path + class TestMisc: def test_generators(self): @@ -147,11 +161,17 @@ class TestMisc: def test_path(self): e = language.Path.expr() assert e.parseString('"/foo"')[0].value.val == "/foo" - e = language.Path("/foo") - assert e.value.val == "/foo" - s = e.spec() - assert s == e.expr().parseString(s)[0].spec() + v = language.Path("/foo") + assert v.value.val == "/foo" + + v = e.parseString("@100")[0] + v2 = v.freeze({}) + v3 = v2.freeze({}) + assert v2.value.val == v3.value.val + + s = v.spec() + assert s == v.expr().parseString(s)[0].spec() def test_method(self): e = language.Method.expr() @@ -168,10 +188,16 @@ class TestMisc: s = e.parseString("'foo'")[0].spec() assert s == e.parseString(s)[0].spec() + v = e.parseString("@100")[0] + v2 = v.freeze({}) + v3 = v2.freeze({}) + assert v2.value.val == v3.value.val + def test_raw(self): e = language.Raw.expr().parseString("r")[0] assert e assert e.spec() == "r" + assert e.freeze({}).spec() == "r" def test_body(self): e = language.Body.expr() @@ -180,6 +206,9 @@ class TestMisc: v = e.parseString("b@100")[0] assert str(v.value) == "@100" + v2 = v.freeze({}) + v3 = v2.freeze({}) + assert v2.value.val == v3.value.val v = e.parseString("b@100g,digits", parseAll=True)[0] assert v.value.datatype == "digits" @@ -194,6 +223,8 @@ class TestMisc: assert v.string() == "200" assert v.spec() == "200" + assert v.freeze({}).code == v.code + def test_reason(self): e = language.Reason.expr() v = e.parseString("m'msg'")[0] @@ -202,6 +233,11 @@ class TestMisc: s = v.spec() assert s == e.parseString(s)[0].spec() + v = e.parseString("m@100")[0] + v2 = v.freeze({}) + v3 = v2.freeze({}) + assert v2.value.val == v3.value.val + def test_internal_response(self): d = cStringIO.StringIO() s = language.PathodErrorResponse("foo") @@ -222,6 +258,14 @@ class TestHeaders: s = v.spec() assert s == e.parseString(s)[0].spec() + def test_header_freeze(self): + e = language.Header.expr() + v = e.parseString("h@10=@10'")[0] + v2 = v.freeze({}) + v3 = v2.freeze({}) + assert v2.key.val == v3.key.val + assert v2.value.val == v3.value.val + def test_ctype_shortcut(self): e = language.ShortcutContentType.expr() v = e.parseString("c'foo'")[0] @@ -231,6 +275,12 @@ class TestHeaders: s = v.spec() assert s == e.parseString(s)[0].spec() + e = language.ShortcutContentType.expr() + v = e.parseString("c@100")[0] + v2 = v.freeze({}) + v3 = v2.freeze({}) + assert v2.value.val == v3.value.val + def test_location_shortcut(self): e = language.ShortcutLocation.expr() v = e.parseString("l'foo'")[0] @@ -240,12 +290,19 @@ class TestHeaders: s = v.spec() assert s == e.parseString(s)[0].spec() + e = language.ShortcutLocation.expr() + v = e.parseString("l@100")[0] + v2 = v.freeze({}) + v3 = v2.freeze({}) + assert v2.value.val == v3.value.val + def test_shortcut_content_type(self): assert language.parse_response({}, "400:c'foo'").headers[0].key.val == "Content-Type" assert language.parse_response({}, "400:l'foo'").headers[0].key.val == "Location" + class Test_Action: def test_cmp(self): a = language.DisconnectAt(0) @@ -267,6 +324,10 @@ class Test_Action: e = language.DisconnectAt("r") assert repr(e) + def test_freeze(self): + l = language.DisconnectAt(5) + assert l.freeze({}).spec() == l.spec() + class TestDisconnects: def test_parse_response(self): @@ -323,6 +384,13 @@ class TestInject: v = e.parseString("i0,'foo'")[0] assert v.spec() == 'i0,"foo"' + def test_spec(self): + e = language.InjectAt.expr() + v = e.parseString("i0,@100")[0] + v2 = v.freeze({}) + v3 = v2.freeze({}) + assert v2.value.val == v3.value.val + class TestPauses: def test_parse_response(self): @@ -349,9 +417,12 @@ class TestPauses: assert language.PauseAt(0, 5).spec() == "p0,5" assert language.PauseAt(0, "f").spec() == "p0,f" + def test_freeze(self): + l = language.PauseAt("r", 5) + assert l.freeze({}).spec() == l.spec() -class TestParseRequest: +class TestRequest: def test_file(self): p = tutils.test_data.path("data") d = dict(staticdir=p) @@ -413,46 +484,9 @@ class TestParseRequest: rt("get:/foo") rt("get:/foo:da") - -class TestParseResponse: - def test_parse_err(self): - tutils.raises(language.ParseException, language.parse_response, {}, "400:msg,b:") - try: - language.parse_response({}, "400'msg':b:") - except language.ParseException, v: - assert v.marked() - assert str(v) - - def test_nonascii(self): - tutils.raises("ascii", language.parse_response, {}, "foo:b\xf0") - - def test_parse_header(self): - r = language.parse_response({}, '400:h"foo"="bar"') - assert utils.get_header("foo", r.headers) - - def test_parse_pause_before(self): - r = language.parse_response({}, "400:p0,10") - assert r.actions[0].spec() == "p0,10" - - def test_parse_pause_after(self): - r = language.parse_response({}, "400:pa,10") - assert r.actions[0].spec() == "pa,10" - - def test_parse_pause_random(self): - r = language.parse_response({}, "400:pr,10") - assert r.actions[0].spec() == "pr,10" - - def test_parse_stress(self): - r = language.parse_response({}, "400:b@100g") - assert r.length({}) - - def test_spec(self): - def rt(s): - s = language.parse_response({}, s).spec() - assert language.parse_response({}, s).spec() == s - rt("400:b@100g") - rt("400") - rt("400:da") + def test_freeze(self): + r = language.parse_request({}, "GET:/:b@100").freeze({}) + assert len(r.spec()) > 100 class TestWriteValues: @@ -610,6 +644,45 @@ class TestResponse: s = r.preview_safe() assert not "p0" in s.spec() + def test_parse_err(self): + tutils.raises(language.ParseException, language.parse_response, {}, "400:msg,b:") + try: + language.parse_response({}, "400'msg':b:") + except language.ParseException, v: + assert v.marked() + assert str(v) + + def test_nonascii(self): + tutils.raises("ascii", language.parse_response, {}, "foo:b\xf0") + + def test_parse_header(self): + r = language.parse_response({}, '400:h"foo"="bar"') + assert utils.get_header("foo", r.headers) + + def test_parse_pause_before(self): + r = language.parse_response({}, "400:p0,10") + assert r.actions[0].spec() == "p0,10" + + def test_parse_pause_after(self): + r = language.parse_response({}, "400:pa,10") + assert r.actions[0].spec() == "pa,10" + + def test_parse_pause_random(self): + r = language.parse_response({}, "400:pr,10") + assert r.actions[0].spec() == "pr,10" + + def test_parse_stress(self): + r = language.parse_response({}, "400:b@100g") + assert r.length({}) + + def test_spec(self): + def rt(s): + s = language.parse_response({}, s).spec() + assert language.parse_response({}, s).spec() == s + rt("400:b@100g") + rt("400") + rt("400:da") + def test_read_file(): -- cgit v1.2.3 From 8c6cc8140c6ac51966bdfabd5bb1180c2dd623b0 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Wed, 31 Oct 2012 09:48:55 +1300 Subject: Fix generated values in request path specification. --- libpathod/language.py | 4 ++-- test/test_language.py | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/libpathod/language.py b/libpathod/language.py index 0b49c2de..654dc37e 100644 --- a/libpathod/language.py +++ b/libpathod/language.py @@ -136,7 +136,7 @@ v_literal = pp.MatchFirst( v_naked_literal = pp.MatchFirst( [ v_literal, - pp.Word("".join(i for i in pp.printables if i not in ",:\n")) + pp.Word("".join(i for i in pp.printables if i not in ",:\n@\'\"")) ] ) @@ -479,7 +479,7 @@ class Path(_Component): @classmethod def expr(klass): - e = NakedValue.copy() + e = Value | NakedValue return e.setParseAction(lambda x: klass(*x)) def values(self, settings): diff --git a/test/test_language.py b/test/test_language.py index a6f735f1..b02a89dd 100644 --- a/test/test_language.py +++ b/test/test_language.py @@ -169,6 +169,7 @@ class TestMisc: v2 = v.freeze({}) v3 = v2.freeze({}) assert v2.value.val == v3.value.val + assert len(v2.value.val) == 100 s = v.spec() assert s == v.expr().parseString(s)[0].spec() @@ -488,6 +489,10 @@ class TestRequest: r = language.parse_request({}, "GET:/:b@100").freeze({}) assert len(r.spec()) > 100 + def test_path_generator(self): + r = language.parse_request({}, "GET:@100").freeze({}) + assert len(r.spec()) > 100 + class TestWriteValues: def test_send_chunk(self): -- cgit v1.2.3 From 3f50930dc06a980ac5dcf012fa23ecc0a70ce1b4 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Wed, 31 Oct 2012 11:23:53 +1300 Subject: Use .freeze to implement the -e explain flags for pathod and pathoc. This now prints (in pathoc) or logs (in pathod) a frozen specification that includes an expanded record of all generated values and locations. --- libpathod/language.py | 5 +++-- libpathod/pathoc.py | 6 ++++++ libpathod/pathod.py | 6 +++++- pathoc | 8 +++++++- pathod | 15 +++++++++++++-- test/test_pathoc.py | 4 ++++ test/tutils.py | 3 ++- 7 files changed, 40 insertions(+), 7 deletions(-) diff --git a/libpathod/language.py b/libpathod/language.py index 654dc37e..3c545fb9 100644 --- a/libpathod/language.py +++ b/libpathod/language.py @@ -805,8 +805,9 @@ class _Message(object): vals.append(self.body.value.get_generator(settings)) return vals - def freeze(self, settings): - return self.__class__([i.freeze(settings) for i in self.tokens]) + def freeze(self, settings, request_host=None): + r = self.resolve(settings, request_host=None) + return self.__class__([i.freeze(settings) for i in r.tokens]) def __repr__(self): return self.spec() diff --git a/libpathod/pathoc.py b/libpathod/pathoc.py index b4020a3f..dcee353f 100644 --- a/libpathod/pathoc.py +++ b/libpathod/pathoc.py @@ -62,6 +62,9 @@ class Pathoc(tcp.TCPClient): print >> fp, "File access error: %s"%v return + if explain: + r = r.freeze(self.settings, self.host) + resp, req = None, None if showreq: self.wfile.start_log() @@ -83,6 +86,9 @@ class Pathoc(tcp.TCPClient): if req: if ignorecodes and resp and resp[1] in ignorecodes: return + if explain: + print >> fp, ">> Spec:", r.spec() + if showreq: self._show(fp, ">> Request", self.wfile.get_log(), hexdump) diff --git a/libpathod/pathod.py b/libpathod/pathod.py index fb6a7725..bc5a1825 100644 --- a/libpathod/pathod.py +++ b/libpathod/pathod.py @@ -28,6 +28,9 @@ class PathodHandler(tcp.BaseHandler): ) return False, log + if self.server.explain: + crafted = crafted.freeze(self.server.request_settings, None) + print crafted response_log = language.serve(crafted, self.wfile, self.server.request_settings, None) log = dict( type = "crafted", @@ -171,7 +174,7 @@ class Pathod(tcp.TCPServer): def __init__( self, addr, ssloptions=None, craftanchor="/p/", staticdir=None, anchors=None, sizelimit=None, noweb=False, nocraft=False, noapi=False, nohang=False, - timeout=None, logreq=False, logresp=False, hexdump=False + timeout=None, logreq=False, logresp=False, explain=False, hexdump=False ): """ addr: (address, port) tuple. If port is 0, a free port will be @@ -192,6 +195,7 @@ class Pathod(tcp.TCPServer): self.sizelimit = sizelimit self.noweb, self.nocraft, self.noapi, self.nohang = noweb, nocraft, noapi, nohang self.timeout, self.logreq, self.logresp, self.hexdump = timeout, logreq, logresp, hexdump + self.explain = explain if not noapi: app.api() diff --git a/pathoc b/pathoc index c9290587..96cb9f0a 100755 --- a/pathoc +++ b/pathoc @@ -33,7 +33,13 @@ if __name__ == "__main__": 'request', type=str, nargs="+", help='Request specification' ) - group = parser.add_argument_group('Controlling Output') + 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( "-C", dest="ignorecodes", type=str, default="", help="Comma-separated list of response codes to ignore" diff --git a/pathod b/pathod index 41b7578d..09facad4 100755 --- a/pathod +++ b/pathod @@ -92,7 +92,8 @@ def main(parser, args): timeout = args.timeout, logreq = args.logreq, logresp = args.logresp, - hexdump = args.hexdump + hexdump = args.hexdump, + explain = args.explain ) except pathod.PathodError, v: parser.error(str(v)) @@ -164,7 +165,17 @@ if __name__ == "__main__": ) - group = parser.add_argument_group('Controlling Output') + 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.' diff --git a/test/test_pathoc.py b/test/test_pathoc.py index c22fd4f8..ca25be58 100644 --- a/test/test_pathoc.py +++ b/test/test_pathoc.py @@ -65,6 +65,10 @@ class TestDaemon: assert "Invalid headers" in v assert "HTTP/" in v + def test_explain(self): + reqs = [ "get:/p/200:b@100" ] + assert not "b@100" in self.tval(reqs, explain=True) + def test_showreq(self): reqs = [ "get:/api/info:p0,0", "get:/api/info:p0,0" ] assert self.tval(reqs, showreq=True).count("unprintables escaped") == 2 diff --git a/test/tutils.py b/test/tutils.py index a63ed7eb..3de18417 100644 --- a/test/tutils.py +++ b/test/tutils.py @@ -23,7 +23,8 @@ class DaemonTests: timeout = self.timeout, hexdump = self.hexdump, logreq = True, - logresp = True + logresp = True, + explain = True ) @classmethod -- cgit v1.2.3 From 3e3c5af00620a8ff1ebcc4e8623b86c3c5c29368 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Wed, 31 Oct 2012 12:32:13 +1300 Subject: Bump version, update CHANGELOG --- CHANGELOG | 29 +++++++++++++++++++++++++++++ libpathod/version.py | 2 +- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 38053ffe..9689209b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,32 @@ +31 October 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. + + * Major internal refactoring and cleanup. + + * Many bugfixes. + + 22 August 2012: pathod 0.2: * Add pathoc, a pathological HTTP client. diff --git a/libpathod/version.py b/libpathod/version.py index 0faffda9..746c85a5 100644 --- a/libpathod/version.py +++ b/libpathod/version.py @@ -1,4 +1,4 @@ -IVERSION = (0, 2, 1) +IVERSION = (0, 3, 0) VERSION = ".".join(str(i) for i in IVERSION) NAME = "pathod" NAMEVERSION = NAME + " " + VERSION -- cgit v1.2.3 From 27d90a3594102b73ffc206020669f26ca51b1116 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Wed, 31 Oct 2012 12:32:13 +1300 Subject: Bump version, update CHANGELOG --- CHANGELOG | 29 +++++++++++++++++++++++++++++ libpathod/language.py | 2 +- libpathod/pathod.py | 2 +- libpathod/version.py | 2 +- 4 files changed, 32 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 38053ffe..9689209b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,32 @@ +31 October 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. + + * Major internal refactoring and cleanup. + + * Many bugfixes. + + 22 August 2012: pathod 0.2: * Add pathoc, a pathological HTTP client. diff --git a/libpathod/language.py b/libpathod/language.py index 3c545fb9..79657b37 100644 --- a/libpathod/language.py +++ b/libpathod/language.py @@ -216,7 +216,7 @@ class _Token(object): """ return None - def resolve(self, msg, settings): # pragma: no cover + def resolve(self, msg, settings): """ Resolves this token to ready it for transmission. This means that the calculated offsets of actions are fixed. diff --git a/libpathod/pathod.py b/libpathod/pathod.py index bc5a1825..f327ade4 100644 --- a/libpathod/pathod.py +++ b/libpathod/pathod.py @@ -250,7 +250,7 @@ class Pathod(tcp.TCPServer): ) ) return - except tcp.NetLibTimeout: # pragma: no cover + except tcp.NetLibTimeout: h.info("Timeout") self.add_log( dict( diff --git a/libpathod/version.py b/libpathod/version.py index 0faffda9..746c85a5 100644 --- a/libpathod/version.py +++ b/libpathod/version.py @@ -1,4 +1,4 @@ -IVERSION = (0, 2, 1) +IVERSION = (0, 3, 0) VERSION = ".".join(str(i) for i in IVERSION) NAME = "pathod" NAMEVERSION = NAME + " " + VERSION -- cgit v1.2.3 From 791252ac5e26c818397d3b2928300672175d8ce8 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Fri, 2 Nov 2012 14:22:54 +1300 Subject: Examples and documentation. --- .sources/examples_context.py | 22 ------------------ .sources/examples_setup.py | 28 ----------------------- .sources/examples_setupall.py | 37 ------------------------------- .sources/make | 7 +++--- examples/libpathod_pathoc.py | 7 ++++++ examples/pathoc.py | 6 ----- examples/test_context.py | 22 ++++++++++++++++++ examples/test_setup.py | 28 +++++++++++++++++++++++ examples/test_setupall.py | 37 +++++++++++++++++++++++++++++++ libpathod/templates/docs_libpathod.html | 22 +++++++++++++++++- libpathod/templates/examples_setup.html | 4 ++-- libpathod/templates/index.html | 22 ------------------ libpathod/templates/libpathod_pathoc.html | 7 ++++++ 13 files changed, 128 insertions(+), 121 deletions(-) delete mode 100644 .sources/examples_context.py delete mode 100644 .sources/examples_setup.py delete mode 100644 .sources/examples_setupall.py create mode 100644 examples/libpathod_pathoc.py delete mode 100755 examples/pathoc.py create mode 100644 examples/test_context.py create mode 100644 examples/test_setup.py create mode 100644 examples/test_setupall.py create mode 100644 libpathod/templates/libpathod_pathoc.html diff --git a/.sources/examples_context.py b/.sources/examples_context.py deleted file mode 100644 index f3da83cb..00000000 --- a/.sources/examples_context.py +++ /dev/null @@ -1,22 +0,0 @@ -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/.sources/examples_setup.py b/.sources/examples_setup.py deleted file mode 100644 index 5366c9ef..00000000 --- a/.sources/examples_setup.py +++ /dev/null @@ -1,28 +0,0 @@ -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/.sources/examples_setupall.py b/.sources/examples_setupall.py deleted file mode 100644 index c8948971..00000000 --- a/.sources/examples_setupall.py +++ /dev/null @@ -1,37 +0,0 @@ -import requests -from libpathod import test - -class Test: - """ - Testing the requests module with - a single pathod instance started - for the test suite. - """ - @classmethod - def setUpAll(cls): - cls.d = test.Daemon() - - @classmethod - def tearDownAll(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/.sources/make b/.sources/make index e0e9d2cc..1c8b1d69 100755 --- a/.sources/make +++ b/.sources/make @@ -1,4 +1,5 @@ #!/bin/sh -pygmentize -f html ./examples_context.py > ../libpathod/templates/examples_context.html -pygmentize -f html ./examples_setup.py > ../libpathod/templates/examples_setup.html -pygmentize -f html ./examples_setupall.py > ../libpathod/templates/examples_setupall.html +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/examples/libpathod_pathoc.py b/examples/libpathod_pathoc.py new file mode 100644 index 00000000..ca53b08f --- /dev/null +++ b/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/examples/pathoc.py b/examples/pathoc.py deleted file mode 100755 index 4c3fae52..00000000 --- a/examples/pathoc.py +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env python -from libpathod import pathoc - -p = pathoc.Pathoc("google.com", 80) -p.connect() -print p.request("get:/") diff --git a/examples/test_context.py b/examples/test_context.py new file mode 100644 index 00000000..f3da83cb --- /dev/null +++ b/examples/test_context.py @@ -0,0 +1,22 @@ +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/examples/test_setup.py b/examples/test_setup.py new file mode 100644 index 00000000..2918a4dd --- /dev/null +++ b/examples/test_setup.py @@ -0,0 +1,28 @@ +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/examples/test_setupall.py b/examples/test_setupall.py new file mode 100644 index 00000000..c8948971 --- /dev/null +++ b/examples/test_setupall.py @@ -0,0 +1,37 @@ +import requests +from libpathod import test + +class Test: + """ + Testing the requests module with + a single pathod instance started + for the test suite. + """ + @classmethod + def setUpAll(cls): + cls.d = test.Daemon() + + @classmethod + def tearDownAll(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/libpathod/templates/docs_libpathod.html b/libpathod/templates/docs_libpathod.html index 8d51ebba..aa404eed 100644 --- a/libpathod/templates/docs_libpathod.html +++ b/libpathod/templates/docs_libpathod.html @@ -1,7 +1,27 @@ {% extends "frame.html" %} {% block body %} -Foo + +
+
+ +

Behind the pathod and pathoc command-line tools lurks + libpathod, a powerful library for manipulating and serving HTTP + requests and responses. The canonical documentation for the library is + in the code, and can be accessed using pydoc.

+ +
+
+

pathoc

+ + {% include "libpathod_pathoc.html" %} +
+
{% endblock %} diff --git a/libpathod/templates/examples_setup.html b/libpathod/templates/examples_setup.html index bde45840..b80ad379 100644 --- a/libpathod/templates/examples_setup.html +++ b/libpathod/templates/examples_setup.html @@ -3,8 +3,8 @@ class Test: """ - Testing the requests module with - a pathod instance started for + Testing the requests module with + a pathod instance started for each test. """ def setUp(self): diff --git a/libpathod/templates/index.html b/libpathod/templates/index.html index 9561fee1..4b52b6a5 100644 --- a/libpathod/templates/index.html +++ b/libpathod/templates/index.html @@ -66,26 +66,4 @@ - -
-
-
-

libpathod.test

- -

Using pathod in your unit tests.

- -
-
-
-
-

libpathod.test

- -

Using pathod in your unit tests.

- - {% include "examples_context.html" %} - -
-
-
- {% endblock %} diff --git a/libpathod/templates/libpathod_pathoc.html b/libpathod/templates/libpathod_pathoc.html new file mode 100644 index 00000000..f2737c01 --- /dev/null +++ b/libpathod/templates/libpathod_pathoc.html @@ -0,0 +1,7 @@ +
#!/usr/bin/env python
+from libpathod import pathoc
+
+p = pathoc.Pathoc("google.com", 80)
+p.connect()
+print p.request("get:/")
+
-- cgit v1.2.3 From 5d18830f7169fcacac2d23b349a82502bd4171b4 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Fri, 2 Nov 2012 14:24:39 +1300 Subject: Fix typo. --- libpathod/templates/about.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libpathod/templates/about.html b/libpathod/templates/about.html index 4bf473da..602357f7 100644 --- a/libpathod/templates/about.html +++ b/libpathod/templates/about.html @@ -5,13 +5,13 @@

I started pathod to improve testing for for the mitmproxy project. From there, it soon grew - into quite a versatile HTTP Swiss army knife.

+ into a versatile HTTP Swiss army knife in its own right.

-- cgit v1.2.3 From ef9cbe3b25face06f941a9f92aac5c79821e83bf Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Fri, 16 Nov 2012 11:31:04 +1300 Subject: Add u User-agent shortcut. Usage: 200:ua - Shortcut "a" for Android. 200:u"foo" - Or a value literal Shortcuts can be listed using the --show-uas argument to pathoc. --- libpathod/app.py | 3 ++- libpathod/language.py | 25 ++++++++++++++++++++++++- libpathod/pathod.py | 1 - libpathod/templates/docs_lang.html | 21 +++++++++++++++++++++ pathoc | 19 ++++++++++++++++--- test/test_language.py | 21 ++++++++++++++++++++- test/test_pathod.py | 2 +- 7 files changed, 84 insertions(+), 8 deletions(-) diff --git a/libpathod/app.py b/libpathod/app.py index 1fcfa078..78c0fc4a 100644 --- a/libpathod/app.py +++ b/libpathod/app.py @@ -1,6 +1,7 @@ import logging, pprint, cStringIO from flask import Flask, jsonify, render_template, request, abort, make_response import version, language, utils +from netlib import http_uastrings logging.basicConfig(level="DEBUG") app = Flask(__name__) @@ -61,7 +62,7 @@ def docs_pathod(): @app.route('/docs/language') def docs_language(): - return render("docs_lang.html", True, section="docs") + return render("docs_lang.html", True, section="docs", uastrings=http_uastrings.UASTRINGS) @app.route('/docs/pathoc') diff --git a/libpathod/language.py b/libpathod/language.py index 79657b37..30788569 100644 --- a/libpathod/language.py +++ b/libpathod/language.py @@ -2,7 +2,7 @@ import operator, string, random, mmap, os, time, copy import abc from email.utils import formatdate import contrib.pyparsing as pp -from netlib import http_status, tcp +from netlib import http_status, tcp, http_uastrings import utils @@ -449,6 +449,28 @@ class ShortcutLocation(_Header): return ShortcutLocation(self.value.freeze(settings)) +class ShortcutUserAgent(_Header): + def __init__(self, value): + self.specvalue = value + if isinstance(value, basestring): + value = ValueLiteral(http_uastrings.get_by_shortcut(value)[2]) + _Header.__init__(self, ValueLiteral("User-Agent"), value) + + @classmethod + def expr(klass): + e = pp.Literal("u").suppress() + u = reduce(operator.or_, [pp.Literal(i[1]) for i in http_uastrings.UASTRINGS]) + e += u | Value + return e.setParseAction(lambda x: klass(*x)) + + def spec(self): + return "u%s"%self.specvalue + + def freeze(self, settings): + return ShortcutUserAgent(self.value.freeze(settings)) + + + class Body(_Component): def __init__(self, value): self.value = value @@ -824,6 +846,7 @@ class Response(_Message): InjectAt, ShortcutContentType, ShortcutLocation, + ShortcutUserAgent, Raw, Reason ) diff --git a/libpathod/pathod.py b/libpathod/pathod.py index f327ade4..d52af15b 100644 --- a/libpathod/pathod.py +++ b/libpathod/pathod.py @@ -30,7 +30,6 @@ class PathodHandler(tcp.BaseHandler): if self.server.explain: crafted = crafted.freeze(self.server.request_settings, None) - print crafted response_log = language.serve(crafted, self.wfile, self.server.request_settings, None) log = dict( type = "crafted", diff --git a/libpathod/templates/docs_lang.html b/libpathod/templates/docs_lang.html index f01d61fb..26672bb2 100644 --- a/libpathod/templates/docs_lang.html +++ b/libpathod/templates/docs_lang.html @@ -155,6 +155,27 @@ calculate a Content-Length header if a body is set. + +
+ + + +
hVALUE=VALUE - Set a header. -
bVALUE @@ -118,16 +120,23 @@
iOFFSET,VALUE dOFFSET - Inject the specified value at the offset. + Disconnect after OFFSET bytes.
dOFFSET hVALUE=VALUE - Disconnect after OFFSET bytes. + Set a header. +
iOFFSET,VALUE + Inject the specified value at the offset.
uVALUE
uSHORTCUT
+ + Set a User-Agent header on this request. You can + specify either a complete VALUE, or a User-Agent shortcut: + + + {% for i in uastrings %} + + + + + {% endfor %} +
{{ i[1] }}{{ i[0] }}
+ +

diff --git a/pathoc b/pathoc index 96cb9f0a..1d3f6004 100755 --- a/pathoc +++ b/pathoc @@ -1,10 +1,22 @@ #!/usr/bin/env python import argparse, sys from libpathod import pathoc, version, language -from netlib import tcp +from netlib import tcp, http_uastrings if __name__ == "__main__": - parser = argparse.ArgumentParser(description='A perverse HTTP client.') + 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()[0] + if pa.showua: + print "User agent strings:" + for i in http_uastrings.UASTRINGS: + print " ", i[1], i[0] + sys.exit(0) + + parser = argparse.ArgumentParser(description='A perverse HTTP client.', parents=[preparser]) parser.add_argument( "-i", dest="sni", type=str, default=False, help="SSL Server Name Indication" @@ -33,8 +45,9 @@ if __name__ == "__main__": 'request', type=str, nargs="+", help='Request specification' ) + group = parser.add_argument_group( - 'Controlling Output', + 'Controlling Output', """ Some of these options expand generated values for logging - if you're generating large data, use them with caution. diff --git a/test/test_language.py b/test/test_language.py index b02a89dd..d7f7b4cc 100644 --- a/test/test_language.py +++ b/test/test_language.py @@ -297,11 +297,30 @@ class TestHeaders: v3 = v2.freeze({}) assert v2.value.val == v3.value.val - def test_shortcut_content_type(self): + def test_shortcuts(self): assert language.parse_response({}, "400:c'foo'").headers[0].key.val == "Content-Type" assert language.parse_response({}, "400:l'foo'").headers[0].key.val == "Location" + assert 'Android' in language.parse_response({}, "400:ua").headers[0].value.val + assert language.parse_response({}, "400:ua").headers[0].key.val == "User-Agent" +class TestShortcutUserAgent: + def test_location_shortcut(self): + e = language.ShortcutUserAgent.expr() + v = e.parseString("ua")[0] + assert "Android" in str(v.value) + assert v.spec() == "ua" + assert v.key.val == "User-Agent" + + v = e.parseString("u'foo'")[0] + assert "foo" in str(v.value) + assert "foo" in v.spec() + + v = e.parseString("u@100'")[0] + assert len(str(v.freeze({}).value)) > 100 + v2 = v.freeze({}) + v3 = v2.freeze({}) + assert v2.value.val == v3.value.val class Test_Action: diff --git a/test/test_pathod.py b/test/test_pathod.py index 30498c3a..46b1fb1d 100644 --- a/test/test_pathod.py +++ b/test/test_pathod.py @@ -35,7 +35,7 @@ class TestNoWeb(tutils.DaemonTests): class TestTimeout(tutils.DaemonTests): - timeout = 0.01 + timeout = 0.001 def test_noweb(self): # FIXME: Add float values to spec language, reduce test timeout to # increase test performance -- cgit v1.2.3 From 39fcc600a64701564a1a95f317e809957eaaee7f Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Fri, 16 Nov 2012 14:22:20 +1300 Subject: Update changelog. --- CHANGELOG | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 9689209b..7d8f8c79 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,4 @@ -31 October 2012: pathod 0.3: +16 November 2012: pathod 0.3: A release focusing on shoring up our fuzzing capabilities, especially with pathoc. @@ -22,6 +22,12 @@ 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. -- cgit v1.2.3 From af698b2fdac9df8b6eac362b4d857ea80e1a81e5 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Fri, 16 Nov 2012 14:41:02 +1300 Subject: :u belongs on Request not Response --- libpathod/language.py | 2 +- test/test_language.py | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/libpathod/language.py b/libpathod/language.py index 30788569..6aae7dc7 100644 --- a/libpathod/language.py +++ b/libpathod/language.py @@ -846,7 +846,6 @@ class Response(_Message): InjectAt, ShortcutContentType, ShortcutLocation, - ShortcutUserAgent, Raw, Reason ) @@ -893,6 +892,7 @@ class Request(_Message): DisconnectAt, InjectAt, ShortcutContentType, + ShortcutUserAgent, Raw ) logattrs = ["method", "path", "body"] diff --git a/test/test_language.py b/test/test_language.py index d7f7b4cc..409b0eb6 100644 --- a/test/test_language.py +++ b/test/test_language.py @@ -300,8 +300,9 @@ class TestHeaders: def test_shortcuts(self): assert language.parse_response({}, "400:c'foo'").headers[0].key.val == "Content-Type" assert language.parse_response({}, "400:l'foo'").headers[0].key.val == "Location" - assert 'Android' in language.parse_response({}, "400:ua").headers[0].value.val - assert language.parse_response({}, "400:ua").headers[0].key.val == "User-Agent" + + assert 'Android' in language.parse_request({}, "get:/:ua").headers[0].value.val + assert language.parse_request({}, "get:/:ua").headers[0].key.val == "User-Agent" class TestShortcutUserAgent: -- cgit v1.2.3 From 5fb31f916231e8d76da3a3792e8bf596b93556d4 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Fri, 16 Nov 2012 14:51:31 +1300 Subject: Add :u shortcut example. Update trove specifiers, netlib dependency. --- libpathod/templates/request_previewform.html | 6 +++++- setup.py | 4 ++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/libpathod/templates/request_previewform.html b/libpathod/templates/request_previewform.html index 263b501e..78884145 100644 --- a/libpathod/templates/request_previewform.html +++ b/libpathod/templates/request_previewform.html @@ -27,7 +27,11 @@ 100 random bytes as the body - get:/:h"User-Agent"="';drop table browsers;" + get:/:h"Etag"="';drop table browsers;" + Add a header + + + get:/:u"';drop table browsers;" Add a User-Agent header diff --git a/setup.py b/setup.py index 0e4e36f1..4df247b2 100644 --- a/setup.py +++ b/setup.py @@ -80,7 +80,7 @@ setup( scripts = ["pathod", "pathoc"], classifiers = [ "License :: OSI Approved :: MIT License", - "Development Status :: 3 - Alpha", + "Development Status :: 5 - Production/Stable", "Operating System :: POSIX", "Programming Language :: Python", "Topic :: Internet", @@ -89,5 +89,5 @@ setup( "Topic :: Software Development :: Testing :: Traffic Generation", "Topic :: Internet :: WWW/HTTP", ], - install_requires=['netlib>=0.2.1', "requests>=0.13", "flask"], + install_requires=['netlib>=0.2.2', "requests>=0.13", "flask"], ) -- cgit v1.2.3 From b11260f064ef6f2341d2b637f2e9a4dcc5b9f50c Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Fri, 16 Nov 2012 15:00:15 +1300 Subject: Expand basic fuzzing example. --- libpathod/templates/docs_pathoc.html | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/libpathod/templates/docs_pathoc.html b/libpathod/templates/docs_pathoc.html index 5c07aee3..b9338c87 100644 --- a/libpathod/templates/docs_pathoc.html +++ b/libpathod/templates/docs_pathoc.html @@ -89,16 +89,33 @@ the command-line help:

a few of its command-line options makes for quite a powerful basic fuzzer. Here's an example:

-
> pathoc -t 2 -n 1000 localhost get:/:b@10:ir,@1
+
> pathoc -e -C 200 -t 2 -n 1000 localhost get:/:b@10:ir,@1

The request specified here is a valid GET with a body consisting of 10 random bytes, but with 1 random byte inserted in a random place. This could be in the headers, in the initial request line, or in the body itself. - Corrupting the request in this way will often make the server enter a state - where it's awaiting more input from the client. This is where the -t - option comes in, which sets a timeout that causes pathoc to disconnect - after two seconds. Finally, the -n option tells pathoc to repeat the - request 1000 times.

+ There are a few things to note here:

+ +

    + +
  • Corrupting the request in this way will often make the server + enter a state where it's awaiting more input from the client. This is + where the -t option comes in, which sets a timeout that causes + pathoc to disconnect after two seconds.
  • + +
  • The -n option tells pathoc to repeat the request 1000 + times.
  • + +
  • The -C option tells pathoc to ignore HTTP 200 response + codes. You can use this to fine-tune what pathoc considers to be an + exceptional condition, and therefore log-worthy.
  • + +
  • The -e option tells pathoc to print an explanation of each + logged request, in the form of an expanded pathoc specification with + all random portions and automatic header additions resolved. This lets + you precisely replay a request that triggered an error
  • + +
-- cgit v1.2.3 From e40482576ca5b03f7c65332b8f1409366f7adbd9 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Sun, 18 Nov 2012 09:04:49 +1300 Subject: Add disconnect specifiers to test suite. Fixes hangs seen in test suite with recent versions of requetss. --- test/test_app.py | 2 +- test/test_pathod.py | 12 ++++++------ test/test_test.py | 12 ++++++------ test/tutils.py | 2 +- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/test/test_app.py b/test/test_app.py index b3194052..6fc64dbe 100644 --- a/test/test_app.py +++ b/test/test_app.py @@ -24,7 +24,7 @@ class TestApp(tutils.DaemonTests): def test_log(self): assert self.getpath("/log").status_code == 200 - assert self.get("200").status_code == 200 + assert self.get("200:da").status_code == 200 id = self.d.log()[0]["id"] assert self.getpath("/log").status_code == 200 assert self.getpath("/log/%s"%id).status_code == 200 diff --git a/test/test_pathod.py b/test/test_pathod.py index 46b1fb1d..70c9a5ec 100644 --- a/test/test_pathod.py +++ b/test/test_pathod.py @@ -7,10 +7,10 @@ class TestPathod: def test_instantiation(self): p = pathod.Pathod( ("127.0.0.1", 0), - anchors = [(".*", "200")] + anchors = [(".*", "200:da")] ) assert p.anchors - tutils.raises("invalid regex", pathod.Pathod, ("127.0.0.1", 0), anchors=[("*", "200")]) + tutils.raises("invalid regex", pathod.Pathod, ("127.0.0.1", 0), anchors=[("*", "200:da")]) tutils.raises("invalid page spec", pathod.Pathod, ("127.0.0.1", 0), anchors=[("foo", "bar")]) def test_logging(self): @@ -30,16 +30,16 @@ class TestPathod: class TestNoWeb(tutils.DaemonTests): noweb = True def test_noweb(self): - assert self.get("200").status_code == 200 + assert self.get("200:da").status_code == 200 assert self.getpath("/").status_code == 800 class TestTimeout(tutils.DaemonTests): - timeout = 0.001 + timeout = 0.00001 def test_noweb(self): # FIXME: Add float values to spec language, reduce test timeout to # increase test performance - assert self.get("200:p1,1").status_code == 200 + assert self.get("200:p1,2").status_code == 200 assert self.d.last_log()["type"] == "timeout" @@ -89,7 +89,7 @@ class CommonTests(tutils.DaemonTests): def test_logs(self): assert self.d.clear_log() tutils.raises("no requests logged", self.d.last_log) - rsp = self.get("202") + rsp = self.get("202:da") assert len(self.d.log()) == 1 assert self.d.clear_log() assert len(self.d.log()) == 0 diff --git a/test/test_test.py b/test/test_test.py index 538fc056..fc97d263 100644 --- a/test/test_test.py +++ b/test/test_test.py @@ -8,18 +8,18 @@ logging.disable(logging.CRITICAL) class TestDaemonManual: def test_simple(self): with test.Daemon() as d: - rsp = requests.get("http://localhost:%s/p/202"%d.port) + rsp = requests.get("http://localhost:%s/p/202:da"%d.port) assert rsp.ok assert rsp.status_code == 202 - tutils.raises(requests.ConnectionError, requests.get, "http://localhost:%s/p/202"%d.port) + tutils.raises(requests.ConnectionError, requests.get, "http://localhost:%s/p/202:da"%d.port) def test_startstop_ssl(self): d = test.Daemon(ssl=True) - rsp = requests.get("https://localhost:%s/p/202"%d.port, verify=False) + rsp = requests.get("https://localhost:%s/p/202:da"%d.port, verify=False) assert rsp.ok assert rsp.status_code == 202 d.shutdown() - tutils.raises(requests.ConnectionError, requests.get, "http://localhost:%s/p/202"%d.port) + tutils.raises(requests.ConnectionError, requests.get, "http://localhost:%s/p/202:da"%d.port) def test_startstop_ssl_explicit(self): ssloptions = dict( @@ -27,10 +27,10 @@ class TestDaemonManual: certfile = utils.data.path("resources/server.crt"), ) d = test.Daemon(ssl=ssloptions) - rsp = requests.get("https://localhost:%s/p/202"%d.port, verify=False) + rsp = requests.get("https://localhost:%s/p/202:da"%d.port, verify=False) assert rsp.ok assert rsp.status_code == 202 d.shutdown() - tutils.raises(requests.ConnectionError, requests.get, "http://localhost:%s/p/202"%d.port) + tutils.raises(requests.ConnectionError, requests.get, "http://localhost:%s/p/202:da"%d.port) diff --git a/test/tutils.py b/test/tutils.py index 3de18417..0a5455a3 100644 --- a/test/tutils.py +++ b/test/tutils.py @@ -14,7 +14,7 @@ class DaemonTests: def setUpAll(self): self.d = test.Daemon( staticdir=test_data.path("data"), - anchors=[("/anchor/.*", "202")], + anchors=[("/anchor/.*", "202:da")], ssl = self.ssl, sizelimit=1*1024*1024, noweb = self.noweb, -- cgit v1.2.3 From 781592d6c0f6e2987c7ab3a5aa9b5505fa461e95 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Sun, 18 Nov 2012 09:34:15 +1300 Subject: Fix timeout test. --- test/test_pathod.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_pathod.py b/test/test_pathod.py index 70c9a5ec..072d5623 100644 --- a/test/test_pathod.py +++ b/test/test_pathod.py @@ -39,7 +39,7 @@ class TestTimeout(tutils.DaemonTests): def test_noweb(self): # FIXME: Add float values to spec language, reduce test timeout to # increase test performance - assert self.get("200:p1,2").status_code == 200 + tutils.raises("blank server response", self.pathoc, "get:/:p1,1") assert self.d.last_log()["type"] == "timeout" -- cgit v1.2.3 From b07ab253b7a2adec01233989d983d2f180c9fe4e Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Mon, 31 Dec 2012 12:23:42 +1300 Subject: Require requests > 1.0.4 This changes the API slightly, since json is now a method, not a property. --- libpathod/test.py | 4 ++-- pathod | 2 +- setup.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/libpathod/test.py b/libpathod/test.py index e431be18..ed0ee26a 100644 --- a/libpathod/test.py +++ b/libpathod/test.py @@ -30,7 +30,7 @@ class Daemon: Return some basic info about the remote daemon. """ resp = requests.get("%s/api/info"%self.urlbase, verify=False) - return resp.json + return resp.json() def last_log(self): """ @@ -47,7 +47,7 @@ class Daemon: Return the log buffer as a list of dictionaries. """ resp = requests.get("%s/api/log"%self.urlbase, verify=False) - return resp.json["log"] + return resp.json()["log"] def clear_log(self): """ diff --git a/pathod b/pathod index 09facad4..27e7e040 100755 --- a/pathod +++ b/pathod @@ -166,7 +166,7 @@ if __name__ == "__main__": group = parser.add_argument_group( - 'Controlling Logging', + 'Controlling Logging', """ Some of these options expand generated values for logging - if you're generating large data, use them with caution. diff --git a/setup.py b/setup.py index 4df247b2..0653ee70 100644 --- a/setup.py +++ b/setup.py @@ -89,5 +89,5 @@ setup( "Topic :: Software Development :: Testing :: Traffic Generation", "Topic :: Internet :: WWW/HTTP", ], - install_requires=['netlib>=0.2.2', "requests>=0.13", "flask"], + install_requires=['netlib>=0.2.2', "requests>=1.0.4", "flask"], ) -- cgit v1.2.3 From d7f641c6ee1033232110c9b42c3b48cc5b719520 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Fri, 4 Jan 2013 10:37:26 +1300 Subject: Shift SSL parameters into Pathoc class --- libpathod/pathoc.py | 11 ++++++++++- pathoc | 8 +------- test/test_pathoc.py | 32 ++++++++++++++++++++++++++++++-- 3 files changed, 41 insertions(+), 10 deletions(-) diff --git a/libpathod/pathoc.py b/libpathod/pathoc.py index dcee353f..3384dd11 100644 --- a/libpathod/pathoc.py +++ b/libpathod/pathoc.py @@ -7,12 +7,21 @@ class PathocError(Exception): pass class Pathoc(tcp.TCPClient): - def __init__(self, host, port): + def __init__(self, host, port, ssl=None, sni=None): tcp.TCPClient.__init__(self, host, port) self.settings = dict( staticdir = os.getcwd(), unconstrained_file_access = True, ) + self.ssl, self.sni = ssl, sni + + def connect(self): + tcp.TCPClient.connect(self) + if self.ssl: + try: + self.convert_to_ssl(sni=self.sni) + except tcp.NetLibError, v: + raise PathocError(str(v)) def request(self, spec): """ diff --git a/pathoc b/pathoc index 1d3f6004..42369b37 100755 --- a/pathoc +++ b/pathoc @@ -96,18 +96,12 @@ if __name__ == "__main__": try: for i in range(args.repeat): - p = pathoc.Pathoc(args.host, port) + p = pathoc.Pathoc(args.host, port, args.ssl, args.sni) try: p.connect() except tcp.NetLibError, v: print >> sys.stderr, str(v) sys.exit(1) - if args.ssl: - try: - p.convert_to_ssl(sni=args.sni) - except tcp.NetLibError, v: - print "\n>> %s"%v - continue if args.timeout: p.settimeout(args.timeout) for spec in args.request: diff --git a/test/test_pathoc.py b/test/test_pathoc.py index ca25be58..38d3754a 100644 --- a/test/test_pathoc.py +++ b/test/test_pathoc.py @@ -3,10 +3,11 @@ from libpathod import pathoc, test, version import tutils -class TestDaemon: +class _TestDaemon: @classmethod def setUpAll(self): self.d = test.Daemon( + ssl=self.ssl, staticdir=tutils.test_data.path("data"), anchors=[("/anchor/.*", "202")] ) @@ -19,11 +20,34 @@ class TestDaemon: self.d.clear_log() def test_info(self): - c = pathoc.Pathoc("127.0.0.1", self.d.port) + c = pathoc.Pathoc( + "127.0.0.1", + self.d.port, + ssl = self.ssl + ) c.connect() _, _, _, _, content = c.request("get:/api/info") assert tuple(json.loads(content)["version"]) == version.IVERSION + +class TestDaemonSSL(_TestDaemon): + ssl = True + def test_sni(self): + c = pathoc.Pathoc( + "127.0.0.1", + self.d.port, + ssl = True, + sni = "foobar.com" + ) + c.connect() + c.request("get:/p/200") + _, _, _, _, content = c.request("get:/api/log") + d = json.loads(content) + assert d["log"][0]["request"]["sni"] == "foobar.com" + + +class TestDaemon(_TestDaemon): + ssl = False def tval(self, requests, showreq=False, showresp=False, explain=False, hexdump=False, timeout=None, ignorecodes=None, ignoretimeout=None): c = pathoc.Pathoc("127.0.0.1", self.d.port) c.connect() @@ -43,6 +67,10 @@ class TestDaemon: ) return s.getvalue() + def test_ssl_error(self): + c = pathoc.Pathoc("127.0.0.1", self.d.port, ssl = True) + tutils.raises("ssl handshake", c.connect) + def test_ignorecodes(self): assert "200" in self.tval(["get:'/p/200:b@1'"]) assert "200" not in self.tval(["get:'/p/200:b@1'"], ignorecodes=[200]) -- cgit v1.2.3 From 3886ccae9379d065e54e0eb7e961992ff3c0ee62 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Sat, 5 Jan 2013 15:25:09 +1300 Subject: Provisional proxy mode support for pathod. --- libpathod/pathod.py | 21 ++++++++++++++++----- libpathod/templates/docs_pathod.html | 16 ++++++++++++++++ libpathod/test.py | 1 + libpathod/utils.py | 13 +++++++++++++ pathod | 30 ++++++++++++++++++++---------- test/test_test.py | 1 + test/test_utils.py | 10 ++++++++++ 7 files changed, 77 insertions(+), 15 deletions(-) diff --git a/libpathod/pathod.py b/libpathod/pathod.py index d52af15b..587e51bf 100644 --- a/libpathod/pathod.py +++ b/libpathod/pathod.py @@ -1,7 +1,7 @@ import urllib, threading, re, logging, socket, sys, base64 from netlib import tcp, http, odict, wsgi import netlib.utils -import version, app, language +import version, app, language, utils logger = logging.getLogger('pathod') @@ -54,13 +54,24 @@ class PathodHandler(tcp.BaseHandler): # Normal termination return False, None - parts = http.parse_init_http(line) - if not parts: + m = utils.MemBool() + if m(http.parse_init_connect(line)): + self.wfile.write( + 'HTTP/1.1 200 Connection established\r\n' + + ('Proxy-agent: %s\r\n'%version.NAMEVERSION) + + '\r\n' + ) + self.wfile.flush() + + if m(http.parse_init_proxy(line)): + method, _, _, _, path, httpversion = m.v + elif m(http.parse_init_http(line)): + method, path, httpversion = m.v + else: s = "Invalid first line: %s"%repr(line) self.info(s) return False, dict(type = "error", msg = s) - method, path, httpversion = parts headers = http.read_headers(self.rfile) if headers is None: s = "Invalid headers" @@ -133,7 +144,7 @@ class PathodHandler(tcp.BaseHandler): self.info("\n".join(s)) def handle(self): - if self.server.ssloptions: + if self.server.ssloptions and not self.server.ssloptions["ssl_after_connect"]: try: self.convert_to_ssl( self.server.ssloptions["certfile"], diff --git a/libpathod/templates/docs_pathod.html b/libpathod/templates/docs_pathod.html index 8b345d71..42459352 100644 --- a/libpathod/templates/docs_pathod.html +++ b/libpathod/templates/docs_pathod.html @@ -45,6 +45,22 @@ those, use the command-line help:

+
+ + +

Pathod automatically responds to both straight HTTP and proxy requests. For +proxy requests, the upstream host is ignored, and the path portion of the URL +is used to match anchors. This lets you test software that supports a proxy +configuration by spoofing responses from upstream servers.

+ +

Proxy mode operates even when Pathod is run in SSL mode, but we do not +support nested SSL connections. This means that CONNECT requests will cause an +error response.

+ +
+
-

At the moment, pathoc has no explicit support for proxies, but there's a - workaround that serves many use cases. Instead of specifying just a path, - specify an entire URL to the GET request, like so (assuming there's a proxy - running on port 8080 of localhost):

+

Pathoc has a reasonably sophisticated suite of features for interacting + with proxies. The proxy request syntax very closely mirrors that of + straight HTTP, which means that it is possible to make proxy-style requests + using pathoc without any additional syntax, by simply specifying a full URL + instead of a simple path::

> pathoc -p 8080 localhost "get:'http://google.com'"
-

Proxy support is going to be a major focus of development for the next - version of pathoc, so keep an eye on the repo.

+

Another common use case is to use an HTTP CONNECT request to probe + remote servers via a proxy. This is done with the -c command-line + option, which allows you to specify a remote host and port pair:

+ +
> pathoc -c google.com:80 -p 8080 localhost get:/
+ +

Note that pathoc does not negotiate SSL without being explictly + instructed to do so. If you're making a CONNECT request to an SSL-protected + resource, you must also pass the -s flag:

+ +
> pathoc -sc google.com:443 -p 8080 localhost get:/
diff --git a/libpathod/templates/docs_pathod.html b/libpathod/templates/docs_pathod.html index 42459352..d9897245 100644 --- a/libpathod/templates/docs_pathod.html +++ b/libpathod/templates/docs_pathod.html @@ -55,9 +55,11 @@ proxy requests, the upstream host is ignored, and the path portion of the URL is used to match anchors. This lets you test software that supports a proxy configuration by spoofing responses from upstream servers.

-

Proxy mode operates even when Pathod is run in SSL mode, but we do not -support nested SSL connections. This means that CONNECT requests will cause an -error response.

+

By default, we treat all proxy CONNECT requests as HTTPS traffic, serving +the response using either pathod's built-in certificates, or the cert/key pair +specified by the user. You can over-ride this behaviour if you're testing a +client that makes a non-SSL CONNECT request using the -C command-line +option.

-- cgit v1.2.3 From 9bd269c26a02d94f16d7c39f3cb0a4cd46bb40b1 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Sun, 20 Jan 2013 22:37:43 +1300 Subject: Add support for client certificates - pathod request logs now include a clientcert member with details on the client cert, or None if there wasn't one. - pathoc has a -C option to specify a client certificate --- libpathod/pathoc.py | 5 +++-- libpathod/pathod.py | 12 ++++++++++++ libpathod/templates/log.html | 10 +++++++--- pathoc | 32 ++++++++++++++++++++----------- test/data/clientcert/.gitignore | 3 +++ test/data/clientcert/client.cnf | 5 +++++ test/data/clientcert/client.pem | 42 +++++++++++++++++++++++++++++++++++++++++ test/data/clientcert/make | 8 ++++++++ test/test_pathoc.py | 13 +++++++++++++ 9 files changed, 114 insertions(+), 16 deletions(-) create mode 100644 test/data/clientcert/.gitignore create mode 100644 test/data/clientcert/client.cnf create mode 100644 test/data/clientcert/client.pem create mode 100755 test/data/clientcert/make diff --git a/libpathod/pathoc.py b/libpathod/pathoc.py index a2d89aaf..ae9edaf0 100644 --- a/libpathod/pathoc.py +++ b/libpathod/pathoc.py @@ -7,13 +7,14 @@ class PathocError(Exception): pass class Pathoc(tcp.TCPClient): - def __init__(self, host, port, ssl=None, sni=None): + def __init__(self, host, port, ssl=None, sni=None, clientcert=None): tcp.TCPClient.__init__(self, host, port) self.settings = dict( staticdir = os.getcwd(), unconstrained_file_access = True, ) self.ssl, self.sni = ssl, sni + self.clientcert = clientcert def http_connect(self, connect_to, wfile, rfile): wfile.write( @@ -34,7 +35,7 @@ class Pathoc(tcp.TCPClient): self.http_connect(connect_to, self.wfile, self.rfile) if self.ssl: try: - self.convert_to_ssl(sni=self.sni) + self.convert_to_ssl(sni=self.sni, clientcert=self.clientcert) except tcp.NetLibError, v: raise PathocError(str(v)) diff --git a/libpathod/pathod.py b/libpathod/pathod.py index ce64acf9..ac56619b 100644 --- a/libpathod/pathod.py +++ b/libpathod/pathod.py @@ -96,6 +96,17 @@ class PathodHandler(tcp.BaseHandler): self.info(s) return False, dict(type = "error", msg = s) + 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, + ) + request_log = dict( path = path, method = method, @@ -103,6 +114,7 @@ class PathodHandler(tcp.BaseHandler): httpversion = httpversion, sni = self.sni, remote_address = self.client_address, + clientcert = clientcert ) try: diff --git a/libpathod/templates/log.html b/libpathod/templates/log.html index 22747e0e..19468d66 100644 --- a/libpathod/templates/log.html +++ b/libpathod/templates/log.html @@ -17,9 +17,13 @@ {% for i in log %} - {{ i["id"] }} - {{ i["request"]["method"] }} - {{ i["request"]["path"] }} + {% if i["type"] == 'error' %} + ERROR: {{ i["msg"] }} + {% else %} + {{ i["id"] }} + {{ i["request"]["method"] }} + {{ i["request"]["path"] }} + {% endif %} {% endfor %} diff --git a/pathoc b/pathoc index 527b9a22..aa3d5bed 100755 --- a/pathoc +++ b/pathoc @@ -22,10 +22,6 @@ if __name__ == "__main__": metavar = "HOST:PORT", help="Issue an HTTP CONNECT to connect to the specified host." ) - parser.add_argument( - "-i", dest="sni", type=str, default=False, - help="SSL Server Name Indication" - ) parser.add_argument( "-n", dest='repeat', default=1, type=int, metavar="N", help='Repeat requests N times' @@ -34,10 +30,6 @@ if __name__ == "__main__": "-p", dest="port", type=int, default=None, help="Port. Defaults to 80, or 443 if SSL is active" ) - parser.add_argument( - "-s", dest="ssl", action="store_true", default=False, - help="Connect with SSL" - ) parser.add_argument( "-t", dest="timeout", type=int, default=None, help="Connection timeout" @@ -51,6 +43,24 @@ if __name__ == "__main__": help='Request specification' ) + + 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 = parser.add_argument_group( 'Controlling Output', """ @@ -59,7 +69,7 @@ if __name__ == "__main__": """ ) group.add_argument( - "-C", dest="ignorecodes", type=str, default="", + "-I", dest="ignorecodes", type=str, default="", help="Comma-separated list of response codes to ignore" ) group.add_argument( @@ -113,10 +123,10 @@ if __name__ == "__main__": try: for i in range(args.repeat): - p = pathoc.Pathoc(args.host, port, args.ssl, args.sni) + p = pathoc.Pathoc(args.host, port, ssl=args.ssl, sni=args.sni, clientcert=args.clientcert) try: p.connect(connect_to) - except tcp.NetLibError, v: + except (tcp.NetLibError, pathoc.PathocError), v: print >> sys.stderr, str(v) sys.exit(1) if args.timeout: diff --git a/test/data/clientcert/.gitignore b/test/data/clientcert/.gitignore new file mode 100644 index 00000000..07bc53d2 --- /dev/null +++ b/test/data/clientcert/.gitignore @@ -0,0 +1,3 @@ +client.crt +client.key +client.req diff --git a/test/data/clientcert/client.cnf b/test/data/clientcert/client.cnf new file mode 100644 index 00000000..5046a944 --- /dev/null +++ b/test/data/clientcert/client.cnf @@ -0,0 +1,5 @@ +[ ssl_client ] +basicConstraints = CA:FALSE +nsCertType = client +keyUsage = digitalSignature, keyEncipherment +extendedKeyUsage = clientAuth diff --git a/test/data/clientcert/client.pem b/test/data/clientcert/client.pem new file mode 100644 index 00000000..4927bca2 --- /dev/null +++ b/test/data/clientcert/client.pem @@ -0,0 +1,42 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAzCpoRjSTfIN24kkNap/GYmP9zVWj0Gk8R5BB/PvvN0OB1Zk0 +EEYPsWCcuhEdK0ehiDZX030doF0DOncKKa6mop/d0x2o+ts42peDhZM6JNUrm6d+ +ZWQVtio33mpp77UMhR093vaA+ExDnmE26kBTVijJ1+fRAVDXG/cmQINEri91Kk/G +3YJ5e45UrohGI5seBZ4vV0xbHtmczFRhYFlGOvYsoIe4Lvz/eFS2pIrTIpYQ2VM/ +SQQl+JFy+NlQRsWG2NrxtKOzMnnDE7YN4I3z5D5eZFo1EtwZ48LNCeSwrEOdfuzP +G5q5qbs5KpE/x85H9umuRwSCIArbMwBYV8a8JwIDAQABAoIBAFE3FV/IDltbmHEP +iky93hbJm+6QgKepFReKpRVTyqb7LaygUvueQyPWQMIriKTsy675nxo8DQr7tQsO +y3YlSZgra/xNMikIB6e82c7K8DgyrDQw/rCqjZB3Xt4VCqsWJDLXnQMSn98lx0g7 +d7Lbf8soUpKWXqfdVpSDTi4fibSX6kshXyfSTpcz4AdoncEpViUfU1xkEEmZrjT8 +1GcCsDC41xdNmzCpqRuZX7DKSFRoB+0hUzsC1oiqM7FD5kixonRd4F5PbRXImIzt +6YCsT2okxTA04jX7yByis7LlOLTlkmLtKQYuc3erOFvwx89s4vW+AeFei+GGNitn +tHfSwbECgYEA7SzV+nN62hAERHlg8cEQT4TxnsWvbronYWcc/ev44eHSPDWL5tPi +GHfSbW6YAq5Wa0I9jMWfXyhOYEC3MZTC5EEeLOB71qVrTwcy/sY66rOrcgjFI76Q +5JFHQ4wy3SWU50KxE0oWJO9LIowprG+pW1vzqC3VF0T7q0FqESrY4LUCgYEA3F7Z +80ndnCUlooJAb+Hfotv7peFf1o6+m1PTRcz1lLnVt5R5lXj86kn+tXEpYZo1RiGR +2rE2N0seeznWCooakHcsBN7/qmFIhhooJNF7yW+JP2I4P2UV5+tJ+8bcs/voUkQD +1x+rGOuMn8nvHBd2+Vharft8eGL2mgooPVI2XusCgYEAlMZpO3+w8pTVeHaDP2MR +7i/AuQ3cbCLNjSX3Y7jgGCFllWspZRRIYXzYPNkA9b2SbBnTLjjRLgnEkFBIGgvs +7O2EFjaCuDRvydUEQhjq4ErwIsopj7B8h0QyZcbOKTbn3uFQ3n68wVJx2Sv/ADHT +FIHrp/WIE96r19Niy34LKXkCgYB2W59VsuOKnMz01l5DeR5C+0HSWxS9SReIl2IO +yEFSKullWyJeLIgyUaGy0990430feKI8whcrZXYumuah7IDN/KOwzhCk8vEfzWao +N7bzfqtJVrh9HA7C7DVlO+6H4JFrtcoWPZUIomJ549w/yz6EN3ckoMC+a/Ck1TW9 +ka1QFwKBgQCywG6TrZz0UmOjyLQZ+8Q4uvZklSW5NAKBkNnyuQ2kd5rzyYgMPE8C +Er8T88fdVIKvkhDyHhwcI7n58xE5Gr7wkwsrk/Hbd9/ZB2GgAPY3cATskK1v1McU +YeX38CU0fUS4aoy26hWQXkViB47IGQ3jWo3ZCtzIJl8DI9/RsBWTnw== +-----END RSA PRIVATE KEY----- +-----BEGIN CERTIFICATE----- +MIICYDCCAckCAQEwDQYJKoZIhvcNAQEFBQAwKDESMBAGA1UEAxMJbWl0bXByb3h5 +MRIwEAYDVQQKEwltaXRtcHJveHkwHhcNMTMwMTIwMDEwODEzWhcNMTUxMDE3MDEw +ODEzWjBFMQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UE +ChMYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOC +AQ8AMIIBCgKCAQEAzCpoRjSTfIN24kkNap/GYmP9zVWj0Gk8R5BB/PvvN0OB1Zk0 +EEYPsWCcuhEdK0ehiDZX030doF0DOncKKa6mop/d0x2o+ts42peDhZM6JNUrm6d+ +ZWQVtio33mpp77UMhR093vaA+ExDnmE26kBTVijJ1+fRAVDXG/cmQINEri91Kk/G +3YJ5e45UrohGI5seBZ4vV0xbHtmczFRhYFlGOvYsoIe4Lvz/eFS2pIrTIpYQ2VM/ +SQQl+JFy+NlQRsWG2NrxtKOzMnnDE7YN4I3z5D5eZFo1EtwZ48LNCeSwrEOdfuzP +G5q5qbs5KpE/x85H9umuRwSCIArbMwBYV8a8JwIDAQABMA0GCSqGSIb3DQEBBQUA +A4GBAFvI+cd47B85PQ970n2dU/PlA2/Hb1ldrrXh2guR4hX6vYx/uuk5yRI/n0Rd +KOXJ3czO0bd2Fpe3ZoNpkW0pOSDej/Q+58ScuJd0gWCT/Sh1eRk6ZdC0kusOuWoY +bPOPMkG45LPgUMFOnZEsfJP6P5mZIxlbCvSMFC25nPHWlct7 +-----END CERTIFICATE----- diff --git a/test/data/clientcert/make b/test/data/clientcert/make new file mode 100755 index 00000000..d1caea81 --- /dev/null +++ b/test/data/clientcert/make @@ -0,0 +1,8 @@ +#!/bin/sh + +openssl genrsa -out client.key 2048 +openssl req -key client.key -new -out client.req +openssl x509 -req -days 365 -in client.req -signkey client.key -out client.crt -extfile client.cnf -extensions ssl_client +openssl x509 -req -days 1000 -in client.req -CA ~/.mitmproxy/mitmproxy-ca.pem -CAkey ~/.mitmproxy/mitmproxy-ca.pem -set_serial 00001 -out client.crt -extensions ssl_client +cat client.key client.crt > client.pem +openssl x509 -text -noout -in client.pem diff --git a/test/test_pathoc.py b/test/test_pathoc.py index 38d3754a..2c86df11 100644 --- a/test/test_pathoc.py +++ b/test/test_pathoc.py @@ -45,6 +45,19 @@ class TestDaemonSSL(_TestDaemon): d = json.loads(content) assert d["log"][0]["request"]["sni"] == "foobar.com" + def test_clientcert(self): + c = pathoc.Pathoc( + "127.0.0.1", + self.d.port, + ssl = True, + clientcert = tutils.test_data.path("data/clientcert/client.pem") + ) + c.connect() + c.request("get:/p/200") + _, _, _, _, content = c.request("get:/api/log") + d = json.loads(content) + assert d["log"][0]["request"]["clientcert"]["keyinfo"] + class TestDaemon(_TestDaemon): ssl = False -- cgit v1.2.3 From 5288848d03c928cc6b6b541cdf63c3fe22133e24 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Mon, 21 Jan 2013 09:36:20 +1300 Subject: Adjust docs, old -C is now -I. --- libpathod/templates/docs_pathoc.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libpathod/templates/docs_pathoc.html b/libpathod/templates/docs_pathoc.html index eff4920c..cae2e620 100644 --- a/libpathod/templates/docs_pathoc.html +++ b/libpathod/templates/docs_pathoc.html @@ -89,7 +89,7 @@ the command-line help:

a few of its command-line options makes for quite a powerful basic fuzzer. Here's an example:

-
> pathoc -e -C 200 -t 2 -n 1000 localhost get:/:b@10:ir,@1
+
> pathoc -e -I 200 -t 2 -n 1000 localhost get:/:b@10:ir,@1

The request specified here is a valid GET with a body consisting of 10 random bytes, but with 1 random byte inserted in a random place. This could @@ -106,7 +106,7 @@ the command-line help:

  • The -n option tells pathoc to repeat the request 1000 times.
  • -
  • The -C option tells pathoc to ignore HTTP 200 response +
  • The -I option tells pathoc to ignore HTTP 200 response codes. You can use this to fine-tune what pathoc considers to be an exceptional condition, and therefore log-worthy.
  • -- cgit v1.2.3 From 9c9e15341fd38508675b4f0036732a984aeedc3d Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Sat, 23 Feb 2013 21:46:01 +1300 Subject: Make last_log return None if nothing is logged. --- libpathod/test.py | 5 ++--- setup.py | 2 +- test/test_pathod.py | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/libpathod/test.py b/libpathod/test.py index 5ff7180c..2456e211 100644 --- a/libpathod/test.py +++ b/libpathod/test.py @@ -34,12 +34,11 @@ class Daemon: def last_log(self): """ - Returns the last logged request. Raises AssertionError if no - requests have been logged. + Returns the last logged request, or None. """ l = self.log() if not l: - raise AssertionError("No requests logged") + return None return l[-1] def log(self): diff --git a/setup.py b/setup.py index 0653ee70..624ef1b0 100644 --- a/setup.py +++ b/setup.py @@ -89,5 +89,5 @@ setup( "Topic :: Software Development :: Testing :: Traffic Generation", "Topic :: Internet :: WWW/HTTP", ], - install_requires=['netlib>=0.2.2', "requests>=1.0.4", "flask"], + install_requires=['netlib>=0.2.2', "requests>=1.1.0", "flask"], ) diff --git a/test/test_pathod.py b/test/test_pathod.py index 665ef843..7f3edb63 100644 --- a/test/test_pathod.py +++ b/test/test_pathod.py @@ -96,7 +96,7 @@ class CommonTests(tutils.DaemonTests): def test_logs(self): assert self.d.clear_log() - tutils.raises("no requests logged", self.d.last_log) + assert not self.d.last_log() rsp = self.get("202:da") assert len(self.d.log()) == 1 assert self.d.clear_log() -- cgit v1.2.3 From 69339836c20ed5d495c7f2c8c36064f7c3704325 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Sun, 24 Feb 2013 15:36:35 +1300 Subject: Adapt to netlib API changes. --- libpathod/pathoc.py | 2 +- test/test_pathod.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libpathod/pathoc.py b/libpathod/pathoc.py index ae9edaf0..0a3e8d06 100644 --- a/libpathod/pathoc.py +++ b/libpathod/pathoc.py @@ -35,7 +35,7 @@ class Pathoc(tcp.TCPClient): self.http_connect(connect_to, self.wfile, self.rfile) if self.ssl: try: - self.convert_to_ssl(sni=self.sni, clientcert=self.clientcert) + self.convert_to_ssl(sni=self.sni, cert=self.clientcert) except tcp.NetLibError, v: raise PathocError(str(v)) diff --git a/test/test_pathod.py b/test/test_pathod.py index 7f3edb63..412d1031 100644 --- a/test/test_pathod.py +++ b/test/test_pathod.py @@ -39,7 +39,7 @@ class TestTimeout(tutils.DaemonTests): def test_noweb(self): # FIXME: Add float values to spec language, reduce test timeout to # increase test performance - tutils.raises("blank server response", self.pathoc, "get:/:p1,1") + tutils.raises("server disconnect", self.pathoc, "get:/:p1,1") assert self.d.last_log()["type"] == "timeout" -- cgit v1.2.3 From ba0caff2dc35ac47224ab5eedf7de611474e8827 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Sun, 24 Feb 2013 19:42:40 +1300 Subject: Wrap webapp in a creation function. This lets us spawn multiple instances of Pathod. --- libpathod/app.py | 212 ++++++++++++++++++++++++++-------------------------- libpathod/pathod.py | 4 +- 2 files changed, 108 insertions(+), 108 deletions(-) diff --git a/libpathod/app.py b/libpathod/app.py index 78c0fc4a..42989355 100644 --- a/libpathod/app.py +++ b/libpathod/app.py @@ -4,148 +4,150 @@ import version, language, utils from netlib import http_uastrings logging.basicConfig(level="DEBUG") -app = Flask(__name__) - -def api(): - @app.route('/api/info') - def api_info(): - return jsonify( - version = version.IVERSION - ) +def make_app(noapi): + app = Flask(__name__) + 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/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" + @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 + 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") + @app.route('/') + @app.route('/index.html') + def index(): + return render("index.html", True, section="main") -@app.route('/download') -@app.route('/download.html') -def download(): - return render("download.html", True, section="download", 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('/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") + @app.route('/docs/pathod') + def docs_pathod(): + return render("docs_pathod.html", True, section="docs") -@app.route('/docs/language') -def docs_language(): - return render("docs_lang.html", True, section="docs", uastrings=http_uastrings.UASTRINGS) + @app.route('/docs/language') + def docs_language(): + return render("docs_lang.html", True, section="docs", uastrings=http_uastrings.UASTRINGS) -@app.route('/docs/pathoc') -def docs_pathoc(): - return render("docs_pathoc.html", True, section="docs") + @app.route('/docs/pathoc') + def docs_pathoc(): + return render("docs_pathoc.html", True, section="docs") -@app.route('/docs/libpathod') -def docs_libpathod(): - return render("docs_libpathod.html", True, section="docs") + @app.route('/docs/libpathod') + def docs_libpathod(): + return render("docs_libpathod.html", True, section="docs") -@app.route('/docs/test') -def docs_test(): - return render("docs_test.html", True, section="docs") + @app.route('/docs/test') + def docs_test(): + return render("docs_test.html", True, section="docs") -@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 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) + @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"] + def _preview(is_request): + if is_request: + template = "request_preview.html" + else: + template = "response_preview.html" - 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) + spec = request.args["spec"] - try: + 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_request(app.config["pathod"].request_settings, spec) + else: + r = language.parse_response(app.config["pathod"].request_settings, spec) + except language.ParseException, v: + args["syntaxerror"] = str(v) + args["marked"] = v.marked() + return render(template, False, **args) + + s = cStringIO.StringIO() + safe = r.preview_safe() + + c = app.config["pathod"].check_policy(safe, app.config["pathod"].request_settings) + if c: + args["error"] = c + return render(template, False, **args) if is_request: - r = language.parse_request(app.config["pathod"].request_settings, spec) + language.serve(safe, s, app.config["pathod"].request_settings, "example.com") else: - r = language.parse_response(app.config["pathod"].request_settings, spec) - except language.ParseException, v: - args["syntaxerror"] = str(v) - args["marked"] = v.marked() - return render(template, False, **args) - - s = cStringIO.StringIO() - safe = r.preview_safe() + language.serve(safe, s, app.config["pathod"].request_settings, None) - c = app.config["pathod"].check_policy(safe, app.config["pathod"].request_settings) - if c: - args["error"] = c + args["output"] = utils.escape_unprintables(s.getvalue()) return render(template, False, **args) - if is_request: - language.serve(safe, s, app.config["pathod"].request_settings, "example.com") - else: - language.serve(safe, s, app.config["pathod"].request_settings, None) - - args["output"] = utils.escape_unprintables(s.getvalue()) - return render(template, False, **args) -@app.route('/response_preview') -def response_preview(): - return _preview(False) + @app.route('/response_preview') + def response_preview(): + return _preview(False) -@app.route('/request_preview') -def request_preview(): - return _preview(True) + @app.route('/request_preview') + def request_preview(): + return _preview(True) + return app diff --git a/libpathod/pathod.py b/libpathod/pathod.py index ac56619b..a088120d 100644 --- a/libpathod/pathod.py +++ b/libpathod/pathod.py @@ -238,9 +238,7 @@ class Pathod(tcp.TCPServer): self.timeout, self.logreq, self.logresp, self.hexdump = timeout, logreq, logresp, hexdump self.explain = explain - if not noapi: - app.api() - self.app = app.app + self.app = app.make_app(noapi) self.app.config["pathod"] = self self.log = [] self.logid = 0 -- cgit v1.2.3 From 25656f488929a7c74d65a1f86cdc3bccbf8ca2c4 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Wed, 27 Feb 2013 09:05:05 +1300 Subject: Adapt for new netlib SNI API. --- libpathod/pathod.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libpathod/pathod.py b/libpathod/pathod.py index a088120d..31a126b7 100644 --- a/libpathod/pathod.py +++ b/libpathod/pathod.py @@ -75,6 +75,7 @@ class PathodHandler(tcp.BaseHandler): self.convert_to_ssl( self.server.ssloptions.certfile, self.server.ssloptions.keyfile, + handle_sni = self.handle_sni ) except tcp.NetLibError, v: s = str(v) @@ -179,6 +180,7 @@ class PathodHandler(tcp.BaseHandler): self.convert_to_ssl( self.server.ssloptions.certfile, self.server.ssloptions.keyfile, + handle_sni = self.handle_sni ) except tcp.NetLibError, v: s = str(v) -- cgit v1.2.3 From 9167b9b8b6f9235f4d4c5c3cf838dd7927a10116 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Wed, 27 Feb 2013 09:07:16 +1300 Subject: Make pathoc request return more human-friendly. --- libpathod/pathoc.py | 8 +++++++- test/test_pathoc.py | 12 ++++++------ test/test_pathod.py | 16 ++++++++-------- 3 files changed, 21 insertions(+), 15 deletions(-) diff --git a/libpathod/pathoc.py b/libpathod/pathoc.py index 0a3e8d06..650aa42a 100644 --- a/libpathod/pathoc.py +++ b/libpathod/pathoc.py @@ -6,6 +6,12 @@ import language, utils class PathocError(Exception): pass +class Response: + def __init__(self, httpversion, status_code, msg, headers, content): + self.httpversion, self.status_code, self.msg = httpversion, status_code, msg + self.headers, self.content = headers, content + + class Pathoc(tcp.TCPClient): def __init__(self, host, port, ssl=None, sni=None, clientcert=None): tcp.TCPClient.__init__(self, host, port) @@ -49,7 +55,7 @@ class Pathoc(tcp.TCPClient): r = language.parse_request(self.settings, spec) ret = language.serve(r, self.wfile, self.settings, self.host) self.wfile.flush() - return http.read_response(self.rfile, r.method, None) + return Response(*http.read_response(self.rfile, r.method, None)) def _show_summary(self, fp, httpversion, code, msg, headers, content): print >> fp, "<< %s %s: %s bytes"%(code, utils.xrepr(msg), len(content)) diff --git a/test/test_pathoc.py b/test/test_pathoc.py index 2c86df11..52a1b5ee 100644 --- a/test/test_pathoc.py +++ b/test/test_pathoc.py @@ -26,8 +26,8 @@ class _TestDaemon: ssl = self.ssl ) c.connect() - _, _, _, _, content = c.request("get:/api/info") - assert tuple(json.loads(content)["version"]) == version.IVERSION + r = c.request("get:/api/info") + assert tuple(json.loads(r.content)["version"]) == version.IVERSION class TestDaemonSSL(_TestDaemon): @@ -41,8 +41,8 @@ class TestDaemonSSL(_TestDaemon): ) c.connect() c.request("get:/p/200") - _, _, _, _, content = c.request("get:/api/log") - d = json.loads(content) + r = c.request("get:/api/log") + d = json.loads(r.content) assert d["log"][0]["request"]["sni"] == "foobar.com" def test_clientcert(self): @@ -54,8 +54,8 @@ class TestDaemonSSL(_TestDaemon): ) c.connect() c.request("get:/p/200") - _, _, _, _, content = c.request("get:/api/log") - d = json.loads(content) + r = c.request("get:/api/log") + d = json.loads(r.content) assert d["log"][0]["request"]["clientcert"]["keyinfo"] diff --git a/test/test_pathod.py b/test/test_pathod.py index 412d1031..9cd90e94 100644 --- a/test/test_pathod.py +++ b/test/test_pathod.py @@ -56,8 +56,8 @@ class TestNotAfterConnect(tutils.DaemonTests): ssl = False not_after_connect = True def test_connect(self): - v = self.pathoc(r"get:'http://foo.com/p/202':da", connect_to=("localhost", self.d.port)) - assert v[1] == 202 + r = self.pathoc(r"get:'http://foo.com/p/202':da", connect_to=("localhost", self.d.port)) + assert r.status_code == 202 class TestNohang(tutils.DaemonTests): @@ -88,8 +88,8 @@ class CommonTests(tutils.DaemonTests): assert "too large" in l["msg"] def test_preline(self): - v = self.pathoc(r"get:'/p/200':i0,'\r\n'") - assert v[1] == 200 + r = self.pathoc(r"get:'/p/200':i0,'\r\n'") + assert r.status_code == 200 def test_info(self): assert tuple(self.d.info()["version"]) == version.IVERSION @@ -152,15 +152,15 @@ class CommonTests(tutils.DaemonTests): assert "File access denied" in rsp.content def test_proxy(self): - v = self.pathoc(r"get:'http://foo.com/p/202':da") - assert v[1] == 202 + r = self.pathoc(r"get:'http://foo.com/p/202':da") + assert r.status_code == 202 class TestDaemon(CommonTests): ssl = False def test_connect(self): - v = self.pathoc(r"get:'http://foo.com/p/202':da", connect_to=("localhost", self.d.port), ssl=True) - assert v[1] == 202 + r = self.pathoc(r"get:'http://foo.com/p/202':da", connect_to=("localhost", self.d.port), ssl=True) + assert r.status_code == 202 def test_connect_err(self): tutils.raises(http.HttpError, self.pathoc, r"get:'http://foo.com/p/202':da", connect_to=("localhost", self.d.port)) -- cgit v1.2.3 From 155710f9912f0a7370deab2bef6ad0a51ce47f2b Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Sat, 2 Mar 2013 16:57:00 +1300 Subject: Improve robustness of proxy CONNECT, test coverage to 100%. --- libpathod/pathoc.py | 12 ++++++++++-- test/test_pathoc.py | 20 ++++++++++++++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/libpathod/pathoc.py b/libpathod/pathoc.py index 650aa42a..1540d817 100644 --- a/libpathod/pathoc.py +++ b/libpathod/pathoc.py @@ -11,6 +11,9 @@ class Response: self.httpversion, self.status_code, self.msg = httpversion, status_code, msg self.headers, self.content = headers, content + def __repr__(self): + return "Response(%s - %s)"%(self.status_code, self.msg) + class Pathoc(tcp.TCPClient): def __init__(self, host, port, ssl=None, sni=None, clientcert=None): @@ -28,8 +31,13 @@ class Pathoc(tcp.TCPClient): '\r\n' ) wfile.flush() - rfile.readline() - headers = http.read_headers(self.rfile) + l = rfile.readline() + if not l: + raise PathocError("Proxy CONNECT failed") + parsed = http.parse_response_line(l) + if not parsed[1] == 200: + raise PathocError("Proxy CONNECT failed: %s - %s"%(parsed[1], parsed[2])) + headers = http.read_headers(rfile) def connect(self, connect_to=None): """ diff --git a/test/test_pathoc.py b/test/test_pathoc.py index 52a1b5ee..5391167f 100644 --- a/test/test_pathoc.py +++ b/test/test_pathoc.py @@ -2,6 +2,10 @@ import json, cStringIO from libpathod import pathoc, test, version import tutils +def test_response(): + r = pathoc.Response("1.1", 200, "Message", {}, None) + assert repr(r) + class _TestDaemon: @classmethod @@ -126,3 +130,19 @@ class TestDaemon(_TestDaemon): assert "foo" in self.tval(["+%s"%d], showreq=True) assert "File" in self.tval(["+/nonexistent"]) + def test_connect_fail(self): + to = ("foobar", 80) + c = pathoc.Pathoc("127.0.0.1", self.d.port) + r, w = cStringIO.StringIO(), cStringIO.StringIO() + tutils.raises("connect failed", c.http_connect, to, w, r) + r = cStringIO.StringIO( + "HTTP/1.1 500 OK\r\n" + ) + tutils.raises("connect failed", c.http_connect, to, w, r) + r = cStringIO.StringIO( + "HTTP/1.1 200 OK\r\n" + ) + c.http_connect(to, w, r) + + + -- cgit v1.2.3 From 110a8bb594f9a00585e22e01d69eb6bef6b0d9db Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Sun, 3 Mar 2013 16:33:50 +1300 Subject: Print pathod craft explanations to stdout. --- libpathod/language.py | 9 ++++++--- libpathod/pathod.py | 9 +++++---- test/test_language.py | 2 +- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/libpathod/language.py b/libpathod/language.py index 6aae7dc7..f3fe4daa 100644 --- a/libpathod/language.py +++ b/libpathod/language.py @@ -930,15 +930,18 @@ class Request(_Message): return ":".join([i.spec() for i in self.tokens]) -def PathodErrorResponse(reason, body=None): +class PathodErrorResponse(Response): + pass + + +def make_error_response(reason, body=None): tokens = [ Code("800"), Header(ValueLiteral("Content-Type"), ValueLiteral("text/plain")), Reason(ValueLiteral(reason)), Body(ValueLiteral("pathod error: " + (body or reason))), ] - return Response(tokens) - + return PathodErrorResponse(tokens) FILESTART = "+" def read_file(settings, s): diff --git a/libpathod/pathod.py b/libpathod/pathod.py index 31a126b7..4d8a0203 100644 --- a/libpathod/pathod.py +++ b/libpathod/pathod.py @@ -27,7 +27,7 @@ class PathodHandler(tcp.BaseHandler): def serve_crafted(self, crafted, request_log): c = self.server.check_policy(crafted, self.server.request_settings) if c: - err = language.PathodErrorResponse(c) + err = language.make_error_response(c) language.serve(err, self.wfile, self.server.request_settings) log = dict( type = "error", @@ -35,8 +35,9 @@ class PathodHandler(tcp.BaseHandler): ) return False, log - if self.server.explain: + if self.server.explain and not isinstance(crafted, language.PathodErrorResponse): crafted = crafted.freeze(self.server.request_settings, None) + self.info(">> Spec: %s"%crafted.spec()) response_log = language.serve(crafted, self.wfile, self.server.request_settings, None) log = dict( type = "crafted", @@ -140,13 +141,13 @@ class PathodHandler(tcp.BaseHandler): crafted = language.parse_response(self.server.request_settings, spec) except language.ParseException, v: self.info("Parse error: %s"%v.msg) - crafted = language.PathodErrorResponse( + crafted = language.make_error_response( "Parse Error", "Error parsing response spec: %s\n"%v.msg + v.marked() ) return self.serve_crafted(crafted, request_log) elif self.server.noweb: - crafted = language.PathodErrorResponse("Access Denied") + crafted = language.make_error_response("Access Denied") language.serve(crafted, self.wfile, self.server.request_settings) return False, dict(type = "error", msg="Access denied: web interface disabled") else: diff --git a/test/test_language.py b/test/test_language.py index 409b0eb6..40967935 100644 --- a/test/test_language.py +++ b/test/test_language.py @@ -241,7 +241,7 @@ class TestMisc: def test_internal_response(self): d = cStringIO.StringIO() - s = language.PathodErrorResponse("foo") + s = language.make_error_response("foo") language.serve(s, d, {}) -- cgit v1.2.3 From beb47eba5171800ed201f3c9a1e23adf8b032640 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Tue, 5 Mar 2013 09:09:33 +1300 Subject: Sync version number with mitmproxy. --- libpathod/version.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libpathod/version.py b/libpathod/version.py index 746c85a5..06ab76fb 100644 --- a/libpathod/version.py +++ b/libpathod/version.py @@ -1,4 +1,4 @@ -IVERSION = (0, 3, 0) +IVERSION = (0, 9) VERSION = ".".join(str(i) for i in IVERSION) NAME = "pathod" NAMEVERSION = NAME + " " + VERSION diff --git a/setup.py b/setup.py index 624ef1b0..a71499ca 100644 --- a/setup.py +++ b/setup.py @@ -89,5 +89,5 @@ setup( "Topic :: Software Development :: Testing :: Traffic Generation", "Topic :: Internet :: WWW/HTTP", ], - install_requires=['netlib>=0.2.2', "requests>=1.1.0", "flask"], + install_requires=['netlib>=%s'%version.VERSION, "requests>=1.1.0", "flask"], ) -- cgit v1.2.3 From 0504bcfd9645181587ec68a77a13af4b413c13a1 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Tue, 30 Apr 2013 09:32:11 +1200 Subject: Changelog, minor cleanups. --- CHANGELOG | 14 ++++++++++++++ libpathod/pathoc.py | 1 + libpathod/static/bootstrap.min.css | 6 +++--- test/test_app.py | 3 +++ 4 files changed, 21 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 7d8f8c79..4e86e265 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,17 @@ +30 April 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 + tragets. + + * Pathoc client certificate support. + + * API improvements, bugfixes. + + + 16 November 2012: pathod 0.3: A release focusing on shoring up our fuzzing capabilities, especially with diff --git a/libpathod/pathoc.py b/libpathod/pathoc.py index 1540d817..32707899 100644 --- a/libpathod/pathoc.py +++ b/libpathod/pathoc.py @@ -1,4 +1,5 @@ import sys, os +import json from netlib import tcp, http import netlib.utils import language, utils diff --git a/libpathod/static/bootstrap.min.css b/libpathod/static/bootstrap.min.css index 23f25a89..1275a8c0 100644 --- a/libpathod/static/bootstrap.min.css +++ b/libpathod/static/bootstrap.min.css @@ -1,9 +1,9 @@ -@import url('https://fonts.googleapis.com/css?family=Open+Sans:400,700');/*! - * Bootstrap v2.0.4 +/*! + * 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. - */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{max-width:100%;vertical-align:middle;border:0;-ms-interpolation-mode:bicubic}#map_canvas 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,input[type="button"],input[type="reset"],input[type="submit"]{cursor:pointer;-webkit-appearance:button}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}.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;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:28px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;box-sizing:border-box}body{margin:0;font-family:'Open Sans',"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:18px;color:#222;background-color:#fcfbfd}a{color:#4380d3;text-decoration:none}a:hover{color:#c00;text-decoration:underline}.row{margin-left:-20px;*zoom:1}.row:before,.row:after{display:table;content:""}.row:after{clear:both}[class*="span"]{float:left;margin-left:20px}.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;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:28px;margin-left:2.127659574%;*margin-left:2.0744680846382977%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .span12{width:99.99999998999999%;*width:99.94680850063828%}.row-fluid .span11{width:91.489361693%;*width:91.4361702036383%}.row-fluid .span10{width:82.97872339599999%;*width:82.92553190663828%}.row-fluid .span9{width:74.468085099%;*width:74.4148936096383%}.row-fluid .span8{width:65.95744680199999%;*width:65.90425531263828%}.row-fluid .span7{width:57.446808505%;*width:57.3936170156383%}.row-fluid .span6{width:48.93617020799999%;*width:48.88297871863829%}.row-fluid .span5{width:40.425531911%;*width:40.3723404216383%}.row-fluid .span4{width:31.914893614%;*width:31.8617021246383%}.row-fluid .span3{width:23.404255317%;*width:23.3510638276383%}.row-fluid .span2{width:14.89361702%;*width:14.8404255306383%}.row-fluid .span1{width:6.382978723%;*width:6.329787233638298%}.container{margin-right:auto;margin-left:auto;*zoom:1}.container:before,.container:after{display:table;content:""}.container:after{clear:both}.container-fluid{padding-right:20px;padding-left:20px;*zoom:1}.container-fluid:before,.container-fluid:after{display:table;content:""}.container-fluid:after{clear:both}p{margin:0 0 9px}p small{font-size:12px;color:#999}.lead{margin-bottom:18px;font-size:20px;font-weight:200;line-height:27px}h1,h2,h3,h4,h5,h6{margin:0;font-family:inherit;font-weight:bold;color:inherit;text-rendering:optimizelegibility}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small{font-weight:normal;color:#999}h1{font-size:30px;line-height:36px}h1 small{font-size:18px}h2{font-size:24px;line-height:36px}h2 small{font-size:18px}h3{font-size:18px;line-height:27px}h3 small{font-size:14px}h4,h5,h6{line-height:18px}h4{font-size:14px}h4 small{font-size:12px}h5{font-size:12px}h6{font-size:11px;color:#999;text-transform:uppercase}.page-header{padding-bottom:17px;margin:18px 0;border-bottom:1px solid #eee}.page-header h1{line-height:1}ul,ol{padding:0;margin:0 0 9px 25px}ul ul,ul ol,ol ol,ol ul{margin-bottom:0}ul{list-style:disc}ol{list-style:decimal}li{line-height:18px}ul.unstyled,ol.unstyled{margin-left:0;list-style:none}dl{margin-bottom:18px}dt,dd{line-height:18px}dt{font-weight:bold;line-height:17px}dd{margin-left:9px}.dl-horizontal dt{float:left;width:120px;overflow:hidden;clear:left;text-align:right;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:130px}hr{margin:18px 0;border:0;border-top:1px solid #eee;border-bottom:1px solid #fff}strong{font-weight:bold}em{font-style:italic}.muted{color:#999}abbr[title]{cursor:help;border-bottom:1px dotted #999}abbr.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:0 0 0 15px;margin:0 0 18px;border-left:5px solid #eee}blockquote p{margin-bottom:0;font-size:16px;font-weight:300;line-height:22.5px}blockquote small{display:block;line-height:18px;color:#999}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}q:before,q:after,blockquote:before,blockquote:after{content:""}address{display:block;margin-bottom:18px;font-style:normal;line-height:18px}small{font-size:100%}cite{font-style:normal}code,pre{padding:0 3px 2px;font-family:Menlo,Monaco,Consolas,"Courier New",monospace;font-size:13px;color:#333;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}code{padding:2px 4px;color:#d14;background-color:#f7f7f9;border:1px solid #e1e1e8}pre{display:block;padding:8.5px;margin:0 0 9px;font-size:12.950000000000001px;line-height:18px;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:4px;-moz-border-radius:4px;border-radius:4px}pre.prettyprint{margin-bottom:18px}pre code{padding:0;color:inherit;background-color:transparent;border:0}.pre-scrollable{max-height:340px;overflow-y:scroll}form{margin:0 0 18px}fieldset{padding:0;margin:0;border:0}legend{display:block;width:100%;padding:0;margin-bottom:27px;font-size:21px;line-height:36px;color:#333;border:0;border-bottom:1px solid #e5e5e5}legend small{font-size:13.5px;color:#999}label,input,button,select,textarea{font-size:14px;font-weight:normal;line-height:18px}input,button,select,textarea{font-family:'Open Sans',"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:18px;padding:4px;margin-bottom:9px;font-size:14px;line-height:18px;color:#888}input,textarea{width:210px}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 #ccc;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;-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;-ms-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:3px 0;*margin-top:0;line-height:normal;cursor:pointer}input[type="submit"],input[type="reset"],input[type="button"],input[type="radio"],input[type="checkbox"]{width:auto}.uneditable-textarea{width:auto;height:auto}select,input[type="file"]{height:28px;*margin-top:4px;line-height:28px}select{width:220px;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}.radio,.checkbox{min-height:18px;padding-left:18px}.radio input[type="radio"],.checkbox input[type="checkbox"]{float:left;margin-left:-18px}.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-prepend [class*="span"],.row-fluid .input-append [class*="span"]{display:inline-block}input,textarea,.uneditable-input{margin-left:0}input.span12,textarea.span12,.uneditable-input.span12{width:930px}input.span11,textarea.span11,.uneditable-input.span11{width:850px}input.span10,textarea.span10,.uneditable-input.span10{width:770px}input.span9,textarea.span9,.uneditable-input.span9{width:690px}input.span8,textarea.span8,.uneditable-input.span8{width:610px}input.span7,textarea.span7,.uneditable-input.span7{width:530px}input.span6,textarea.span6,.uneditable-input.span6{width:450px}input.span5,textarea.span5,.uneditable-input.span5{width:370px}input.span4,textarea.span4,.uneditable-input.span4{width:290px}input.span3,textarea.span3,.uneditable-input.span3{width:210px}input.span2,textarea.span2,.uneditable-input.span2{width:130px}input.span1,textarea.span1,.uneditable-input.span1{width:50px}input[disabled],select[disabled],textarea[disabled],input[readonly],select[readonly],textarea[readonly]{cursor:not-allowed;background-color:#eee;border-color:#ddd}input[type="radio"][disabled],input[type="checkbox"][disabled],input[type="radio"][readonly],input[type="checkbox"][readonly]{background-color:transparent}.control-group.warning>label,.control-group.warning .help-block,.control-group.warning .help-inline{color:#c09853}.control-group.warning .checkbox,.control-group.warning .radio,.control-group.warning input,.control-group.warning select,.control-group.warning textarea{color:#c09853;border-color:#c09853}.control-group.warning .checkbox:focus,.control-group.warning .radio:focus,.control-group.warning input:focus,.control-group.warning select:focus,.control-group.warning textarea:focus{border-color:#a47e3c;-webkit-box-shadow:0 0 6px #dbc59e;-moz-box-shadow:0 0 6px #dbc59e;box-shadow:0 0 6px #dbc59e}.control-group.warning .input-prepend .add-on,.control-group.warning .input-append .add-on{color:#c09853;background-color:#fcf8e3;border-color:#c09853}.control-group.error>label,.control-group.error .help-block,.control-group.error .help-inline{color:#b94a48}.control-group.error .checkbox,.control-group.error .radio,.control-group.error input,.control-group.error select,.control-group.error textarea{color:#b94a48;border-color:#b94a48}.control-group.error .checkbox:focus,.control-group.error .radio:focus,.control-group.error input:focus,.control-group.error select:focus,.control-group.error textarea:focus{border-color:#953b39;-webkit-box-shadow:0 0 6px #d59392;-moz-box-shadow:0 0 6px #d59392;box-shadow:0 0 6px #d59392}.control-group.error .input-prepend .add-on,.control-group.error .input-append .add-on{color:#b94a48;background-color:#f2dede;border-color:#b94a48}.control-group.success>label,.control-group.success .help-block,.control-group.success .help-inline{color:#468847}.control-group.success .checkbox,.control-group.success .radio,.control-group.success input,.control-group.success select,.control-group.success textarea{color:#468847;border-color:#468847}.control-group.success .checkbox:focus,.control-group.success .radio:focus,.control-group.success input:focus,.control-group.success select:focus,.control-group.success textarea:focus{border-color:#356635;-webkit-box-shadow:0 0 6px #7aba7b;-moz-box-shadow:0 0 6px #7aba7b;box-shadow:0 0 6px #7aba7b}.control-group.success .input-prepend .add-on,.control-group.success .input-append .add-on{color:#468847;background-color:#dff0d8;border-color:#468847}input:focus:required:invalid,textarea:focus:required:invalid,select:focus:required:invalid{color:#b94a48;border-color:#ee5f5b}input:focus:required:invalid:focus,textarea:focus:required:invalid:focus,select:focus:required: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:17px 20px 18px;margin-top:18px;margin-bottom:18px;background-color:#eee;border-top:1px solid #e5e5e5;*zoom:1}.form-actions:before,.form-actions:after{display:table;content:""}.form-actions:after{clear:both}.uneditable-input{overflow:hidden;white-space:nowrap;cursor:not-allowed;background-color:#fff;border-color:#eee;-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)}:-moz-placeholder{color:#999}:-ms-input-placeholder{color:#999}::-webkit-input-placeholder{color:#999}.help-block,.help-inline{color:#888}.help-block{display:block;margin-bottom:9px}.help-inline{display:inline-block;*display:inline;padding-left:5px;vertical-align:middle;*zoom:1}.input-prepend,.input-append{margin-bottom:5px}.input-prepend input,.input-append input,.input-prepend select,.input-append select,.input-prepend .uneditable-input,.input-append .uneditable-input{position:relative;margin-bottom:0;*margin-left:0;vertical-align:middle;-webkit-border-radius:0 3px 3px 0;-moz-border-radius:0 3px 3px 0;border-radius:0 3px 3px 0}.input-prepend input:focus,.input-append input:focus,.input-prepend select:focus,.input-append select:focus,.input-prepend .uneditable-input:focus,.input-append .uneditable-input:focus{z-index:2}.input-prepend .uneditable-input,.input-append .uneditable-input{border-left-color:#ccc}.input-prepend .add-on,.input-append .add-on{display:inline-block;width:auto;height:18px;min-width:16px;padding:4px 5px;font-weight:normal;line-height:18px;text-align:center;text-shadow:0 1px 0 #fff;vertical-align:middle;background-color:#eee;border:1px solid #ccc}.input-prepend .add-on,.input-append .add-on,.input-prepend .btn,.input-append .btn{margin-left:-1px;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.input-prepend .active,.input-append .active{background-color:#85e8a2;border-color:#22b24c}.input-prepend .add-on,.input-prepend .btn{margin-right:-1px}.input-prepend .add-on:first-child,.input-prepend .btn:first-child{-webkit-border-radius:3px 0 0 3px;-moz-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px}.input-append input,.input-append select,.input-append .uneditable-input{-webkit-border-radius:3px 0 0 3px;-moz-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px}.input-append .uneditable-input{border-right-color:#ccc;border-left-color:#eee}.input-append .add-on:last-child,.input-append .btn:last-child{-webkit-border-radius:0 3px 3px 0;-moz-border-radius:0 3px 3px 0;border-radius:0 3px 3px 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 .add-on:first-child,.input-prepend.input-append .btn:first-child{margin-right:-1px;-webkit-border-radius:3px 0 0 3px;-moz-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px}.input-prepend.input-append .add-on:last-child,.input-prepend.input-append .btn:last-child{margin-left:-1px;-webkit-border-radius:0 3px 3px 0;-moz-border-radius:0 3px 3px 0;border-radius:0 3px 3px 0}.search-query{padding-right:14px;padding-right:4px \9;padding-left:14px;padding-left:4px \9;margin-bottom:0;-webkit-border-radius:14px;-moz-border-radius:14px;border-radius: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;*zoom:1}.form-search .hide,.form-inline .hide,.form-horizontal .hide{display:none}.form-search label,.form-inline label{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:9px}legend+.control-group{margin-top:18px;-webkit-margin-top-collapse:separate}.form-horizontal .control-group{margin-bottom:18px;*zoom:1}.form-horizontal .control-group:before,.form-horizontal .control-group:after{display:table;content:""}.form-horizontal .control-group:after{clear:both}.form-horizontal .control-label{float:left;width:140px;padding-top:5px;text-align:right}.form-horizontal .controls{*display:inline-block;*padding-left:20px;margin-left:160px;*margin-left:0}.form-horizontal .controls:first-child{*padding-left:160px}.form-horizontal .help-block{margin-top:9px;margin-bottom:0}.form-horizontal .form-actions{padding-left:160px}table{max-width:100%;background-color:transparent;border-collapse:collapse;border-spacing:0}.table{width:100%;margin-bottom:18px}.table th,.table td{padding:8px;line-height:18px;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-condensed th,.table-condensed td{padding:4px 5px}.table-bordered{border:1px solid #ddd;border-collapse:separate;*border-collapse:collapsed;border-left:0;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.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{-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-topleft:4px}.table-bordered thead:first-child tr:first-child th:last-child,.table-bordered tbody:first-child tr:first-child td:last-child{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-moz-border-radius-topright:4px}.table-bordered thead:last-child tr:last-child th:first-child,.table-bordered tbody:last-child tr:last-child td:first-child{-webkit-border-radius:0 0 0 4px;-moz-border-radius:0 0 0 4px;border-radius:0 0 0 4px;-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px;-moz-border-radius-bottomleft:4px}.table-bordered thead:last-child tr:last-child th:last-child,.table-bordered tbody:last-child tr:last-child td:last-child{-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px;-moz-border-radius-bottomright:4px}.table-striped tbody tr:nth-child(odd) td,.table-striped tbody tr:nth-child(odd) th{background-color:#eee}.table tbody tr:hover td,.table tbody tr:hover th{background-color:#f5f5f5}table .span1{float:none;width:44px;margin-left:0}table .span2{float:none;width:124px;margin-left:0}table .span3{float:none;width:204px;margin-left:0}table .span4{float:none;width:284px;margin-left:0}table .span5{float:none;width:364px;margin-left:0}table .span6{float:none;width:444px;margin-left:0}table .span7{float:none;width:524px;margin-left:0}table .span8{float:none;width:604px;margin-left:0}table .span9{float:none;width:684px;margin-left:0}table .span10{float:none;width:764px;margin-left:0}table .span11{float:none;width:844px;margin-left:0}table .span12{float:none;width:924px;margin-left:0}table .span13{float:none;width:1004px;margin-left:0}table .span14{float:none;width:1084px;margin-left:0}table .span15{float:none;width:1164px;margin-left:0}table .span16{float:none;width:1244px;margin-left:0}table .span17{float:none;width:1324px;margin-left:0}table .span18{float:none;width:1404px;margin-left:0}table .span19{float:none;width:1484px;margin-left:0}table .span20{float:none;width:1564px;margin-left:0}table .span21{float:none;width:1644px;margin-left:0}table .span22{float:none;width:1724px;margin-left:0}table .span23{float:none;width:1804px;margin-left:0}table .span24{float:none;width:1884px;margin-left:0}[class^="icon-"],[class*=" icon-"]{display:inline-block;width:14px;height:14px;*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}[class^="icon-"]:last-child,[class*=" icon-"]:last-child{*margin-left:0}.icon-white{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{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{background-position:-384px -120px}.icon-folder-open{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:"";opacity:.3;filter:alpha(opacity=30)}.dropdown .caret{margin-top:8px;margin-left:2px}.dropdown:hover .caret,.open .caret{opacity:1;filter:alpha(opacity=100)}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:4px 0;margin:1px 0 0;list-style:none;background-color:#fcfbfd;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);*border-right-width:2px;*border-bottom-width:2px;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px;-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:8px 1px;*margin:-5px 0 5px;overflow:hidden;background-color:#e5e5e5;border-bottom:1px solid #fff}.dropdown-menu a{display:block;padding:3px 15px;clear:both;font-weight:normal;line-height:18px;color:#222;white-space:nowrap}.dropdown-menu li>a:hover,.dropdown-menu .active>a,.dropdown-menu .active>a:hover{color:#222;text-decoration:none;background-color:#eee}.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:"\2191"}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:1px}.typeahead{margin-top:2px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #eee;border:1px solid rgba(0,0,0,0.05);-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-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:6px;-moz-border-radius:6px;border-radius:6px}.well-small{padding:9px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.fade{opacity:0;-webkit-transition:opacity .15s linear;-moz-transition:opacity .15s linear;-ms-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;-ms-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:18px;color:#000;text-shadow:0 1px 0 #fff;opacity:.2;filter:alpha(opacity=20)}.close:hover{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 10px 4px;margin-bottom:0;*margin-left:.3em;font-size:14px;line-height:18px;*line-height:20px;color:#333;text-align:center;text-shadow:0 1px 1px rgba(255,255,255,0.75);vertical-align:middle;cursor:pointer;background-color:#f5f5f5;*background-color:#e6e6e6;background-image:-ms-linear-gradient(top,#fff,#e6e6e6);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fff),to(#e6e6e6));background-image:-webkit-linear-gradient(top,#fff,#e6e6e6);background-image:-o-linear-gradient(top,#fff,#e6e6e6);background-image:linear-gradient(top,#fff,#e6e6e6);background-image:-moz-linear-gradient(top,#fff,#e6e6e6);background-repeat:repeat-x;border:1px solid #ccc;*border:0;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);border-color:#e6e6e6 #e6e6e6 #bfbfbf;border-bottom-color:#b3b3b3;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;filter:progid:dximagetransform.microsoft.gradient(startColorstr='#ffffff',endColorstr='#e6e6e6',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:active,.btn.active,.btn.disabled,.btn[disabled]{background-color:#e6e6e6;*background-color:#d9d9d9}.btn:active,.btn.active{background-color:#ccc \9}.btn:first-child{*margin-left:0}.btn:hover{color:#333;text-decoration:none;background-color:#e6e6e6;*background-color:#d9d9d9;background-position:0 -15px;-webkit-transition:background-position .1s linear;-moz-transition:background-position .1s linear;-ms-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-color:#e6e6e6;background-color:#d9d9d9 \9;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-color:#e6e6e6;background-image:none;opacity:.65;filter:alpha(opacity=65);-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.btn-large{padding:9px 14px;font-size:16px;line-height:normal;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px}.btn-large [class^="icon-"]{margin-top:1px}.btn-small{padding:5px 9px;font-size:12px;line-height:16px}.btn-small [class^="icon-"]{margin-top:-1px}.btn-mini{padding:2px 6px;font-size:12px;line-height:14px}.btn-primary,.btn-primary:hover,.btn-warning,.btn-warning:hover,.btn-danger,.btn-danger:hover,.btn-success,.btn-success:hover,.btn-info,.btn-info:hover,.btn-inverse,.btn-inverse:hover{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25)}.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{border-color:#ccc;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25)}.btn-primary{background-color:#4372d3;*background-color:#435cd3;background-image:-ms-linear-gradient(top,#4380d3,#435cd3);background-image:-webkit-gradient(linear,0 0,0 100%,from(#4380d3),to(#435cd3));background-image:-webkit-linear-gradient(top,#4380d3,#435cd3);background-image:-o-linear-gradient(top,#4380d3,#435cd3);background-image:-moz-linear-gradient(top,#4380d3,#435cd3);background-image:linear-gradient(top,#4380d3,#435cd3);background-repeat:repeat-x;border-color:#435cd3 #435cd3 #263ca3;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='#4380d3',endColorstr='#435cd3',GradientType=0);filter:progid:dximagetransform.microsoft.gradient(enabled=false)}.btn-primary:hover,.btn-primary:active,.btn-primary.active,.btn-primary.disabled,.btn-primary[disabled]{background-color:#435cd3;*background-color:#304bcd}.btn-primary:active,.btn-primary.active{background-color:#2b44b8 \9}.btn-warning{background-color:#ff8e1f;*background-color:#ff7f00;background-image:-ms-linear-gradient(top,#f93,#ff7f00);background-image:-webkit-gradient(linear,0 0,0 100%,from(#f93),to(#ff7f00));background-image:-webkit-linear-gradient(top,#f93,#ff7f00);background-image:-o-linear-gradient(top,#f93,#ff7f00);background-image:-moz-linear-gradient(top,#f93,#ff7f00);background-image:linear-gradient(top,#f93,#ff7f00);background-repeat:repeat-x;border-color:#ff7f00 #ff7f00 #b35900;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='#ff9933',endColorstr='#ff7f00',GradientType=0);filter:progid:dximagetransform.microsoft.gradient(enabled=false)}.btn-warning:hover,.btn-warning:active,.btn-warning.active,.btn-warning.disabled,.btn-warning[disabled]{background-color:#ff7f00;*background-color:#e67200}.btn-warning:active,.btn-warning.active{background-color:#c60 \9}.btn-danger{background-color:#da4f49;*background-color:#bd362f;background-image:-ms-linear-gradient(top,#ee5f5b,#bd362f);background-image:-webkit-gradient(linear,0 0,0 100%,from(#ee5f5b),to(#bd362f));background-image:-webkit-linear-gradient(top,#ee5f5b,#bd362f);background-image:-o-linear-gradient(top,#ee5f5b,#bd362f);background-image:-moz-linear-gradient(top,#ee5f5b,#bd362f);background-image:linear-gradient(top,#ee5f5b,#bd362f);background-repeat:repeat-x;border-color:#bd362f #bd362f #802420;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='#ee5f5b',endColorstr='#bd362f',GradientType=0);filter:progid:dximagetransform.microsoft.gradient(enabled=false)}.btn-danger:hover,.btn-danger:active,.btn-danger.active,.btn-danger.disabled,.btn-danger[disabled]{background-color:#bd362f;*background-color:#a9302a}.btn-danger:active,.btn-danger.active{background-color:#942a25 \9}.btn-success{background-color:#5bb75b;*background-color:#51a351;background-image:-ms-linear-gradient(top,#62c462,#51a351);background-image:-webkit-gradient(linear,0 0,0 100%,from(#62c462),to(#51a351));background-image:-webkit-linear-gradient(top,#62c462,#51a351);background-image:-o-linear-gradient(top,#62c462,#51a351);background-image:-moz-linear-gradient(top,#62c462,#51a351);background-image:linear-gradient(top,#62c462,#51a351);background-repeat:repeat-x;border-color:#51a351 #51a351 #387038;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='#62c462',endColorstr='#51a351',GradientType=0);filter:progid:dximagetransform.microsoft.gradient(enabled=false)}.btn-success:hover,.btn-success:active,.btn-success.active,.btn-success.disabled,.btn-success[disabled]{background-color:#51a351;*background-color:#499249}.btn-success:active,.btn-success.active{background-color:#408140 \9}.btn-info{background-color:#49afcd;*background-color:#2f96b4;background-image:-ms-linear-gradient(top,#5bc0de,#2f96b4);background-image:-webkit-gradient(linear,0 0,0 100%,from(#5bc0de),to(#2f96b4));background-image:-webkit-linear-gradient(top,#5bc0de,#2f96b4);background-image:-o-linear-gradient(top,#5bc0de,#2f96b4);background-image:-moz-linear-gradient(top,#5bc0de,#2f96b4);background-image:linear-gradient(top,#5bc0de,#2f96b4);background-repeat:repeat-x;border-color:#2f96b4 #2f96b4 #1f6377;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='#5bc0de',endColorstr='#2f96b4',GradientType=0);filter:progid:dximagetransform.microsoft.gradient(enabled=false)}.btn-info:hover,.btn-info:active,.btn-info.active,.btn-info.disabled,.btn-info[disabled]{background-color:#2f96b4;*background-color:#2a85a0}.btn-info:active,.btn-info.active{background-color:#24748c \9}.btn-inverse{background-color:#3b7bd0;*background-color:#3072cd;background-image:-ms-linear-gradient(top,#4380d3,#3072cd);background-image:-webkit-gradient(linear,0 0,0 100%,from(#4380d3),to(#3072cd));background-image:-webkit-linear-gradient(top,#4380d3,#3072cd);background-image:-o-linear-gradient(top,#4380d3,#3072cd);background-image:-moz-linear-gradient(top,#4380d3,#3072cd);background-image:linear-gradient(top,#4380d3,#3072cd);background-repeat:repeat-x;border-color:#3072cd #3072cd #21508f;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='#4380d3',endColorstr='#3072cd',GradientType=0);filter:progid:dximagetransform.microsoft.gradient(enabled=false)}.btn-inverse:hover,.btn-inverse:active,.btn-inverse.active,.btn-inverse.disabled,.btn-inverse[disabled]{background-color:#3072cd;*background-color:#2b67b8}.btn-inverse:active,.btn-inverse.active{background-color:#265ba3 \9}button.btn,input[type="submit"].btn{*padding-top:2px;*padding-bottom:2px}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-group{position:relative;*margin-left:.3em;*zoom:1}.btn-group:before,.btn-group:after{display:table;content:""}.btn-group:after{clear:both}.btn-group:first-child{*margin-left:0}.btn-group+.btn-group{margin-left:5px}.btn-toolbar{margin-top:9px;margin-bottom:9px}.btn-toolbar .btn-group{display:inline-block;*display:inline;*zoom:1}.btn-group>.btn{position:relative;float:left;margin-left:-1px;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.btn-group>.btn:first-child{margin-left:0;-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px;-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-bottomleft:4px;-moz-border-radius-topleft:4px}.btn-group>.btn:last-child,.btn-group>.dropdown-toggle{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px;-moz-border-radius-topright:4px;-moz-border-radius-bottomright:4px}.btn-group>.btn.large:first-child{margin-left:0;-webkit-border-bottom-left-radius:6px;border-bottom-left-radius:6px;-webkit-border-top-left-radius:6px;border-top-left-radius:6px;-moz-border-radius-bottomleft:6px;-moz-border-radius-topleft:6px}.btn-group>.btn.large:last-child,.btn-group>.large.dropdown-toggle{-webkit-border-top-right-radius:6px;border-top-right-radius:6px;-webkit-border-bottom-right-radius:6px;border-bottom-right-radius:6px;-moz-border-radius-topright:6px;-moz-border-radius-bottomright:6px}.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>.dropdown-toggle{*padding-top:4px;padding-right:8px;*padding-bottom:4px;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-right:5px;padding-left:5px}.btn-group>.btn-small.dropdown-toggle{*padding-top:4px;*padding-bottom:4px}.btn-group>.btn-large.dropdown-toggle{padding-right:12px;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:#e6e6e6}.btn-group.open .btn-primary.dropdown-toggle{background-color:#435cd3}.btn-group.open .btn-warning.dropdown-toggle{background-color:#ff7f00}.btn-group.open .btn-danger.dropdown-toggle{background-color:#bd362f}.btn-group.open .btn-success.dropdown-toggle{background-color:#51a351}.btn-group.open .btn-info.dropdown-toggle{background-color:#2f96b4}.btn-group.open .btn-inverse.dropdown-toggle{background-color:#3072cd}.btn .caret{margin-top:7px;margin-left:0}.btn:hover .caret,.open.btn-group .caret{opacity:1;filter:alpha(opacity=100)}.btn-mini .caret{margin-top:5px}.btn-small .caret{margin-top:6px}.btn-large .caret{margin-top:6px;border-top-width:5px;border-right-width:5px;border-left-width:5px}.dropup .btn-large .caret{border-top:0;border-bottom:5px solid #000}.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;opacity:.75;filter:alpha(opacity=75)}.alert{padding:8px 35px 8px 14px;margin-bottom:18px;color:#c09853;text-shadow:0 1px 0 rgba(255,255,255,0.5);background-color:#fcf8e3;border:1px solid #fbeed5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.alert-heading{color:inherit}.alert .close{position:relative;top:-2px;right:-21px;line-height:18px}.alert-success{color:#468847;background-color:#dff0d8;border-color:#d6e9c6}.alert-danger,.alert-error{color:#b94a48;background-color:#f2dede;border-color:#eed3d7}.alert-info{color:#3a87ad;background-color:#d9edf7;border-color:#bce8f1}.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:18px;margin-left:0;list-style:none}.nav>li>a{display:block}.nav>li>a:hover{text-decoration:none;background-color:#eee}.nav>.pull-right{float:right}.nav .nav-header{display:block;padding:3px 15px;font-size:11px;font-weight:bold;line-height:18px;color:#999;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{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.2);background-color:#4380d3}.nav-list [class^="icon-"]{margin-right:2px}.nav-list .divider{*width:100%;height:1px;margin:8px 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;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:18px;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{border-color:#eee #eee #ddd}.nav-tabs>.active>a,.nav-tabs>.active>a:hover{color:#888;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{color:#fff;background-color:#4380d3}.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-radius:4px 4px 0 0;-moz-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0}.nav-tabs.nav-stacked>li:last-child>a{-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px}.nav-tabs.nav-stacked>li>a:hover{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 5px 5px;-moz-border-radius:0 0 5px 5px;border-radius:0 0 5px 5px}.nav-pills .dropdown-menu{-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.nav-tabs .dropdown-toggle .caret,.nav-pills .dropdown-toggle .caret{margin-top:6px;border-top-color:#4380d3;border-bottom-color:#4380d3}.nav-tabs .dropdown-toggle:hover .caret,.nav-pills .dropdown-toggle:hover .caret{border-top-color:#c00;border-bottom-color:#c00}.nav-tabs .active .dropdown-toggle .caret,.nav-pills .active .dropdown-toggle .caret{border-top-color:#333;border-bottom-color:#333}.nav>.dropdown.active>a:hover{color:#000;cursor:pointer}.nav-tabs .open .dropdown-toggle,.nav-pills .open .dropdown-toggle,.nav>li.dropdown.open.active>a:hover{color:#fff;background-color:#999;border-color:#999}.nav li.dropdown.open .caret,.nav li.dropdown.open.active .caret,.nav li.dropdown.open a:hover .caret{border-top-color:#fff;border-bottom-color:#fff;opacity:1;filter:alpha(opacity=100)}.tabs-stacked .open>a:hover{border-color:#999}.tabbable{*zoom:1}.tabbable:before,.tabbable:after{display:table;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{border-top-color:#ddd;border-bottom-color:transparent}.tabs-below>.nav-tabs>.active>a,.tabs-below>.nav-tabs>.active>a:hover{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{border-color:#eee #ddd #eee #eee}.tabs-left>.nav-tabs .active>a,.tabs-left>.nav-tabs .active>a:hover{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{border-color:#eee #eee #eee #ddd}.tabs-right>.nav-tabs .active>a,.tabs-right>.nav-tabs .active>a:hover{border-color:#ddd #ddd #ddd transparent;*border-left-color:#fff}.navbar{*position:relative;*z-index:2;margin-bottom:18px;overflow:visible}.navbar-inner{min-height:50px;padding-right:20px;padding-left:20px;background-color:#fcfbfd;background-image:-moz-linear-gradient(top,#fcfbfd,#fcfbfd);background-image:-ms-linear-gradient(top,#fcfbfd,#fcfbfd);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fcfbfd),to(#fcfbfd));background-image:-webkit-linear-gradient(top,#fcfbfd,#fcfbfd);background-image:-o-linear-gradient(top,#fcfbfd,#fcfbfd);background-image:linear-gradient(top,#fcfbfd,#fcfbfd);background-repeat:repeat-x;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;filter:progid:dximagetransform.microsoft.gradient(startColorstr='#fcfbfd',endColorstr='#fcfbfd',GradientType=0);-webkit-box-shadow:0 1px 3px rgba(0,0,0,0.25),inset 0 -1px 0 rgba(0,0,0,0.1);-moz-box-shadow:0 1px 3px rgba(0,0,0,0.25),inset 0 -1px 0 rgba(0,0,0,0.1);box-shadow:0 1px 3px rgba(0,0,0,0.25),inset 0 -1px 0 rgba(0,0,0,0.1)}.navbar .container{width:auto}.nav-collapse.collapse{height:auto}.navbar{color:#222}.navbar .brand:hover{text-decoration:none}.navbar .brand{display:block;float:left;padding:13px 20px 17px;margin-left:-20px;font-size:20px;font-weight:200;line-height:1;color:#4380d3}.navbar .navbar-text{margin-bottom:0;line-height:50px}.navbar .navbar-link{color:#4380d3}.navbar .navbar-link:hover{color:#4380d3}.navbar .btn,.navbar .btn-group{margin-top:10px}.navbar .btn-group .btn{margin:0}.navbar-form{margin-bottom:0;*zoom:1}.navbar-form:before,.navbar-form:after{display:table;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{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:6px;white-space:nowrap}.navbar-form .input-append input,.navbar-form .input-prepend input{margin-top:0}.navbar-search{position:relative;float:left;margin-top:11px;margin-bottom:0}.navbar-search .search-query{padding:4px 9px;font-family:'Open Sans',"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:13px;font-weight:normal;line-height:1;color:#fff;background-color:#fff;border:1px solid #b3b3b3;-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;-ms-transition:none;-o-transition:none;transition:none}.navbar-search .search-query:-moz-placeholder{color:#ccc}.navbar-search .search-query:-ms-input-placeholder{color:#ccc}.navbar-search .search-query::-webkit-input-placeholder{color:#ccc}.navbar-search .search-query:focus,.navbar-search .search-query.focused{padding:5px 10px;color:#333;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-fixed-top,.navbar-fixed-bottom{position:fixed;right:0;left:0;z-index:1030;margin-bottom: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-fixed-top .container,.navbar-fixed-bottom .container{width:940px}.navbar-fixed-top{top:0}.navbar-fixed-bottom{bottom:0}.navbar .nav{position:relative;left:0;display:block;float:left;margin:0 10px 0 0}.navbar .nav.pull-right{float:right}.navbar .nav>li{display:block;float:left}.navbar .nav>li>a{float:none;padding:14px 10px 16px;line-height:19px;color:#4380d3;text-decoration:none;text-shadow:0 -1px 0 rgba(0,0,0,0.25)}.navbar .btn{display:inline-block;padding:4px 10px 4px;margin:10px 5px 11px;line-height:18px}.navbar .btn-group{padding:10px 5px 11px;margin:0}.navbar .nav>li>a:hover{color:#4380d3;text-decoration:none;background-color:#eee}.navbar .nav .active>a,.navbar .nav .active>a:hover{color:#4380d3;text-decoration:none;background-color:#eee}.navbar .divider-vertical{width:1px;height:50px;margin:0 9px;overflow:hidden;background-color:#fcfbfd;border-right:1px solid #fcfbfd}.navbar .nav.pull-right{margin-right:0;margin-left:10px}.navbar .btn-navbar{display:none;float:right;padding:7px 10px;margin-right:5px;margin-left:5px;background-color:#fcfbfd;*background-color:#fcfbfd;background-image:-ms-linear-gradient(top,#fcfbfd,#fcfbfd);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fcfbfd),to(#fcfbfd));background-image:-webkit-linear-gradient(top,#fcfbfd,#fcfbfd);background-image:-o-linear-gradient(top,#fcfbfd,#fcfbfd);background-image:linear-gradient(top,#fcfbfd,#fcfbfd);background-image:-moz-linear-gradient(top,#fcfbfd,#fcfbfd);background-repeat:repeat-x;border-color:#fcfbfd #fcfbfd #d6c8e4;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='#fcfbfd',endColorstr='#fcfbfd',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:active,.navbar .btn-navbar.active,.navbar .btn-navbar.disabled,.navbar .btn-navbar[disabled]{background-color:#fcfbfd;*background-color:#efeaf5}.navbar .btn-navbar:active,.navbar .btn-navbar.active{background-color:#e3d9ec \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 .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 .dropdown-menu:after{position:absolute;top:-6px;left:10px;display:inline-block;border-right:6px solid transparent;border-bottom:6px solid #fcfbfd;border-left:6px solid transparent;content:''}.navbar-fixed-bottom .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 .dropdown-menu:after{top:auto;bottom:-6px;border-top:6px solid #fcfbfd;border-bottom:0}.navbar .nav li.dropdown .dropdown-toggle .caret,.navbar .nav li.dropdown.open .caret{border-top-color:#fff;border-bottom-color:#fff}.navbar .nav li.dropdown.active .caret{opacity:1;filter:alpha(opacity=100)}.navbar .nav li.dropdown.open>.dropdown-toggle,.navbar .nav li.dropdown.active>.dropdown-toggle,.navbar .nav li.dropdown.open.active>.dropdown-toggle{background-color:transparent}.navbar .nav li.dropdown.active>.dropdown-toggle:hover{color:#fff}.navbar .pull-right .dropdown-menu,.navbar .dropdown-menu.pull-right{right:0;left:auto}.navbar .pull-right .dropdown-menu:before,.navbar .dropdown-menu.pull-right:before{right:12px;left:auto}.navbar .pull-right .dropdown-menu:after,.navbar .dropdown-menu.pull-right:after{right:13px;left:auto}.breadcrumb{padding:7px 14px;margin:0 0 18px;list-style:none;background-color:#fbfbfb;background-image:-moz-linear-gradient(top,#fff,#f5f5f5);background-image:-ms-linear-gradient(top,#fff,#f5f5f5);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fff),to(#f5f5f5));background-image:-webkit-linear-gradient(top,#fff,#f5f5f5);background-image:-o-linear-gradient(top,#fff,#f5f5f5);background-image:linear-gradient(top,#fff,#f5f5f5);background-repeat:repeat-x;border:1px solid #ddd;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;filter:progid:dximagetransform.microsoft.gradient(startColorstr='#ffffff',endColorstr='#f5f5f5',GradientType=0);-webkit-box-shadow:inset 0 1px 0 #fff;-moz-box-shadow:inset 0 1px 0 #fff;box-shadow:inset 0 1px 0 #fff}.breadcrumb li{display:inline-block;*display:inline;text-shadow:0 1px 0 #fff;*zoom:1}.breadcrumb .divider{padding:0 5px;color:#999}.breadcrumb .active a{color:#333}.pagination{height:36px;margin:18px 0}.pagination ul{display:inline-block;*display:inline;margin-bottom:0;margin-left:0;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;*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 li{display:inline}.pagination a{float:left;padding:0 14px;line-height:34px;text-decoration:none;border:1px solid #ddd;border-left-width:0}.pagination a:hover,.pagination .active a{background-color:#f5f5f5}.pagination .active a{color:#999;cursor:default}.pagination .disabled span,.pagination .disabled a,.pagination .disabled a:hover{color:#999;cursor:default;background-color:transparent}.pagination li:first-child a{border-left-width:1px;-webkit-border-radius:3px 0 0 3px;-moz-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px}.pagination li:last-child a{-webkit-border-radius:0 3px 3px 0;-moz-border-radius:0 3px 3px 0;border-radius:0 3px 3px 0}.pagination-centered{text-align:center}.pagination-right{text-align:right}.pager{margin-bottom:18px;margin-left:0;text-align:center;list-style:none;*zoom:1}.pager:before,.pager:after{display:table;content:""}.pager:after{clear:both}.pager li{display:inline}.pager a{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 a:hover{text-decoration:none;background-color:#f5f5f5}.pager .next a{float:right}.pager .previous a{float:left}.pager .disabled a,.pager .disabled a:hover{color:#999;cursor:default;background-color:#fff}.modal-open .dropdown-menu{z-index:2050}.modal-open .dropdown.open{*z-index:2050}.modal-open .popover{z-index:2060}.modal-open .tooltip{z-index:2070}.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:50%;left:50%;z-index:1050;width:560px;margin:-250px 0 0 -280px;overflow:auto;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;-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;-ms-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:50%}.modal-header{padding:9px 15px;border-bottom:1px solid #eee}.modal-header .close{margin-top:2px}.modal-body{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;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}.tooltip{position:absolute;z-index:1020;display:block;padding:5px;font-size:11px;opacity:0;filter:alpha(opacity=0);visibility:visible}.tooltip.in{opacity:.8;filter:alpha(opacity=80)}.tooltip.top{margin-top:-2px}.tooltip.right{margin-left:2px}.tooltip.bottom{margin-top:2px}.tooltip.left{margin-left:-2px}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-top:5px solid #000;border-right:5px solid transparent;border-left:5px solid transparent}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-top:5px solid transparent;border-bottom:5px solid transparent;border-left:5px solid #000}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-right:5px solid transparent;border-bottom:5px solid #000;border-left:5px solid transparent}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-top:5px solid transparent;border-right:5px solid #000;border-bottom:5px solid transparent}.tooltip-inner{max-width:200px;padding:3px 8px;color:#fff;text-align:center;text-decoration:none;background-color:#000;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0}.popover{position:absolute;top:0;left:0;z-index:1010;display:none;padding:5px}.popover.top{margin-top:-5px}.popover.right{margin-left:5px}.popover.bottom{margin-top:5px}.popover.left{margin-left:-5px}.popover.top .arrow{bottom:0;left:50%;margin-left:-5px;border-top:5px solid #000;border-right:5px solid transparent;border-left:5px solid transparent}.popover.right .arrow{top:50%;left:0;margin-top:-5px;border-top:5px solid transparent;border-right:5px solid #000;border-bottom:5px solid transparent}.popover.bottom .arrow{top:0;left:50%;margin-left:-5px;border-right:5px solid transparent;border-bottom:5px solid #000;border-left:5px solid transparent}.popover.left .arrow{top:50%;right:0;margin-top:-5px;border-top:5px solid transparent;border-bottom:5px solid transparent;border-left:5px solid #000}.popover .arrow{position:absolute;width:0;height:0}.popover-inner{width:280px;padding:3px;overflow:hidden;background:#000;background:rgba(0,0,0,0.8);-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-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)}.popover-title{padding:9px 15px;line-height:1;background-color:#f5f5f5;border-bottom:1px solid #eee;-webkit-border-radius:3px 3px 0 0;-moz-border-radius:3px 3px 0 0;border-radius:3px 3px 0 0}.popover-content{padding:14px;background-color:#fff;-webkit-border-radius:0 0 3px 3px;-moz-border-radius:0 0 3px 3px;border-radius:0 0 3px 3px;-webkit-background-clip:padding-box;-moz-background-clip:padding-box;background-clip:padding-box}.popover-content p,.popover-content ul,.popover-content ol{margin-bottom:0}.thumbnails{margin-left:-20px;list-style:none;*zoom:1}.thumbnails:before,.thumbnails:after{display:table;content:""}.thumbnails:after{clear:both}.row-fluid .thumbnails{margin-left:0}.thumbnails>li{float:left;margin-bottom:18px;margin-left:20px}.thumbnail{display:block;padding:4px;line-height:1;border:1px solid #ddd;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:0 1px 1px rgba(0,0,0,0.075);box-shadow:0 1px 1px rgba(0,0,0,0.075)}a.thumbnail:hover{border-color:#4380d3;-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}.label,.badge{font-size:11.844px;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:#999}.label{padding:1px 4px 2px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.badge{padding:1px 9px 2px;-webkit-border-radius:9px;-moz-border-radius:9px;border-radius:9px}a.label:hover,a.badge:hover{color:#fff;text-decoration:none;cursor:pointer}.label-important,.badge-important{background-color:#b94a48}.label-important[href],.badge-important[href]{background-color:#953b39}.label-warning,.badge-warning{background-color:#ff7f00}.label-warning[href],.badge-warning[href]{background-color:#c60}.label-success,.badge-success{background-color:#468847}.label-success[href],.badge-success[href]{background-color:#356635}.label-info,.badge-info{background-color:#3a87ad}.label-info[href],.badge-info[href]{background-color:#2d6987}.label-inverse,.badge-inverse{background-color:#333}.label-inverse[href],.badge-inverse[href]{background-color:#1a1a1a}@-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:18px;margin-bottom:18px;overflow:hidden;background-color:#f7f7f7;background-image:-moz-linear-gradient(top,#f5f5f5,#f9f9f9);background-image:-ms-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(top,#f5f5f5,#f9f9f9);background-repeat:repeat-x;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;filter:progid:dximagetransform.microsoft.gradient(startColorstr='#f5f5f5',endColorstr='#f9f9f9',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{width:0;height:18px;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(top,#149bdf,#0480be);background-image:-ms-linear-gradient(top,#149bdf,#0480be);background-repeat:repeat-x;filter:progid:dximagetransform.microsoft.gradient(startColorstr='#149bdf',endColorstr='#0480be',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;-ms-box-sizing:border-box;box-sizing:border-box;-webkit-transition:width .6s ease;-moz-transition:width .6s ease;-ms-transition:width .6s ease;-o-transition:width .6s ease;transition:width .6s ease}.progress-striped .bar{background-color:#149bdf;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:-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:-ms-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:-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: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{background-color:#dd514c;background-image:-moz-linear-gradient(top,#ee5f5b,#c43c35);background-image:-ms-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(top,#ee5f5b,#c43c35);background-repeat:repeat-x;filter:progid:dximagetransform.microsoft.gradient(startColorstr='#ee5f5b',endColorstr='#c43c35',GradientType=0)}.progress-danger.progress-striped .bar{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:-ms-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{background-color:#5eb95e;background-image:-moz-linear-gradient(top,#62c462,#57a957);background-image:-ms-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(top,#62c462,#57a957);background-repeat:repeat-x;filter:progid:dximagetransform.microsoft.gradient(startColorstr='#62c462',endColorstr='#57a957',GradientType=0)}.progress-success.progress-striped .bar{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:-ms-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{background-color:#4bb1cf;background-image:-moz-linear-gradient(top,#5bc0de,#339bb9);background-image:-ms-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(top,#5bc0de,#339bb9);background-repeat:repeat-x;filter:progid:dximagetransform.microsoft.gradient(startColorstr='#5bc0de',endColorstr='#339bb9',GradientType=0)}.progress-info.progress-striped .bar{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:-ms-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{background-color:#ff962e;background-image:-moz-linear-gradient(top,#ffa54d,#ff7f00);background-image:-ms-linear-gradient(top,#ffa54d,#ff7f00);background-image:-webkit-gradient(linear,0 0,0 100%,from(#ffa54d),to(#ff7f00));background-image:-webkit-linear-gradient(top,#ffa54d,#ff7f00);background-image:-o-linear-gradient(top,#ffa54d,#ff7f00);background-image:linear-gradient(top,#ffa54d,#ff7f00);background-repeat:repeat-x;filter:progid:dximagetransform.microsoft.gradient(startColorstr='#ffa54d',endColorstr='#ff7f00',GradientType=0)}.progress-warning.progress-striped .bar{background-color:#ffa54d;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:-ms-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:18px}.accordion-group{margin-bottom:2px;border:1px solid #e5e5e5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.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:18px;line-height:1}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel .item{position:relative;display:none;-webkit-transition:.6s ease-in-out left;-moz-transition:.6s ease-in-out left;-ms-transition:.6s ease-in-out left;-o-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel .item>img{display:block;line-height:1}.carousel .active,.carousel .next,.carousel .prev{display:block}.carousel .active{left:0}.carousel .next,.carousel .prev{position:absolute;top:0;width:100%}.carousel .next{left:100%}.carousel .prev{left:-100%}.carousel .next.left,.carousel .prev.right{left:0}.carousel .active.left{left:-100%}.carousel .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:#222;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{color:#fff;text-decoration:none;opacity:.9;filter:alpha(opacity=90)}.carousel-caption{position:absolute;right:0;bottom:0;left:0;padding:10px 15px 5px;background:#333;background:rgba(0,0,0,0.75)}.carousel-caption h4,.carousel-caption p{color:#fff}.hero-unit{padding:60px;margin-bottom:30px;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 p{font-size:18px;font-weight:200;line-height:27px;color:inherit}.pull-right{float:right}.pull-left{float:left}.hide{display:none}.show{display:block}.invisible{visibility:hidden}h1,h2,h3,h4,h5,h6,.navbar .brand{font-weight:700}a{text-decoration:none}.nav a,.navbar .brand,.subnav a,a.btn,.dropdown-menu a{text-decoration:none}.navbar .navbar-inner{border-top:1px solid #e5e5e5;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:0 2px 4px rgba(0,0,0,0.25),inset 0 -1px 0 rgba(0,0,0,0.1);-moz-box-shadow:0 2px 4px rgba(0,0,0,0.25),inset 0 -1px 0 rgba(0,0,0,0.1);box-shadow:0 2px 4px rgba(0,0,0,0.25),inset 0 -1px 0 rgba(0,0,0,0.1)}.navbar .brand{text-shadow:none}.navbar .brand:hover{background-color:#eee}.navbar .navbar-text{line-height:68px}.navbar .nav>li>a{text-shadow:none}.navbar .dropdown-menu{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.navbar .nav li.dropdown.active>.dropdown-toggle,.navbar .nav li.dropdown.active>.dropdown-toggle:hover,.navbar .nav li.dropdown.open>.dropdown-toggle,.navbar .nav li.dropdown.active.open>.dropdown-toggle,.navbar .nav li.dropdown.active.open>.dropdown-toggle:hover{color:#4380d3;background-color:#eee}.navbar .nav li.dropdown .dropdown-toggle .caret,.navbar .nav .open .caret,.navbar .nav .open .dropdown-toggle:hover .caret{border-top-color:#000;opacity:1}.navbar .nav-collapse.in .nav li>a:hover{background-color:#eee}.navbar .nav-collapse .nav li>a{font-weight:normal;color:#222;text-decoration:none}.navbar .nav-collapse .navbar-form,.navbar .nav-collapse .navbar-search{border-color:transparent}.navbar .navbar-search .search-query,.navbar .navbar-search .search-query:hover{color:#222;border:1px solid #eee}.navbar .navbar-search .search-query:-moz-placeholder,.navbar .navbar-search .search-query:hover:-moz-placeholder{color:#888}.navbar .navbar-search .search-query:-ms-input-placeholder,.navbar .navbar-search .search-query:hover:-ms-input-placeholder{color:#888}.navbar .navbar-search .search-query::-webkit-input-placeholder,.navbar .navbar-search .search-query:hover::-webkit-input-placeholder{color:#888}div.subnav{background-color:#fcfbfd;background-image:none;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:0 1px 2px rgba(0,0,0,0.25);-moz-box-shadow:0 1px 2px rgba(0,0,0,0.25);box-shadow:0 1px 2px rgba(0,0,0,0.25)}div.subnav.subnav-fixed{top:50px}div.subnav .nav>li>a:hover,div.subnav .nav>.active>a,div.subnav .nav>.active>a:hover{font-weight:normal;color:#222;text-decoration:none}div.subnav .nav>li:first-child>a,div.subnav .nav>li:first-child>a:hover{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.btn-primary{background-color:#4f88d6;*background-color:#4380d3;background-image:-ms-linear-gradient(top,#588ed8,#4380d3);background-image:-webkit-gradient(linear,0 0,0 100%,from(#588ed8),to(#4380d3));background-image:-webkit-linear-gradient(top,#588ed8,#4380d3);background-image:-o-linear-gradient(top,#588ed8,#4380d3);background-image:-moz-linear-gradient(top,#588ed8,#4380d3);background-image:linear-gradient(top,#588ed8,#4380d3);background-repeat:repeat-x;border-color:#4380d3 #4380d3 #265ba3;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='#588ed8',endColorstr='#4380d3',GradientType=0);filter:progid:dximagetransform.microsoft.gradient(enabled=false)}.btn-primary:hover,.btn-primary:active,.btn-primary.active,.btn-primary.disabled,.btn-primary[disabled]{background-color:#4380d3;*background-color:#3072cd}.btn-primary:active,.btn-primary.active{background-color:#2b67b8 \9}[class^="icon-"],[class*=" icon-"]{vertical-align:-2px}.modal{background:#fcfbfd;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.modal-header{border-bottom:0}.modal-header .close{text-decoration:none}.modal-footer{background:transparent;border-top:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}code,pre,pre.prettyprint,.well{background-color:#eee}.hero-unit{border:1px solid rgba(0,0,0,0.05);-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)}.table-bordered,.well,.prettyprint{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0} + */.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:22px 0 33px;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/test/test_app.py b/test/test_app.py index 6fc64dbe..f35def00 100644 --- a/test/test_app.py +++ b/test/test_app.py @@ -30,6 +30,9 @@ class TestApp(tutils.DaemonTests): assert self.getpath("/log/%s"%id).status_code == 200 assert self.getpath("/log/9999999").status_code == 404 + def test_log_binary(self): + assert self.get("200:h@10b=@10b:da") + def test_response_preview(self): r = self.getpath("/response_preview", params=dict(spec="200")) assert r.status_code == 200 -- cgit v1.2.3 From 3217fcad140d3f9322052f5f1c0b6ce22479f15a Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Thu, 2 May 2013 09:11:16 +1200 Subject: Doc layout refactoring. --- libpathod/app.py | 14 +++-- libpathod/templates/docframe.html | 26 +++++++++ libpathod/templates/docs_lang.html | 2 +- libpathod/templates/docs_libpathod.html | 2 +- libpathod/templates/docs_pathoc.html | 2 +- libpathod/templates/docs_pathod.html | 2 +- libpathod/templates/docs_test.html | 2 +- libpathod/templates/frame.html | 97 +++------------------------------ libpathod/templates/layout.html | 74 +++++++++++++++++++++++++ 9 files changed, 122 insertions(+), 99 deletions(-) create mode 100644 libpathod/templates/docframe.html create mode 100644 libpathod/templates/layout.html diff --git a/libpathod/app.py b/libpathod/app.py index 42989355..fb1d6a2d 100644 --- a/libpathod/app.py +++ b/libpathod/app.py @@ -58,27 +58,31 @@ def make_app(noapi): @app.route('/docs/pathod') def docs_pathod(): - return render("docs_pathod.html", True, section="docs") + 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=http_uastrings.UASTRINGS) + return render( + "docs_lang.html", True, + section="docs", uastrings=http_uastrings.UASTRINGS, + subsection="lang" + ) @app.route('/docs/pathoc') def docs_pathoc(): - return render("docs_pathoc.html", True, section="docs") + 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") + 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") + return render("docs_test.html", True, section="docs", subsection="test") @app.route('/log') diff --git a/libpathod/templates/docframe.html b/libpathod/templates/docframe.html new file mode 100644 index 00000000..f780f379 --- /dev/null +++ b/libpathod/templates/docframe.html @@ -0,0 +1,26 @@ +{% extends "layout.html" %} + +{% macro subs(s) %} +{% if subsection == s %} class="active" {% endif %} +{% endmacro %} + + +{% block content %} +
    + +
    + {% block body %} + {% endblock %} +
    +
    +{% endblock %} diff --git a/libpathod/templates/docs_lang.html b/libpathod/templates/docs_lang.html index 26672bb2..aef12a8d 100644 --- a/libpathod/templates/docs_lang.html +++ b/libpathod/templates/docs_lang.html @@ -1,4 +1,4 @@ -{% extends "frame.html" %} +{% extends "docframe.html" %} {% block body %}