--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/.project Mon Jul 26 14:12:44 2010 -0400
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>redbot</name>
+ <comment></comment>
+ <projects>
+ </projects>
+ <buildSpec>
+ <buildCommand>
+ <name>org.python.pydev.PyDevBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ </buildSpec>
+ <natures>
+ <nature>org.python.pydev.pythonNature</nature>
+ </natures>
+</projectDescription>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/.pydevproject Mon Jul 26 14:12:44 2010 -0400
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<?eclipse-pydev version="1.0"?>
+
+<pydev_project>
+<pydev_pathproperty name="org.python.pydev.PROJECT_SOURCE_PATH">
+<path>/redbot/src</path>
+</pydev_pathproperty>
+<pydev_property name="org.python.pydev.PYTHON_PROJECT_VERSION">python 2.6</pydev_property>
+<pydev_property name="org.python.pydev.PYTHON_PROJECT_INTERPRETER">py26</pydev_property>
+<pydev_pathproperty name="org.python.pydev.PROJECT_EXTERNAL_SOURCE_PATH">
+<path>/opt/local/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/site-packages/nbhttp</path>
+</pydev_pathproperty>
+</pydev_project>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/README.rst Mon Jul 26 14:12:44 2010 -0400
@@ -0,0 +1,78 @@
+===
+RED
+===
+
+This is RED, the Resource Expert Droid.
+
+Requirements
+------------
+
+RED needs:
+
+1. Python 2.5 or greater; see <http://python.org/>
+2. a Web server that implements the CGI interface; e.g., Apache
+ <http://httpd.apache.org/>.
+3. The nbhttp library; see <http://github.com/mnot/nbhttp/>
+4. Optionally, RED will take advantage of the pyevent extension, if installed.
+ See PyEvent <http://code.google.com/p/pyevent/>.
+
+Installing RED
+--------------
+
+Unpack the RED tarball. There are a number of interesting files:
+
+- src/webui.py - the Web frontend for RED. This is what is run by the server.
+- src/\*.py - other Python files necessary for RED.
+- web/\* - RED's CSS stylesheet and JavaScript libraries.
+
+Place webui.py where you wish it to be served from the Web server. For example,
+with Apache you can put it in a directory and add these configuration directives
+(e.g., in .htaccess, if enabled)::
+
+ AddHandler cgi-script .py
+ DirectoryIndex webui.py
+
+If the directory is the root directory for your server "example.com",
+this will configure RED to be at the URI "http://example.com/".
+
+The contents of the web directory need to be made available on the server;
+by default, they're in the 'static' subdirectory of the script's URI. This
+can be changed using the 'static_root' configuration variable in webui.py.
+
+Finally, the other .py files in src must be available to Python; you can either
+place them in the same directory, or somewhere else on your PYTHONPATH. See
+Python's documentation for more information.
+
+Support, Reporting Issues and Contributing
+------------------------------------------
+
+See <http://REDbot.org/project> to give feedback, report issues, and contribute
+to the project. You can also join the redbot-users mailing list there.
+
+Credits
+-------
+
+Icons by Momenticon <http://momenticon.com/>.
+
+License
+-------
+
+Copyright (c) 2008-2009 Mark Nottingham
+
+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.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/link_parse.py Mon Jul 26 14:12:44 2010 -0400
@@ -0,0 +1,163 @@
+#!/usr/bin/env python
+
+"""
+Parsing links from streams of data.
+"""
+
+__author__ = "Mark Nottingham <mnot@mnot.net>"
+__copyright__ = """\
+Copyright (c) 2008-2010 Mark Nottingham
+
+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 htmlentitydefs import entitydefs
+from HTMLParser import HTMLParser
+
+import response_analyse
+RHP = response_analyse.ResponseHeaderParser
+
+class HTMLLinkParser(HTMLParser):
+ """
+ Parse the links out of an HTML document in a very forgiving way.
+
+ feed() accepts a RedFetcher object (which it uses HTTP response headers
+ from) and a chunk of the document at a time.
+
+ When links are found, process_link will be called for eac with the
+ following arguments;
+ - link (absolute URI as a unicode string)
+ - tag (name of the element that contained it)
+ - title (title attribute as a unicode string, if any)
+ """
+
+ link_parseable_types = [
+ 'text/html',
+ 'application/xhtml+xml',
+ 'application/atom+xml'
+ ]
+
+ def __init__(self, base_uri, process_link, err):
+ self.base = base_uri
+ self.process_link = process_link
+ self.err = err
+ self.http_enc = 'latin-1'
+ self.doc_enc = None
+ self.links = {
+ 'link': 'href',
+ 'a': 'href',
+ 'img': 'src',
+ 'script': 'src',
+ 'frame': 'src',
+ 'iframe': 'src',
+ }
+ self.count = 0
+ self.errors = 0
+ self.last_err_pos = None
+ self.ok = True
+ HTMLParser.__init__(self)
+
+ def feed(self, response, chunk):
+ "Feed a given chunk of HTML data to the parser"
+ if not self.ok:
+ return
+ if response.parsed_hdrs.get('content-type', [None])[0] in self.link_parseable_types:
+ self.http_enc = response.parsed_hdrs['content-type'][1].get('charset', self.http_enc)
+ try:
+ if chunk.__class__.__name__ != 'unicode':
+ try:
+ chunk = unicode(chunk, self.doc_enc or self.http_enc, 'ignore')
+ except LookupError:
+ pass
+ HTMLParser.feed(self, chunk)
+ except BadErrorIReallyMeanIt:
+ pass
+ except Exception, why: # oh, well...
+ self.err("feed problem: %s" % why)
+ self.errors += 1
+
+ def handle_starttag(self, tag, attrs):
+ attr_d = dict(attrs)
+ title = attr_d.get('title', '').strip()
+ if tag in self.links.keys():
+ target = attr_d.get(self.links[tag], "")
+ if target:
+ self.count += 1
+ if "#" in target:
+ target = target[:target.index('#')]
+ self.process_link(target, tag, title)
+ elif tag == 'base':
+ self.base = attr_d.get('href', self.base)
+ elif tag == 'meta' and attr_d.get('http-equiv', '').lower() == 'content-type':
+ ct = attr_d.get('content', None)
+ if ct:
+ try:
+ media_type, params = ct.split(";", 1)
+ except ValueError:
+ media_type, params = ct, ''
+ media_type = media_type.lower()
+ param_dict = {}
+ for param in RHP._splitString(params, response_analyse.PARAMETER, "\s*;\s*"):
+ try:
+ a, v = param.split("=", 1)
+ param_dict[a.lower()] = RHP._unquoteString(v)
+ except ValueError:
+ param_dict[param.lower()] = None
+ self.doc_enc = param_dict.get('charset', self.doc_enc)
+
+ def handle_charref(self, name):
+ return entitydefs.get(name, '')
+
+ def handle_entityref(self, name):
+ return entitydefs.get(name, '')
+
+ def error(self, message):
+ self.errors += 1
+ if self.getpos() == self.last_err_pos:
+ # we're in a loop; give up.
+ self.err("giving up on link parsing after %s errors" % self.errors)
+ self.ok = False
+ raise BadErrorIReallyMeanIt()
+ else:
+ self.last_err_pos = self.getpos()
+ self.err(message)
+
+class BadErrorIReallyMeanIt(Exception):
+ """See http://bugs.python.org/issue8885 for why this is necessary."""
+ pass
+
+if "__main__" == __name__:
+ import sys
+ from red_fetcher import RedFetcher
+ uri = sys.argv[1]
+ req_hdrs = [('Accept-Encoding', 'gzip')]
+ class TestFetcher(RedFetcher):
+ count = 0
+ def done(self):
+ pass
+ @staticmethod
+ def err(mesg):
+ sys.stderr.write("ERROR: %s\n" % mesg)
+ @staticmethod
+ def show_link(link, tag, title):
+ TestFetcher.count += 1
+ out = "%.3d) [%s] %s" % (TestFetcher.count, tag, link)
+ print out.encode('utf-8', 'strict')
+ p = HTMLLinkParser(uri, TestFetcher.show_link, TestFetcher.err)
+ TestFetcher(uri, req_hdrs=req_hdrs, body_procs=[p.feed])
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/red.py Mon Jul 26 14:12:44 2010 -0400
@@ -0,0 +1,476 @@
+#!/usr/bin/env python
+
+"""
+The Resource Expert Droid.
+
+RED will examine a HTTP resource for problems and other interesting
+characteristics, making a list of these observation messages available
+for presentation to the user. It does so by potentially making a number
+of requests to probe the resource's behaviour.
+
+See webui.py for the Web front-end.
+"""
+
+__version__ = "1"
+__author__ = "Mark Nottingham <mnot@mnot.net>"
+__copyright__ = """\
+Copyright (c) 2008-2010 Mark Nottingham
+
+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.
+"""
+
+import re
+import time
+import random
+from cgi import escape as e
+
+import red_speak as rs
+from red_fetcher import RedFetcher
+from response_analyse import relative_time, f_num
+from uri_validate import absolute_URI
+
+### configuration
+cacheable_methods = ['GET']
+heuristic_cacheable_status = ['200', '203', '206', '300', '301', '410']
+max_uri = 8 * 1024
+max_clock_skew = 5 # seconds
+
+class ResourceExpertDroid(RedFetcher):
+ """
+ Given a URI (optionally with method, request headers and body), as well
+ as an optional status callback and list of body processors, examine the
+ URI for issues and notable conditions, making any necessary additional
+ requests.
+
+ Note that this primary request negotiates for gzip content-encoding;
+ see ConnegCheck.
+
+ After processing the response-specific attributes of RedFetcher will be
+ populated, as well as its messages; see that class for details.
+ """
+ def __init__(self, uri, method="GET", req_hdrs=None, req_body=None,
+ status_cb=None, body_procs=None):
+ self.orig_req_hdrs = req_hdrs or []
+ if 'user-agent' not in [i[0].lower() for i in self.orig_req_hdrs]:
+ self.orig_req_hdrs.append(
+ ("User-Agent", "RED/%s (http://redbot.org/about)" % __version__))
+ rh = self.orig_req_hdrs + [('Accept-Encoding', 'gzip')]
+ RedFetcher.__init__(self, uri, method, rh, req_body,
+ status_cb, body_procs, req_type=method)
+
+ # check the URI
+ if not re.match("^\s*%s\s*$" % absolute_URI, self.uri, re.VERBOSE):
+ self.setMessage('uri', rs.URI_BAD_SYNTAX)
+ if len(self.uri) > max_uri:
+ self.setMessage('uri', rs.URI_TOO_LONG, uri_len=f_num(len(uri)))
+
+ def done(self):
+ """
+ Response is available; perform further processing that's specific to
+ the "main" response.
+ """
+ if self.res_complete:
+ self.checkCaching()
+ ConnegCheck(self)
+ RangeRequest(self)
+ ETagValidate(self)
+ LmValidate(self)
+
+ def checkCaching(self):
+ "Examine HTTP caching characteristics."
+ # TODO: check URI for query string, message about HTTP/1.0 if so
+ # known Cache-Control directives that don't allow duplicates
+ known_cc = ["max-age", "no-store", "s-maxage", "public", "private"
+ "pre-check", "post-check", "stale-while-revalidate", "stale-if-error",
+ ]
+
+ cc_set = self.parsed_hdrs.get('cache-control', [])
+ cc_list = [k for (k,v) in cc_set]
+ cc_dict = dict(cc_set)
+ cc_keys = cc_dict.keys()
+
+ # check for mis-capitalised directives /
+ # assure there aren't any dup directives with different values
+ for cc in cc_keys:
+ if cc.lower() in known_cc and cc != cc.lower():
+ self.setMessage('header-cache-control', rs.CC_MISCAP,
+ cc_lower = cc.lower(), cc=cc
+ )
+ if cc in known_cc and cc_list.count(cc) > 1:
+ self.setMessage('header-cache-control', rs.CC_DUP,
+ cc=cc
+ )
+
+ # Who can store this?
+ if self.method not in cacheable_methods:
+ self.store_shared = self.store_private = False
+ self.setMessage('method', rs.METHOD_UNCACHEABLE, method=self.method)
+ return # bail; nothing else to see here
+ elif 'no-store' in cc_keys:
+ self.store_shared = self.store_private = False
+ self.setMessage('header-cache-control', rs.NO_STORE)
+ return # bail; nothing else to see here
+ elif 'private' in cc_keys:
+ self.store_shared = False
+ self.store_private = True
+ self.setMessage('header-cache-control', rs.PRIVATE_CC)
+ elif 'authorization' in [k.lower() for k, v in self.req_hdrs] and \
+ not 'public' in cc_keys:
+ self.store_shared = False
+ self.store_private = True
+ self.setMessage('header-cache-control', rs.PRIVATE_AUTH)
+ else:
+ self.store_shared = self.store_private = True
+ self.setMessage('header-cache-control', rs.STOREABLE)
+
+ # no-cache?
+ if 'no-cache' in cc_keys:
+ if "last-modified" not in self.parsed_hdrs.keys() and \
+ "etag" not in self.parsed_hdrs.keys():
+ self.setMessage('header-cache-control', rs.NO_CACHE_NO_VALIDATOR)
+ else:
+ self.setMessage('header-cache-control', rs.NO_CACHE)
+ return
+
+ # pre-check / post-check
+ if 'pre-check' in cc_keys or 'post-check' in cc_keys:
+ if 'pre-check' not in cc_keys or 'post_check' not in cc_keys:
+ self.setMessage('header-cache-control', rs.CHECK_SINGLE)
+ else:
+ pre_check = post_check = None
+ try:
+ pre_check = int(cc_dict['pre-check'])
+ post_check = int(cc_dict['post-check'])
+ except ValueError:
+ self.setMessage('header-cache-control', rs.CHECK_NOT_INTEGER)
+ if pre_check is not None and post_check is not None:
+ if pre_check == 0 and post_check == 0:
+ self.setMessage('header-cache-control', rs.CHECK_ALL_ZERO)
+ elif post_check > pre_check:
+ self.setMessage('header-cache-control', rs.CHECK_POST_BIGGER)
+ post_check = pre_check
+ elif post_check == 0:
+ self.setMessage('header-cache-control', rs.CHECK_POST_ZERO)
+ else:
+ self.setMessage('header-cache-control', rs.CHECK_POST_PRE,
+ pre_check=pre_check, post_check=post_check)
+
+ # vary?
+ vary = self.parsed_hdrs.get('vary', set())
+ if "*" in vary:
+ self.setMessage('header-vary', rs.VARY_ASTERISK)
+ return # bail; nothing else to see here
+ elif len(vary) > 3:
+ self.setMessage('header-vary', rs.VARY_COMPLEX, vary_count=f_num(len(vary)))
+ else:
+ if "user-agent" in vary:
+ self.setMessage('header-vary', rs.VARY_USER_AGENT)
+ if "host" in vary:
+ self.setMessage('header-vary', rs.VARY_HOST)
+ # TODO: enumerate the axes in a message
+
+ # calculate age
+ age_hdr = self.parsed_hdrs.get('age', 0)
+ date_hdr = self.parsed_hdrs.get('date', 0)
+ if date_hdr > 0:
+ apparent_age = max(0,
+ int(self.timestamp - date_hdr))
+ else:
+ apparent_age = 0
+ current_age = max(apparent_age, age_hdr)
+ current_age_str = relative_time(current_age, 0, 0)
+ age_str = relative_time(age_hdr, 0, 0)
+ self.age = age_hdr
+ if age_hdr >= 1:
+ self.setMessage('header-age header-date', rs.CURRENT_AGE,
+ age=age_str)
+
+ # Check for clock skew and dateless origin server.
+ skew = date_hdr - self.timestamp + age_hdr
+ if not date_hdr:
+ self.setMessage('', rs.DATE_CLOCKLESS)
+ if self.parsed_hdrs.has_key('expires') or \
+ self.parsed_hdrs.has_key('last-modified'):
+ self.setMessage('header-expires header-last-modified',
+ rs.DATE_CLOCKLESS_BAD_HDR)
+ elif age_hdr > max_clock_skew and current_age - skew < max_clock_skew:
+ self.setMessage('header-date header-age', rs.AGE_PENALTY)
+ elif abs(skew) > max_clock_skew:
+ self.setMessage('header-date', rs.DATE_INCORRECT,
+ clock_skew_string=relative_time(skew, 0, 2)
+ )
+ else:
+ self.setMessage('header-date', rs.DATE_CORRECT)
+
+ # calculate freshness
+ freshness_lifetime = 0
+ has_explicit_freshness = False
+ freshness_hdrs = ['header-date', 'header-expires']
+ if 's-maxage' in cc_keys: # TODO: differentiate message for s-maxage
+ freshness_lifetime = cc_dict['s-maxage']
+ freshness_hdrs.append('header-cache-control')
+ has_explicit_freshness = True
+ elif 'max-age' in cc_keys:
+ freshness_lifetime = cc_dict['max-age']
+ freshness_hdrs.append('header-cache-control')
+ has_explicit_freshness = True
+ elif self.parsed_hdrs.has_key('expires'):
+ has_explicit_freshness = True
+ if self.parsed_hdrs.has_key('date'):
+ freshness_lifetime = self.parsed_hdrs['expires'] - \
+ self.parsed_hdrs['date']
+ else:
+ freshness_lifetime = self.parsed_hdrs['expires'] - \
+ self.timestamp # ?
+
+ freshness_left = freshness_lifetime - current_age
+ freshness_left_str = relative_time(abs(int(freshness_left)), 0, 0)
+ freshness_lifetime_str = relative_time(int(freshness_lifetime), 0, 0)
+
+ self.freshness_lifetime = freshness_lifetime
+ fresh = freshness_left > 0
+ if has_explicit_freshness:
+ if fresh:
+ self.setMessage(" ".join(freshness_hdrs), rs.FRESHNESS_FRESH,
+ freshness_lifetime=freshness_lifetime_str,
+ freshness_left=freshness_left_str,
+ current_age = current_age_str
+ )
+ else:
+ self.setMessage(" ".join(freshness_hdrs), rs.FRESHNESS_STALE,
+ freshness_lifetime=freshness_lifetime_str,
+ freshness_left=freshness_left_str,
+ current_age = current_age_str
+ )
+
+ # can heuristic freshness be used?
+ elif self.res_status in heuristic_cacheable_status:
+ self.setMessage('header-last-modified', rs.FRESHNESS_HEURISTIC)
+ else:
+ self.setMessage('', rs.FRESHNESS_NONE)
+
+ # can stale responses be served?
+ if 'must-revalidate' in cc_keys:
+ if fresh:
+ self.setMessage('header-cache-control', rs.FRESH_MUST_REVALIDATE)
+ elif has_explicit_freshness:
+ self.setMessage('header-cache-control', rs.STALE_MUST_REVALIDATE)
+ elif 'proxy-revalidate' in cc_keys or 's-maxage' in cc_keys:
+ if fresh:
+ self.setMessage('header-cache-control', rs.FRESH_PROXY_REVALIDATE)
+ elif has_explicit_freshness:
+ self.setMessage('header-cache-control', rs.STALE_PROXY_REVALIDATE)
+ else:
+ if fresh:
+ self.setMessage('header-cache-control', rs.FRESH_SERVABLE)
+ elif has_explicit_freshness:
+ self.setMessage('header-cache-control', rs.STALE_SERVABLE)
+
+ # public?
+ if 'public' in cc_keys: # TODO: check for authentication in request
+ self.setMessage('header-cache-control', rs.PUBLIC)
+
+
+class ConnegCheck(RedFetcher):
+ """
+ See if content negotiation for compression is supported, and how.
+
+ Note that this depends on the "main" request being sent with
+ Accept-Encoding: gzip
+ """
+ def __init__(self, red):
+ self.red = red
+ if "gzip" in red.parsed_hdrs.get('content-encoding', []):
+ req_hdrs = [h for h in red.orig_req_hdrs if
+ h[0].lower() != 'accept-encoding']
+ RedFetcher.__init__(self, red.uri, red.method, req_hdrs, red.req_body,
+ red.status_cb, [], "conneg")
+ else:
+ self.red.gzip_support = False
+
+ def done(self):
+ if self.res_body_len > 0:
+ savings = int(100 * ((float(self.res_body_len) - \
+ self.red.res_body_len) / self.res_body_len))
+ else:
+ savings = 0
+ self.red.gzip_support = True
+ self.red.gzip_savings = savings
+ if savings >= 0:
+ self.red.setMessage('header-content-encoding', rs.CONNEG_GZIP_GOOD, self,
+ savings=savings,
+ orig_size=f_num(self.res_body_len),
+ gzip_size=f_num(self.red.res_body_len)
+ )
+ else:
+ self.red.setMessage('header-content-encoding', rs.CONNEG_GZIP_BAD, self,
+ savings=abs(savings),
+ orig_size=f_num(self.res_body_len),
+ gzip_size=f_num(self.red.res_body_len)
+ )
+ vary_headers = self.red.parsed_hdrs.get('vary', [])
+ if (not "accept-encoding" in vary_headers) and (not "*" in vary_headers):
+ self.red.setMessage('header-vary header-%s', rs.CONNEG_NO_VARY)
+ # FIXME: verify that the status/body/hdrs are the same; if it's different, alert
+ no_conneg_vary_headers = self.parsed_hdrs.get('vary', [])
+ if 'gzip' in self.parsed_hdrs.get('content-encoding', []) or \
+ 'x-gzip' in self.parsed_hdrs.get('content-encoding', []):
+ self.red.setMessage('header-vary header-content-encoding',
+ rs.CONNEG_GZIP_WITHOUT_ASKING)
+ if no_conneg_vary_headers != vary_headers:
+ self.red.setMessage('header-vary', rs.VARY_INCONSISTENT,
+ conneg_vary=e(", ".join(vary_headers)),
+ no_conneg_vary=e(", ".join(no_conneg_vary_headers))
+ )
+ if self.parsed_hdrs.get('etag', 1) == self.red.parsed_hdrs.get('etag', 2):
+ self.red.setMessage('header-etag', rs.ETAG_DOESNT_CHANGE) # TODO: weakness?
+
+
+class RangeRequest(RedFetcher):
+ "Check for partial content support (if advertised)"
+ def __init__(self, red):
+ self.red = red
+ if 'bytes' in red.parsed_hdrs.get('accept-ranges', []):
+ if len(red.res_body_sample) == 0: return
+ sample_num = random.randint(0, len(red.res_body_sample) - 1)
+ sample_len = min(96, len(red.res_body_sample[sample_num][1]))
+ self.range_start = red.res_body_sample[sample_num][0]
+ self.range_end = self.range_start + sample_len
+ self.range_target = red.res_body_sample[sample_num][1][:sample_len + 1]
+ if self.range_start == self.range_end: return # wow, that's a small body.
+ # TODO: currently uses the compressed version (if available. Revisit.
+ req_hdrs = red.req_hdrs + [
+ ('Range', "bytes=%s-%s" % (self.range_start, self.range_end))
+ ]
+ RedFetcher.__init__(self, red.uri, red.method, req_hdrs, red.req_body,
+ red.status_cb, [], "range")
+ else:
+ self.red.partial_support = False
+
+ def done(self):
+ if self.res_status == '206':
+ # TODO: check entity headers
+ # TODO: check content-range
+ if ('gzip' in self.red.parsed_hdrs.get('content-encoding', [])) == \
+ ('gzip' not in self.parsed_hdrs.get('content-encoding', [])):
+ self.red.setMessage('header-accept-ranges header-content-encoding',
+ rs.RANGE_NEG_MISMATCH, self)
+ return
+ if self.res_body == self.range_target:
+ self.red.partial_support = True
+ self.red.setMessage('header-accept-ranges', rs.RANGE_CORRECT, self)
+ else:
+ # the body samples are just bags of bits
+ self.red.partial_support = False
+ self.red.setMessage('header-accept-ranges', rs.RANGE_INCORRECT, self,
+ range="bytes=%s-%s" % (self.range_start, self.range_end),
+ range_expected=e(self.range_target.encode('string_escape')),
+ range_expected_bytes = f_num(len(self.range_target)),
+ range_received=e(self.res_body.encode('string_escape')),
+ range_received_bytes = f_num(self.res_body_len)
+ )
+ # TODO: address 416 directly
+ elif self.res_status == self.red.res_status:
+ self.red.partial_support = False
+ self.red.setMessage('header-accept-ranges', rs.RANGE_FULL)
+ else:
+ self.red.setMessage('header-accept-ranges', rs.RANGE_STATUS,
+ range_status=self.res_status,
+ enc_range_status=e(self.res_status))
+
+
+class ETagValidate(RedFetcher):
+ "If an ETag is present, see if it will validate."
+ def __init__(self, red):
+ self.red = red
+ if red.parsed_hdrs.has_key('etag'):
+ weak, etag = red.parsed_hdrs['etag']
+ if weak:
+ weak_str = "W/"
+ # TODO: message on weak etag
+ else:
+ weak_str = ""
+ etag_str = '%s"%s"' % (weak_str, etag)
+ req_hdrs = red.req_hdrs + [
+ ('If-None-Match', etag_str),
+ ]
+ RedFetcher.__init__(self, red.uri, red.method, req_hdrs, red.req_body,
+ red.status_cb, [], "ETag validation")
+ else:
+ self.red.inm_support = False
+
+ def done(self):
+ if self.res_status == '304':
+ self.red.inm_support = True
+ self.red.setMessage('header-etag', rs.INM_304, self)
+ # TODO : check Content- headers, esp. length.
+ elif self.res_status == self.red.res_status:
+ if self.res_body_md5 == self.red.res_body_md5:
+ self.red.inm_support = False
+ self.red.setMessage('header-etag', rs.INM_FULL, self)
+ else:
+ self.red.setMessage('header-etag', rs.INM_UNKNOWN, self)
+ else:
+ self.red.setMessage('header-etag', rs.INM_STATUS, self,
+ inm_status=self.res_status,
+ enc_inm_status=e(self.res_status)
+ )
+ # TODO: check entity headers
+
+class LmValidate(RedFetcher):
+ "If Last-Modified is present, see if it will validate."
+ def __init__(self, red):
+ self.red = red
+ if red.parsed_hdrs.has_key('last-modified'):
+ date_str = time.strftime('%a, %d %b %Y %H:%M:%S GMT',
+ time.gmtime(red.parsed_hdrs['last-modified']))
+ req_hdrs = red.req_hdrs + [
+ ('If-Modified-Since', date_str),
+ ]
+ RedFetcher.__init__(self, red.uri, red.method, req_hdrs, red.req_body,
+ red.status_cb, [], "LM validation")
+ else:
+ self.red.ims_support = False
+
+ def done(self):
+ if self.res_status == '304':
+ self.red.ims_support = True
+ self.red.setMessage('header-last-modified', rs.IMS_304, self)
+ # TODO : check Content- headers, esp. length.
+ elif self.res_status == self.red.res_status:
+ if self.res_body_md5 == self.red.res_body_md5:
+ self.red.ims_support = False
+ self.red.setMessage('header-last-modified', rs.IMS_FULL, self)
+ else:
+ self.red.setMessage('header-last-modified', rs.IMS_UNKNOWN, self)
+ else:
+ self.red.setMessage('header-last-modified', rs.IMS_STATUS, self,
+ ims_status=self.res_status,
+ enc_ims_status=e(self.res_status)
+ )
+ # TODO: check entity headers
+
+
+if "__main__" == __name__:
+ import sys
+ uri = sys.argv[1]
+ def status_p(msg):
+ print msg
+ red = ResourceExpertDroid(uri, status_cb=status_p)
+ print red.messages
\ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/red_defns.py Mon Jul 26 14:12:44 2010 -0400
@@ -0,0 +1,471 @@
+"""
+Header- and Status-specific detail/definition messages
+
+Each should be in the form:
+
+ HDR_HEADER_NAME = {'lang': u'message'}
+or
+ STATUS_NNN = {'lang': u'message'}
+
+where HEADER_NAME is the header's field name in all capitals and with hyphens
+replace with underscores, NNN is the three-digit status code, lang' is a
+language tag, and 'message' is a description of the header that may
+contain HTML.
+
+The following %(var)s style variable interpolations are available:
+ field_name - the name of the header
+
+PLEASE NOTE: the description IS NOT ESCAPED, and therefore all variables to be
+interpolated into it need to be escaped.
+"""
+
+__author__ = "Mark Nottingham <mnot@mnot.net>"
+__copyright__ = """\
+Copyright (c) 2009-2010 Mark Nottingham
+
+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.
+"""
+
+HDR_KEEP_ALIVE = {
+ 'en': u"""The <code>Keep-Alive</code> header is completely optional; it
+ is defined primarily because the <code>keep-alive</code> connection token
+ implies that such a header exists, not because anyone actually uses it.<p>
+ Some implementations (e.g., <a href="http://httpd.apache.org/">Apache</a>)
+ do generate a <code>Keep-Alive</code> header to convey how many requests
+ they're willing to serve on a single connection, what the connection timeout
+ is and other information. However, this isn't usually used by clients.<p>
+ It's safe to remove this header if you wish to save a few bytes in the
+ response."""
+}
+
+HDR_NNCOECTION = \
+HDR_CNEONCTION = \
+HDR_YYYYYYYYYY = \
+HDR_XXXXXXXXXX = \
+HDR_X_CNECTION = \
+HDR__ONNECTION = {
+ 'en': u"""
+ The <code>%(field_name)s</code> field usually means that a HTTP load
+ balancer, proxy or other intermediary in front of the server has rewritten
+ the <code>Connection</code> header, to allow it to insert its own.<p>
+ Usually, this is done so that clients won't see <code>Connection: close</code>
+ so that the connection can be reused.<p>
+ It takes this form because the most efficient way of assuring that clients
+ don't see the header is to rearrange or change individual characters in its
+ name.
+ """
+}
+
+HDR_CTEONNT_LENGTH = {
+ 'en': u"""
+ The <code>%(field_name)s</code> field usually means that a HTTP load
+ balancer, proxy or other intermediary in front of the server has rewritten
+ the <code>Content-Length</code> header, to allow it to insert its own.<p>
+ Usually, this is done because an intermediary has dynamically compressed
+ the response.<p>
+ It takes this form because the most efficient way of assuring that clients
+ don't see the header is to rearrange or change individual characters in its
+ name.
+ """
+}
+
+
+HDR_X_PAD_FOR_NETSCRAPE_BUG = \
+HDR_X_PAD = \
+HDR_XX_PAD = \
+HDR_X_BROWSERALIGNMENT = {
+ 'en': u"""The <code>%(field_name)s</code> header is used to "pad" the
+ response header size.<p>
+ Very old versions of the Netscape browser had a
+ bug whereby a response whose headers were exactly 256 or 257 bytes long,
+ the browser would consider the response (e.g., an image) invalid.<p>
+ Since the affected browsers (specifically, Netscape 2.x, 3.x and 4.0 up to
+ beta 2) are no longer widely used, it's probably safe to omit this header.
+ """
+}
+
+HDR_CONNECTION = {
+ 'en': u"""The <code>Connection</code> header allows senders to specify
+ which headers are hop-by-hop; that is, those that are not forwarded
+ by intermediaries. <p>It also indicates options that are
+ desired for this particular connection; e.g., <code>close</code> means
+ that it should not be reused."""
+}
+
+HDR_CONTENT_LENGTH = {
+ 'en': u"""The <code>Content-Length</code> header indicates the size
+ of the body, in number of bytes. In responses to the HEAD
+ method, it indicates the size of the body that would have been sent
+ had the request been a GET.<p>
+ If Content-Length is incorrect, persistent connections will not work,
+ and caches may not store the response (since they can't be sure if
+ they have the whole response)."""
+}
+
+HDR_DATE = {
+ 'en': u"""The <code>Date</code> header represents the time
+ when the message was generated, regardless of caching that
+ happened since.<p>
+ It is used by caches as input to expiration calculations, and to detect
+ clock drift."""
+}
+
+HDR_HOST = {
+ 'en': u"""The <code>Host</code> header specifies the host
+ and port number (if it's not the default) of the resource
+ being requested.<p>
+ HTTP/1.1 requires servers to reject requests without a <code>Host</code>
+ header."""
+}
+
+HDR_TE = {
+ 'en': u"""The <code>TE</code> header indicates what
+ transfer-codings the client is willing to accept in the
+ response, and whether or not it is willing to accept
+ trailer fields after the body when the response uses chunked
+ transfer-coding.<p>
+ The most common transfer-coding, <code>chunked</code>, doesn't need
+ to be listed in <code>TE</code>."""
+}
+
+HDR_TRAILER = {
+ 'en': u"""The <code>Trailer</code> header indicates that the given set of
+ header fields will be present in the trailer of the message, after the body."""
+}
+
+HDR_TRANSFER_ENCODING = {
+ 'en': u"""The <code>Transfer-Encoding</code> header indicates what
+ (if any) type of transformation has been applied to
+ the message body.<p>
+ This differs from <code>Content-Encoding</code> in that transfer-codings
+ are a property of the message, not of the representation; i.e., it will
+ be removed by the next "hop", whereas content-codings are end-to-end.<p>
+ The most commonly used transfer-coding is <code>chunked</code>, which
+ allows persistent connections to be used without knowing the entire
+ body's length."""
+}
+
+HDR_UPGRADE = {
+ 'en': u"""The <code>Upgrade</code> header allows the client to
+ specify what additional communication protocols it
+ supports and would like to use if the server finds
+ it appropriate to switch protocols. Servers use it to confirm
+ upgrade to a specific protocol."""
+}
+
+HDR_VIA = {
+ 'en': u"""The <code>Via</code> header is added to requests and responses
+ by proxies and other HTTP intermediaries.
+ It can
+ be used to help avoid request loops and identify the protocol
+ capabilities of all senders along the request/response chain."""
+}
+
+HDR_ALLOW = {
+ 'en': u"""The <code>Allow</code> header advertises the set of methods
+ that are supported by the resource."""
+}
+
+HDR_EXPECT = {
+ 'en': u"""The <code>Expect</code> header is used to indicate that
+ particular server behaviors are required by the client.<p>
+ Currently, it has one use; the <code>100-continue</code>
+ directive, which indicates that the client wants the server to indicate
+ that the request is acceptable before the request body is sent.<p>
+ If the expectation isn't met, the server will generate a
+ <code>417 Expectation Failed</code> response."""
+}
+
+HDR_FROM = {
+ 'en': u"""The <code>From</code> header contains an
+ e-mail address for the user.<p>
+ It is not commonly used, because servers don't often record or otherwise
+ use it."""
+}
+
+HDR_LOCATION = {
+ 'en': u"""The <code>Location</code> header is used in <code>3xx</code>
+ responses to redirect the recipient to a different location to
+ complete the request.<p>In <code>201 Created</code> responses, it
+ identifies a newly created resource.<p>
+"""
+}
+
+HDR_MAX_FORWARDS = {
+ 'en': u"""The <code>Max-Forwards</code> header allows
+ for the TRACE and OPTIONS methods to limit the
+ number of times the message can be forwarded the
+ request to the next server (e.g., proxy or gateway).<p>
+ This can be useful when the client is attempting to trace a
+ request which appears to be looping."""
+}
+
+HDR_REFERER = {
+ 'en': u"""The <code>Referer</code> [sic] header allows the client to
+ specify the address of the resource from where the request URI was
+ obtained (the "referrer", albeit misspelled)."""
+}
+
+HDR_RETRY_AFTER = {
+ 'en': u"""The <code>Retry-After</code> header can be used with a
+ <code>503 Service Unavailable</code> response to indicate how long
+ the service is expected to be unavailable to the
+ requesting client.<p>
+ The value of this field can be either a date or an integer
+ number of seconds."""
+}
+
+HDR_SERVER = {
+ 'en': u"""The <code>Server</code> header contains information about
+ the software used by the origin server to handle the
+ request."""
+}
+
+HDR_USER_AGENT = {
+ 'en': u"""The <code>User-Agent</code> header contains information
+ about the user agent originating the request. """
+}
+
+HDR_ACCEPT = {
+ 'en': u"""The <code>Accept</code> header can be used to specify
+ the media types which are acceptable for the
+ response."""
+}
+
+HDR_ACCEPT_CHARSET = {
+ 'en': u"""The <code>Accept-Charset</code> header can be used to
+ indicate what character sets are acceptable for the
+ response."""
+}
+
+HDR_ACCEPT_ENCODING = {
+ 'en': u"""The <code>Accept-Encoding</code> header can be used to
+ restrict the content-codings that are
+ acceptable in the response."""
+}
+
+HDR_ACCEPT_LANGUAGE = {
+ 'en': u"""The <code>Accept-Language</code> header can be used to
+ restrict the set of natural languages
+ that are preferred as a response to the request."""
+}
+
+HDR_CONTENT_ENCODING = {
+ 'en': u"""The <code>Content-Encoding</code> header's value
+ indicates what additional content codings have been
+ applied to the body, and thus what decoding
+ mechanisms must be applied in order to obtain the
+ media-type referenced by the Content-Type header
+ field.<p>
+ Content-Encoding is primarily used to allow a
+ document to be compressed without losing the
+ identity of its underlying media type; e.g.,
+ <code>gzip</code> and <code>deflate</code>."""
+}
+
+HDR_CONTENT_LANGUAGE = {
+ 'en': u"""The <code>Content-Language</code> header describes the
+ natural language(s) of the intended audience.
+ Note that this might not convey all of the
+ languages used within the body."""
+}
+
+HDR_CONTENT_LOCATION = {
+ 'en': u"""The <code>Content-Location</code> header can used to
+ supply an address for the representation when it is accessible from a
+ location separate from the request URI."""
+}
+
+HDR_CONTENT_MD5 = {
+ 'en': u"""The <code>Content-MD5</code> header is
+ an MD5 digest of the body, and provides an end-to-end
+ message integrity check (MIC).<p>
+ Note that while a MIC is good for
+ detecting accidental modification of the body
+ in transit, it is not proof against malicious
+ attacks."""
+}
+
+HDR_CONTENT_TYPE = {
+ 'en': u"""The <code>Content-Type</code> header indicates the media
+ type of the body sent to the recipient or, in
+ the case of responses to the HEAD method, the media type that
+ would have been sent had the request been a GET."""
+}
+
+HDR_MIME_VERSION = {
+ 'en': u"""HTTP is not a MIME-compliant protocol. However, HTTP/1.1
+ messages can include a single MIME-Version general-
+ header field to indicate what version of the MIME
+ protocol was used to construct the message. Use of
+ the MIME-Version header field indicates that the
+ message is in full compliance with the MIME
+ protocol."""
+}
+
+HDR_CONTENT_DISPOSITION = {
+ 'en': u"""The <code>Content-Disposition</code> header field allows
+ the origin server to suggest
+ a default filename if the user saves the body to a file."""
+}
+
+HDR_ETAG = {
+ 'en': u"""The <code>ETag</code> header provides an opaque identifier
+ for the representation."""
+}
+
+HDR_IF_MATCH = {
+ 'en': u"""The <code>If-Match</code> header makes a request
+ conditional. A client that has one or more
+ representations previously obtained from the resource can
+ verify that one of them is current by
+ including a list of their associated entity tags in
+ the If-Match header field.<p>
+ This allows
+ efficient updates of cached information with a
+ minimum amount of transaction overhead. It is also
+ used, on updating requests, to prevent inadvertent
+ modification of the wrong version of a resource. As
+ a special case, the value "*" matches any current
+ representation of the resource."""
+}
+
+HDR_IF_MODIFIED_SINCE = {
+ 'en': u"""The <code>If-Modified-Since</code> header is used with a
+ method to make it conditional: if the requested
+ variant has not been modified since the time
+ specified in this field, a representation will not be
+ returned from the server; instead, a 304 (Not
+ Modified) response will be returned without any
+ body."""
+}
+
+HDR_IF_NONE_MATCH = {
+ 'en': u"""The <code>If-None-Match</code> header makes a request
+ conditional. A client that has one or
+ more representations previously obtained from the resource
+ can verify that none of them is current by
+ including a list of their associated entity tags in
+ the If-None-Match header field.<p>
+ This allows efficient updates of cached
+ information with a minimum amount of transaction
+ overhead. It is also used to prevent a method (e.g.
+ PUT) from inadvertently modifying an existing
+ resource when the client believes that the resource
+ does not exist."""
+}
+
+HDR_IF_UNMODIFIED_SINCE = {
+ 'en': u"""The <code>If-Unmodified-Since</code> header makes a request
+ conditional."""
+}
+
+HDR_LAST_MODIFIED = {
+ 'en': u"""The <code>Last-Modified</code> header indicates the time
+ that the origin server believes the
+ representation was last modified."""
+}
+
+HDR_ACCEPT_RANGES = {
+ 'en': u"""The <code>Accept-Ranges</code> header allows the server to
+ indicate that it accepts range requests for a
+ resource."""
+}
+
+HDR_CONTENT_RANGE = {
+ 'en': u"""The <code>Content-Range</code> header is sent with a
+ partial body to specify where in the full
+ body the partial body should be applied."""
+}
+
+HDR_AGE = {
+ 'en': u"""The <code>Age</code> header conveys the sender's estimate
+ of the amount of time since the response (or its
+ validation) was generated at the origin server."""
+}
+
+HDR_CACHE_CONTROL = {
+ 'en': u"""The <code>Cache-Control</code> header is used to specify
+ directives that must be obeyed by all caches along
+ the request/response chain. Cache
+ directives are unidirectional in that the presence
+ of a directive in a request does not imply that the
+ same directive is in effect in the response."""
+}
+
+HDR_EXPIRES = {
+ 'en': u"""The <code>Expires</code> header gives a time after
+ which the response is considered stale."""
+}
+
+HDR_PRAGMA = {
+ 'en': u"""The <code>Pragma</code> header is used to include
+ implementation-specific directives that might apply
+ to any recipient along the request/response chain.<p>
+ This header is deprecated, in favour of <code>Cache-Control</code>.
+"""
+}
+
+HDR_VARY = {
+ 'en': u"""The <code>Vary</code> header indicates the set
+ of request headers that determines whether a cache is permitted to
+ use the response to reply to a subsequent request
+ without validation.<p>
+ In uncacheable or stale responses, the Vary field value advises
+ the user agent about the criteria that were used to select the representation."""
+}
+
+HDR_WARNING = {
+ 'en': u"""The <code>Warning</code> header is used to carry additional
+ information about the status or transformation of a
+ message that might not be reflected in it.
+ This information is typically used to warn about
+ possible incorrectness introduced by caching
+ operations or transformations applied to the
+ body of the message."""
+}
+
+HDR_AUTHORIZATION = {
+ 'en': u"""The <code>Authorization</code> request header
+ contains authentication information
+ for the user agent to the origin server."""
+}
+
+HDR_PROXY_AUTHENTICATE = {
+ 'en': u"""The <code>Proxy-Authenticate</code> response header
+ consists of a challenge
+ that indicates the authentication scheme and
+ parameters applicable to the proxy for this request-
+ target."""
+}
+
+HDR_PROXY_AUTHORIZATION = {
+ 'en': u"""The <code>Proxy-Authorization</code> request header
+ contains authentication information for the
+ user agent to the proxy and/or realm of the
+ resource being requested."""
+}
+
+HDR_WWW_AUTHENTICATE = {
+ 'en': u"""The <code>WWW-Authenticate</code> response header
+ consists of at least one challenge that
+ indicates the authentication scheme(s) and
+ parameters applicable."""
+}
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/red_fetcher.py Mon Jul 26 14:12:44 2010 -0400
@@ -0,0 +1,326 @@
+#!/usr/bin/env python
+
+"""
+The Resource Expert Droid Fetcher.
+
+RedFetcher fetches a single URI and analyses that response for common
+problems and other interesting characteristics. It only makes one request,
+based upon the provided headers.
+
+See red.py for the main RED engine and webui.py for the Web front-end.
+"""
+
+__version__ = "1"
+__author__ = "Mark Nottingham <mnot@mnot.net>"
+__copyright__ = """\
+Copyright (c) 2008-2010 Mark Nottingham
+
+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.
+"""
+
+import base64
+import hashlib
+import time
+import urllib
+import urlparse
+import zlib
+from cgi import escape as e
+
+import nbhttp
+import red_speak as rs
+import response_analyse as ra
+from response_analyse import f_num
+
+outstanding_requests = [] # requests in process
+total_requests = 0
+
+class RedHttpClient(nbhttp.Client):
+ connect_timeout = 8
+ read_timeout = 8
+
+class RedFetcher:
+ """
+ Fetches the given URI (with the provided method, headers and body) and calls:
+ - status_cb as it progresses, and
+ - every function in the body_procs list with each chunk of the body, and
+ - done when it's done.
+ If provided, type indicates the type of the request, and is used to
+ help set messages and status_cb appropriately.
+
+ Messages is a list of messages, each of which being a tuple that
+ follows the following form:
+ (
+ subject, # The subject(s) of the msg, as a space-separated string.
+ # e.g., "header-cache-control header-expires"
+ message, # The message structure; see red_speak.py
+ subrequest, # Optionally, a RedFetcher object representing a
+ # request with additional details about another request
+ # made in order to generate the message
+ **variables # Optionally, key=value pairs intended for interpolation
+ # into the message; e.g., time_left="5d3h"
+ )
+ """
+
+ def __init__(self, iri, method="GET", req_hdrs=None, req_body=None,
+ status_cb=None, body_procs=None, req_type=None):
+ self.uri = iri_to_uri(iri)
+ self.method = method
+ self.req_hdrs = req_hdrs or []
+ self.req_body = req_body
+ self.status_cb = status_cb
+ self.body_procs = body_procs or []
+ self.type = req_type
+ self.timestamp = None # when the request was started
+ # response attributes; populated by RedFetcher
+ self.res_version = ""
+ self.res_status = None
+ self.res_phrase = ""
+ self.res_hdrs = []
+ self.parsed_hdrs = {}
+ self.res_body = "" # note: only partial responses get this populated; bytes, not unicode
+ self.res_body_len = 0
+ self.res_body_md5 = None
+ self.res_body_sample = [] # array of (offset, chunk), max size 4. Bytes, not unicode.
+ self.res_complete = False
+ self.res_error = None # any parse errors encountered; see nbhttp.error
+ # interesting things about the response; set by a variety of things
+ self.messages = [] # messages (see above)
+ self.age = None
+ self.store_shared = None
+ self.store_private = None
+ self.freshness_lifetime = None
+ self.stale_serveable = None
+ self.partial_support = None
+ self.inm_support = None
+ self.ims_support = None
+ self.gzip_support = None
+ self.gzip_savings = 0
+ self.client = None
+ self._md5_processor = hashlib.md5()
+ self._decompress = zlib.decompressobj(-zlib.MAX_WBITS).decompress
+ self._in_gzip_body = False
+ self._gzip_header_buffer = ""
+ self._gzip_ok = True # turn False if we have a problem
+ self._makeRequest()
+
+ def setMessage(self, subject, msg, subreq=None, **vars):
+ "Set a message."
+ vars['response'] = rs.response.get(self.type, rs.response['this'])['en']
+ self.messages.append(msg(subject, subreq, vars))
+
+ def done(self):
+ "Callback for when the response is complete and analysed."
+ raise NotImplementedError
+
+ def _makeRequest(self):
+ """
+ Make an asynchronous HTTP request to uri, calling status_cb as it's updated and
+ done_cb when it's done. Reason is used to explain what the request is in the
+ status callback.
+ """
+ global outstanding_requests
+ global total_requests
+ outstanding_requests.append(self)
+ total_requests += 1
+ self.client = RedHttpClient(self._response_start)
+ if self.status_cb and self.type:
+ self.status_cb("fetching %s (%s)" % (self.uri, self.type))
+ req_body, req_done = self.client.req_start(
+ self.method, self.uri, self.req_hdrs, nbhttp.dummy)
+ if self.req_body != None:
+ req_body(self.req_body)
+ req_done(None)
+ if len(outstanding_requests) == 1:
+ nbhttp.run()
+
+ def _response_start(self, version, status, phrase, res_headers, res_pause):
+ "Process the response start-line and headers."
+ self.timestamp = time.time()
+ self.res_version = version
+ self.res_status = status.decode('iso-8859-1', 'replace')
+ self.res_phrase = phrase.decode('iso-8859-1', 'replace')
+ self.res_hdrs = res_headers
+ ra.ResponseHeaderParser(self)
+ ra.ResponseStatusChecker(self)
+ return self._response_body, self._response_done
+
+ def _response_body(self, chunk):
+ "Process a chunk of the response body."
+ self._md5_processor.update(chunk)
+ self.res_body_sample.append((self.res_body_len, chunk))
+ if len(self.res_body_sample) > 4:
+ self.res_body_sample.pop(0)
+ self.res_body_len += len(chunk)
+ if self.res_status == "206":
+ # Store only partial responses completely, for error reporting
+ self.res_body += chunk
+ # Don't actually try to make sense of a partial body...
+ return
+ content_codings = self.parsed_hdrs.get('content-encoding', [])
+ content_codings.reverse()
+ for coding in content_codings:
+ # TODO: deflate support
+ if coding == 'gzip' and self._gzip_ok:
+ if not self._in_gzip_body:
+ self._gzip_header_buffer += chunk
+ try:
+ chunk = self._read_gzip_header(self._gzip_header_buffer)
+ self._in_gzip_body = True
+ except IndexError:
+ return # not a full header yet
+ except IOError, gzip_error:
+ self.setMessage('header-content-encoding', rs.BAD_GZIP,
+ gzip_error=e(str(gzip_error))
+ )
+ self._gzip_ok = False
+ return
+ try:
+ chunk = self._decompress(chunk)
+ except zlib.error, zlib_error:
+ self.setMessage('header-content-encoding', rs.BAD_ZLIB,
+ zlib_error=e(str(zlib_error)),
+ ok_zlib_len=f_num(self.res_body_sample[-1][0]),
+ chunk_sample=e(chunk[:20].encode('string_escape'))
+ )
+ self._gzip_ok = False
+ return
+ else:
+ # we can't handle other codecs, so punt on body processing.
+ return
+ if self._gzip_ok:
+ for processor in self.body_procs:
+ processor(self, chunk)
+
+ def _response_done(self, err):
+ "Finish anaylsing the response, handling any parse errors."
+ global outstanding_requests
+ self.res_complete = True
+ self.res_error = err
+ if self.status_cb and self.type:
+ self.status_cb("fetched %s (%s)" % (self.uri, self.type))
+ self.res_body_md5 = self._md5_processor.digest()
+ if err == None:
+ pass
+ elif err['desc'] == nbhttp.error.ERR_BODY_FORBIDDEN['desc']:
+ self.setMessage('header-none', rs.BODY_NOT_ALLOWED)
+ elif err['desc'] == nbhttp.error.ERR_EXTRA_DATA['desc']:
+ self.res_body_len += len(err.get('detail', ''))
+ elif err['desc'] == nbhttp.error.ERR_CHUNK['desc']:
+ self.setMessage('header-transfer-encoding', rs.BAD_CHUNK,
+ chunk_sample=e(err.get('detail', '')[:20]))
+ elif err['desc'] == nbhttp.error.ERR_CONNECT['desc']:
+ self.res_complete = False
+ elif err['desc'] == nbhttp.error.ERR_LEN_REQ['desc']:
+ pass # FIXME: length required
+ elif err['desc'] == nbhttp.error.ERR_URL['desc']:
+ self.res_complete = False
+ elif err['desc'] == nbhttp.error.ERR_READ_TIMEOUT['desc']:
+ self.res_complete = False
+ elif err['desc'] == nbhttp.error.ERR_HTTP_VERSION['desc']:
+ self.res_complete = False
+ else:
+ raise AssertionError, "Unknown response error: %s" % err
+
+ if self.res_complete \
+ and self.method not in ['HEAD'] \
+ and self.res_status not in ['304']:
+ # check payload basics
+ if self.parsed_hdrs.has_key('content-length'):
+ if self.res_body_len == self.parsed_hdrs['content-length']:
+ self.setMessage('header-content-length', rs.CL_CORRECT)
+ else:
+ self.setMessage('header-content-length', rs.CL_INCORRECT,
+ body_length=f_num(self.res_body_len))
+ if self.parsed_hdrs.has_key('content-md5'):
+ c_md5_calc = base64.encodestring(self.res_body_md5)[:-1]
+ if self.parsed_hdrs['content-md5'] == c_md5_calc:
+ self.setMessage('header-content-md5', rs.CMD5_CORRECT)
+ else:
+ self.setMessage('header-content-md5', rs.CMD5_INCORRECT,
+ calc_md5=c_md5_calc)
+ # analyse, check to see if we're done
+ self.done()
+ outstanding_requests.remove(self)
+ if self.status_cb:
+ self.status_cb("%s outstanding requests" % len(outstanding_requests))
+ if len(outstanding_requests) == 0:
+ nbhttp.stop()
+
+ @staticmethod
+ def _read_gzip_header(content):
+ "Parse a string for a GZIP header; if present, return remainder of gzipped content."
+ # adapted from gzip.py
+ FTEXT, FHCRC, FEXTRA, FNAME, FCOMMENT = 1, 2, 4, 8, 16
+ if len(content) < 10:
+ raise IndexError, "Header not complete yet"
+ magic = content[:2]
+ if magic != '\037\213':
+ raise IOError, u'Not a gzip header (magic is hex %s, should be 1f8b)' % magic.encode('hex-codec')
+ method = ord( content[2:3] )
+ if method != 8:
+ raise IOError, 'Unknown compression method'
+ flag = ord( content[3:4] )
+ content_l = list(content[10:])
+ if flag & FEXTRA:
+ # Read & discard the extra field, if present
+ xlen = ord(content_l.pop())
+ xlen = xlen + 256*ord(content_l.pop())
+ content_l = content_l[xlen:]
+ if flag & FNAME:
+ # Read and discard a null-terminated string containing the filename
+ while True:
+ s = content_l.pop()
+ if not content_l or s == '\000':
+ break
+ if flag & FCOMMENT:
+ # Read and discard a null-terminated string containing a comment
+ while True:
+ s = content_l.pop()
+ if not content_l or s == '\000':
+ break
+ if flag & FHCRC:
+ content_l = content_l[2:] # Read & discard the 16-bit header CRC
+ return "".join(content_l)
+
+
+def iri_to_uri(iri):
+ "Takes a Unicode string that possibly contains an IRI and emits a URI."
+ scheme, authority, path, query, frag = urlparse.urlsplit(iri)
+ scheme = scheme.encode('utf-8')
+ if ":" in authority:
+ host, port = authority.split(":", 1)
+ authority = host.encode('idna') + ":%s" % port
+ else:
+ authority = authority.encode('idna')
+ path = urllib.quote(path.encode('utf-8'), safe="/;%[]=:$&()+,!?*@'~")
+ query = urllib.quote(query.encode('utf-8'), safe="/;%[]=:$&()+,!?*@'~")
+ frag = urllib.quote(frag.encode('utf-8'), safe="/;%[]=:$&()+,!?*@'~")
+ return urlparse.urlunsplit((scheme, authority, path, query, frag))
+
+
+if "__main__" == __name__:
+ import sys
+ uri = sys.argv[1]
+ req_hdrs = [('Accept-Encoding', 'gzip')]
+ def status_p(msg):
+ print msg
+ class TestFetcher(RedFetcher):
+ def done(self):
+ print self.messages
+ TestFetcher(uri, req_hdrs=req_hdrs, status_cb=status_p, req_type='test')
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/red_header.py Mon Jul 26 14:12:44 2010 -0400
@@ -0,0 +1,390 @@
+"""\
+<!DOCTYPE html>
+<html>
+<head>
+ <title>RED: <%(html_uri)s></title>
+ <meta http-equiv="content-type" content="text/html; charset=utf-8" />
+ <meta name="ROBOTS" content="INDEX, NOFOLLOW" />
+ <link rel="stylesheet" type="text/css" href="%(static)s/style.css">
+ <!--[if IE]>
+ <style type="text/css">
+ #right_column {
+ width: 650px;
+ float: left;
+ </style>
+ <![endif]-->
+ <script src="%(static)s/script.js" type="text/javascript"></script>
+ <script>
+
+var tid = false;
+jQuery.fn.hoverPopup = function(fnText, fnOver, fnOut) {
+ var fnOverPopup = function(e) {
+ if (tid != false) {
+ clearTimeout(tid);
+ tid = false;
+ }
+ var text = fnText.call(this, e);
+ if (text) {
+ var pop_top = $(this).position().top + 15;
+ var popup_width = 450;
+ var popup_x;
+ if (popup_width + 10 > window.innerWidth) {
+ popup_x = 5;
+ popup_width = window.innerWidth - 10;
+ } else if (e.pageX + popup_width + 10 < window.innerWidth) {
+ popup_x = e.pageX + 5;
+ } else if (e.pageX - popup_width - 5 > 5) {
+ popup_x = e.pageX - popup_width + 5;
+ } else {
+ popup_x = 5;
+ }
+ $("#popup")
+ .fadeIn("fast")
+ .css("top", pop_top + "px")
+ .css("left", popup_x + "px")
+ .css("width", popup_width + "px")
+ .html(text);
+ var margin = 28;
+ var pop_height = $("#popup").height();
+ var win_bottom = $(window).height() + $(window).scrollTop();
+ if (win_bottom < pop_top + pop_height - margin) {
+ var placement = win_bottom - margin - pop_height;
+ $("#popup").animate({ top:placement }, 100);
+ }
+ } else {
+ $("#popup").hide();
+ }
+ if (fnOver) {
+ fnOver.call(this, e);
+ }
+ };
+ var fnOutPopup = function(e) {
+ tid = setTimeout(function(){
+ $("#popup").fadeOut("fast");
+ }, 350);
+ if (fnOut) {
+ fnOut.call(this, e);
+ }
+ };
+ return jQuery.fn.hoverIntent.call(this, fnOverPopup, fnOutPopup);
+};
+
+
+$(document).ready(function(){
+ var hidden_list = $("#hidden_list");
+
+ /* popup */
+
+ $("#popup").hoverIntent(function(){
+ if (tid != false) {
+ clearTimeout(tid);
+ tid = false;
+ }
+ }, function(){
+ $("#popup").fadeOut("fast");
+ });
+
+
+ /* single response display */
+
+ $("span.hdr").hoverPopup(
+ function(e){
+ var name = $(this).attr('name');
+ return $("li#" + name, hidden_list).html();
+ },
+ function(e){
+ var name = $(this).attr('name');
+ $("span." + name).css({"font-weight": "bold", "color": "white"});
+ $("li.msg:not(." + name + ")").fadeTo(100, 0.15);
+ $("li.msg." + name).fadeTo(50, 1.0);
+ },
+ function(e){
+ var name = $(this).attr('name');
+ $("span." + name).css({"font-weight": "normal", "color": "#ddd"});
+ $("li.msg").fadeTo(100, 1.0);
+ }
+ );
+
+ $("li.msg span").hoverPopup(
+ function(e){
+ return $("li#" + $(this).parent().attr('name'), hidden_list).html();
+ },
+ function(e){
+ var classes = $(this).parent().attr("class").split(" ");
+ for (var i=0; i < classes.length; i++) {
+ var c = classes[i];
+ $("span.hdr[name='" + c +"']").css({"font-weight": "bold", "color": "white"});
+ }
+ },
+ function(e){
+ var classes = $(this).parent().attr("class").split(" ");
+ for (var i=0; i < classes.length; i++) {
+ var c = classes[i];
+ $("span.hdr[name='" + c +"']").css({"font-weight": "normal", "color": "#ddd"});
+ }
+ }
+ );
+
+ $("h3").click(function(){
+ $(this).next().slideToggle("normal");
+ });
+
+ $("#body_view").toggle(function() {
+ $("#details").fadeOut('fast', function() {
+ $("#body").fadeIn('fast');
+ prettyPrint();
+ $("#body_view").text("show messages");
+ });
+ return false;
+ }, function() {
+ $("#body").fadeOut('fast', function() {
+ $("#details").fadeIn('fast');
+ $("#body_view").text("show body");
+ });
+ return false;
+ });
+
+ /* URI */
+
+ var check_phrase = "Enter a HTTP URI to check";
+ var uri = "%(js_uri)s";
+ if (uri) {
+ $("#uri").val(uri);
+ } else if (! $("#uri").val()) {
+ $("#uri").val(check_phrase);
+ $("#uri").css({'color': '#ccc'});
+ }
+
+ $("#uri").focus(function(){
+ if ($(this).val() == check_phrase) {
+ $(this).val("");
+ $("#uri").css({'color': '#111'});
+ }
+ });
+
+
+
+ /* multiple result display */
+
+ $("tr.droid").hoverIntent(
+ function(){
+ var classes = this.className.split(" ");
+ $("li.msg").fadeTo(100, 0.15);
+ for (var i=0; i < classes.length; i++) {
+ var c = classes[i];
+ if (c != 'droid') {
+ $("li.msg:eq(" + c +")").fadeTo(50, 1.0);
+ }
+ }
+ if (tid != false) {
+ clearTimeout(tid);
+ tid = false;
+ }
+ }, function(){
+ tid = setTimeout(function(){
+ $("li.msg").fadeTo(50, 1.0);
+ }, 100);
+ });
+
+ $("span.prob_num").hoverPopup(
+ function(e){
+ return $(this).children(".hidden").html();
+ });
+
+ $("a.preview").hoverPopup(
+ function(e){
+ var link = (this.title != "") ? this.title : this.href;
+ return "<img src='" + link + "'/><br />" + link;
+ }
+ );
+
+
+
+ /* request headers */
+
+ $("#add_req_hdr").click(function(){
+ add_req_hdr();
+ return false;
+ });
+
+ /* catch enter to work around IE8 */
+ $("input").keypress(function (e) {
+ if (e.which == 13) {
+ $("#request_form").submit();
+ }
+ });
+
+});
+
+
+function add_req_hdr(hdr_name, hdr_val){
+ var hdr_shell = "<div class='req_hdr'>";
+ hdr_shell += "<a href='#' class='delete_req_hdr'>x</a> ";
+ hdr_shell += "<span class='hdr_name'></span>";
+ hdr_shell += ": <span class='hdr_val'></span>";
+ hdr_shell += "<input type='hidden' name='req_hdr' value=''/>";
+ hdr_shell += "</div>";
+ $("#req_hdrs").append(hdr_shell);
+ var req_hdr = $("#req_hdrs > .req_hdr:last");
+ if (hdr_name == null || hdr_name in known_req_hdrs) {
+ var name_html = "<select class='hdr_name'><option/>";
+ for (name in known_req_hdrs) {
+ if (name == hdr_name) {
+ name_html += "<option selected='true'>" + name + "</option>";
+ } else {
+ name_html += "<option>" + name + "</option>";
+ }
+ }
+ name_html += "<option value='other...'>other...</option>";
+ name_html += "</select>";
+ $(".hdr_name", req_hdr).replaceWith(name_html);
+
+ if (hdr_name != null) {
+ known_hdr_vals = known_req_hdrs[hdr_name];
+ if (known_hdr_vals == null) {
+ $(".hdr_val", req_hdr).replaceWith('<input class="hdr_val" type="text"/>');
+ $(".hdr_val", req_hdr).val(hdr_val);
+ } else if (jQuery.inArray(hdr_val, known_hdr_vals) > -1) {
+ var val_html = "<select class='hdr_val'>";
+ val_html += "<option />";
+ for (i in known_hdr_vals) {
+ var val = known_hdr_vals[i];
+ if (hdr_val == val) {
+ val_html += "<option selected='true'>" + val + "</option>";
+ } else {
+ val_html += "<option>" + val + "</option>";
+ }
+ }
+ val_html += "<option value='other...'>other...</option>";
+ val_html += "</select>";
+ $(".hdr_val", req_hdr).replaceWith(val_html);
+ } else if (hdr_val != null) {
+ $(".hdr_val", req_hdr).replaceWith('<input class="hdr_val" type="text"/>');
+ $(".hdr_val", req_hdr).val(hdr_val);
+ }
+ $("input[type=hidden]", req_hdr).val(hdr_name + ":" + hdr_val);
+ }
+ } else {
+ $(".hdr_name", req_hdr).replaceWith('<input class="hdr_name" type="text"/>');
+ $(".hdr_name", req_hdr).val(hdr_name);
+ $(".hdr_val", req_hdr).replaceWith('<input class="hdr_val" type="text"/>');
+ $(".hdr_val", req_hdr).val(hdr_val);
+ $("input[type=hidden]", req_hdr).val(hdr_name + ":" + hdr_val);
+ }
+
+ /* populate the value when the header name is changed */
+ $("select.hdr_name:last").bind("change", function(){
+ var req_hdr = $(this).closest(".req_hdr");
+ var hdr_name = $(".hdr_name > option:selected", req_hdr).val()
+ var hdr_val = "";
+ if (hdr_name in known_req_hdrs) {
+ if (known_req_hdrs[hdr_name] == null) {
+ hdr_val = "<input class='hdr_val' type='text'/>";
+ $(".hdr_val", req_hdr).replaceWith(hdr_val);
+ bind_text_changes(req_hdr);
+ } else {
+ hdr_val = "<select class='hdr_val'>";
+ for (val in known_req_hdrs[hdr_name]) {
+ hdr_val += "<option>" + known_req_hdrs[hdr_name][val] + "</option>";
+ }
+ hdr_val += "<option value='other...'>other...</option>";
+ hdr_val += "</select>";
+ $(".hdr_val", req_hdr).replaceWith(hdr_val);
+ $("select.hdr_val", req_hdr).bind("change", function(){
+ var hdr_val = "";
+ if ($(".hdr_val > option:selected", req_hdr).val() == "other...") {
+ $(".hdr_val", req_hdr).replaceWith("<input class='hdr_val' type='text'/>");
+ bind_text_changes(req_hdr);
+ } else {
+ hdr_val = $(".hdr_val > option:selected", req_hdr).val();
+ }
+ $("input[type=hidden]", req_hdr).val(hdr_name + ":" + hdr_val);
+ });
+ }
+ } else if (hdr_name == "other...") {
+ $(".hdr_name", req_hdr).replaceWith("<input class='hdr_name' type='text'/>");
+ $(".hdr_val", req_hdr).replaceWith("<input class='hdr_val' type='text'/>");
+ bind_text_changes(req_hdr);
+
+ /* alert on dangerous changes */
+ $("input.hdr_name", req_hdr).bind("change", function() {
+ var hdr_name = $(".hdr_name:text", req_hdr).val();
+ if (jQuery.inArray(hdr_name.toLowerCase(), red_req_hdrs) > -1) {
+ alert("The " + hdr_name + " request header is used by RED in its \
+tests. Setting it yourself can lead to unpredictable results; please remove it.");
+ }
+ });
+ }
+ });
+
+ /* handle the delete button */
+ $(".delete_req_hdr:last").bind("click", function(){
+ var req_hdr = $(this).closest(".req_hdr");
+ req_hdr.remove();
+ return false;
+ });
+
+}
+
+function bind_text_changes(req_hdr) {
+ /* handle textbox changes */
+ $("input.hdr_name, input.hdr_val", req_hdr).bind("change", function() {
+ var hdr_name = $(".hdr_name", req_hdr).val();
+ var hdr_val = $(".hdr_val:text", req_hdr).val();
+ $("input[type=hidden]", req_hdr).val(hdr_name + ":" + hdr_val);
+ });
+}
+
+function init_req_hdrs() {
+ var req_hdrs = [%(js_req_hdrs)s];
+ for (i in req_hdrs) {
+ var req_hdr = req_hdrs[i];
+ add_req_hdr(req_hdr[0], req_hdr[1]);
+ }
+ return false;
+}
+
+
+var known_req_hdrs = {
+ 'Accept-Language': ['', 'en', 'en-us', 'en-uk', 'fr'],
+ 'Cache-Control': ['', 'no-cache', 'only-if-cached'],
+ 'Cookie': null,
+ 'Referer': null,
+ 'User-Agent': [ "RED/%(version)s (http://redbot.org/about)",
+ "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Win64; x64; Trident/4.0)",
+ "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; )",
+ "Mozilla/5.0 (X11; U; Linux x86_64; en-US) Gecko Firefox/3.0.8",
+ "Opera/9.80 (Windows NT 5.1; U; cs) Presto/2.2.15 Version/10.00",
+ ]
+};
+
+var red_req_hdrs = [
+ 'accept-encoding',
+ 'if-modified-since',
+ 'if-none-match',
+ 'connection',
+ 'transfer-encoding',
+ 'content-length'
+];
+
+</script>
+%(extra_js)s
+</head>
+
+<body>
+
+<div id="popup"></div>
+
+<div id="request">
+ <h1><a href="?"><span class="hilight">R</span>esource <span class="hilight">E</span>xpert <span class="hilight">D</span>roid</a></h1>
+
+ <form method="GET" onLoad="init_req_hdrs();" id="request_form">
+ <input type="url" name="uri" value="%(html_uri)s" id="uri"/><br />
+ <div id="req_hdrs"></div>
+ <div class="add_req_hdr">
+ <a href="#" id="add_req_hdr">add a header</a>
+ </div>
+ </form>
+ <script>init_req_hdrs();</script>
+</div>
+"""
\ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/red_speak.patch Mon Jul 26 14:12:44 2010 -0400
@@ -0,0 +1,1312 @@
+*** redbot/src/red_speak.py 2010-07-26 14:23:49.000000000 -0400
+--- red_speak.py 2010-07-27 16:43:08.000000000 -0400
+***************
+*** 171,177 ****
+ }
+ text = {
+ 'en': u"""HTTP headers use the ISO-8859-1 character set, but in most
+! cases are pure ASCII (a subset of this encoding).<p>
+ This header has non-ASCII characters, which RED has interpreted as
+ being encoded in ISO-8859-1. If another encoding is used (e.g., UTF-8),
+ the results may be unpredictable."""
+--- 171,177 ----
+ }
+ text = {
+ 'en': u"""HTTP headers use the ISO-8859-1 character set, but in most
+! cases are pure ASCII (a subset of this encoding).<br />
+ This header has non-ASCII characters, which RED has interpreted as
+ being encoded in ISO-8859-1. If another encoding is used (e.g., UTF-8),
+ the results may be unpredictable."""
+***************
+*** 199,205 ****
+ 'en': u"""This header is designed to only occur once in a message. When it
+ occurs more than once, a receiver needs to choose the one to use, which
+ can lead to interoperability problems, since different implementations may
+! make different choices.<p>
+ For the purposes of its tests, RED uses the last instance of the header that
+ is present; other implementations may behave differently."""
+ }
+--- 199,205 ----
+ 'en': u"""This header is designed to only occur once in a message. When it
+ occurs more than once, a receiver needs to choose the one to use, which
+ can lead to interoperability problems, since different implementations may
+! make different choices.<br />
+ For the purposes of its tests, RED uses the last instance of the header that
+ is present; other implementations may behave differently."""
+ }
+***************
+*** 213,219 ****
+ text = {
+ 'en': u"""HTTP defines a few special situations where a response does not
+ allow a body. This includes 101, 204 and 304 responses, as well as responses
+! to the <code>HEAD</code> method.<p>
+ %(response)s had a body, despite it being disallowed. Clients receiving
+ it may treat the body as the next response in the connection, leading to
+ interoperability and security issues."""
+--- 213,219 ----
+ text = {
+ 'en': u"""HTTP defines a few special situations where a response does not
+ allow a body. This includes 101, 204 and 304 responses, as well as responses
+! to the <code>HEAD</code> method.<br />
+ %(response)s had a body, despite it being disallowed. Clients receiving
+ it may treat the body as the next response in the connection, leading to
+ interoperability and security issues."""
+***************
+*** 275,288 ****
+ }
+ text = {
+ 'en': u"""The response indicates it uses HTTP chunked encoding, but there
+! was a problem decoding the chunking.<p>
+! A valid chunk looks something like this:<p>
+! <code>[chunk-size in hex]\\r\\n[chunk-data]\\r\\n</code><p>
+! However, the chunk sent started like this:<p>
+! <code>%(chunk_sample)s</code><p>
+ This is a serious problem, because HTTP uses chunking to delimit one
+ response from the next one; incorrect chunking can lead to interoperability
+! and security problems.<p>
+ This issue is often caused by sending an integer chunk size instead of one
+ in hex, or by sending <code>Transfer-Encoding: chunked</code> without
+ actually chunking the response body."""
+--- 275,288 ----
+ }
+ text = {
+ 'en': u"""The response indicates it uses HTTP chunked encoding, but there
+! was a problem decoding the chunking.<br />
+! A valid chunk looks something like this:<br />
+! <code>[chunk-size in hex]\\r\\n[chunk-data]\\r\\n</code><br />
+! However, the chunk sent started like this:<br />
+! <code>%(chunk_sample)s</code><br />
+ This is a serious problem, because HTTP uses chunking to delimit one
+ response from the next one; incorrect chunking can lead to interoperability
+! and security problems.<br />
+ This issue is often caused by sending an integer chunk size instead of one
+ in hex, or by sending <code>Transfer-Encoding: chunked</code> without
+ actually chunking the response body."""
+***************
+*** 309,315 ****
+ text = {
+ 'en': u"""GZip-compressed responses use zlib compression to reduce the number
+ of bytes transferred on the wire. However, this response could not be decompressed;
+! the error encountered was "<code>%(zlib_error)s</code>".<p>
+ %(ok_zlib_len)s bytes were decompressed successfully before this; the erroneous
+ chunk starts with "<code>%(chunk_sample)s</code>"."""
+ }
+--- 309,315 ----
+ text = {
+ 'en': u"""GZip-compressed responses use zlib compression to reduce the number
+ of bytes transferred on the wire. However, this response could not be decompressed;
+! the error encountered was "<code>%(zlib_error)s</code>".<br />
+ %(ok_zlib_len)s bytes were decompressed successfully before this; the erroneous
+ chunk starts with "<code>%(chunk_sample)s</code>"."""
+ }
+***************
+*** 323,329 ****
+ text = {
+ 'en': u"""%(response)s's <code>Content-Encoding</code> header indicates it
+ has the %(encoding)s content-coding applied, but RED didn't ask for it
+! to be.<p>
+ Normally, clients ask for the encodings they want in the
+ <code>Accept-Encoding</code> request header. Using encodings that the
+ client doesn't explicitly request can lead to interoperability problems."""
+--- 323,329 ----
+ text = {
+ 'en': u"""%(response)s's <code>Content-Encoding</code> header indicates it
+ has the %(encoding)s content-coding applied, but RED didn't ask for it
+! to be.<br />
+ Normally, clients ask for the encodings they want in the
+ <code>Accept-Encoding</code> request header. Using encodings that the
+ client doesn't explicitly request can lead to interoperability problems."""
+***************
+*** 338,344 ****
+ text = {
+ 'en': u"""HTTP defines <em>transfer-codings</em> as a hop-by-hop encoding
+ of the message body. The <code>identity</code> tranfer-coding was defined
+! as the absence of encoding; it doesn't do anything, so it's necessary.<p>
+ You can remove this token to save a few bytes."""
+ }
+
+--- 338,344 ----
+ text = {
+ 'en': u"""HTTP defines <em>transfer-codings</em> as a hop-by-hop encoding
+ of the message body. The <code>identity</code> tranfer-coding was defined
+! as the absence of encoding; it doesn't do anything, so it's necessary.<br />
+ You can remove this token to save a few bytes."""
+ }
+
+***************
+*** 351,357 ****
+ text = {
+ 'en': u"""%(response)s's <code>Transfer-Encoding</code> header indicates it
+ has the %(encoding)s transfer-coding applied, but RED didn't ask for it
+! to be.<p>
+ Normally, clients ask for the encodings they want in the
+ <code>TE</code> request header. Using codings that the
+ client doesn't explicitly request can lead to interoperability problems."""
+--- 351,357 ----
+ text = {
+ 'en': u"""%(response)s's <code>Transfer-Encoding</code> header indicates it
+ has the %(encoding)s transfer-coding applied, but RED didn't ask for it
+! to be.<br />
+ Normally, clients ask for the encodings they want in the
+ <code>TE</code> request header. Using codings that the
+ client doesn't explicitly request can lead to interoperability problems."""
+***************
+*** 394,400 ****
+ text = {
+ 'en': u"""The <code>Last-Modified</code> header indicates the last point in
+ time that the resource has changed. It is used in HTTP for validating cached
+! responses, and for calculating heuristic freshness in caches.<p>
+ This resource last changed %(last_modified_string)s."""
+ }
+
+--- 394,400 ----
+ text = {
+ 'en': u"""The <code>Last-Modified</code> header indicates the last point in
+ time that the resource has changed. It is used in HTTP for validating cached
+! responses, and for calculating heuristic freshness in caches.<br />
+ This resource last changed %(last_modified_string)s."""
+ }
+
+***************
+*** 442,452 ****
+ text = {
+ 'en': u"""The <code>Via</code> header indicates that one or more
+ intermediaries are present between RED and the origin server for the
+! resource.<p>
+ This may indicate that a proxy is in between RED and the server, or that
+! the server uses a "reverse proxy" or CDN in front of it.<p>
+ %(via_list)s
+! <p>
+ There field has three space-separated components; first, the HTTP version
+ of the message that the intermediary received, then the identity of the
+ intermediary (usually but not always its hostname), and then optionally a
+--- 442,452 ----
+ text = {
+ 'en': u"""The <code>Via</code> header indicates that one or more
+ intermediaries are present between RED and the origin server for the
+! resource.<br />
+ This may indicate that a proxy is in between RED and the server, or that
+! the server uses a "reverse proxy" or CDN in front of it.<br />
+ %(via_list)s
+! <br />
+ There field has three space-separated components; first, the HTTP version
+ of the message that the intermediary received, then the identity of the
+ intermediary (usually but not always its hostname), and then optionally a
+***************
+*** 463,471 ****
+ text = {
+ 'en': u"""The <code>Location</code> header is used for specific purposes
+ in HTTP; mostly to indicate the URI of another resource (e.g., in
+! redirection, or when a new resource is created).<p>
+ In other status codes (such as this one) it doesn't have a defined meaning,
+! so any use of it won't be interoperable.<p>
+ Sometimes <code>Location</code> is confused with <code>Content-Location</code>,
+ which indicates a URI for the payload of the message that it appears in."""
+ }
+--- 463,471 ----
+ text = {
+ 'en': u"""The <code>Location</code> header is used for specific purposes
+ in HTTP; mostly to indicate the URI of another resource (e.g., in
+! redirection, or when a new resource is created).<br />
+ In other status codes (such as this one) it doesn't have a defined meaning,
+! so any use of it won't be interoperable.<br />
+ Sometimes <code>Location</code> is confused with <code>Content-Location</code>,
+ which indicates a URI for the payload of the message that it appears in."""
+ }
+***************
+*** 482,488 ****
+ Most (but not all) clients will work around this, but since this field isn't
+ defined to take a relative URI, they may behave differently (for example,
+ if the body contains a base URI).</p>
+! The correct value for this field is (probably):<br>
+ <code>%(full_uri)s</code>"""
+ }
+
+--- 482,488 ----
+ Most (but not all) clients will work around this, but since this field isn't
+ defined to take a relative URI, they may behave differently (for example,
+ if the body contains a base URI).</p>
+! The correct value for this field is (probably):<br />
+ <code>%(full_uri)s</code>"""
+ }
+
+***************
+*** 495,504 ****
+ text = {
+ 'en': u"""Many Web browers "sniff" the media type of responses to figure out
+ whether they're HTML, RSS or another format, no matter what the
+! <code>Content-Type</code> header says.<p>
+ This header instructs Microsoft's Internet Explorer not to do this, but to
+ always respect the Content-Type header. It probably won't have any effect in
+! other clients.<p>
+ See <a href="http://blogs.msdn.com/ie/archive/2008/09/02/ie8-security-part-vi-beta-2-update.aspx">this blog entry</a>
+ for more information about this header."""
+ }
+--- 495,504 ----
+ text = {
+ 'en': u"""Many Web browers "sniff" the media type of responses to figure out
+ whether they're HTML, RSS or another format, no matter what the
+! <code>Content-Type</code> header says.<br />
+ This header instructs Microsoft's Internet Explorer not to do this, but to
+ always respect the Content-Type header. It probably won't have any effect in
+! other clients.<br />
+ See <a href="http://blogs.msdn.com/ie/archive/2008/09/02/ie8-security-part-vi-beta-2-update.aspx">this blog entry</a>
+ for more information about this header."""
+ }
+***************
+*** 512,518 ****
+ text = {
+ 'en': u"""Only one value is currently defined for this header, <code>nosniff</code>.
+ Using other values here won't necessarily cause problems, but they probably
+! won't have any effect either.<p>
+ See <a href="http://blogs.msdn.com/ie/archive/2008/09/02/ie8-security-part-vi-beta-2-update.aspx">this blog entry</a> for more information about this header."""
+ }
+
+--- 512,518 ----
+ text = {
+ 'en': u"""Only one value is currently defined for this header, <code>nosniff</code>.
+ Using other values here won't necessarily cause problems, but they probably
+! won't have any effect either.<br />
+ See <a href="http://blogs.msdn.com/ie/archive/2008/09/02/ie8-security-part-vi-beta-2-update.aspx">this blog entry</a> for more information about this header."""
+ }
+
+***************
+*** 528,535 ****
+ from directly opening a file download; instead, they must first save the
+ file locally. When the locally saved file is later opened, it no longer
+ executes in the security context of your site, helping to prevent script
+! injection.<p>
+! This header probably won't have any effect in other clients.<p>
+ See <a href="http://blogs.msdn.com/ie/archive/2008/07/02/ie8-security-part-v-comprehensive-protection.aspx">this blog article</a> for more details."""
+ }
+
+--- 528,535 ----
+ from directly opening a file download; instead, they must first save the
+ file locally. When the locally saved file is later opened, it no longer
+ executes in the security context of your site, helping to prevent script
+! injection.<br />
+! This header probably won't have any effect in other clients.<br />
+ See <a href="http://blogs.msdn.com/ie/archive/2008/07/02/ie8-security-part-v-comprehensive-protection.aspx">this blog article</a> for more details."""
+ }
+
+***************
+*** 542,548 ****
+ text = {
+ 'en': u"""Only one value is currently defined for this header, <code>noopen</code>.
+ Using other values here won't necessarily cause problems, but they probably
+! won't have any effect either.<p>
+ See <a href="http://blogs.msdn.com/ie/archive/2008/07/02/ie8-security-part-v-comprehensive-protection.aspx">this blog article</a> for more details."""
+ }
+
+--- 542,548 ----
+ text = {
+ 'en': u"""Only one value is currently defined for this header, <code>noopen</code>.
+ Using other values here won't necessarily cause problems, but they probably
+! won't have any effect either.<br />
+ See <a href="http://blogs.msdn.com/ie/archive/2008/07/02/ie8-security-part-v-comprehensive-protection.aspx">this blog article</a> for more details."""
+ }
+
+***************
+*** 556,563 ****
+ 'en': u"""The <code>X-Frame-Options</code> response header controls how
+ IE8 handles HTML frames; the <code>DENY</code> value prevents this content
+ from being rendered within a frame, which defends against certain types of
+! attacks.<p>
+! Currently this is supported by IE8 and Safari 4.<p>
+ See <a href="http://blogs.msdn.com/ie/archive/2009/01/27/ie8-security-part-vii-clickjacking-defenses.aspx">this blog entry</a> for more information.
+ """
+ }
+--- 556,563 ----
+ 'en': u"""The <code>X-Frame-Options</code> response header controls how
+ IE8 handles HTML frames; the <code>DENY</code> value prevents this content
+ from being rendered within a frame, which defends against certain types of
+! attacks.<br />
+! Currently this is supported by IE8 and Safari 4.<br />
+ See <a href="http://blogs.msdn.com/ie/archive/2009/01/27/ie8-security-part-vii-clickjacking-defenses.aspx">this blog entry</a> for more information.
+ """
+ }
+***************
+*** 572,579 ****
+ 'en': u"""The <code>X-Frame-Options</code> response header controls how
+ IE8 handles HTML frames; the <code>DENY</code> value prevents this content
+ from being rendered within a frame on another site, which defends against certain types of
+! attacks.<p>
+! Currently this is supported by IE8 and Safari 4.<p>
+ See <a href="http://blogs.msdn.com/ie/archive/2009/01/27/ie8-security-part-vii-clickjacking-defenses.aspx">this blog entry</a> for more information.
+ """
+ }
+--- 572,579 ----
+ 'en': u"""The <code>X-Frame-Options</code> response header controls how
+ IE8 handles HTML frames; the <code>DENY</code> value prevents this content
+ from being rendered within a frame on another site, which defends against certain types of
+! attacks.<br />
+! Currently this is supported by IE8 and Safari 4.<br />
+ See <a href="http://blogs.msdn.com/ie/archive/2009/01/27/ie8-security-part-vii-clickjacking-defenses.aspx">this blog entry</a> for more information.
+ """
+ }
+***************
+*** 588,594 ****
+ 'en': u"""Only two values are currently defined for this header, <code>DENY</code>
+ and <code>SAMEORIGIN</code>.
+ Using other values here won't necessarily cause problems, but they probably
+! won't have any effect either.<p>
+ See <a href="http://blogs.msdn.com/ie/archive/2009/01/27/ie8-security-part-vii-clickjacking-defenses.aspx">this blog entry</a> for more information.
+ """
+ }
+--- 588,594 ----
+ 'en': u"""Only two values are currently defined for this header, <code>DENY</code>
+ and <code>SAMEORIGIN</code>.
+ Using other values here won't necessarily cause problems, but they probably
+! won't have any effect either.<br />
+ See <a href="http://blogs.msdn.com/ie/archive/2009/01/27/ie8-security-part-vii-clickjacking-defenses.aspx">this blog entry</a> for more information.
+ """
+ }
+***************
+*** 613,619 ****
+ }
+ text = {
+ 'en': u"""Internet Explorer 8 allows responses to explicitly set the rendering
+! mode used for a given page (known a the "compatibility mode").<p>
+ See <a href="http://msdn.microsoft.com/en-us/library/cc288325(VS.85).aspx">Microsoft's documentation</a> for more information."""
+ }
+
+--- 613,619 ----
+ }
+ text = {
+ 'en': u"""Internet Explorer 8 allows responses to explicitly set the rendering
+! mode used for a given page (known a the "compatibility mode").<br />
+ See <a href="http://msdn.microsoft.com/en-us/library/cc288325(VS.85).aspx">Microsoft's documentation</a> for more information."""
+ }
+
+***************
+*** 625,633 ****
+ }
+ text = {
+ 'en': u"""Internet Explorer 8 allows responses to explicitly set the rendering mode
+! used for a page.<p>
+ This response has more than one such directive targetted at one browser;
+! this may cause unpredictable results.<p>
+ See <a href="http://msdn.microsoft.com/en-us/library/cc288325(VS.85).aspx">this blog entry</a> for more information."""
+ }
+
+--- 625,633 ----
+ }
+ text = {
+ 'en': u"""Internet Explorer 8 allows responses to explicitly set the rendering mode
+! used for a page.<br />
+ This response has more than one such directive targetted at one browser;
+! this may cause unpredictable results.<br />
+ See <a href="http://msdn.microsoft.com/en-us/library/cc288325(VS.85).aspx">this blog entry</a> for more information."""
+ }
+
+***************
+*** 640,649 ****
+ text = {
+ 'en': u"""Internet Explorer 8 has built-in Cross-Site Scripting (XSS)
+ attack protection; it tries to automatically filter requests that
+! fit a particular profile.<p>
+ %(response)s has explicitly disabled this protection. In some scenarios,
+! this is useful to do, if the protection interferes with the application.<p>
+! This header probably won't have any effect in other clients.<p>
+ See <a href="http://blogs.msdn.com/ie/archive/2008/07/02/ie8-security-part-iv-the-xss-filter.aspx">this blog entry</a> for more information.
+ """
+ }
+--- 640,649 ----
+ text = {
+ 'en': u"""Internet Explorer 8 has built-in Cross-Site Scripting (XSS)
+ attack protection; it tries to automatically filter requests that
+! fit a particular profile.<br />
+ %(response)s has explicitly disabled this protection. In some scenarios,
+! this is useful to do, if the protection interferes with the application.<br />
+! This header probably won't have any effect in other clients.<br />
+ See <a href="http://blogs.msdn.com/ie/archive/2008/07/02/ie8-security-part-iv-the-xss-filter.aspx">this blog entry</a> for more information.
+ """
+ }
+***************
+*** 660,666 ****
+ 'en': u"""The <code>Accept-Ranges</code> response header tells clients
+ what <code>range-unit</code>s a resource is willing to process in future
+ requests. HTTP only defines two: <code>bytes</code> and <code>none</code>.
+! <p>
+ Clients who don't know about the non-standard range-unit will not be
+ able to use it."""
+ }
+--- 660,666 ----
+ 'en': u"""The <code>Accept-Ranges</code> response header tells clients
+ what <code>range-unit</code>s a resource is willing to process in future
+ requests. HTTP only defines two: <code>bytes</code> and <code>none</code>.
+! <br />
+ Clients who don't know about the non-standard range-unit will not be
+ able to use it."""
+ }
+***************
+*** 691,702 ****
+ of this response, but the partial response doesn't correspond with the full
+ response retrieved at the same time. This could indicate that the range
+ implementation isn't working properly.
+! <p>RED sent<br/>
+! <code>Range: %(range)s</code>
+! <p>RED expected %(range_expected_bytes)s bytes:<br/>
+! <code>%(range_expected)s</code>
+! <p>RED received %(range_received_bytes)s bytes:<br/>
+! <code>%(range_received)s</code>"""
+ }
+
+ class RANGE_FULL(Message):
+--- 691,702 ----
+ of this response, but the partial response doesn't correspond with the full
+ response retrieved at the same time. This could indicate that the range
+ implementation isn't working properly.
+! <p>RED sent<br />
+! <code>Range: %(range)s</code></p>
+! <p>RED expected %(range_expected_bytes)s bytes:<br />
+! <code>%(range_expected)s</code></p>
+! <p>RED received %(range_received_bytes)s bytes:<br />
+! <code>%(range_received)s</code></p>"""
+ }
+
+ class RANGE_FULL(Message):
+***************
+*** 735,741 ****
+ }
+ text = {
+ 'en': u"""This resource supports ranged requests and also supports negotiation for
+! gzip compression, but doesn't support compression for both full and partial responses.<p>
+ This can cause problems for clients when they compare the partial and full responses,
+ since the partial response is expressed as a byte range, and compression changes the
+ bytes."""
+--- 735,741 ----
+ }
+ text = {
+ 'en': u"""This resource supports ranged requests and also supports negotiation for
+! gzip compression, but doesn't support compression for both full and partial responses.<br />
+ This can cause problems for clients when they compare the partial and full responses,
+ since the partial response is expressed as a byte range, and compression changes the
+ bytes."""
+***************
+*** 767,773 ****
+ that is, to mark the end of one message and the beginning of the next. RED
+ has checked the length of the body and found the <code>Content-Length</code>
+ is not correct. This can cause problems not only with connection handling,
+! but also caching, since an incomplete response is considered uncacheable.<p>
+ The actual body size sent was %(body_length)s bytes."""
+ }
+
+--- 767,773 ----
+ that is, to mark the end of one message and the beginning of the next. RED
+ has checked the length of the body and found the <code>Content-Length</code>
+ is not correct. This can cause problems not only with connection handling,
+! but also caching, since an incomplete response is considered uncacheable.<br />
+ The actual body size sent was %(body_length)s bytes."""
+ }
+
+***************
+*** 808,814 ****
+ 'en': u"""HTTP supports compression of responses by negotiating for
+ <code>Content-Encoding</code>. When RED asked for a compressed response,
+ the resource provided one, saving %(savings)s%% of its original size
+! (from %(orig_size)s to %(gzip_size)s bytes).<p>
+ The compressed response's headers are displayed."""
+ }
+
+--- 808,814 ----
+ 'en': u"""HTTP supports compression of responses by negotiating for
+ <code>Content-Encoding</code>. When RED asked for a compressed response,
+ the resource provided one, saving %(savings)s%% of its original size
+! (from %(orig_size)s to %(gzip_size)s bytes).<br />
+ The compressed response's headers are displayed."""
+ }
+
+***************
+*** 822,832 ****
+ 'en': u"""HTTP supports compression of responses by negotiating for
+ <code>Content-Encoding</code>. When RED asked for a compressed response,
+ the resource provided one, but it was %(savings)s%% <em>larger</em> than the original
+! response; from %(orig_size)s to %(gzip_size)s bytes.<p>
+ Often, this happens when the uncompressed response is very small, or can't be compressed
+ more; since gzip compression has some overhead, it can make the response larger. Turning compression
+ <strong>off</strong> for this resource may slightly improve response times and save
+! bandwidth.<p>
+ The compressed response's headers are displayed."""
+ }
+
+--- 822,832 ----
+ 'en': u"""HTTP supports compression of responses by negotiating for
+ <code>Content-Encoding</code>. When RED asked for a compressed response,
+ the resource provided one, but it was %(savings)s%% <em>larger</em> than the original
+! response; from %(orig_size)s to %(gzip_size)s bytes.<br />
+ Often, this happens when the uncompressed response is very small, or can't be compressed
+ more; since gzip compression has some overhead, it can make the response larger. Turning compression
+ <strong>off</strong> for this resource may slightly improve response times and save
+! bandwidth.<br />
+ The compressed response's headers are displayed."""
+ }
+
+***************
+*** 851,857 ****
+ text = {
+ 'en': u"""All content negotiated responses need to have a
+ <code>Vary</code> header that reflects the header(s) used to select the
+! response.<p>
+ %(response)s was negotiated for <code>gzip</code> content encoding, so
+ the <code>Vary</code> header needs to contain <code>Accept-Encoding</code>,
+ the request header used."""
+--- 851,857 ----
+ text = {
+ 'en': u"""All content negotiated responses need to have a
+ <code>Vary</code> header that reflects the header(s) used to select the
+! response.<br />
+ %(response)s was negotiated for <code>gzip</code> content encoding, so
+ the <code>Vary</code> header needs to contain <code>Accept-Encoding</code>,
+ the request header used."""
+***************
+*** 879,893 ****
+ text = {
+ 'en': u"""HTTP requires that the <code>Vary</code> response header be sent
+ consistently for all responses if they change based upon different aspects
+! of the request.<p>
+ This resource has both compressed and uncompressed variants
+ available, negotiated by the <code>Accept-Encoding</code> request header,
+! but it sends different Vary headers for each;<p>
+ <ul>
+ <li>"<code>%(conneg_vary)s</code>" when the response is compressed, and</li>
+ <li>"<code>%(no_conneg_vary)s</code>" when it is not.</li>
+ </ul>
+! <p>This can cause problems for downstream caches, because they
+ cannot consistently determine what the cache key for a given URI is."""
+ }
+
+--- 879,893 ----
+ text = {
+ 'en': u"""HTTP requires that the <code>Vary</code> response header be sent
+ consistently for all responses if they change based upon different aspects
+! of the request.<br />
+ This resource has both compressed and uncompressed variants
+ available, negotiated by the <code>Accept-Encoding</code> request header,
+! but it sends different Vary headers for each;<br />
+ <ul>
+ <li>"<code>%(conneg_vary)s</code>" when the response is compressed, and</li>
+ <li>"<code>%(no_conneg_vary)s</code>" when it is not.</li>
+ </ul>
+! <br />This can cause problems for downstream caches, because they
+ cannot consistently determine what the cache key for a given URI is."""
+ }
+
+***************
+*** 900,906 ****
+ text = {
+ 'en': u"""HTTP requires that the <code>ETag</code>s for two different
+ responses associated with the same URI be different as well, to help caches
+! and other receivers disambiguate them.<p>
+ This resource, however, sent the same
+ ETag for both its compressed and uncompressed versions (negotiated by
+ <code>Accept-Encoding</code>. This can cause interoperability problems,
+--- 900,906 ----
+ text = {
+ 'en': u"""HTTP requires that the <code>ETag</code>s for two different
+ responses associated with the same URI be different as well, to help caches
+! and other receivers disambiguate them.<br />
+ This resource, however, sent the same
+ ETag for both its compressed and uncompressed versions (negotiated by
+ <code>Accept-Encoding</code>. This can cause interoperability problems,
+***************
+*** 929,935 ****
+ }
+ text = {
+ 'en': u"""Using RED's local clock, the server's clock does not appear to
+! be well-synchronised.<p>
+ HTTP's caching model assumes reasonable synchronisation between
+ clocks on the server and client; clock skew can cause responses that
+ should be cacheable to be considered uncacheable (especially if their freshness
+--- 929,935 ----
+ }
+ text = {
+ 'en': u"""Using RED's local clock, the server's clock does not appear to
+! be well-synchronised.<br />
+ HTTP's caching model assumes reasonable synchronisation between
+ clocks on the server and client; clock skew can cause responses that
+ should be cacheable to be considered uncacheable (especially if their freshness
+***************
+*** 954,963 ****
+ text = {
+ 'en': u"""It appears that this response has been cached by a reverse proxy or
+ <abbr title="Content Delivery Network">CDN</abbr>, because the <code>Age</code>
+! header is present, but the <code>Date</code> header is more recent than it indicates.<p>
+ Generally, reverse proxies should either omit the <code>Age</code> header (if they
+ have another means of determining how fresh the response is), or
+! leave the <code>Date</code> header alone (i.e., act as a normal HTTP cache).<p>
+ See <a href="http://www2.research.att.com/~edith/Papers/HTML/usits01/index.html">
+ this paper</a> for more information."""
+ }
+--- 954,963 ----
+ text = {
+ 'en': u"""It appears that this response has been cached by a reverse proxy or
+ <abbr title="Content Delivery Network">CDN</abbr>, because the <code>Age</code>
+! header is present, but the <code>Date</code> header is more recent than it indicates.<br />
+ Generally, reverse proxies should either omit the <code>Age</code> header (if they
+ have another means of determining how fresh the response is), or
+! leave the <code>Date</code> header alone (i.e., act as a normal HTTP cache).<br />
+ See <a href="http://www2.research.att.com/~edith/Papers/HTML/usits01/index.html">
+ this paper</a> for more information."""
+ }
+***************
+*** 1007,1013 ****
+ }
+ text = {
+ 'en': u"""Cache-Control directive names are case-sensitive, and will not
+! be recognised by most implementations if the capitalisation is wrong.<p>
+ Did you mean to use %(cc_lower)s instead of %(cc)s?"""
+ }
+
+--- 1007,1013 ----
+ }
+ text = {
+ 'en': u"""Cache-Control directive names are case-sensitive, and will not
+! be recognised by most implementations if the capitalisation is wrong.<br />
+ Did you mean to use %(cc_lower)s instead of %(cc)s?"""
+ }
+
+***************
+*** 1084,1090 ****
+ while caches <strong>can</strong> store this response, they cannot use
+ it to satisfy a request unless it has been validated (either with an
+ <code>If-None-Match</code> or <code>If-Modified-Since</code> conditional)
+! for that request.<p>"""
+ }
+
+ class NO_CACHE_NO_VALIDATOR(Message):
+--- 1084,1090 ----
+ while caches <strong>can</strong> store this response, they cannot use
+ it to satisfy a request unless it has been validated (either with an
+ <code>If-None-Match</code> or <code>If-Modified-Since</code> conditional)
+! for that request.<br />"""
+ }
+
+ class NO_CACHE_NO_VALIDATOR(Message):
+***************
+*** 1098,1104 ****
+ while caches <strong>can</strong> store this response, they cannot use
+ it to satisfy a request unless it has been validated (either with an
+ <code>If-None-Match</code> or <code>If-Modified-Since</code> conditional)
+! for that request.<p>
+ %(response)s doesn't have a <code>Last-Modified</code> or
+ <code>ETag</code> header, so it effectively can't be used by a cache."""
+ }
+--- 1098,1104 ----
+ while caches <strong>can</strong> store this response, they cannot use
+ it to satisfy a request unless it has been validated (either with an
+ <code>If-None-Match</code> or <code>If-Modified-Since</code> conditional)
+! for that request.<br />
+ %(response)s doesn't have a <code>Last-Modified</code> or
+ <code>ETag</code> header, so it effectively can't be used by a cache."""
+ }
+***************
+*** 1124,1130 ****
+ text = {
+ 'en': u"""Sending <code>Vary: User-Agent</code> requires caches to store
+ a separate copy of the response for every <code>User-Agent</code> request
+! header they see.<p>
+ Since there are so many different <code>User-Agent</code>s, this can
+ "bloat" caches with many copies of the same thing, or cause them to give
+ up on storing these responses at all."""
+--- 1124,1130 ----
+ text = {
+ 'en': u"""Sending <code>Vary: User-Agent</code> requires caches to store
+ a separate copy of the response for every <code>User-Agent</code> request
+! header they see.<br />
+ Since there are so many different <code>User-Agent</code>s, this can
+ "bloat" caches with many copies of the same thing, or cause them to give
+ up on storing these responses at all."""
+***************
+*** 1142,1150 ****
+ <a href="http://httpd.apache.org/docs/2.0/mod/mod_rewrite.html">mod_rewrite</a>)
+ will send <code>Host</code> in the <code>Vary</code> header, in the belief
+ that since it affects how the server selects what to send back,
+! this is necessary.<p>
+ This is not the case; HTTP specifies that the URI is the basis of the cache
+! key, and the URI incorporates the <code>Host</code> header.<p>
+ The presence of <code>Vary: Host</code> may make some caches not store
+ an otherwise cacheable response (since some cache implementations will
+ not store anything that has a <code>Vary</code> header)."""
+--- 1142,1150 ----
+ <a href="http://httpd.apache.org/docs/2.0/mod/mod_rewrite.html">mod_rewrite</a>)
+ will send <code>Host</code> in the <code>Vary</code> header, in the belief
+ that since it affects how the server selects what to send back,
+! this is necessary.<br />
+ This is not the case; HTTP specifies that the URI is the basis of the cache
+! key, and the URI incorporates the <code>Host</code> header.<br />
+ The presence of <code>Vary: Host</code> may make some caches not store
+ an otherwise cacheable response (since some cache implementations will
+ not store anything that has a <code>Vary</code> header)."""
+***************
+*** 1159,1165 ****
+ text = {
+ 'en': u"""The <code>Vary</code> mechanism allows a resource to describe the
+ dimensions that its responses vary, or change, over; each listed header
+! is another dimension.<p>Varying by too many dimensions makes using this
+ information impractical."""
+ }
+
+--- 1159,1165 ----
+ text = {
+ 'en': u"""The <code>Vary</code> mechanism allows a resource to describe the
+ dimensions that its responses vary, or change, over; each listed header
+! is another dimension.<br />Varying by too many dimensions makes using this
+ information impractical."""
+ }
+
+***************
+*** 1172,1184 ****
+ text = {
+ 'en': u"""The <code>Cache-Control: public</code> directive
+ makes a response cacheable even when the request had an
+! <code>Authorization</code> header (i.e., HTTP authentication was in use).<p>
+ Additionally, <a href="http://firefox.org/">Firefox</a>'s cache
+ will store SSL-protected responses on disk when <code>public</code> is
+! present; otherwise, they are only cached in memory.<p>
+! <p>Therefore, SSL-protected or HTTP-authenticated (NOT cookie-authenticated)
+ resources <em>may</em> have use for <code>public</code> to improve
+! cacheability, if used judiciously.<p>
+ However, other responses <strong>do not need to contain <code>public</code>
+ </strong>; it does not make the response "more cacheable", and only
+ makes the headers larger."""
+--- 1172,1184 ----
+ text = {
+ 'en': u"""The <code>Cache-Control: public</code> directive
+ makes a response cacheable even when the request had an
+! <code>Authorization</code> header (i.e., HTTP authentication was in use).<br />
+ Additionally, <a href="http://firefox.org/">Firefox</a>'s cache
+ will store SSL-protected responses on disk when <code>public</code> is
+! present; otherwise, they are only cached in memory.<br />
+! <br />Therefore, SSL-protected or HTTP-authenticated (NOT cookie-authenticated)
+ resources <em>may</em> have use for <code>public</code> to improve
+! cacheability, if used judiciously.<br />
+ However, other responses <strong>do not need to contain <code>public</code>
+ </strong>; it does not make the response "more cacheable", and only
+ makes the headers larger."""
+***************
+*** 1216,1222 ****
+ }
+ text = {
+ 'en': u"""A cache considers a HTTP response stale when its age (here, %(current_age)s)
+! is equal to or exceeds its freshness lifetime (in this case, %(freshness_lifetime)s).<p>
+ HTTP allows caches to use stale responses to satisfy requests only under exceptional
+ circumstances; e.g., when they lose contact with the origin server."""
+ }
+--- 1216,1222 ----
+ }
+ text = {
+ 'en': u"""A cache considers a HTTP response stale when its age (here, %(current_age)s)
+! is equal to or exceeds its freshness lifetime (in this case, %(freshness_lifetime)s).<br />
+ HTTP allows caches to use stale responses to satisfy requests only under exceptional
+ circumstances; e.g., when they lose contact with the origin server."""
+ }
+***************
+*** 1230,1239 ****
+ text = {
+ 'en': u"""When responses with certain status codes don't have explicit freshness information (like a <code>
+ Cache-Control: max-age</code> directive, or <code>Expires</code> header), caches are
+! allowed to estimate how fresh it is using a heuristic.<p>
+ Usually, but not always, this is done using the <code>Last-Modified</code> header. For
+ example, if your response was last modified a week ago, a cache might decide to consider
+! the response fresh for a day.<p>
+ Consider adding a <code>Cache-Control</code> header; otherwise, it may be cached for longer
+ or shorter than you'd like."""
+ }
+--- 1230,1239 ----
+ text = {
+ 'en': u"""When responses with certain status codes don't have explicit freshness information (like a <code>
+ Cache-Control: max-age</code> directive, or <code>Expires</code> header), caches are
+! allowed to estimate how fresh it is using a heuristic.<br />
+ Usually, but not always, this is done using the <code>Last-Modified</code> header. For
+ example, if your response was last modified a week ago, a cache might decide to consider
+! the response fresh for a day.<br />
+ Consider adding a <code>Cache-Control</code> header; otherwise, it may be cached for longer
+ or shorter than you'd like."""
+ }
+***************
+*** 1247,1257 ****
+ text = {
+ 'en': u"""%(response)s doesn't have explicit freshness information (like a <code>
+ Cache-Control: max-age</code> directive, or <code>Expires</code> header), and this
+! status code doesn't allow caches to calculate their own.<p>
+ Therefore, while caches may be allowed to store it, they can't use it, except in unusual
+! cirucumstances, such a when the origin server can't be contacted.<p>
+ This behaviour can be prevented by using the <code>Cache-Control: must-revalidate</code>
+! response directive.<p>
+ Note that many caches will not store the response at all, because it is not generally useful to do so.
+ """
+ }
+--- 1247,1257 ----
+ text = {
+ 'en': u"""%(response)s doesn't have explicit freshness information (like a <code>
+ Cache-Control: max-age</code> directive, or <code>Expires</code> header), and this
+! status code doesn't allow caches to calculate their own.<br />
+ Therefore, while caches may be allowed to store it, they can't use it, except in unusual
+! cirucumstances, such a when the origin server can't be contacted.<br />
+ This behaviour can be prevented by using the <code>Cache-Control: must-revalidate</code>
+! response directive.<br />
+ Note that many caches will not store the response at all, because it is not generally useful to do so.
+ """
+ }
+***************
+*** 1265,1271 ****
+ text = {
+ 'en': u"""HTTP allows stale responses to be served under some circumstances;
+ for example, if the origin server can't be contacted, a stale response can
+! be used (even if it doesn't have explicit freshness information).<p>
+ This behaviour can be prevented by using the <code>Cache-Control: must-revalidate</code>
+ response directive."""
+ }
+--- 1265,1271 ----
+ text = {
+ 'en': u"""HTTP allows stale responses to be served under some circumstances;
+ for example, if the origin server can't be contacted, a stale response can
+! be used (even if it doesn't have explicit freshness information).<br />
+ This behaviour can be prevented by using the <code>Cache-Control: must-revalidate</code>
+ response directive."""
+ }
+***************
+*** 1279,1285 ****
+ text = {
+ 'en': u"""HTTP allows stale responses to be served under some circumstances;
+ for example, if the origin server can't be contacted, a stale response can
+! be used (even if it doesn't have explicit freshness information).<p>This
+ behaviour can be prevented by using the <code>Cache-Control: must-revalidate</code>
+ response directive."""
+ }
+--- 1279,1285 ----
+ text = {
+ 'en': u"""HTTP allows stale responses to be served under some circumstances;
+ for example, if the origin server can't be contacted, a stale response can
+! be used (even if it doesn't have explicit freshness information).<br />This
+ behaviour can be prevented by using the <code>Cache-Control: must-revalidate</code>
+ response directive."""
+ }
+***************
+*** 1292,1298 ****
+ }
+ text = {
+ 'en': u"""The <code>Cache-Control: must-revalidate</code> directive forbids
+! caches from using stale responses to satisfy requests.<p>For example,
+ caches often use stale responses when they cannot connect to the origin
+ server; when this directive is present, they will return an error rather
+ than a stale response."""
+--- 1292,1298 ----
+ }
+ text = {
+ 'en': u"""The <code>Cache-Control: must-revalidate</code> directive forbids
+! caches from using stale responses to satisfy requests.<br />For example,
+ caches often use stale responses when they cannot connect to the origin
+ server; when this directive is present, they will return an error rather
+ than a stale response."""
+***************
+*** 1306,1312 ****
+ }
+ text = {
+ 'en': u"""The <code>Cache-Control: must-revalidate</code> directive forbids
+! caches from using stale responses to satisfy requests.<p>For example,
+ caches often use stale responses when they cannot connect to the origin
+ server; when this directive is present, they will return an error rather
+ than a stale response."""
+--- 1306,1312 ----
+ }
+ text = {
+ 'en': u"""The <code>Cache-Control: must-revalidate</code> directive forbids
+! caches from using stale responses to satisfy requests.<br />For example,
+ caches often use stale responses when they cannot connect to the origin
+ server; when this directive is present, they will return an error rather
+ than a stale response."""
+***************
+*** 1321,1330 ****
+ text = {
+ 'en': u"""The presence of the <code>Cache-Control: proxy-revalidate</code>
+ and/or <code>s-maxage</code> directives forbids shared caches (e.g., proxy
+! caches) from using stale responses to satisfy requests.<p>For example,
+ caches often use stale responses when they cannot connect to the origin
+ server; when this directive is present, they will return an error rather
+! than a stale response.<p>These directives do not affect private caches; for
+ example, those in browsers."""
+ }
+
+--- 1321,1330 ----
+ text = {
+ 'en': u"""The presence of the <code>Cache-Control: proxy-revalidate</code>
+ and/or <code>s-maxage</code> directives forbids shared caches (e.g., proxy
+! caches) from using stale responses to satisfy requests.<br />For example,
+ caches often use stale responses when they cannot connect to the origin
+ server; when this directive is present, they will return an error rather
+! than a stale response.<br />These directives do not affect private caches; for
+ example, those in browsers."""
+ }
+
+***************
+*** 1337,1346 ****
+ text = {
+ 'en': u"""The presence of the <code>Cache-Control: proxy-revalidate</code>
+ and/or <code>s-maxage</code> directives forbids shared caches (e.g., proxy
+! caches) from using stale responses to satisfy requests.<p>For example,
+ caches often use stale responses when they cannot connect to the origin
+ server; when this directive is present, they will return an error rather
+! than a stale response.<p>These directives do not affect private caches; for
+ example, those in browsers."""
+ }
+
+--- 1337,1346 ----
+ text = {
+ 'en': u"""The presence of the <code>Cache-Control: proxy-revalidate</code>
+ and/or <code>s-maxage</code> directives forbids shared caches (e.g., proxy
+! caches) from using stale responses to satisfy requests.<br />For example,
+ caches often use stale responses when they cannot connect to the origin
+ server; when this directive is present, they will return an error rather
+! than a stale response.<br />These directives do not affect private caches; for
+ example, those in browsers."""
+ }
+
+***************
+*** 1353,1361 ****
+ text = {
+ 'en': u"""Microsoft Internet Explorer implements two <code>Cache-Control</code>
+ extensions, <code>pre-check</code> and <code>post-check</code>, to give
+! more control over how its cache stores responses.<p>
+ %(response)s uses only one of these directives; as a result, Internet
+! Explorer will ignore the directive, since it requires both to be present.<p>
+ See <a href="http://blogs.msdn.com/ieinternals/archive/2009/07/20/Using-post_2D00_check-and-pre_2D00_check-cache-directives.aspx">this blog entry</a> for more information.
+ """
+ }
+--- 1353,1361 ----
+ text = {
+ 'en': u"""Microsoft Internet Explorer implements two <code>Cache-Control</code>
+ extensions, <code>pre-check</code> and <code>post-check</code>, to give
+! more control over how its cache stores responses.<br />
+ %(response)s uses only one of these directives; as a result, Internet
+! Explorer will ignore the directive, since it requires both to be present.<br />
+ See <a href="http://blogs.msdn.com/ieinternals/archive/2009/07/20/Using-post_2D00_check-and-pre_2D00_check-cache-directives.aspx">this blog entry</a> for more information.
+ """
+ }
+***************
+*** 1369,1377 ****
+ text = {
+ 'en': u"""Microsoft Internet Explorer implements two <code>Cache-Control</code>
+ extensions, <code>pre-check</code> and <code>post-check</code>, to give
+! more control over how its cache stores responses.<p>
+ Their values are required to be integers, but here at least one is not. As a
+! result, Internet Explorer will ignore the directive.<p>
+ See <a href="http://blogs.msdn.com/ieinternals/archive/2009/07/20/Using-post_2D00_check-and-pre_2D00_check-cache-directives.aspx">this blog entry</a> for more information.
+ """
+ }
+--- 1369,1377 ----
+ text = {
+ 'en': u"""Microsoft Internet Explorer implements two <code>Cache-Control</code>
+ extensions, <code>pre-check</code> and <code>post-check</code>, to give
+! more control over how its cache stores responses.<br />
+ Their values are required to be integers, but here at least one is not. As a
+! result, Internet Explorer will ignore the directive.<br />
+ See <a href="http://blogs.msdn.com/ieinternals/archive/2009/07/20/Using-post_2D00_check-and-pre_2D00_check-cache-directives.aspx">this blog entry</a> for more information.
+ """
+ }
+***************
+*** 1385,1395 ****
+ text = {
+ 'en': u"""Microsoft Internet Explorer implements two <code>Cache-Control</code>
+ extensions, <code>pre-check</code> and <code>post-check</code>, to give
+! more control over how its cache stores responses.<p>
+ %(response)s gives a value of "0" for both; as a result, Internet
+! Explorer will ignore the directive, since it requires both to be present.<p>
+ In other words, setting these to zero has <strong>no effect</strong> (besides
+! wasting bandwidth), and may trigger bugs in some beta versions of IE.<p>
+ See <a href="http://blogs.msdn.com/ieinternals/archive/2009/07/20/Using-post_2D00_check-and-pre_2D00_check-cache-directives.aspx">this blog entry</a> for more information.
+ """
+ }
+--- 1385,1395 ----
+ text = {
+ 'en': u"""Microsoft Internet Explorer implements two <code>Cache-Control</code>
+ extensions, <code>pre-check</code> and <code>post-check</code>, to give
+! more control over how its cache stores responses.<br />
+ %(response)s gives a value of "0" for both; as a result, Internet
+! Explorer will ignore the directive, since it requires both to be present.<br />
+ In other words, setting these to zero has <strong>no effect</strong> (besides
+! wasting bandwidth), and may trigger bugs in some beta versions of IE.<br />
+ See <a href="http://blogs.msdn.com/ieinternals/archive/2009/07/20/Using-post_2D00_check-and-pre_2D00_check-cache-directives.aspx">this blog entry</a> for more information.
+ """
+ }
+***************
+*** 1403,1412 ****
+ text = {
+ 'en': u"""Microsoft Internet Explorer implements two <code>Cache-Control</code>
+ extensions, <code>pre-check</code> and <code>post-check</code>, to give
+! more control over how its cache stores responses.<p>
+ %(response)s assigns a higher value to <code>post-check</code> than to
+ <code>pre-check</code>; this means that Internet Explorer will treat
+! <code>post-check</code> as if its value is the same as <code>pre-check</code>'s.<p>
+ See <a href="http://blogs.msdn.com/ieinternals/archive/2009/07/20/Using-post_2D00_check-and-pre_2D00_check-cache-directives.aspx">this blog entry</a> for more information.
+ """
+ }
+--- 1403,1412 ----
+ text = {
+ 'en': u"""Microsoft Internet Explorer implements two <code>Cache-Control</code>
+ extensions, <code>pre-check</code> and <code>post-check</code>, to give
+! more control over how its cache stores responses.<br />
+ %(response)s assigns a higher value to <code>post-check</code> than to
+ <code>pre-check</code>; this means that Internet Explorer will treat
+! <code>post-check</code> as if its value is the same as <code>pre-check</code>'s.<br />
+ See <a href="http://blogs.msdn.com/ieinternals/archive/2009/07/20/Using-post_2D00_check-and-pre_2D00_check-cache-directives.aspx">this blog entry</a> for more information.
+ """
+ }
+***************
+*** 1420,1429 ****
+ text = {
+ 'en': u"""Microsoft Internet Explorer implements two <code>Cache-Control</code>
+ extensions, <code>pre-check</code> and <code>post-check</code>, to give
+! more control over how its cache stores responses.<p>
+ %(response)s assigns a value of "0" to <code>post-check</code>, which means
+ that Internet Explorer will reload the content as soon as it enters the
+! browser cache, effectively <strong>doubling the load on the server</strong>.<p>
+ See <a href="http://blogs.msdn.com/ieinternals/archive/2009/07/20/Using-post_2D00_check-and-pre_2D00_check-cache-directives.aspx">this blog entry</a> for more information.
+ """
+ }
+--- 1420,1429 ----
+ text = {
+ 'en': u"""Microsoft Internet Explorer implements two <code>Cache-Control</code>
+ extensions, <code>pre-check</code> and <code>post-check</code>, to give
+! more control over how its cache stores responses.<br />
+ %(response)s assigns a value of "0" to <code>post-check</code>, which means
+ that Internet Explorer will reload the content as soon as it enters the
+! browser cache, effectively <strong>doubling the load on the server</strong>.<br />
+ See <a href="http://blogs.msdn.com/ieinternals/archive/2009/07/20/Using-post_2D00_check-and-pre_2D00_check-cache-directives.aspx">this blog entry</a> for more information.
+ """
+ }
+***************
+*** 1437,1448 ****
+ text = {
+ 'en': u"""Microsoft Internet Explorer implements two <code>Cache-Control</code>
+ extensions, <code>pre-check</code> and <code>post-check</code>, to give
+! more control over how its cache stores responses.<p>
+ Once it has been cached for more than %(post-check)s seconds, a new request
+ will result in the cached response being served while it is refreshed in the
+ background. However, if it has been cached for more than %(pre-check)s seconds,
+! the browser will download a fresh response before showing it to the user.<p>
+! Note that these directives do not have any effect on other clients or caches.<p>
+ See <a href="http://blogs.msdn.com/ieinternals/archive/2009/07/20/Using-post_2D00_check-and-pre_2D00_check-cache-directives.aspx">this blog entry</a> for more information.
+ """
+ }
+--- 1437,1448 ----
+ text = {
+ 'en': u"""Microsoft Internet Explorer implements two <code>Cache-Control</code>
+ extensions, <code>pre-check</code> and <code>post-check</code>, to give
+! more control over how its cache stores responses.<br />
+ Once it has been cached for more than %(post-check)s seconds, a new request
+ will result in the cached response being served while it is refreshed in the
+ background. However, if it has been cached for more than %(pre-check)s seconds,
+! the browser will download a fresh response before showing it to the user.<br />
+! Note that these directives do not have any effect on other clients or caches.<br />
+ See <a href="http://blogs.msdn.com/ieinternals/archive/2009/07/20/Using-post_2D00_check-and-pre_2D00_check-cache-directives.aspx">this blog entry</a> for more information.
+ """
+ }
+***************
+*** 1522,1528 ****
+ 'en': u"""HTTP allows clients to make conditional requests to see if a copy
+ that they hold is still valid. Since this response has a
+ <code>Last-Modified</code> header, clients should be able to use an
+! <code>If-Modified-Since</code> request header for validation.<p>
+ RED has done this and found that the resource sends a
+ <code>304 Not Modified</code> response, indicating that it supports
+ <code>Last-Modified</code> validation."""
+--- 1522,1528 ----
+ 'en': u"""HTTP allows clients to make conditional requests to see if a copy
+ that they hold is still valid. Since this response has a
+ <code>Last-Modified</code> header, clients should be able to use an
+! <code>If-Modified-Since</code> request header for validation.<br />
+ RED has done this and found that the resource sends a
+ <code>304 Not Modified</code> response, indicating that it supports
+ <code>Last-Modified</code> validation."""
+***************
+*** 1538,1544 ****
+ 'en': u"""HTTP allows clients to make conditional requests to see if a copy
+ that they hold is still valid. Since this response has a
+ <code>Last-Modified</code> header, clients should be able to use an
+! <code>If-Modified-Since</code> request header for validation.<p>
+ RED has done this and found that the resource sends a full response even
+ though it hadn't changed, indicating that it doesn't support
+ <code>Last-Modified</code> validation."""
+--- 1538,1544 ----
+ 'en': u"""HTTP allows clients to make conditional requests to see if a copy
+ that they hold is still valid. Since this response has a
+ <code>Last-Modified</code> header, clients should be able to use an
+! <code>If-Modified-Since</code> request header for validation.<br />
+ RED has done this and found that the resource sends a full response even
+ though it hadn't changed, indicating that it doesn't support
+ <code>Last-Modified</code> validation."""
+***************
+*** 1554,1560 ****
+ 'en': u"""HTTP allows clients to make conditional requests to see if a copy
+ that they hold is still valid. Since this response has a
+ <code>Last-Modified</code> header, clients should be able to use an
+! <code>If-Modified-Since</code> request header for validation.<p>
+ RED has done this, but the response changed between the original request and
+ the validating request, so RED can't tell whether or not
+ <code>Last-Modified</code> validation is supported."""
+--- 1554,1560 ----
+ 'en': u"""HTTP allows clients to make conditional requests to see if a copy
+ that they hold is still valid. Since this response has a
+ <code>Last-Modified</code> header, clients should be able to use an
+! <code>If-Modified-Since</code> request header for validation.<br />
+ RED has done this, but the response changed between the original request and
+ the validating request, so RED can't tell whether or not
+ <code>Last-Modified</code> validation is supported."""
+***************
+*** 1570,1576 ****
+ 'en': u"""HTTP allows clients to make conditional requests to see if a copy
+ that they hold is still valid. Since this response has a
+ <code>Last-Modified</code> header, clients should be able to use an
+! <code>If-Modified-Since</code> request header for validation.<p>
+ RED has done this, but the response had a %(enc_ims_status)s status code, so
+ RED can't tell whether or not <code>Last-Modified</code> validation is
+ supported."""
+--- 1570,1576 ----
+ 'en': u"""HTTP allows clients to make conditional requests to see if a copy
+ that they hold is still valid. Since this response has a
+ <code>Last-Modified</code> header, clients should be able to use an
+! <code>If-Modified-Since</code> request header for validation.<br />
+ RED has done this, but the response had a %(enc_ims_status)s status code, so
+ RED can't tell whether or not <code>Last-Modified</code> validation is
+ supported."""
+***************
+*** 1587,1597 ****
+ text = {
+ 'en': u"""HTTP allows clients to ask a server if a request with a body
+ (e.g., uploading a large file) will succeed before sending it, using
+! a mechanism called "Expect/continue".<p>
+ When used, the client sends an <code>Expect: 100-continue</code>, in
+ the request headers, and if the server is willing to process it, it
+ will send a <code> 100 Continue</code> status code to indicte that the
+! request should continue.<p>
+ This response has a <code>100 Continue</code> status code, but RED
+ did not ask for it (with the <code>Expect</code> request header). Sending
+ this status code without it being requested can cause interoperability
+--- 1587,1597 ----
+ text = {
+ 'en': u"""HTTP allows clients to ask a server if a request with a body
+ (e.g., uploading a large file) will succeed before sending it, using
+! a mechanism called "Expect/continue".<br />
+ When used, the client sends an <code>Expect: 100-continue</code>, in
+ the request headers, and if the server is willing to process it, it
+ will send a <code> 100 Continue</code> status code to indicte that the
+! request should continue.<br />
+ This response has a <code>100 Continue</code> status code, but RED
+ did not ask for it (with the <code>Expect</code> request header). Sending
+ this status code without it being requested can cause interoperability
+***************
+*** 1607,1616 ****
+ text = {
+ 'en': u"""HTTP defines the <code>Upgrade</code> header as a means
+ of negotiating a change of protocol; i.e., it allows you to switch
+! the protocol on a given connection from HTTP to something else.<p>
+ However, it must be first requested by the client; this response
+ contains an <code>Upgrade</code> header, even though RED did not
+! ask for it.<p>
+ Trying to upgrade the connection without the client's participation
+ obviously won't work."""
+ }
+--- 1607,1616 ----
+ text = {
+ 'en': u"""HTTP defines the <code>Upgrade</code> header as a means
+ of negotiating a change of protocol; i.e., it allows you to switch
+! the protocol on a given connection from HTTP to something else.<br />
+ However, it must be first requested by the client; this response
+ contains an <code>Upgrade</code> header, even though RED did not
+! ask for it.<br />
+ Trying to upgrade the connection without the client's participation
+ obviously won't work."""
+ }
+***************
+*** 1623,1631 ****
+ }
+ text = {
+ 'en': u"""The <code>201 Created</code> status code indicates that
+! processing the request had the side effect of creating a new resource.<p>
+ However, the request method that RED used (%(method)s) is defined as
+! a "safe" method; that is, it should not have any side effects.<p>
+ Creating resources as a side effect of a safe method can have unintended
+ consequences; for example, search engine spiders and similar automated
+ agents often follow links, and intermediaries sometimes re-try safe
+--- 1623,1631 ----
+ }
+ text = {
+ 'en': u"""The <code>201 Created</code> status code indicates that
+! processing the request had the side effect of creating a new resource.<br />
+ However, the request method that RED used (%(method)s) is defined as
+! a "safe" method; that is, it should not have any side effects.<br />
+ Creating resources as a side effect of a safe method can have unintended
+ consequences; for example, search engine spiders and similar automated
+ agents often follow links, and intermediaries sometimes re-try safe
+***************
+*** 1640,1646 ****
+ }
+ text = {
+ 'en': u"""The <code>201 Created</code> status code indicates that
+! processing the request had the side effect of creating a new resource.<p>
+ HTTP specifies that the URL of the new resource is to be indicated in
+ the <code>Location</code> header, but it isn't present in this response."""
+ }
+--- 1640,1646 ----
+ }
+ text = {
+ 'en': u"""The <code>201 Created</code> status code indicates that
+! processing the request had the side effect of creating a new resource.<br />
+ HTTP specifies that the URL of the new resource is to be indicated in
+ the <code>Location</code> header, but it isn't present in this response."""
+ }
+***************
+*** 1654,1660 ****
+ text = {
+ 'en': u"""HTTP only defines meaning for the <code>Content-Range</code>
+ header in responses with a <code>206 Partial Content</code> or
+! <code>416 Requested Range Not Satisfiable</code> status code.<p>
+ Putting a <code>Content-Range</code> header in this response may
+ confuse caches and clients."""
+ }
+--- 1654,1660 ----
+ text = {
+ 'en': u"""HTTP only defines meaning for the <code>Content-Range</code>
+ header in responses with a <code>206 Partial Content</code> or
+! <code>416 Requested Range Not Satisfiable</code> status code.<br />
+ Putting a <code>Content-Range</code> header in this response may
+ confuse caches and clients."""
+ }
+***************
+*** 1667,1673 ****
+ }
+ text = {
+ 'en': u"""The <code>206 Partial Response</code> status code indicates that
+! the response body is only partial.<p>
+ However, for a response to be partial, it needs to have a
+ <code>Content-Range</code> header to indicate what part of the full
+ response it carries. This response does not have one, and as a result
+--- 1667,1673 ----
+ }
+ text = {
+ 'en': u"""The <code>206 Partial Response</code> status code indicates that
+! the response body is only partial.<br />
+ However, for a response to be partial, it needs to have a
+ <code>Content-Range</code> header to indicate what part of the full
+ response it carries. This response does not have one, and as a result
+***************
+*** 1682,1689 ****
+ }
+ text = {
+ 'en': u"""The <code>206 Partial Response</code> status code indicates that
+! the response body is only partial.<p>
+! However, the client needs to ask for it with the <code>Range</code> header.<p>
+ RED did not request a partial response; sending one without the client
+ requesting it leads to interoperability problems."""
+ }
+--- 1682,1689 ----
+ }
+ text = {
+ 'en': u"""The <code>206 Partial Response</code> status code indicates that
+! the response body is only partial.<br />
+! However, the client needs to ask for it with the <code>Range</code> header.<br />
+ RED did not request a partial response; sending one without the client
+ requesting it leads to interoperability problems."""
+ }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/red_speak.py Mon Jul 26 14:12:44 2010 -0400
@@ -0,0 +1,1903 @@
+"""
+A collection of messages that the RED can emit.
+
+Each should be in the form:
+
+MESSAGE_ID = (classification, level,
+ {'lang': u'message'}
+ {'lang': u'long message'}
+)
+
+where 'lang' is a language tag, 'message' is a string (NO HTML) that
+contains the message in that language, and 'long message' is a longer
+explanation that may contain HTML.
+
+Both message forms may contain %(var)s style variable interpolation.
+
+PLEASE NOTE: the message field is automatically HTML escaped in webui.py, so
+it can contain arbitrary text (as long as it's unicode). However, the long
+message IS NOT ESCAPED, and therefore all variables to be interpolated into
+it (but not the short version) need to be escaped.
+"""
+
+__author__ = "Mark Nottingham <mnot@mnot.net>"
+__copyright__ = """\
+Copyright (c) 2009-2010 Mark Nottingham
+
+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.
+"""
+
+# message classifications
+class _Classifications:
+ GENERAL = u"General"
+ CONNEG = u"Content Negotiation"
+ CACHING = u"Caching"
+ VALIDATION = u"Validation"
+ CONNECTION = u"Connection"
+ RANGE = u"Partial Content"
+c = _Classifications()
+
+# message levels
+class _Levels:
+ GOOD = u'good'
+ WARN = u'warning'
+ BAD = u'bad'
+ INFO = u'info'
+l = _Levels()
+
+class Message:
+ """
+ A message about an HTTP resource, representation, or other component
+ related to the URI under test.
+ """
+ category = None
+ level = None
+ summary = {}
+ text = {}
+ def __init__(self, subject, subrequest=None, vrs=None):
+ self.subject = subject
+ self.subrequest = subrequest
+ self.vars = vrs or {}
+
+ def __eq__(self, other):
+ if self.__class__ == other.__class__ and \
+ self.vars == other.vars and\
+ self.subject == other.subject:
+ return True
+ else:
+ return False
+
+
+response = {
+ 'this': {'en': 'This response'},
+ 'conneg': {'en': 'The uncompressed response'},
+ 'LM validation': {'en': 'The 304 response'},
+ 'ETag validation': {'en': 'The 304 response'},
+ 'range': {'en': 'The partial response'},
+}
+
+class URI_TOO_LONG(Message):
+ category = c.GENERAL
+ level = l.WARN
+ summary = {
+ 'en': u"The URI is very long (%(uri_len)s characters)."
+ }
+ text = {
+ 'en': u"Long URIs aren't supported by some implementations, including proxies. \
+ A reasonable upper size limit is 8192 characters."
+ }
+
+class URI_BAD_SYNTAX(Message):
+ category = c.GENERAL
+ level = l.BAD
+ summary = {
+ 'en': u"The URI's syntax isn't valid."
+ }
+ text = {
+ 'en': u"""This isn't a valid URI. Look for illegal characters \
+ and other problems; see <a href='http://www.ietf.org/rfc/rfc3986.txt'>RFC3986</a>
+ for more information."""
+ }
+
+class FIELD_NAME_BAD_SYNTAX(Message):
+ category = c.GENERAL
+ level = l.BAD
+ summary = {
+ 'en': u'"%(field_name)s" is not a valid header field-name.'
+ }
+ text = {
+ 'en': u"Header names are limited to the TOKEN production in HTTP; i.e., \
+ they can't contain parenthesis, angle brackes (<>), ampersands (@), \
+ commas, semicolons, colons, backslashes (\\), forward slashes (/), quotes, \
+ square brackets ([]), question marks, equals signs (=), curly brackets ({}) \
+ spaces or tabs."
+ }
+
+class HEADER_BLOCK_TOO_LARGE(Message):
+ category = c.GENERAL
+ level = l.BAD
+ summary = {
+ 'en': u"%(response)s's headers are very large (%(header_block_size)s)."
+ }
+ text = {
+ 'en': u"""Some implementations have limits on the total size of headers
+ that they'll accept. For example, Squid's default configuration limits
+ header blocks to 20k."""
+ }
+
+class HEADER_TOO_LARGE(Message):
+ category = c.GENERAL
+ level = l.WARN
+ summary = {
+ 'en': u"The %(header_name)s header is very large (%(header_size)s)."
+ }
+ text = {
+ 'en': u"""Some implementations limit the size of any single header line."""
+ }
+
+class HEADER_NAME_ENCODING(Message):
+ category = c.GENERAL
+ level = l.BAD
+ summary = {
+ 'en': u"The %(header_name)s header's name contains non-ASCII characters."
+ }
+ text = {
+ 'en': u"""HTTP header field-names can only contain ASCII characters. RED
+ has detected (and possibly removed) non-ASCII characters in this header
+ name."""
+ }
+
+class HEADER_VALUE_ENCODING(Message):
+ category = c.GENERAL
+ level = l.WARN
+ summary = {
+ 'en': u"The %(header_name)s header's value contains non-ASCII characters."
+ }
+ text = {
+ 'en': u"""HTTP headers use the ISO-8859-1 character set, but in most
+ cases are pure ASCII (a subset of this encoding).<br />
+ This header has non-ASCII characters, which RED has interpreted as
+ being encoded in ISO-8859-1. If another encoding is used (e.g., UTF-8),
+ the results may be unpredictable."""
+ }
+
+class HEADER_DEPRECATED(Message):
+ category = c.GENERAL
+ level = l.WARN
+ summary = {
+ 'en': u"The %(header_name)s header is deprecated."
+ }
+ text = {
+ 'en': u"""This header field is no longer recommended for use, because of
+ interoperability problems and/or lack of use. See
+ <a href="%(ref)s">its documentation</a> for more information."""
+ }
+
+class SINGLE_HEADER_REPEAT(Message):
+ category = c.GENERAL
+ level = l.BAD
+ summary = {
+ 'en': u"Only one %(field_name)s header is allowed in a response."
+ }
+ text = {
+ 'en': u"""This header is designed to only occur once in a message. When it
+ occurs more than once, a receiver needs to choose the one to use, which
+ can lead to interoperability problems, since different implementations may
+ make different choices.<br />
+ For the purposes of its tests, RED uses the last instance of the header that
+ is present; other implementations may behave differently."""
+ }
+
+class BODY_NOT_ALLOWED(Message):
+ category = c.CONNECTION
+ level = l.BAD
+ summary = {
+ 'en': u"%(response)s is not allowed to have a body."
+ }
+ text = {
+ 'en': u"""HTTP defines a few special situations where a response does not
+ allow a body. This includes 101, 204 and 304 responses, as well as responses
+ to the <code>HEAD</code> method.<br />
+ %(response)s had a body, despite it being disallowed. Clients receiving
+ it may treat the body as the next response in the connection, leading to
+ interoperability and security issues."""
+ }
+
+class BAD_SYNTAX(Message):
+ category = c.GENERAL
+ level = l.BAD
+ summary = {
+ 'en': u"The %(field_name)s header's syntax isn't valid."
+ }
+ text = {
+ 'en': u"""The value for this header doesn't conform to its specified syntax; see
+ <a href="%(ref_uri)s">its definition</a> for more information.
+ """
+ }
+
+# Specific headers
+
+class BAD_CC_SYNTAX(Message):
+ category = c.CACHING
+ level = l.BAD
+ summary = {
+ 'en': u"The %(bad_cc_attr)s Cache-Control directive's syntax is incorrect."
+ }
+ text = {
+ 'en': u"This value must be an integer."
+ }
+
+class AGE_NOT_INT(Message):
+ category = c.CACHING
+ level = l.BAD
+ summary = {
+ 'en': u"The Age header's value should be an integer."
+ }
+ text = {
+ 'en': u"""The <code>Age</code> header indicates the age of the response; i.e.,
+ how long it has been cached since it was generated. The value given was not
+ an integer, so it is not a valid age."""
+ }
+
+class AGE_NEGATIVE(Message):
+ category = c.CACHING
+ level = l.BAD
+ summary = {
+ 'en': u"The Age headers' value must be a positive integer."
+ }
+ text = {
+ 'en': u"""The <code>Age</code> header indicates the age of the response; i.e.,
+ how long it has been cached since it was generated. The value given was
+ negative, so it is not a valid age."""
+ }
+
+class BAD_CHUNK(Message):
+ category = c.CONNECTION
+ level = l.BAD
+ summary = {
+ 'en': u"%(response)s had chunked encoding errors."
+ }
+ text = {
+ 'en': u"""The response indicates it uses HTTP chunked encoding, but there
+ was a problem decoding the chunking.<br />
+ A valid chunk looks something like this:<br />
+ <code>[chunk-size in hex]\\r\\n[chunk-data]\\r\\n</code><br />
+ However, the chunk sent started like this:<br />
+ <code>%(chunk_sample)s</code><br />
+ This is a serious problem, because HTTP uses chunking to delimit one
+ response from the next one; incorrect chunking can lead to interoperability
+ and security problems.<br />
+ This issue is often caused by sending an integer chunk size instead of one
+ in hex, or by sending <code>Transfer-Encoding: chunked</code> without
+ actually chunking the response body."""
+ }
+
+class BAD_GZIP(Message):
+ category = c.CONNEG
+ level = l.BAD
+ summary = {
+ 'en': u"%(response)s was compressed using GZip, but the header wasn't valid."
+ }
+ text = {
+ 'en': u"""GZip-compressed responses have a header that contains metadata.
+ %(response)s's header wasn't valid; the error encountered was
+ "<code>%(gzip_error)s</code>"."""
+ }
+
+class BAD_ZLIB(Message):
+ category = c.CONNEG
+ level = l.BAD
+ summary = {
+ 'en': u"%(response)s was compressed using GZip, but the data was corrupt."
+ }
+ text = {
+ 'en': u"""GZip-compressed responses use zlib compression to reduce the number
+ of bytes transferred on the wire. However, this response could not be decompressed;
+ the error encountered was "<code>%(zlib_error)s</code>".<br />
+ %(ok_zlib_len)s bytes were decompressed successfully before this; the erroneous
+ chunk starts with "<code>%(chunk_sample)s</code>"."""
+ }
+
+class ENCODING_UNWANTED(Message):
+ category = c.CONNEG
+ level = l.WARN
+ summary = {
+ 'en': u"The %(encoding)s content-coding wasn't asked for."
+ }
+ text = {
+ 'en': u"""%(response)s's <code>Content-Encoding</code> header indicates it
+ has the %(encoding)s content-coding applied, but RED didn't ask for it
+ to be.<br />
+ Normally, clients ask for the encodings they want in the
+ <code>Accept-Encoding</code> request header. Using encodings that the
+ client doesn't explicitly request can lead to interoperability problems."""
+ }
+
+class TRANSFER_CODING_IDENTITY(Message):
+ category = c.CONNECTION
+ level = l.INFO
+ summary = {
+ 'en': u"The identity transfer-coding isn't necessary."
+ }
+ text = {
+ 'en': u"""HTTP defines <em>transfer-codings</em> as a hop-by-hop encoding
+ of the message body. The <code>identity</code> tranfer-coding was defined
+ as the absence of encoding; it doesn't do anything, so it's necessary.<br />
+ You can remove this token to save a few bytes."""
+ }
+
+class TRANSFER_CODING_UNWANTED(Message):
+ category = c.CONNECTION
+ level = l.BAD
+ summary = {
+ 'en': u"The %(encoding)s transfer-coding wasn't asked for."
+ }
+ text = {
+ 'en': u"""%(response)s's <code>Transfer-Encoding</code> header indicates it
+ has the %(encoding)s transfer-coding applied, but RED didn't ask for it
+ to be.<br />
+ Normally, clients ask for the encodings they want in the
+ <code>TE</code> request header. Using codings that the
+ client doesn't explicitly request can lead to interoperability problems."""
+ }
+
+class BAD_DATE_SYNTAX(Message):
+ category = c.GENERAL
+ level = l.BAD
+ summary = {
+ 'en': u"The %(field_name)s header's value isn't a valid date."
+ }
+ text = {
+ 'en': u"""HTTP dates have very specific syntax, and sending an invalid date can
+ cause a number of problems, especially around caching. Common problems include
+ sending "1 May" instead of "01 May" (the month is a fixed-width field), and
+ sending a date in a timezone other than GMT. See
+ <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3">the
+ HTTP specification</a> for more information."""
+ }
+
+class LM_FUTURE(Message):
+ category = c.CACHING
+ level = l.BAD
+ summary = {
+ 'en': u"The Last-Modified time is in the future."
+ }
+ text = {
+ 'en': u"""The <code>Last-Modified</code> header indicates the last point in
+ time that the resource has changed. %(response)s's
+ <code>Last-Modified</code> time is in the future, which doesn't have any
+ defined meaning in HTTP."""
+ }
+
+class LM_PRESENT(Message):
+ category = c.CACHING
+ level = l.INFO
+ summary = {
+ 'en': u"The resource last changed %(last_modified_string)s."
+ }
+ text = {
+ 'en': u"""The <code>Last-Modified</code> header indicates the last point in
+ time that the resource has changed. It is used in HTTP for validating cached
+ responses, and for calculating heuristic freshness in caches.<br />
+ This resource last changed %(last_modified_string)s."""
+ }
+
+class MIME_VERSION(Message):
+ category = c.GENERAL
+ level = l.INFO
+ summary = {
+ 'en': u"The MIME-Version header generally isn't necessary in HTTP."
+ }
+ text = {
+ 'en': u"""<code>MIME_Version</code> is a MIME header, not a HTTP header; it's
+ only used when HTTP messages are moved over MIME-based protocols
+ (e.g., SMTP), which is uncommon."""
+ }
+
+class PRAGMA_NO_CACHE(Message):
+ category = c.CACHING
+ level = l.WARN
+ summary = {
+ 'en': u"Pragma: no-cache is a request directive, not a response directive."
+ }
+ text = {
+ 'en': u"""<code>Pragma</code> is a very old request header that is sometimes
+ used as a response header, even though this is not specified behaviour.
+ <code>Cache-Control: no-cache</code> is more appropriate."""
+ }
+
+class PRAGMA_OTHER(Message):
+ category = c.GENERAL
+ level = l.WARN
+ summary = {
+ 'en': u"""The Pragma header is being used in an undefined way."""
+ }
+ text = {
+ 'en': u"""HTTP only defines <code>Pragma: no-cache</code>; other uses of
+ this header are deprecated."""
+ }
+
+class VIA_PRESENT(Message):
+ category = c.GENERAL
+ level = l.INFO
+ summary = {
+ 'en': u"One or more intermediaries are present."
+ }
+ text = {
+ 'en': u"""The <code>Via</code> header indicates that one or more
+ intermediaries are present between RED and the origin server for the
+ resource.<br />
+ This may indicate that a proxy is in between RED and the server, or that
+ the server uses a "reverse proxy" or CDN in front of it.<br />
+ %(via_list)s
+ <br />
+ There field has three space-separated components; first, the HTTP version
+ of the message that the intermediary received, then the identity of the
+ intermediary (usually but not always its hostname), and then optionally a
+ product identifier or comment (usually used to identify the software being
+ used)."""
+ }
+
+class LOCATION_UNDEFINED(Message):
+ category = c.GENERAL
+ level = l.WARN
+ summary = {
+ 'en': u"%(response)s doesn't define any meaning for the Location header."
+ }
+ text = {
+ 'en': u"""The <code>Location</code> header is used for specific purposes
+ in HTTP; mostly to indicate the URI of another resource (e.g., in
+ redirection, or when a new resource is created).<br />
+ In other status codes (such as this one) it doesn't have a defined meaning,
+ so any use of it won't be interoperable.<br />
+ Sometimes <code>Location</code> is confused with <code>Content-Location</code>,
+ which indicates a URI for the payload of the message that it appears in."""
+ }
+
+class LOCATION_NOT_ABSOLUTE(Message):
+ category = c.GENERAL
+ level = l.BAD
+ summary = {
+ 'en': u"The Location header contains a relative URI."
+ }
+ text = {
+ 'en': u"""<code>Location</code> is specified to contain an absolute,
+ not relative, URI.<p>
+ Most (but not all) clients will work around this, but since this field isn't
+ defined to take a relative URI, they may behave differently (for example,
+ if the body contains a base URI).</p>
+ The correct value for this field is (probably):<br />
+ <code>%(full_uri)s</code>"""
+ }
+
+class CONTENT_TYPE_OPTIONS(Message):
+ category = c.GENERAL
+ level = l.INFO
+ summary = {
+ 'en': u"%(response)s instructs Internet Explorer not to 'sniff' its media type."
+ }
+ text = {
+ 'en': u"""Many Web browers "sniff" the media type of responses to figure out
+ whether they're HTML, RSS or another format, no matter what the
+ <code>Content-Type</code> header says.<br />
+ This header instructs Microsoft's Internet Explorer not to do this, but to
+ always respect the Content-Type header. It probably won't have any effect in
+ other clients.<br />
+ See <a href="http://blogs.msdn.com/ie/archive/2008/09/02/ie8-security-part-vi-beta-2-update.aspx">this blog entry</a>
+ for more information about this header."""
+ }
+
+class CONTENT_TYPE_OPTIONS_UNKNOWN(Message):
+ category = c.GENERAL
+ level = l.WARN
+ summary = {
+ 'en': u"%(response)s contains an X-Content-Type-Options header with an unknown value."
+ }
+ text = {
+ 'en': u"""Only one value is currently defined for this header, <code>nosniff</code>.
+ Using other values here won't necessarily cause problems, but they probably
+ won't have any effect either.<br />
+ See <a href="http://blogs.msdn.com/ie/archive/2008/09/02/ie8-security-part-vi-beta-2-update.aspx">this blog entry</a> for more information about this header."""
+ }
+
+class DOWNLOAD_OPTIONS(Message):
+ category = c.GENERAL
+ level = l.INFO
+ summary = {
+ 'en': u"%(response)s can't be directly opened directly by Internet Explorer when downloaded."
+ }
+ text = {
+ 'en': u"""When the <code>X-Download-Options</code> header is present
+ with the value <code>noopen</code>, Internet Explorer users are prevented
+ from directly opening a file download; instead, they must first save the
+ file locally. When the locally saved file is later opened, it no longer
+ executes in the security context of your site, helping to prevent script
+ injection.<br />
+ This header probably won't have any effect in other clients.<br />
+ See <a href="http://blogs.msdn.com/ie/archive/2008/07/02/ie8-security-part-v-comprehensive-protection.aspx">this blog article</a> for more details."""
+ }
+
+class DOWNLOAD_OPTIONS_UNKNOWN(Message):
+ category = c.GENERAL
+ level = l.WARN
+ summary = {
+ 'en': u"%(response)s contains an X-Download-Options header with an unknown value."
+ }
+ text = {
+ 'en': u"""Only one value is currently defined for this header, <code>noopen</code>.
+ Using other values here won't necessarily cause problems, but they probably
+ won't have any effect either.<br />
+ See <a href="http://blogs.msdn.com/ie/archive/2008/07/02/ie8-security-part-v-comprehensive-protection.aspx">this blog article</a> for more details."""
+ }
+
+class FRAME_OPTIONS_DENY(Message):
+ category = c.GENERAL
+ level = l.INFO
+ summary = {
+ 'en': u"%(response)s prevents some browsers from rendering it if it will be contained within a frame."
+ }
+ text = {
+ 'en': u"""The <code>X-Frame-Options</code> response header controls how
+ IE8 handles HTML frames; the <code>DENY</code> value prevents this content
+ from being rendered within a frame, which defends against certain types of
+ attacks.<br />
+ Currently this is supported by IE8 and Safari 4.<br />
+ See <a href="http://blogs.msdn.com/ie/archive/2009/01/27/ie8-security-part-vii-clickjacking-defenses.aspx">this blog entry</a> for more information.
+ """
+ }
+
+class FRAME_OPTIONS_SAMEORIGIN(Message):
+ category = c.GENERAL
+ level = l.INFO
+ summary = {
+ 'en': u"%(response)s prevents some browsers from rendering it if it will be contained within a frame on another site."
+ }
+ text = {
+ 'en': u"""The <code>X-Frame-Options</code> response header controls how
+ IE8 handles HTML frames; the <code>DENY</code> value prevents this content
+ from being rendered within a frame on another site, which defends against certain types of
+ attacks.<br />
+ Currently this is supported by IE8 and Safari 4.<br />
+ See <a href="http://blogs.msdn.com/ie/archive/2009/01/27/ie8-security-part-vii-clickjacking-defenses.aspx">this blog entry</a> for more information.
+ """
+ }
+
+class FRAME_OPTIONS_UNKNOWN(Message):
+ category = c.GENERAL
+ level = l.WARN
+ summary = {
+ 'en': u"%(response)s contains an X-Frame-Options header with an unknown value."
+ }
+ text = {
+ 'en': u"""Only two values are currently defined for this header, <code>DENY</code>
+ and <code>SAMEORIGIN</code>.
+ Using other values here won't necessarily cause problems, but they probably
+ won't have any effect either.<br />
+ See <a href="http://blogs.msdn.com/ie/archive/2009/01/27/ie8-security-part-vii-clickjacking-defenses.aspx">this blog entry</a> for more information.
+ """
+ }
+
+class SMART_TAG_NO_WORK(Message):
+ category = c.GENERAL
+ level = l.WARN
+ summary = {
+ 'en': u"The %(field_name)s header doesn't have any effect on smart tags."
+ }
+ text = {
+ 'en': u"""This header doesn't have any effect on Microsoft Smart Tags,
+ except in certain beta versions of IE6. To turn them off, you'll need
+ to make changes in the HTML content it"""
+ }
+
+class UA_COMPATIBLE(Message):
+ category = c.GENERAL
+ level = l.INFO
+ summary = {
+ 'en': u"%(response)s explicitly sets a rendering mode for Internet Explorer 8."
+ }
+ text = {
+ 'en': u"""Internet Explorer 8 allows responses to explicitly set the rendering
+ mode used for a given page (known a the "compatibility mode").<br />
+ See <a href="http://msdn.microsoft.com/en-us/library/cc288325(VS.85).aspx">Microsoft's documentation</a> for more information."""
+ }
+
+class UA_COMPATIBLE_REPEAT(Message):
+ category = c.GENERAL
+ level = l.BAD
+ summary = {
+ 'en': u"%(response)s has multiple X-UA-Compatible directives targetted at the same UA."
+ }
+ text = {
+ 'en': u"""Internet Explorer 8 allows responses to explicitly set the rendering mode
+ used for a page.<br />
+ This response has more than one such directive targetted at one browser;
+ this may cause unpredictable results.<br />
+ See <a href="http://msdn.microsoft.com/en-us/library/cc288325(VS.85).aspx">this blog entry</a> for more information."""
+ }
+
+class XSS_PROTECTION(Message):
+ category = c.GENERAL
+ level = l.INFO
+ summary = {
+ 'en': u"%(response)s disables XSS filtering in IE8."
+ }
+ text = {
+ 'en': u"""Internet Explorer 8 has built-in Cross-Site Scripting (XSS)
+ attack protection; it tries to automatically filter requests that
+ fit a particular profile.<br />
+ %(response)s has explicitly disabled this protection. In some scenarios,
+ this is useful to do, if the protection interferes with the application.<br />
+ This header probably won't have any effect in other clients.<br />
+ See <a href="http://blogs.msdn.com/ie/archive/2008/07/02/ie8-security-part-iv-the-xss-filter.aspx">this blog entry</a> for more information.
+ """
+ }
+
+### Ranges
+
+class UNKNOWN_RANGE(Message):
+ category = c.RANGE
+ level = l.WARN
+ summary = {
+ 'en': u"%(response)s advertises support for non-standard range-units."
+ }
+ text = {
+ 'en': u"""The <code>Accept-Ranges</code> response header tells clients
+ what <code>range-unit</code>s a resource is willing to process in future
+ requests. HTTP only defines two: <code>bytes</code> and <code>none</code>.
+ <br />
+ Clients who don't know about the non-standard range-unit will not be
+ able to use it."""
+ }
+
+class RANGE_CORRECT(Message):
+ category = c.RANGE
+ level = l.GOOD
+ summary = {
+ 'en': u"A ranged request returned the correct partial content."
+ }
+ text = {
+ 'en': u"""This resource advertises support for ranged requests with
+ <code>Accept-Ranges</code>; that is, it allows clients to specify that only
+ part of it should be sent. RED has tested this by requesting part
+ of this response, which was returned correctly."""
+ }
+
+class RANGE_INCORRECT(Message):
+ category = c.RANGE
+ level = l.BAD
+ summary = {
+ 'en': u'A ranged request returned partial content, but it was incorrect.'
+ }
+ text = {
+ 'en': u"""This resource advertises support for ranged requests with
+ <code>Accept-Ranges</code>; that is, it allows clients to specify that only
+ part of the response should be sent. RED has tested this by requesting part
+ of this response, but the partial response doesn't correspond with the full
+ response retrieved at the same time. This could indicate that the range
+ implementation isn't working properly.
+ <p>RED sent<br />
+ <code>Range: %(range)s</code></p>
+ <p>RED expected %(range_expected_bytes)s bytes:<br />
+ <code>%(range_expected)s</code></p>
+ <p>RED received %(range_received_bytes)s bytes:<br />
+ <code>%(range_received)s</code></p>"""
+ }
+
+class RANGE_FULL(Message):
+ category = c.RANGE
+ level = l.WARN
+ summary = {
+ 'en': u"A ranged request returned the full rather than partial content."
+ }
+ text = {
+ 'en': u"""This resource advertises support for ranged requests with
+ <code>Accept-Ranges</code>; that is, it allows clients to specify that only
+ part of the response should be sent. RED has tested this by requesting part
+ of this response, but the entire response was returned. In other words,
+ although the resource advertises support for partial content, it
+ doesn't appear to actually do so."""
+ }
+
+class RANGE_STATUS(Message):
+ category = c.RANGE
+ level = l.INFO
+ summary = {
+ 'en': u"A ranged request returned a %(range_status)s status."
+ }
+ text = {
+ 'en': u"""This resource advertises support for ranged requests; that is, it allows
+ clients to specify that only part of the response should be sent. RED has tested
+ this by requesting part of this response, but a %(enc_range_status)s
+ response code was returned, which RED was not expecting."""
+ }
+
+class RANGE_NEG_MISMATCH(Message):
+ category = c.RANGE
+ level = l.BAD
+ summary = {
+ 'en': u"Partial responses don't have the same support for compression that full ones do."
+ }
+ text = {
+ 'en': u"""This resource supports ranged requests and also supports negotiation for
+ gzip compression, but doesn't support compression for both full and partial responses.<br />
+ This can cause problems for clients when they compare the partial and full responses,
+ since the partial response is expressed as a byte range, and compression changes the
+ bytes."""
+ }
+
+### Body
+
+class CL_CORRECT(Message):
+ category = c.GENERAL
+ level = l.GOOD
+ summary = {
+ 'en': u'The Content-Length header is correct.'
+ }
+ text = {
+ 'en': u"""<code>Content-Length</code> is used by HTTP to delimit messages;
+ that is, to mark the end of one message and the beginning of the next. RED
+ has checked the length of the body and found the <code>Content-Length</code>
+ to be correct."""
+ }
+
+class CL_INCORRECT(Message):
+ category = c.GENERAL
+ level = l.BAD
+ summary = {
+ 'en': u"%(response)s's Content-Length header is incorrect."
+ }
+ text = {
+ 'en': u"""<code>Content-Length</code> is used by HTTP to delimit messages;
+ that is, to mark the end of one message and the beginning of the next. RED
+ has checked the length of the body and found the <code>Content-Length</code>
+ is not correct. This can cause problems not only with connection handling,
+ but also caching, since an incomplete response is considered uncacheable.<br />
+ The actual body size sent was %(body_length)s bytes."""
+ }
+
+class CMD5_CORRECT(Message):
+ category = c.GENERAL
+ level = l.GOOD
+ summary = {
+ 'en': u'The Content-MD5 header is correct.'
+ }
+ text = {
+ 'en': u"""<code>Content-MD5</code> is a hash of the body, and can be used to
+ ensure integrity of the response. RED has checked its value and found it to
+ be correct."""
+ }
+
+class CMD5_INCORRECT(Message):
+ category = c.GENERAL
+ level = l.BAD
+ summary = {
+ 'en': u'The Content-MD5 header is incorrect.'
+ }
+ text = {
+ 'en': u"""<code>Content-MD5</code> is a hash of the body, and can be used to
+ ensure integrity of the response. RED has checked its value and found it to
+ be incorrect; i.e., the given <code>Content-MD5</code> does not match what
+ RED thinks it should be (%(calc_md5)s)."""
+ }
+
+### Conneg
+
+class CONNEG_GZIP_GOOD(Message):
+ category = c.CONNEG
+ level = l.GOOD
+ summary = {
+ 'en': u'Content negotiation for gzip compression is supported, saving %(savings)s%%.'
+ }
+ text = {
+ 'en': u"""HTTP supports compression of responses by negotiating for
+ <code>Content-Encoding</code>. When RED asked for a compressed response,
+ the resource provided one, saving %(savings)s%% of its original size
+ (from %(orig_size)s to %(gzip_size)s bytes).<br />
+ The compressed response's headers are displayed."""
+ }
+
+class CONNEG_GZIP_BAD(Message):
+ category = c.CONNEG
+ level = l.WARN
+ summary = {
+ 'en': u'Content negotiation for gzip compression makes the response %(savings)s%% larger.'
+ }
+ text = {
+ 'en': u"""HTTP supports compression of responses by negotiating for
+ <code>Content-Encoding</code>. When RED asked for a compressed response,
+ the resource provided one, but it was %(savings)s%% <em>larger</em> than the original
+ response; from %(orig_size)s to %(gzip_size)s bytes.<br />
+ Often, this happens when the uncompressed response is very small, or can't be compressed
+ more; since gzip compression has some overhead, it can make the response larger. Turning compression
+ <strong>off</strong> for this resource may slightly improve response times and save
+ bandwidth.<br />
+ The compressed response's headers are displayed."""
+ }
+
+class CONNEG_NO_GZIP(Message):
+ category = c.CONNEG
+ level = l.INFO
+ summary = {
+ 'en': u'Content negotiation for gzip compression isn\'t supported.'
+ }
+ text = {
+ 'en': u"""HTTP supports compression of responses by negotiating for
+ <code>Content-Encoding</code>. When RED asked for a compressed response,
+ the resource did not provide one."""
+ }
+
+class CONNEG_NO_VARY(Message):
+ category = c.CONNEG
+ level = l.BAD
+ summary = {
+ 'en': u"%(response)s is negotiated, but doesn't have an appropriate Vary header."
+ }
+ text = {
+ 'en': u"""All content negotiated responses need to have a
+ <code>Vary</code> header that reflects the header(s) used to select the
+ response.<br />
+ %(response)s was negotiated for <code>gzip</code> content encoding, so
+ the <code>Vary</code> header needs to contain <code>Accept-Encoding</code>,
+ the request header used."""
+ }
+
+class CONNEG_GZIP_WITHOUT_ASKING(Message):
+ category = c.CONNEG
+ level = l.BAD
+ summary = {
+ 'en': u"A gzip-compressed response was sent when it wasn't asked for."
+ }
+ text = {
+ 'en': u"""HTTP supports compression of responses by negotiating for
+ <code>Content-Encoding</code>. Even though RED didn't ask for a compressed
+ response, the resource provided one anyway. Doing so can break clients that
+ aren't expecting a compressed response."""
+ }
+
+class VARY_INCONSISTENT(Message):
+ category = c.CONNEG
+ level = l.BAD
+ summary = {
+ 'en': u"The resource doesn't send Vary consistently."
+ }
+ text = {
+ 'en': u"""HTTP requires that the <code>Vary</code> response header be sent
+ consistently for all responses if they change based upon different aspects
+ of the request.<br />
+ This resource has both compressed and uncompressed variants
+ available, negotiated by the <code>Accept-Encoding</code> request header,
+ but it sends different Vary headers for each;<br />
+ <ul>
+ <li>"<code>%(conneg_vary)s</code>" when the response is compressed, and</li>
+ <li>"<code>%(no_conneg_vary)s</code>" when it is not.</li>
+ </ul>
+ <br />This can cause problems for downstream caches, because they
+ cannot consistently determine what the cache key for a given URI is."""
+ }
+
+class ETAG_DOESNT_CHANGE(Message):
+ category = c.CONNEG
+ level = l.BAD
+ summary = {
+ 'en': u"The ETag doesn't change between representations."
+ }
+ text = {
+ 'en': u"""HTTP requires that the <code>ETag</code>s for two different
+ responses associated with the same URI be different as well, to help caches
+ and other receivers disambiguate them.<br />
+ This resource, however, sent the same
+ ETag for both its compressed and uncompressed versions (negotiated by
+ <code>Accept-Encoding</code>. This can cause interoperability problems,
+ especially with caches."""
+ }
+
+### Clock
+
+class DATE_CORRECT(Message):
+ category = c.GENERAL
+ level = l.GOOD
+ summary = {
+ 'en': u"The server's clock is correct."
+ }
+ text = {
+ 'en': u"""HTTP's caching model assumes reasonable synchronisation between
+ clocks on the server and client; using RED's local clock, the server's clock
+ appears to be well-synchronised."""
+ }
+
+class DATE_INCORRECT(Message):
+ category = c.GENERAL
+ level = l.BAD
+ summary = {
+ 'en': u"The server's clock is %(clock_skew_string)s."
+ }
+ text = {
+ 'en': u"""Using RED's local clock, the server's clock does not appear to
+ be well-synchronised.<br />
+ HTTP's caching model assumes reasonable synchronisation between
+ clocks on the server and client; clock skew can cause responses that
+ should be cacheable to be considered uncacheable (especially if their freshness
+ lifetime is short).<p>
+ Ask your server administrator to synchronise the clock, e.g., using
+ <a href="http://en.wikipedia.org/wiki/Network_Time_Protocol"
+ title="Network Time Protocol">NTP</a>.</p>
+ Apparent clock skew can also be caused by caching the response without adjusting
+ the <code>Age</code> header; e.g., in a reverse proxy or
+ <abbr title="Content Delivery Network">CDN</abbr>. See
+ <a href="http://www2.research.att.com/~edith/Papers/HTML/usits01/index.html">
+ this paper</a> for more information.
+ """
+ }
+
+class AGE_PENALTY(Message):
+ category = c.GENERAL
+ level = l.WARN
+ summary = {
+ 'en': u"It appears that the Date header has been changed by an intermediary."
+ }
+ text = {
+ 'en': u"""It appears that this response has been cached by a reverse proxy or
+ <abbr title="Content Delivery Network">CDN</abbr>, because the <code>Age</code>
+ header is present, but the <code>Date</code> header is more recent than it indicates.<br />
+ Generally, reverse proxies should either omit the <code>Age</code> header (if they
+ have another means of determining how fresh the response is), or
+ leave the <code>Date</code> header alone (i.e., act as a normal HTTP cache).<br />
+ See <a href="http://www2.research.att.com/~edith/Papers/HTML/usits01/index.html">
+ this paper</a> for more information."""
+ }
+
+class DATE_CLOCKLESS(Message):
+ category = c.GENERAL
+ level = l.WARN
+ summary = {
+ 'en': u"%(response)s doesn't have a Date header."
+ }
+ text = {
+ 'en': u"""Although HTTP allowes a server not to send a <code>Date</code> header if it
+ doesn't have a local clock, this can make calculation of the response's age
+ inexact."""
+ }
+
+class DATE_CLOCKLESS_BAD_HDR(Message):
+ category = c.CACHING
+ level = l.BAD
+ summary = {
+ 'en': u"Responses without a Date aren't allowed to have Expires or Last-Modified values."
+ }
+ text = {
+ 'en': u"""Because both the <code>Expires</code> and <code>Last-Modified</code>
+ headers are date-based, it's necessary to know when the message was generated
+ for them to be useful; otherwise, clock drift, transit times between nodes as
+ well as caching could skew their application."""
+ }
+
+### Caching
+
+class METHOD_UNCACHEABLE(Message):
+ category = c.CACHING
+ level = l.INFO
+ summary = {
+ 'en': u"Responses to the %(method)s method can't be stored by caches."
+ }
+ text = {
+ 'en': u""""""
+ }
+
+class CC_MISCAP(Message):
+ category = c.CACHING
+ level = l.WARN
+ summary = {
+ 'en': u"The %(cc)s Cache-Control directive appears to have incorrect capitalisation."
+ }
+ text = {
+ 'en': u"""Cache-Control directive names are case-sensitive, and will not
+ be recognised by most implementations if the capitalisation is wrong.<br />
+ Did you mean to use %(cc_lower)s instead of %(cc)s?"""
+ }
+
+class CC_DUP(Message):
+ category = c.CACHING
+ level = l.WARN
+ summary = {
+ 'en': u"The %(cc)s Cache-Control directive appears more than once."
+ }
+ text = {
+ 'en': u"""The %(cc)s Cache-Control directive is only defined to appear
+ once; it is used more than once here, so implementations may use different
+ instances (e.g., the first, or the last), making their behaviour
+ unpredictable."""
+ }
+
+class NO_STORE(Message):
+ category = c.CACHING
+ level = l.INFO
+ summary = {
+ 'en': u"%(response)s can't be stored by a cache."
+ }
+ text = {
+ 'en': u"""The <code>Cache-Control: no-store</code> directive indicates that
+ this response can't be stored by a cache."""
+ }
+
+class PRIVATE_CC(Message):
+ category = c.CACHING
+ level = l.INFO
+ summary = {
+ 'en': u"%(response)s only allows a private cache to store it."
+ }
+ text = {
+ 'en': u"""The <code>Cache-Control: private</code> directive indicates that the
+ response can only be stored by caches that are specific to a single user; for
+ example, a browser cache. Shared caches, such as those in proxies, cannot store
+ it."""
+ }
+
+class PRIVATE_AUTH(Message):
+ category = c.CACHING
+ level = l.INFO
+ summary = {
+ 'en': u"%(response)s only allows a private cache to store it."
+ }
+ text = {
+ 'en': u"""Because the request was authenticated and this response doesn't contain
+ a <code>Cache-Control: public</code> directive, this response can only be
+ stored by caches that are specific to a single user; for example, a browser
+ cache. Shared caches, such as those in proxies, cannot store
+ it."""
+ }
+
+class STOREABLE(Message):
+ category = c.CACHING
+ level = l.INFO
+ summary = {
+ 'en': u"""%(response)s allows all caches to store it."""
+ }
+ text = {
+ 'en': u"""A cache can store this response; it may or may not be able to
+ use it to satisfy a particular request."""
+ }
+
+class NO_CACHE(Message):
+ category = c.CACHING
+ level = l.INFO
+ summary = {
+ 'en': u"%(response)s cannot be served from cache without validation."
+ }
+ text = {
+ 'en': u"""The <code>Cache-Control: no-cache</code> directive means that
+ while caches <strong>can</strong> store this response, they cannot use
+ it to satisfy a request unless it has been validated (either with an
+ <code>If-None-Match</code> or <code>If-Modified-Since</code> conditional)
+ for that request.<br />"""
+ }
+
+class NO_CACHE_NO_VALIDATOR(Message):
+ category = c.CACHING
+ level = l.INFO
+ summary = {
+ 'en': u"%(response)s cannot be served from cache without validation."
+ }
+ text = {
+ 'en': u"""The <code>Cache-Control: no-cache</code> directive means that
+ while caches <strong>can</strong> store this response, they cannot use
+ it to satisfy a request unless it has been validated (either with an
+ <code>If-None-Match</code> or <code>If-Modified-Since</code> conditional)
+ for that request.<br />
+ %(response)s doesn't have a <code>Last-Modified</code> or
+ <code>ETag</code> header, so it effectively can't be used by a cache."""
+ }
+
+class VARY_ASTERISK(Message):
+ category = c.CACHING
+ level = l.WARN
+ summary = {
+ 'en': u"Vary: * effectively makes this response uncacheable."
+ }
+ text = {
+ 'en': u"""<code>Vary *</code> indicates that responses for this resource vary
+ by some aspect that can't (or won't) be described by the server. This makes
+ this response effectively uncacheable."""
+ }
+
+class VARY_USER_AGENT(Message):
+ category = c.CACHING
+ level = l.WARN
+ summary = {
+ 'en': u"Vary: User-Agent can cause cache inefficiency."
+ }
+ text = {
+ 'en': u"""Sending <code>Vary: User-Agent</code> requires caches to store
+ a separate copy of the response for every <code>User-Agent</code> request
+ header they see.<br />
+ Since there are so many different <code>User-Agent</code>s, this can
+ "bloat" caches with many copies of the same thing, or cause them to give
+ up on storing these responses at all."""
+ }
+
+class VARY_HOST(Message):
+ category = c.CACHING
+ level = l.WARN
+ summary = {
+ 'en': u"Vary: Host is not necessary."
+ }
+ text = {
+ 'en': u"""Some servers (e.g., <a href="http://httpd.apache.org/">Apache</a>
+ with
+ <a href="http://httpd.apache.org/docs/2.0/mod/mod_rewrite.html">mod_rewrite</a>)
+ will send <code>Host</code> in the <code>Vary</code> header, in the belief
+ that since it affects how the server selects what to send back,
+ this is necessary.<br />
+ This is not the case; HTTP specifies that the URI is the basis of the cache
+ key, and the URI incorporates the <code>Host</code> header.<br />
+ The presence of <code>Vary: Host</code> may make some caches not store
+ an otherwise cacheable response (since some cache implementations will
+ not store anything that has a <code>Vary</code> header)."""
+ }
+
+class VARY_COMPLEX(Message):
+ category = c.CACHING
+ level = l.WARN
+ summary = {
+ 'en': u"This resource varies in %(vary_count)s ways."
+ }
+ text = {
+ 'en': u"""The <code>Vary</code> mechanism allows a resource to describe the
+ dimensions that its responses vary, or change, over; each listed header
+ is another dimension.<br />Varying by too many dimensions makes using this
+ information impractical."""
+ }
+
+class PUBLIC(Message):
+ category = c.CACHING
+ level = l.WARN
+ summary = {
+ 'en': u"Cache-Control: public is rarely necessary."
+ }
+ text = {
+ 'en': u"""The <code>Cache-Control: public</code> directive
+ makes a response cacheable even when the request had an
+ <code>Authorization</code> header (i.e., HTTP authentication was in use).<br />
+ Additionally, <a href="http://firefox.org/">Firefox</a>'s cache
+ will store SSL-protected responses on disk when <code>public</code> is
+ present; otherwise, they are only cached in memory.<br />
+ <br />Therefore, SSL-protected or HTTP-authenticated (NOT cookie-authenticated)
+ resources <em>may</em> have use for <code>public</code> to improve
+ cacheability, if used judiciously.<br />
+ However, other responses <strong>do not need to contain <code>public</code>
+ </strong>; it does not make the response "more cacheable", and only
+ makes the headers larger."""
+ }
+
+class CURRENT_AGE(Message):
+ category = c.CACHING
+ level = l.INFO
+ summary = {
+ 'en': u"%(response)s has been cached for %(age)s."
+ }
+ text = {
+ 'en': u"""The <code>Age</code> header indicates the age of the response;
+ i.e., how long it has been cached since it was generated. HTTP takes this
+ as well as any apparent clock skew into account in computing how old the
+ response already is."""
+ }
+
+class FRESHNESS_FRESH(Message):
+ category = c.CACHING
+ level = l.GOOD
+ summary = {
+ 'en': u"%(response)s is fresh until %(freshness_left)s from now."
+ }
+ text = {
+ 'en': u"""A response can be considered fresh when its age (here, %(current_age)s)
+ is less than its freshness lifetime (in this case, %(freshness_lifetime)s)."""
+ }
+
+class FRESHNESS_STALE(Message):
+ category = c.CACHING
+ level = l.INFO
+ summary = {
+ 'en': u"%(response)s is stale."
+ }
+ text = {
+ 'en': u"""A cache considers a HTTP response stale when its age (here, %(current_age)s)
+ is equal to or exceeds its freshness lifetime (in this case, %(freshness_lifetime)s).<br />
+ HTTP allows caches to use stale responses to satisfy requests only under exceptional
+ circumstances; e.g., when they lose contact with the origin server."""
+ }
+
+class FRESHNESS_HEURISTIC(Message):
+ category = c.CACHING
+ level = l.WARN
+ summary = {
+ 'en': u"%(response)s allows a cache to assign its own freshness lifetime."
+ }
+ text = {
+ 'en': u"""When responses with certain status codes don't have explicit freshness information (like a <code>
+ Cache-Control: max-age</code> directive, or <code>Expires</code> header), caches are
+ allowed to estimate how fresh it is using a heuristic.<br />
+ Usually, but not always, this is done using the <code>Last-Modified</code> header. For
+ example, if your response was last modified a week ago, a cache might decide to consider
+ the response fresh for a day.<br />
+ Consider adding a <code>Cache-Control</code> header; otherwise, it may be cached for longer
+ or shorter than you'd like."""
+ }
+
+class FRESHNESS_NONE(Message):
+ category = c.CACHING
+ level = l.INFO
+ summary = {
+ 'en': u"%(response)s can only be served by a cache under exceptional circumstances."
+ }
+ text = {
+ 'en': u"""%(response)s doesn't have explicit freshness information (like a <code>
+ Cache-Control: max-age</code> directive, or <code>Expires</code> header), and this
+ status code doesn't allow caches to calculate their own.<br />
+ Therefore, while caches may be allowed to store it, they can't use it, except in unusual
+ cirucumstances, such a when the origin server can't be contacted.<br />
+ This behaviour can be prevented by using the <code>Cache-Control: must-revalidate</code>
+ response directive.<br />
+ Note that many caches will not store the response at all, because it is not generally useful to do so.
+ """
+ }
+
+class FRESH_SERVABLE(Message):
+ category = c.CACHING
+ level = l.INFO
+ summary = {
+ 'en': u"%(response)s may still be served by a cache once it becomes stale."
+ }
+ text = {
+ 'en': u"""HTTP allows stale responses to be served under some circumstances;
+ for example, if the origin server can't be contacted, a stale response can
+ be used (even if it doesn't have explicit freshness information).<br />
+ This behaviour can be prevented by using the <code>Cache-Control: must-revalidate</code>
+ response directive."""
+ }
+
+class STALE_SERVABLE(Message):
+ category = c.CACHING
+ level = l.INFO
+ summary = {
+ 'en': u"%(response)s might be served by a cache, even though it is stale."
+ }
+ text = {
+ 'en': u"""HTTP allows stale responses to be served under some circumstances;
+ for example, if the origin server can't be contacted, a stale response can
+ be used (even if it doesn't have explicit freshness information).<br />This
+ behaviour can be prevented by using the <code>Cache-Control: must-revalidate</code>
+ response directive."""
+ }
+
+class FRESH_MUST_REVALIDATE(Message):
+ category = c.CACHING
+ level = l.INFO
+ summary = {
+ 'en': u"%(response)s cannot be served by a cache once it becomes stale."
+ }
+ text = {
+ 'en': u"""The <code>Cache-Control: must-revalidate</code> directive forbids
+ caches from using stale responses to satisfy requests.<br />For example,
+ caches often use stale responses when they cannot connect to the origin
+ server; when this directive is present, they will return an error rather
+ than a stale response."""
+ }
+
+class STALE_MUST_REVALIDATE(Message):
+ category = c.CACHING
+ level = l.INFO
+ summary = {
+ 'en': u"%(response)s cannot be served by a cache, because it is stale."
+ }
+ text = {
+ 'en': u"""The <code>Cache-Control: must-revalidate</code> directive forbids
+ caches from using stale responses to satisfy requests.<br />For example,
+ caches often use stale responses when they cannot connect to the origin
+ server; when this directive is present, they will return an error rather
+ than a stale response."""
+ }
+
+class FRESH_PROXY_REVALIDATE(Message):
+ category = c.CACHING
+ level = l.INFO
+ summary = {
+ 'en': u"%(response)s cannot be served by a shared cache once it becomes stale."
+ }
+ text = {
+ 'en': u"""The presence of the <code>Cache-Control: proxy-revalidate</code>
+ and/or <code>s-maxage</code> directives forbids shared caches (e.g., proxy
+ caches) from using stale responses to satisfy requests.<br />For example,
+ caches often use stale responses when they cannot connect to the origin
+ server; when this directive is present, they will return an error rather
+ than a stale response.<br />These directives do not affect private caches; for
+ example, those in browsers."""
+ }
+
+class STALE_PROXY_REVALIDATE(Message):
+ category = c.CACHING
+ level = l.INFO
+ summary = {
+ 'en': u"%(response)s cannot be served by a shared cache, because it is stale."
+ }
+ text = {
+ 'en': u"""The presence of the <code>Cache-Control: proxy-revalidate</code>
+ and/or <code>s-maxage</code> directives forbids shared caches (e.g., proxy
+ caches) from using stale responses to satisfy requests.<br />For example,
+ caches often use stale responses when they cannot connect to the origin
+ server; when this directive is present, they will return an error rather
+ than a stale response.<br />These directives do not affect private caches; for
+ example, those in browsers."""
+ }
+
+class CHECK_SINGLE(Message):
+ category = c.CACHING
+ level = l.WARN
+ summary = {
+ 'en': u"Only one of the pre-check and post-check Cache-Control directives is present."
+ }
+ text = {
+ 'en': u"""Microsoft Internet Explorer implements two <code>Cache-Control</code>
+ extensions, <code>pre-check</code> and <code>post-check</code>, to give
+ more control over how its cache stores responses.<br />
+ %(response)s uses only one of these directives; as a result, Internet
+ Explorer will ignore the directive, since it requires both to be present.<br />
+ See <a href="http://blogs.msdn.com/ieinternals/archive/2009/07/20/Using-post_2D00_check-and-pre_2D00_check-cache-directives.aspx">this blog entry</a> for more information.
+ """
+ }
+
+class CHECK_NOT_INTEGER(Message):
+ category = c.CACHING
+ level = l.WARN
+ summary = {
+ 'en': u"One of the pre-check/post-check Cache-Control directives has a non-integer value."
+ }
+ text = {
+ 'en': u"""Microsoft Internet Explorer implements two <code>Cache-Control</code>
+ extensions, <code>pre-check</code> and <code>post-check</code>, to give
+ more control over how its cache stores responses.<br />
+ Their values are required to be integers, but here at least one is not. As a
+ result, Internet Explorer will ignore the directive.<br />
+ See <a href="http://blogs.msdn.com/ieinternals/archive/2009/07/20/Using-post_2D00_check-and-pre_2D00_check-cache-directives.aspx">this blog entry</a> for more information.
+ """
+ }
+
+class CHECK_ALL_ZERO(Message):
+ category = c.CACHING
+ level = l.WARN
+ summary = {
+ 'en': u"The pre-check and post-check Cache-Control directives are both '0'."
+ }
+ text = {
+ 'en': u"""Microsoft Internet Explorer implements two <code>Cache-Control</code>
+ extensions, <code>pre-check</code> and <code>post-check</code>, to give
+ more control over how its cache stores responses.<br />
+ %(response)s gives a value of "0" for both; as a result, Internet
+ Explorer will ignore the directive, since it requires both to be present.<br />
+ In other words, setting these to zero has <strong>no effect</strong> (besides
+ wasting bandwidth), and may trigger bugs in some beta versions of IE.<br />
+ See <a href="http://blogs.msdn.com/ieinternals/archive/2009/07/20/Using-post_2D00_check-and-pre_2D00_check-cache-directives.aspx">this blog entry</a> for more information.
+ """
+ }
+
+class CHECK_POST_BIGGER(Message):
+ category = c.CACHING
+ level = l.WARN
+ summary = {
+ 'en': u"The post-check Cache-control directive's value is larger than pre-check's."
+ }
+ text = {
+ 'en': u"""Microsoft Internet Explorer implements two <code>Cache-Control</code>
+ extensions, <code>pre-check</code> and <code>post-check</code>, to give
+ more control over how its cache stores responses.<br />
+ %(response)s assigns a higher value to <code>post-check</code> than to
+ <code>pre-check</code>; this means that Internet Explorer will treat
+ <code>post-check</code> as if its value is the same as <code>pre-check</code>'s.<br />
+ See <a href="http://blogs.msdn.com/ieinternals/archive/2009/07/20/Using-post_2D00_check-and-pre_2D00_check-cache-directives.aspx">this blog entry</a> for more information.
+ """
+ }
+
+class CHECK_POST_ZERO(Message):
+ category = c.CACHING
+ level = l.BAD
+ summary = {
+ 'en': u"The post-check Cache-control directive's value is '0'."
+ }
+ text = {
+ 'en': u"""Microsoft Internet Explorer implements two <code>Cache-Control</code>
+ extensions, <code>pre-check</code> and <code>post-check</code>, to give
+ more control over how its cache stores responses.<br />
+ %(response)s assigns a value of "0" to <code>post-check</code>, which means
+ that Internet Explorer will reload the content as soon as it enters the
+ browser cache, effectively <strong>doubling the load on the server</strong>.<br />
+ See <a href="http://blogs.msdn.com/ieinternals/archive/2009/07/20/Using-post_2D00_check-and-pre_2D00_check-cache-directives.aspx">this blog entry</a> for more information.
+ """
+ }
+
+class CHECK_POST_PRE(Message):
+ category = c.CACHING
+ level = l.INFO
+ summary = {
+ 'en': u"%(response)s may be refreshed in the background by Internet Explorer."
+ }
+ text = {
+ 'en': u"""Microsoft Internet Explorer implements two <code>Cache-Control</code>
+ extensions, <code>pre-check</code> and <code>post-check</code>, to give
+ more control over how its cache stores responses.<br />
+ Once it has been cached for more than %(post-check)s seconds, a new request
+ will result in the cached response being served while it is refreshed in the
+ background. However, if it has been cached for more than %(pre-check)s seconds,
+ the browser will download a fresh response before showing it to the user.<br />
+ Note that these directives do not have any effect on other clients or caches.<br />
+ See <a href="http://blogs.msdn.com/ieinternals/archive/2009/07/20/Using-post_2D00_check-and-pre_2D00_check-cache-directives.aspx">this blog entry</a> for more information.
+ """
+ }
+
+
+### ETag Validation
+
+class INM_304(Message):
+ category = c.VALIDATION
+ level = l.GOOD
+ summary = {
+ 'en': u"If-None-Match conditional requests are supported."
+ }
+ text = {
+ 'en': u"""HTTP allows clients to make conditional requests to see if a copy
+ that they hold is still valid. Since this response has an <code>ETag</code>,
+ clients should be able to use an <code>If-None-Match</code> request header
+ for validation. RED has done this and found that the resource sends a
+ <code>304 Not Modified</code> response, indicating that it supports
+ <code>ETag</code> validation."""
+ }
+
+class INM_FULL(Message):
+ category = c.VALIDATION
+ level = l.WARN
+ summary = {
+ 'en': u"An If-None-Match conditional request returned the full content unchanged."
+ }
+ text = {
+ 'en': u"""HTTP allows clients to make conditional requests to see if a copy
+ that they hold is still valid. Since this response has an <code>ETag</code>,
+ clients should be able to use an <code>If-None-Match</code> request header
+ for validation. RED has done this and found that the resource sends a full
+ response even though it hadn't changed, indicating that it doesn't support
+ <code>ETag</code> validation."""
+ }
+
+class INM_UNKNOWN(Message):
+ category = c.VALIDATION
+ level = l.INFO
+ summary = {
+ 'en': u"An If-None-Match conditional request returned the full content, but it had changed."
+ }
+ text = {
+ 'en': u"""HTTP allows clients to make conditional requests to see if a copy
+ that they hold is still valid. Since this response has an <code>ETag</code>,
+ clients should be able to use an <code>If-None-Match</code> request header
+ for validation. RED has done this, but the response changed between the
+ original request and the validating request, so RED can't tell whether or
+ not <code>ETag</code> validation is supported."""
+ }
+
+class INM_STATUS(Message):
+ category = c.VALIDATION
+ level = l.INFO
+ summary = {
+ 'en': u"An If-None-Match conditional request returned a %(inm_status)s status."
+ }
+ text = {
+ 'en': u"""HTTP allows clients to make conditional requests to see if a copy
+ that they hold is still valid. Since this response has an <code>ETag</code>,
+ clients should be able to use an <code>If-None-Match</code> request header
+ for validation. RED has done this, but the response had a %(enc_inm_status)s
+ status code, so RED can't tell whether or not <code>ETag</code> validation
+ is supported."""
+ }
+
+### Last-Modified Validation
+
+class IMS_304(Message):
+ category = c.VALIDATION
+ level = l.GOOD
+ summary = {
+ 'en': u"If-Modified-Since conditional requests are supported."
+ }
+ text = {
+ 'en': u"""HTTP allows clients to make conditional requests to see if a copy
+ that they hold is still valid. Since this response has a
+ <code>Last-Modified</code> header, clients should be able to use an
+ <code>If-Modified-Since</code> request header for validation.<br />
+ RED has done this and found that the resource sends a
+ <code>304 Not Modified</code> response, indicating that it supports
+ <code>Last-Modified</code> validation."""
+ }
+
+class IMS_FULL(Message):
+ category = c.VALIDATION
+ level = l.WARN
+ summary = {
+ 'en': u"An If-Modified-Since conditional request returned the full content unchanged."
+ }
+ text = {
+ 'en': u"""HTTP allows clients to make conditional requests to see if a copy
+ that they hold is still valid. Since this response has a
+ <code>Last-Modified</code> header, clients should be able to use an
+ <code>If-Modified-Since</code> request header for validation.<br />
+ RED has done this and found that the resource sends a full response even
+ though it hadn't changed, indicating that it doesn't support
+ <code>Last-Modified</code> validation."""
+ }
+
+class IMS_UNKNOWN(Message):
+ category = c.VALIDATION
+ level = l.INFO
+ summary = {
+ 'en': u"An If-Modified-Since conditional request returned the full content, but it had changed."
+ }
+ text = {
+ 'en': u"""HTTP allows clients to make conditional requests to see if a copy
+ that they hold is still valid. Since this response has a
+ <code>Last-Modified</code> header, clients should be able to use an
+ <code>If-Modified-Since</code> request header for validation.<br />
+ RED has done this, but the response changed between the original request and
+ the validating request, so RED can't tell whether or not
+ <code>Last-Modified</code> validation is supported."""
+ }
+
+class IMS_STATUS(Message):
+ category = c.VALIDATION
+ level = l.INFO
+ summary = {
+ 'en': u"An If-Modified-Since conditional request returned a %(ims_status)s status."
+ }
+ text = {
+ 'en': u"""HTTP allows clients to make conditional requests to see if a copy
+ that they hold is still valid. Since this response has a
+ <code>Last-Modified</code> header, clients should be able to use an
+ <code>If-Modified-Since</code> request header for validation.<br />
+ RED has done this, but the response had a %(enc_ims_status)s status code, so
+ RED can't tell whether or not <code>Last-Modified</code> validation is
+ supported."""
+ }
+
+### Status checks
+
+class UNEXPECTED_CONTINUE(Message):
+ category = c.GENERAL
+ level = l.BAD
+ summary = {
+ 'en': u"A 100 Continue response was sent when it wasn't asked for."
+ }
+ text = {
+ 'en': u"""HTTP allows clients to ask a server if a request with a body
+ (e.g., uploading a large file) will succeed before sending it, using
+ a mechanism called "Expect/continue".<br />
+ When used, the client sends an <code>Expect: 100-continue</code>, in
+ the request headers, and if the server is willing to process it, it
+ will send a <code> 100 Continue</code> status code to indicte that the
+ request should continue.<br />
+ This response has a <code>100 Continue</code> status code, but RED
+ did not ask for it (with the <code>Expect</code> request header). Sending
+ this status code without it being requested can cause interoperability
+ problems."""
+ }
+
+class UPGRADE_NOT_REQUESTED(Message):
+ category = c.GENERAL
+ level = l.BAD
+ summary = {
+ 'en': u"The protocol was upgraded without being requested."
+ }
+ text = {
+ 'en': u"""HTTP defines the <code>Upgrade</code> header as a means
+ of negotiating a change of protocol; i.e., it allows you to switch
+ the protocol on a given connection from HTTP to something else.<br />
+ However, it must be first requested by the client; this response
+ contains an <code>Upgrade</code> header, even though RED did not
+ ask for it.<br />
+ Trying to upgrade the connection without the client's participation
+ obviously won't work."""
+ }
+
+class CREATED_SAFE_METHOD(Message):
+ category = c.GENERAL
+ level = l.WARN
+ summary = {
+ 'en': u"A new resource was created in response to a safe request."
+ }
+ text = {
+ 'en': u"""The <code>201 Created</code> status code indicates that
+ processing the request had the side effect of creating a new resource.<br />
+ However, the request method that RED used (%(method)s) is defined as
+ a "safe" method; that is, it should not have any side effects.<br />
+ Creating resources as a side effect of a safe method can have unintended
+ consequences; for example, search engine spiders and similar automated
+ agents often follow links, and intermediaries sometimes re-try safe
+ methods when they fail."""
+ }
+
+class CREATED_WITHOUT_LOCATION(Message):
+ category = c.GENERAL
+ level = l.BAD
+ summary = {
+ 'en': u"A new resource was created without its location being sent."
+ }
+ text = {
+ 'en': u"""The <code>201 Created</code> status code indicates that
+ processing the request had the side effect of creating a new resource.<br />
+ HTTP specifies that the URL of the new resource is to be indicated in
+ the <code>Location</code> header, but it isn't present in this response."""
+ }
+
+class CONTENT_RANGE_MEANINGLESS(Message):
+ category = c.RANGE
+ level = l.WARN
+ summary = {
+ 'en': u"%(response)s shouldn't have a Content-Range header."
+ }
+ text = {
+ 'en': u"""HTTP only defines meaning for the <code>Content-Range</code>
+ header in responses with a <code>206 Partial Content</code> or
+ <code>416 Requested Range Not Satisfiable</code> status code.<br />
+ Putting a <code>Content-Range</code> header in this response may
+ confuse caches and clients."""
+ }
+
+class PARTIAL_WITHOUT_RANGE(Message):
+ category = c.GENERAL
+ level = l.BAD
+ summary = {
+ 'en': u"%(response)s doesn't have a Content-Range header."
+ }
+ text = {
+ 'en': u"""The <code>206 Partial Response</code> status code indicates that
+ the response body is only partial.<br />
+ However, for a response to be partial, it needs to have a
+ <code>Content-Range</code> header to indicate what part of the full
+ response it carries. This response does not have one, and as a result
+ clients won't be able to process it."""
+ }
+
+class PARTIAL_NOT_REQUESTED(Message):
+ category = c.GENERAL
+ level = l.BAD
+ summary = {
+ 'en': u"A partial response was sent when it wasn't requested."
+ }
+ text = {
+ 'en': u"""The <code>206 Partial Response</code> status code indicates that
+ the response body is only partial.<br />
+ However, the client needs to ask for it with the <code>Range</code> header.<br />
+ RED did not request a partial response; sending one without the client
+ requesting it leads to interoperability problems."""
+ }
+
+class REDIRECT_WITHOUT_LOCATION(Message):
+ category = c.GENERAL
+ level = l.BAD
+ summary = {
+ 'en': u"Redirects need to have a Location header."
+ }
+ text = {
+ 'en': u"""The %(enc_status)s status code redirects users to another URI. The
+ <code>Location</code> header is used to convey this URI, but a valid one
+ isn't present in this response."""
+ }
+
+class STATUS_DEPRECATED(Message):
+ category = c.GENERAL
+ level = l.BAD
+ summary = {
+ 'en': u"The %(status)s status code is deprecated."
+ }
+ text = {
+ 'en': u"""When a status code is deprecated, it should not be used,
+ because its meaning is not well-defined enough to ensure interoperability."""
+ }
+
+class STATUS_RESERVED(Message):
+ category = c.GENERAL
+ level = l.BAD
+ summary = {
+ 'en': u"The %(status)s status code is reserved."
+ }
+ text = {
+ 'en': u"""Reserved status codes can only be used by future, standard protocol
+ extensions; they are not for private use."""
+ }
+
+class STATUS_NONSTANDARD(Message):
+ category = c.GENERAL
+ level = l.BAD
+ summary = {
+ 'en': u"%(status)s is not a standard HTTP status code."
+ }
+ text = {
+ 'en': u"""Non-standard status codes are not well-defined and interoperable.
+ Instead of defining your own status code, you should reuse one of the more
+ generic ones; for example, 400 for a client-side problem, or 500 for a
+ server-side problem."""
+ }
+
+class STATUS_BAD_REQUEST(Message):
+ category = c.GENERAL
+ level = l.WARN
+ summary = {
+ 'en': u"The server didn't understand the request."
+ }
+ text = {
+ 'en': u""" """
+ }
+
+class STATUS_FORBIDDEN(Message):
+ category = c.GENERAL
+ level = l.INFO
+ summary = {
+ 'en': u"The server has forbidden this request."
+ }
+ text = {
+ 'en': u""" """
+ }
+
+class STATUS_NOT_FOUND(Message):
+ category = c.GENERAL
+ level = l.INFO
+ summary = {
+ 'en': u"The resource could not be found."
+ }
+ text = {
+ 'en': u"""The server couldn't find any resource to serve for the
+ given URI."""
+ }
+
+class STATUS_NOT_ACCEPTABLE(Message):
+ category = c.GENERAL
+ level = l.INFO
+ summary = {
+ 'en': u"The resource could not be found."
+ }
+ text = {
+ 'en': u""""""
+ }
+
+class STATUS_CONFLICT(Message):
+ category = c.GENERAL
+ level = l.INFO
+ summary = {
+ 'en': u"The request conflicted with the state of the resource."
+ }
+ text = {
+ 'en': u""" """
+ }
+
+class STATUS_GONE(Message):
+ category = c.GENERAL
+ level = l.INFO
+ summary = {
+ 'en': u"The resource is gone."
+ }
+ text = {
+ 'en': u"""The server previously had a resource at the given URI, but it
+ is no longer there."""
+ }
+
+class STATUS_REQUEST_ENTITY_TOO_LARGE(Message):
+ category = c.GENERAL
+ level = l.INFO
+ summary = {
+ 'en': u"The request body was too large for the server."
+ }
+ text = {
+ 'en': u"""The server rejected the request because the request body sent
+ was too large."""
+ }
+
+class STATUS_URI_TOO_LONG(Message):
+ category = c.GENERAL
+ level = l.BAD
+ summary = {
+ 'en': u"The server won't accept a URI this long (%(uri_len)s characters)."
+ }
+ text = {
+ 'en': u"""The %(enc_status)s status code means that the server can't or won't accept
+ a request-uri this long."""
+ }
+
+class STATUS_UNSUPPORTED_MEDIA_TYPE(Message):
+ category = c.GENERAL
+ level = l.INFO
+ summary = {
+ 'en': u"The resource doesn't support this media type in requests."
+ }
+ text = {
+ 'en': u""" """
+ }
+
+class STATUS_INTERNAL_SERVICE_ERROR(Message):
+ category = c.GENERAL
+ level = l.INFO
+ summary = {
+ 'en': u"There was a general server error."
+ }
+ text = {
+ 'en': u""" """
+ }
+
+class STATUS_NOT_IMPLEMENTED(Message):
+ category = c.GENERAL
+ level = l.INFO
+ summary = {
+ 'en': u"The server doesn't implement the request method."
+ }
+ text = {
+ 'en': u""" """
+ }
+
+class STATUS_BAD_GATEWAY(Message):
+ category = c.GENERAL
+ level = l.INFO
+ summary = {
+ 'en': u"An intermediary encountered an error."
+ }
+ text = {
+ 'en': u""" """
+ }
+
+class STATUS_SERVICE_UNAVAILABLE(Message):
+ category = c.GENERAL
+ level = l.INFO
+ summary = {
+ 'en': u"The server is temporarily unavailable."
+ }
+ text = {
+ 'en': u""" """
+ }
+
+class STATUS_GATEWAY_TIMEOUT(Message):
+ category = c.GENERAL
+ level = l.INFO
+ summary = {
+ 'en': u"An intermediary timed out."
+ }
+ text = {
+ 'en': u""" """
+ }
+
+class STATUS_VERSION_NOT_SUPPORTED(Message):
+ category = c.GENERAL
+ level = l.BAD
+ summary = {
+ 'en': u"The request HTTP version isn't supported."
+ }
+ text = {
+ 'en': u""" """
+ }
+
+if __name__ == '__main__':
+ # do a sanity check on all of the defined messages
+ import types
+ for n, v in locals().items():
+ if type(v) is types.ClassType and issubclass(v, Message) and n != "Message":
+ print "checking", n
+ assert v.category in c.__class__.__dict__.values(), n
+ assert v.level in l.__class__.__dict__.values(), n
+ assert type(v.summary) is types.DictType, n
+ assert v.summary != {}, n
+ assert type(v.text) is types.DictType, n
+ assert v.text != {}, n
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/response_analyse.py Mon Jul 26 14:12:44 2010 -0400
@@ -0,0 +1,793 @@
+#!/usr/bin/env python
+
+"""
+The Resource Expert Droid Response Analyser.
+
+Provides two classes: ResponseHeaderParser and ResponseStatusChecker.
+
+Both take a RedFetcher instance (post-done()) as their only argument.
+
+ResponseHeaderParser will examine the response headers and set messages
+on the RedFetcher instance as appropriate. It will also parse the
+headers and populate parsed_hdrs.
+
+ResponseStatusChecker will examine the response based upon its status
+code and also set messages as appropriate.
+
+ResponseHeaderParser MUST be called on the RedFetcher instance before
+running ResponseStatusChecker, because it relies on the headers being
+parsed.
+
+See red.py for the main RED engine and webui.py for the Web front-end.
+red_fetcher.py is the actual response fetching engine.
+"""
+
+__version__ = "1"
+__author__ = "Mark Nottingham <mnot@mnot.net>"
+__copyright__ = """\
+Copyright (c) 2008-2010 Mark Nottingham
+
+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.
+"""
+
+import calendar
+import locale
+import re
+import time
+from cgi import escape as e
+from email.utils import parsedate as lib_parsedate
+from urlparse import urljoin
+
+import nbhttp.error
+import red_speak as rs
+from uri_validate import URI, URI_reference
+
+
+# base URL for RFC2616 references
+rfc2616 = "http://www.apps.ietf.org/rfc/rfc2616.html#%s"
+
+### configuration
+max_hdr_size = 4 * 1024
+max_ttl_hdr = 20 * 1024
+
+# generic syntax regexen (assume processing with re.VERBOSE)
+TOKEN = r'(?:[!#\$%&\'\*\+\-\.\^_`|~A-Za-z0-9]+?)'
+QUOTED_STRING = r'(?:"(?:[ \t\x21\x23-\x5B\x5D-\x7E]|\\[\x01-\x09\x0B-\x0C\x0E\xFF])*")'
+PARAMETER = r'(?:%(TOKEN)s(?:=(?:%(TOKEN)s|%(QUOTED_STRING)s))?)' % locals()
+TOK_PARAM = r'(?:%(TOKEN)s(?:\s*;\s*%(PARAMETER)s)*)' % locals()
+PRODUCT = r'(?:%(TOKEN)s(?:/%(TOKEN)s)?)' % locals()
+COMMENT = r"""(?:
+ \((?:
+ [^\(\)] |
+ \\\( |
+ \\\) |
+ (?:
+ \((?:
+ [^\(\)] |
+ \\\( |
+ \\\) |
+ (?:
+ \((?:
+ [^\(\)] |
+ \\\( |
+ \\\)
+ )*\)
+ )
+ )*\)
+ )
+ )*\)
+)""" # only handles two levels of nested comments; does not check chars
+COMMA = r'(?:\s*(?:,\s*)+)'
+DIGITS = r'(?:[0-9]+)'
+DATE = r"""(?:\w{3},\ [0-9]{2}\ \w{3}\ [0-9]{4}\ [0-9]{2}:[0-9]{2}:[0-9]{2}\ GMT |
+ \w{6,9},\ [0-9]{2}\-\w{3}\-[0-9]{2}\ [0-9]{2}:[0-9]{2}:[0-9]{2}\ GMT |
+ \w{3}\ \w{3}\ [0-9 ][0-9]\ [0-9]{2}:[0-9]{2}:[0-9]{2}\ [0-9]{4})
+ """
+
+
+def GenericHeaderSyntax(meth):
+ """
+ Decorator to take a list of header values, split on commas (except where
+ escaped) and return a list of header field-values. This will not work for
+ Set-Cookie (which contains an unescaped comma) and similar headers
+ containing bare dates.
+
+ E.g.,
+ ["foo,bar", "baz, bat"]
+ becomes
+ ["foo", "bar", "baz", "bat"]
+ """
+ def new(self, name, values):
+ values = sum(
+ [[f.strip() for f in re.findall(r'((?:[^",]|%s)+)(?=%s|\s*$)' %
+ (QUOTED_STRING, COMMA), v)] for v in values], []
+ ) or ['']
+ return meth(self, name, values)
+ return new
+
+def SingleFieldValue(meth):
+ """
+ Decorator to make sure that there's only one value.
+ """
+ def new(self, name, values):
+ if len(values) > 1:
+ self.setMessage(name, rs.SINGLE_HEADER_REPEAT)
+ return meth(self, name, values)
+ return new
+
+def CheckFieldSyntax(exp, ref):
+ """
+ Decorator to check each header field-value to conform to the regex exp,
+ and if not to point users to url ref.
+ """
+ def wrap(meth):
+ def new(self, name, values):
+ for value in values:
+ if not re.match(r"^\s*(?:%s)\s*$" % exp, value, re.VERBOSE):
+ self.setMessage(name, rs.BAD_SYNTAX, ref_uri=ref)
+ def bad_syntax(self, name, values):
+ return None
+ return bad_syntax(self, name, values)
+ return meth(self, name, values)
+ return new
+ return wrap
+
+class ResponseHeaderParser(object):
+ """
+ Parse and check the response for obvious syntactic errors,
+ as well as semantic errors that are self-contained (i.e.,
+ it can be determined without examining other headers, etc.).
+ """
+ def __init__(self, red):
+ self.red = red
+ hdr_dict = {}
+ header_block_size = len(red.res_phrase) + 13
+ clean_res_hdrs = []
+ for name, value in red.res_hdrs:
+ hdr_size = len(name) + len(value)
+ if hdr_size > max_hdr_size:
+ self.setMessage(name.lower(), rs.HEADER_TOO_LARGE,
+ header_name=name, header_size=f_num(hdr_size))
+ header_block_size += hdr_size
+ try:
+ name = name.decode('ascii', 'strict')
+ except UnicodeError:
+ name = name.decode('ascii', 'ignore')
+ self.setMessage('%s' % name.lower(), rs.HEADER_NAME_ENCODING,
+ header_name=name)
+ try:
+ value = value.decode('ascii', 'strict')
+ except UnicodeError:
+ value = value.decode('iso-8859-1', 'replace')
+ self.setMessage('%s' % name.lower(), rs.HEADER_VALUE_ENCODING,
+ header_name=name)
+ clean_res_hdrs.append((name, value))
+ if not re.match("^\s*%s\s*$" % TOKEN, name):
+ self.setMessage(name, rs.FIELD_NAME_BAD_SYNTAX)
+ norm_name = name.lower()
+ value = value.strip()
+ if hdr_dict.has_key(norm_name):
+ hdr_dict[norm_name][1].append(value)
+ else:
+ hdr_dict[norm_name] = (name, [value])
+ # replace the original header tuple with ones that are clean unicode
+ red.res_hdrs = clean_res_hdrs
+ # check the total header block size
+ if header_block_size > max_ttl_hdr:
+ self.setMessage('header', rs.HEADER_BLOCK_TOO_LARGE,
+ header_block_size=f_num(header_block_size))
+ # build a dictionary of header values
+ for nn, (fn, values) in hdr_dict.items():
+ name_token = nn.replace('-', '_')
+ # anything starting with an underscore or with any caps won't match
+ if hasattr(self, name_token):
+ parsed_value = getattr(self, name_token)(fn, values)
+ if parsed_value != None:
+ self.red.parsed_hdrs[nn] = parsed_value
+
+ def setMessage(self, name, msg, **vars):
+ ident = 'header-%s' % name.lower()
+ self.red.setMessage(ident, msg, field_name=name, **vars)
+
+ @staticmethod
+ def _parseDate(values):
+ """Parse a HTTP date. Raises ValueError if it's bad."""
+ value = values[-1]
+ if not re.match(r"%s$" % DATE, value, re.VERBOSE):
+ raise ValueError
+ date_tuple = lib_parsedate(value)
+ if date_tuple is None:
+ raise ValueError
+ # http://sourceforge.net/tracker/index.php?func=detail&aid=1194222&group_id=5470&atid=105470
+ if date_tuple[0] < 100:
+ if date_tuple[0] > 68:
+ date_tuple = (date_tuple[0]+1900,)+date_tuple[1:]
+ else:
+ date_tuple = (date_tuple[0]+2000,)+date_tuple[1:]
+ date = calendar.timegm(date_tuple)
+ return date
+
+ @staticmethod
+ def _unquoteString(instr):
+ """
+ Unquote a string; does NOT unquote control characters.
+
+ @param instr: string to be unquoted
+ @type instr: string
+ @return: unquoted string
+ @rtype: string
+ """
+ instr = str(instr).strip()
+ if not instr or instr == '*':
+ return instr
+ if instr[0] == instr[-1] == '"':
+ instr = instr[1:-1]
+ instr = re.sub(r'\\(.)', r'\1', instr)
+ return instr
+
+ @staticmethod
+ def _splitString(instr, item, split):
+ """
+ Split instr as a list of items separated by splits.
+
+ @param instr: string to be split
+ @param item: regex for item to be split out
+ @param split: regex for splitter
+ @return: list of strings
+ """
+ if not instr:
+ return []
+ return [h.strip() for h in re.findall(r'%s(?=%s|\s*$)' % (item, split), instr)]
+
+ @GenericHeaderSyntax
+ def accept_ranges(self, name, values):
+ for value in values:
+ if value not in ['bytes', 'none']:
+ self.setMessage(name, rs.UNKNOWN_RANGE)
+ break
+ return values
+
+ @GenericHeaderSyntax
+ @SingleFieldValue
+ @CheckFieldSyntax(DIGITS, rfc2616 % "sec-14.6")
+ def age(self, name, values):
+ try:
+ age = int(values[-1])
+ except ValueError:
+ self.setMessage(name, rs.AGE_NOT_INT)
+ return None
+ if age < 0:
+ self.setMessage(name, rs.AGE_NEGATIVE)
+ return None
+ return age
+
+ @GenericHeaderSyntax
+ @CheckFieldSyntax(TOKEN, rfc2616 % "sec-14.7")
+ def allow(self, name, values):
+ return values
+
+ @GenericHeaderSyntax
+ @CheckFieldSyntax(PARAMETER, rfc2616 % "sec-14.9")
+ def cache_control(self, name, values):
+ directives = set()
+ for directive in values:
+ try:
+ attr, value = directive.split("=", 1)
+ value = self._unquoteString(value)
+ except ValueError:
+ attr = directive
+ value = None
+ if attr in ['max-age', 's-maxage']:
+ try:
+ value = int(value)
+ except (ValueError, TypeError):
+ self.setMessage(name, rs.BAD_CC_SYNTAX, bad_cc_attr=attr)
+ continue
+ directives.add((attr, value))
+ return directives
+
+ @SingleFieldValue
+ def content_base(self, name, values):
+ self.setMessage(name, rs.HEADER_DEPRECATED, ref=rfc2616 % "sec-19.6.3")
+ return values[-1]
+
+ def content_disposition(self, name, values):
+ # TODO: check syntax, parse
+ pass
+
+ @GenericHeaderSyntax
+ @CheckFieldSyntax(TOKEN, rfc2616 % "sec-14.11")
+ def content_encoding(self, name, values):
+ values = [v.lower() for v in values]
+ for value in values:
+ # check to see if there are any non-gzip encodings, because
+ # that's the only one we ask for.
+ if value != 'gzip':
+ self.setMessage(name, rs.ENCODING_UNWANTED, encoding=e(value))
+ break
+ return values
+
+ @GenericHeaderSyntax
+ @SingleFieldValue
+ @CheckFieldSyntax(DIGITS, rfc2616 % "sec-14.13")
+ def content_length(self, name, values):
+ return int(values[-1])
+
+ @SingleFieldValue
+ def content_md5(self, name, values):
+ return values[-1]
+
+ def content_range(self, name, values):
+ # TODO: check syntax, values?
+ if self.red.res_status not in ["206", "416"]:
+ self.setMessage(name, rs.CONTENT_RANGE_MEANINGLESS)
+ return values
+
+ @GenericHeaderSyntax
+ @CheckFieldSyntax(r'(?:%(TOKEN)s/%(TOKEN)s(?:\s*;\s*%(PARAMETER)s)*)' % globals(),
+ rfc2616 % "sec-14.17")
+ @SingleFieldValue
+ def content_type(self, name, values):
+ try:
+ media_type, params = values[-1].split(";", 1)
+ except ValueError:
+ media_type, params = values[-1], ''
+ media_type = media_type.lower()
+ param_dict = {}
+ for param in self._splitString(params, PARAMETER, "\s*;\s*"):
+ try:
+ a, v = param.split("=", 1)
+ param_dict[a.lower()] = self._unquoteString(v)
+ except ValueError:
+ param_dict[param.lower()] = None
+ return media_type, param_dict
+
+ @SingleFieldValue
+ def date(self, name, values):
+ try:
+ date = self._parseDate(values)
+ except ValueError:
+ self.setMessage(name, rs.BAD_DATE_SYNTAX)
+ return None
+ return date
+
+ @SingleFieldValue
+ def expires(self, name, values):
+ try:
+ date = self._parseDate(values)
+ except ValueError:
+ self.setMessage(name, rs.BAD_DATE_SYNTAX)
+ return None
+ return date
+
+ @GenericHeaderSyntax
+ @SingleFieldValue
+ @CheckFieldSyntax(r'\*|(?:W/)?%s' % QUOTED_STRING, rfc2616 % "sec-14.19")
+ def etag(self, name, values):
+ instr = values[-1]
+ if instr[:2] == 'W/':
+ return (True, self._unquoteString(instr[2:]))
+ else:
+ return (False, self._unquoteString(instr))
+
+ @GenericHeaderSyntax
+ def keep_alive(self, name, values):
+ directives = set()
+ for directive in values:
+ try:
+ attr, value = directive.split("=", 1)
+ value = self._unquoteString(value)
+ except ValueError:
+ attr = directive
+ value = None
+ attr = attr.lower()
+ directives.add((attr, value))
+ return values
+
+ @SingleFieldValue
+ def last_modified(self, name, values):
+ try:
+ date = self._parseDate(values)
+ except ValueError:
+ self.setMessage(name, rs.BAD_DATE_SYNTAX)
+ return None
+ if date > self.red.timestamp:
+ self.setMessage(name, rs.LM_FUTURE)
+ return
+ else:
+ self.setMessage(name, rs.LM_PRESENT,
+ last_modified_string=relative_time(date, self.red.timestamp))
+ return date
+
+ @GenericHeaderSyntax
+ def link(self, name, values):
+ # TODO: check syntax, values?
+ pass
+
+ # The most common problem with Location is a non-absolute URI, so we separate
+ # that from the syntax check.
+ @CheckFieldSyntax(URI_reference, rfc2616 % "sec-14.30")
+ @SingleFieldValue
+ def location(self, name, values):
+ if self.red.res_status not in ["201", "300", "301", "302", "303", "305", "307"]:
+ self.setMessage(name, rs.LOCATION_UNDEFINED)
+ if not re.match(r"^\s*%s\s*$" % URI, values[-1], re.VERBOSE):
+ self.setMessage(name, rs.LOCATION_NOT_ABSOLUTE,
+ full_uri=e(urljoin(self.red.uri, values[-1])))
+ return values[-1]
+
+ def mime_version(self, name, values):
+ self.setMessage(name, rs.MIME_VERSION)
+ return values
+
+ @GenericHeaderSyntax
+ def p3p(self, name, values):
+ # TODO: check syntax, values
+ pass
+
+ @GenericHeaderSyntax
+ def pragma(self, name, values):
+ values = set([v.lower() for v in values])
+ if "no-cache" in values:
+ self.setMessage(name, rs.PRAGMA_NO_CACHE)
+ others = [True for v in values if v != "no-cache"]
+ if others:
+ self.setMessage(name, rs.PRAGMA_OTHER)
+ return values
+
+ @GenericHeaderSyntax
+ @CheckFieldSyntax(r"(?:%s|%s)" % (DIGITS, DATE), rfc2616 % "sec-14.37")
+ @SingleFieldValue
+ def retry_after(self, name, values):
+ pass
+
+ def server(self, name, values):
+ # TODO: check syntax, flag servers?
+ pass
+
+ @SingleFieldValue
+ def soapaction(self, name, values):
+ return values[-1]
+
+ def set_cookie(self, name, values):
+ # TODO: check syntax, values?
+ pass
+
+ @GenericHeaderSyntax
+ def tcn(self, name, values):
+ # TODO: check syntax, values?
+ pass
+
+ @GenericHeaderSyntax
+ @CheckFieldSyntax(TOK_PARAM, rfc2616 % "sec-14.41")
+ # TODO: accommodate transfer-parameters
+ def transfer_encoding(self, name, values):
+ values = [v.lower() for v in values]
+ if 'identity' in values:
+ self.setMessage(name, rs.TRANSFER_CODING_IDENTITY)
+ for value in values:
+ # check to see if there are any non-chunked encodings, because
+ # that's the only one we ask for.
+ if value not in ['chunked', 'identity']:
+ self.setMessage(name, rs.TRANSFER_CODING_UNWANTED,
+ encoding=e(value))
+ break
+ return values
+
+ @GenericHeaderSyntax
+ @CheckFieldSyntax(TOKEN, rfc2616 % "sec-14.44")
+ def vary(self, name, values):
+ values = set([v.lower() for v in values])
+ return values
+
+ @GenericHeaderSyntax
+ @CheckFieldSyntax(r'(?:%s/)?%s\s+[^,\s]+(?:\s+%s)?' % (TOKEN, TOKEN, COMMENT),
+ rfc2616 % "sec-14.45")
+ def via(self, name, values):
+ via_list = u"<ul>" + u"\n".join(
+ [u"<li><code>%s</code></li>" % e(v) for v in values]
+ ) + u"</ul>"
+ self.setMessage(name, rs.VIA_PRESENT, via_list=via_list)
+
+ @GenericHeaderSyntax
+ def warning(self, name, values):
+ # TODO: check syntax, values?
+ pass
+
+ @GenericHeaderSyntax
+ def x_cache(self, name, values):
+ # TODO: explain
+ pass
+
+ @GenericHeaderSyntax
+ def x_content_type_options(self, name, values):
+ if 'nosniff' in values:
+ self.setMessage(name, rs.CONTENT_TYPE_OPTIONS)
+ else:
+ self.setMessage(name, rs.CONTENT_TYPE_OPTIONS_UNKNOWN)
+ return values
+
+ @GenericHeaderSyntax
+ def x_download_options(self, name, values):
+ if 'noopen' in values:
+ self.setMessage(name, rs.DOWNLOAD_OPTIONS)
+ else:
+ self.setMessage(name, rs.DOWNLOAD_OPTIONS_UNKNOWN)
+ return values
+
+ @GenericHeaderSyntax
+ def x_frame_options(self, name, values):
+ if 'DENY' in values:
+ self.setMessage(name, rs.FRAME_OPTIONS_DENY)
+ elif 'SAMEORIGIN' in values:
+ self.setMessage(name, rs.FRAME_OPTIONS_SAMEORIGIN)
+ else:
+ self.setMessage(name, rs.FRAME_OPTIONS_UNKNOWN)
+ return values
+
+ @GenericHeaderSyntax
+ def x_meta_mssmarttagspreventparsing(self, name, values):
+ self.setMessage(name, rs.SMART_TAG_NO_WORK)
+ return values
+
+ @GenericHeaderSyntax
+ @CheckFieldSyntax(PARAMETER, "http://msdn.microsoft.com/en-us/library/cc288325(VS.85).aspx")
+ def x_ua_compatible(self, name, values):
+ directives = {}
+ for directive in values:
+ try:
+ attr, value = directive.split("=", 1)
+ except ValueError:
+ attr = directive
+ value = None
+ if directives.has_key(attr):
+ self.setMessage(name, rs.UA_COMPATIBLE_REPEAT)
+ directives[attr] = value
+ uac_list = u"\n".join([u"<li>%s - %s</li>" % (e(k), e(v)) for
+ k, v in directives.items()])
+ self.setMessage(name, rs.UA_COMPATIBLE, uac_list=uac_list)
+ return directives
+
+
+ @GenericHeaderSyntax
+ @SingleFieldValue
+ @CheckFieldSyntax(DIGITS, 'http://blogs.msdn.com/ie/archive/2008/07/02/ie8-security-part-iv-the-xss-filter.aspx')
+ def x_xss_protection(self, name, values):
+ if int(values[-1]) == 0:
+ self.setMessage(name, rs.XSS_PROTECTION)
+ return values[-1]
+
+ @GenericHeaderSyntax
+ @SingleFieldValue
+ def x_xrds_location(self, name, values):
+ pass
+
+ @SingleFieldValue
+ def x_pingback(self, name, values):
+ #TODO: message, perhaps allow a ping
+ pass
+
+class ResponseStatusChecker:
+ """
+ Given a RED, check out the status
+ code and perform appropriate tests on it.
+ """
+ def __init__(self, red):
+ self.red = red
+ try:
+ getattr(self, "status%s" % red.res_status)()
+ except AttributeError:
+ self.setMessage('status', rs.STATUS_NONSTANDARD)
+
+ def setMessage(self, name, msg, **vars):
+ if name:
+ ident = 'status %s' % name
+ else:
+ ident = 'status'
+ self.red.setMessage(ident, msg,
+ status=self.red.res_status,
+ enc_status=e(self.red.res_status),
+ **vars
+ )
+
+ def status100(self): # Continue
+ if not "100-continue" in nbhttp.get_hdr(self.red.req_hdrs, 'expect'):
+ self.setMessage('', rs.UNEXPECTED_CONTINUE)
+ def status101(self): # Switching Protocols
+ if not 'upgrade' in nbhttp.header_dict(self.red.req_hdrs).keys():
+ self.setMessage('', rs.UPGRADE_NOT_REQUESTED)
+ def status102(self): # Processing
+ pass
+ def status200(self): # OK
+ pass
+ def status201(self): # Created
+ if self.red.method in nbhttp.safe_methods:
+ self.setMessage('status', rs.CREATED_SAFE_METHOD, method=self.red.method)
+ if not self.red.parsed_hdrs.has_key('location'):
+ self.setMessage('header-location', rs.CREATED_WITHOUT_LOCATION)
+ def status202(self): # Accepted
+ pass
+ def status203(self): # Non-Authoritative Information
+ pass
+ def status204(self): # No Content
+ pass
+ def status205(self): # Reset Content
+ pass
+ def status206(self): # Partial Content
+ if not "range" in nbhttp.header_dict(self.red.req_hdrs).keys():
+ self.setMessage('', rs.PARTIAL_NOT_REQUESTED)
+ if not self.red.parsed_hdrs.has_key('content-range'):
+ print self.red.parsed_hdrs.keys()
+ self.setMessage('header-location', rs.PARTIAL_WITHOUT_RANGE)
+ def status207(self): # Multi-Status
+ pass
+ def status226(self): # IM Used
+ pass
+ def status300(self): # Multiple Choices
+ pass
+ def status301(self): # Moved Permanently
+ if not self.red.parsed_hdrs.has_key('location'):
+ self.setMessage('header-location', rs.REDIRECT_WITHOUT_LOCATION)
+ def status302(self): # Found
+ if not self.red.parsed_hdrs.has_key('location'):
+ self.setMessage('header-location', rs.REDIRECT_WITHOUT_LOCATION)
+ def status303(self): # See Other
+ if not self.red.parsed_hdrs.has_key('location'):
+ self.setMessage('header-location', rs.REDIRECT_WITHOUT_LOCATION)
+ def status304(self): # Not Modified
+ pass # TODO: check to make sure required headers are present, stable
+ def status305(self): # Use Proxy
+ self.setMessage('', rs.STATUS_DEPRECATED)
+ def status306(self): # Reserved
+ self.setMessage('', rs.STATUS_RESERVED)
+ def status307(self): # Temporary Redirect
+ if not self.red.parsed_hdrs.has_key('location'):
+ self.setMessage('header-location', rs.REDIRECT_WITHOUT_LOCATION)
+ def status400(self): # Bad Request
+ self.setMessage('', rs.STATUS_BAD_REQUEST)
+ def status401(self): # Unauthorized
+ pass # TODO: prompt for credentials
+ def status402(self): # Payment Required
+ pass
+ def status403(self): # Forbidden
+ self.setMessage('', rs.STATUS_FORBIDDEN)
+ def status404(self): # Not Found
+ self.setMessage('', rs.STATUS_NOT_FOUND)
+ def status405(self): # Method Not Allowed
+ pass # TODO: show allowed methods?
+ def status406(self): # Not Acceptable
+ self.setMessage('', rs.STATUS_NOT_ACCEPTABLE)
+ def status407(self): # Proxy Authentication Required
+ pass
+ def status408(self): # Request Timeout
+ pass
+ def status409(self): # Conflict
+ self.setMessage('', rs.STATUS_CONFLICT)
+ def status410(self): # Gone
+ self.setMessage('', rs.STATUS_GONE)
+ def status411(self): # Length Required
+ pass
+ def status412(self): # Precondition Failed
+ pass # TODO: test to see if it's true, alert if not
+ def status413(self): # Request Entity Too Large
+ self.setMessage('', rs.STATUS_REQUEST_ENTITY_TOO_LARGE)
+ def status414(self): # Request-URI Too Long
+ self.setMessage('uri', rs.STATUS_URI_TOO_LONG,
+ uri_len=len(self.red.uri))
+ def status415(self): # Unsupported Media Type
+ self.setMessage('', rs.STATUS_UNSUPPORTED_MEDIA_TYPE)
+ def status416(self): # Requested Range Not Satisfiable
+ pass # TODO: test to see if it's true, alter if not
+ def status417(self): # Expectation Failed
+ pass # TODO: explain, alert if it's 100-continue
+ def status422(self): # Unprocessable Entity
+ pass
+ def status423(self): # Locked
+ pass
+ def status424(self): # Failed Dependency
+ pass
+ def status426(self): # Upgrade Required
+ pass
+ def status500(self): # Internal Server Error
+ self.setMessage('', rs.STATUS_INTERNAL_SERVICE_ERROR)
+ def status501(self): # Not Implemented
+ self.setMessage('', rs.STATUS_NOT_IMPLEMENTED)
+ def status502(self): # Bad Gateway
+ self.setMessage('', rs.STATUS_BAD_GATEWAY)
+ def status503(self): # Service Unavailable
+ self.setMessage('', rs.STATUS_SERVICE_UNAVAILABLE)
+ def status504(self): # Gateway Timeout
+ self.setMessage('', rs.STATUS_GATEWAY_TIMEOUT)
+ def status505(self): # HTTP Version Not Supported
+ self.setMessage('', rs.STATUS_VERSION_NOT_SUPPORTED)
+ def status506(self): # Variant Also Negotiates
+ pass
+ def status507(self): # Insufficient Storage
+ pass
+ def status510(self): # Not Extended
+ pass
+
+
+def f_num(instr):
+ "Format a number according to the locale."
+ return locale.format("%d", instr, grouping=True)
+
+
+def relative_time(utime, now=None, show_sign=1):
+ '''
+ Given two times, return a string that explains how far apart they are.
+ show_sign can be:
+ 0 - don't show
+ 1 - ago / from now [DEFAULT]
+ 2 - early / late
+ '''
+
+ signs = {
+ 0: ('0', '', ''),
+ 1: ('now', 'ago', 'from now'),
+ 2: ('none', 'behind', 'ahead'),
+ }
+
+ if utime == None:
+ return None
+ if now == None:
+ now = time.time()
+ age = int(now - utime)
+ if age == 0:
+ return signs[show_sign][0]
+
+ a = abs(age)
+ yrs = int(a / 60 / 60 / 24 / 7 / 52)
+ wks = int(a / 60 / 60 / 24 / 7) % 52
+ day = int(a / 60 / 60 / 24) % 7
+ hrs = int(a / 60 / 60) % 24
+ mnt = int(a / 60) % 60
+ sec = int(a % 60)
+
+ if age > 0:
+ sign = signs[show_sign][1]
+ else:
+ sign = signs[show_sign][2]
+ if not sign:
+ sign = signs[show_sign][0]
+
+ arr = []
+ if yrs == 1:
+ arr.append(str(yrs) + ' year')
+ elif yrs > 1:
+ arr.append(str(yrs) + ' years')
+ if wks == 1:
+ arr.append(str(wks) + ' week')
+ elif wks > 1:
+ arr.append(str(wks) + ' weeks')
+ if day == 1:
+ arr.append(str(day) + ' day')
+ elif day > 1:
+ arr.append(str(day) + ' days')
+ if hrs:
+ arr.append(str(hrs) + ' hr')
+ if mnt:
+ arr.append(str(mnt) + ' min')
+ if sec:
+ arr.append(str(sec) + ' sec')
+ arr = arr[:2] # resolution
+ if show_sign:
+ arr.append(sign)
+ return " ".join(arr)
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/unicorn_ui.py Mon Jul 26 14:12:44 2010 -0400
@@ -0,0 +1,195 @@
+# -*- coding: utf-8 -*-
+
+"""
+Created on Jun 30, 2010
+
+@author: Hirotaka Nakajima <hiro@w3.org>
+
+"""
+from red import ResourceExpertDroid
+from xml.dom import minidom
+from xml.dom.minidom import parseString
+from cgi import escape as e
+import re
+import cgi
+import nbhttp
+import logging
+
+__date__ = "Jun 30, 2010"
+__author__ = "Hirotaka Nakajima <hiro@w3.org>"
+
+class UnicornUi(object):
+ """
+ Unicorn Interface of Red Cacheability checker
+ """
+ def __init__(self, test_uri):
+ """
+ Constractor
+ @param test_uri: Test Uri
+ """
+ self.test_uri = test_uri
+ try:
+ self.red = ResourceExpertDroid(self.test_uri)
+ self.result = ""
+ self.done = False
+ logger = logging.getLogger()
+ logger.setLevel(logging.DEBUG)
+ if self.red.res_complete:
+ self.result = self._generate_output_xml(test_uri, self.red.messages).toprettyxml()
+ else:
+ error_string = ""
+ if self.red.res_error['desc'] == nbhttp.error.ERR_CONNECT['desc']:
+ error_string = "Could not connect to the server (%s)" % self.red.res_error.get('detail', "unknown")
+ elif self.red.res_error['desc'] == nbhttp.error.ERR_URL['desc']:
+ error_string = self.red.res_error.get('detail', "RED can't fetch that URL.")
+ elif self.red.res_error['desc'] == nbhttp.error.ERR_READ_TIMEOUT['desc']:
+ error_string = self.red.res_error['desc']
+ elif self.red.res_error['desc'] == nbhttp.error.ERR_HTTP_VERSION['desc']:
+ error_string = "<code>%s</code> isn't HTTP." % e(self.red.res_error.get('detail', '')[:20])
+ else:
+ raise AssertionError, "Unidentified incomplete response error."
+ self.result = self._generate_error_xml(error_string).toprettyxml()
+ except:
+ import traceback
+ logging.error(traceback.format_exc())
+ self.result = """<?xml version="1.0" ?>
+<observationresponse ref="None" xml:lang="en" xmlns="http://www.w3.org/2009/10/unicorn/observationresponse">
+ <message type="error">
+ <title>
+ Internal Server Error
+ </title>
+ <description>
+ Internal Server Error occured.
+ </description>
+ </message>
+</observationresponse>"""
+
+ def get_result(self):
+ """
+ Return result if cacheability check was finished.
+ If not, return None
+ @return: Result of cacheablity checker.
+ """
+ return str(self.result)
+
+ def _get_response_document(self):
+ """
+ Generate response document
+ @return: Root response document DOM object
+ """
+ doc = minidom.Document()
+ rootDoc = doc.createElement("observationresponse")
+ rootDoc.setAttribute("xmlns", "http://www.w3.org/2009/10/unicorn/observationresponse")
+ rootDoc.setAttribute("xml:lang", "en")
+ rootDoc.setAttribute("ref", self.test_uri)
+ doc.appendChild(rootDoc)
+ return rootDoc, doc
+
+ def _generate_output_xml(self, test_uri, messages):
+ """
+ Generate Output XML Document
+ @return: Output XML Document
+ """
+ rootDoc, doc = self._get_response_document()
+ for i in messages:
+ m = doc.createElement("message")
+ m.setAttribute("type", self._convert_level(i.level))
+ title = doc.createElement("title")
+ title.appendChild(doc.createTextNode(i.summary['en'] % i.vars))
+ text = "<description>" + (i.text['en'] % i.vars) + "</description>"
+ try:
+ text_dom = parseString(text)
+ except:
+ logging.error(text)
+ text_dom = parseString("<description>Internal Error</description>")
+ text_element = text_dom.getElementsByTagName("description")
+ m.appendChild(title)
+ m.appendChild(text_element[0])
+ rootDoc.appendChild(m)
+ return doc
+
+ def _generate_error_xml(self, error_message):
+ '''
+ Return Error XML Document
+ @return: Error XML Document
+ '''
+ rootDoc, doc = self._get_response_document()
+ m = doc.createElement("message")
+ m.setAttribute("type", "error")
+ title = doc.createElement("title")
+ title.appendChild(doc.createTextNode("Checker Error"))
+ text = "<description>" + error_message + "</description>"
+ try:
+ text_dom = parseString(text)
+ except:
+ logging.error(text)
+ text_dom = parseString("<description>Internal Error</description>")
+ text_element = text_dom.getElementsByTagName("description")
+ m.appendChild(title)
+ m.appendChild(text_element[0])
+ rootDoc.appendChild(m)
+ return doc
+
+ def _convert_level(self, level):
+ '''
+ Convert verbose level string from Redbot style to unicorn style
+ '''
+ level = re.sub("good", "info", level)
+ level = re.sub("bad", "error", level)
+ return level
+
+def application(environ, start_response):
+ method = environ.get('REQUEST_METHOD')
+ test_uri = None
+ result = None
+ run_engine = False
+ response_headers = None
+ if method == "GET":
+ query = cgi.parse_qsl(environ.get('QUERY_STRING'))
+ for q in query:
+ if len(q) == 2:
+ if q[0] == "ca_uri":
+ test_uri = q[1]
+ if q[0] == "output":
+ if q[1] == "ucn":
+ run_engine = True
+
+
+ if test_uri != None:
+ if run_engine == True:
+ red = UnicornUi(test_uri)
+ result = red.get_result()
+ status = '200 OK'
+ response_headers = [('Content-type', 'application/xml'),
+ ('Content-Length', str(len(result)))]
+ else:
+ status = '303 See Other'
+ response_headers = [('Content-type', 'text/plain'), ('Location', "http://redbot.org/?uri=" + test_uri)]
+ result = ""
+ if result == None:
+ status = '200 OK'
+ result = """<?xml version="1.0" ?>
+<observationresponse ref="None" xml:lang="en" xmlns="http://www.w3.org/2009/10/unicorn/observationresponse">
+ <message type="error">
+ <title>
+ No URI provided
+ </title>
+ <description>
+ URI isn't provided
+ </description>
+ </message>
+</observationresponse>"""
+ response_headers = [('Content-type', 'application/xml'), ('Content-Length', str(len(result)))]
+
+ start_response(status, response_headers)
+ return [result]
+
+def standalone_main(test_uri):
+ red = UnicornUi(test_uri)
+ print red.get_result()
+
+if __name__ == "__main__":
+ import sys
+ test_uri = sys.argv[1]
+ standalone_main(test_uri)
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/uri_validate.py Mon Jul 26 14:12:44 2010 -0400
@@ -0,0 +1,254 @@
+#!/usr/bin/env python
+
+"""
+Regex for URIs
+
+These regex are directly derived from the collected ABNF in RFC3986
+(except for DIGIT, ALPHA and HEXDIG, defined by RFC2234).
+
+They should be processed with re.VERBOSE.
+"""
+
+__author__ = "Mark Nottingham <mnot@mnot.net>"
+__license__ = """
+Copyright (c) 2009-2010 Mark Nottingham (code portions)
+
+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.
+"""
+
+### basics
+
+DIGIT = r"[\x30-\x39]"
+
+ALPHA = r"[\x41-\x5A\x61-\x7A]"
+
+HEXDIG = r"[\x30-\x39A-Fa-f]"
+
+# pct-encoded = "%" HEXDIG HEXDIG
+pct_encoded = r" %% %(HEXDIG)s %(HEXDIG)s" % locals()
+
+# unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
+unreserved = r"(?: %(ALPHA)s | %(DIGIT)s | \- | \. | _ | ~ )" % locals()
+
+# gen-delims = ":" / "/" / "?" / "#" / "[" / "]" / "@"
+gen_delims = r"(?: : | / | \? | \# | \[ | \] | @ )"
+
+# sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
+# / "*" / "+" / "," / ";" / "="
+sub_delims = r"""(?: ! | \$ | & | ' | \( | \) |
+ \* | \+ | , | ; | = )"""
+
+# pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
+pchar = r"(?: %(unreserved)s | %(pct_encoded)s | %(sub_delims)s | : | @ )" % locals()
+
+# reserved = gen-delims / sub-delims
+reserved = r"(?: %(gen_delims)s | %(sub_delims)s )" % locals()
+
+
+### scheme
+
+# scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
+scheme = r"%(ALPHA)s (?: %(ALPHA)s | %(DIGIT)s | \+ | \- | \. )*" % locals()
+
+
+### authority
+
+# dec-octet = DIGIT ; 0-9
+# / %x31-39 DIGIT ; 10-99
+# / "1" 2DIGIT ; 100-199
+# / "2" %x30-34 DIGIT ; 200-249
+# / "25" %x30-35 ; 250-255
+dec_octet = r"""(?: %(DIGIT)s |
+ [\x31-\x39] %(DIGIT)s |
+ 1 %(DIGIT)s{2} |
+ 2 [\x30-\x34] %(DIGIT)s |
+ 25 [\x30-\x35]
+ )
+""" % locals()
+
+# IPv4address = dec-octet "." dec-octet "." dec-octet "." dec-octet
+IPv4address = r"%(dec_octet)s \. %(dec_octet)s \. %(dec_octet)s \. %(dec_octet)s" % locals()
+
+# h16 = 1*4HEXDIG
+h16 = r"(?: %(HEXDIG)s ){1,4}" % locals()
+
+# ls32 = ( h16 ":" h16 ) / IPv4address
+ls32 = r"(?: (?: %(h16)s : %(h16)s ) | %(IPv4address)s )" % locals()
+
+# IPv6address = 6( h16 ":" ) ls32
+# / "::" 5( h16 ":" ) ls32
+# / [ h16 ] "::" 4( h16 ":" ) ls32
+# / [ *1( h16 ":" ) h16 ] "::" 3( h16 ":" ) ls32
+# / [ *2( h16 ":" ) h16 ] "::" 2( h16 ":" ) ls32
+# / [ *3( h16 ":" ) h16 ] "::" h16 ":" ls32
+# / [ *4( h16 ":" ) h16 ] "::" ls32
+# / [ *5( h16 ":" ) h16 ] "::" h16
+# / [ *6( h16 ":" ) h16 ] "::"
+IPv6address = r"""(?: (?: %(h16)s : ){6} %(ls32)s |
+ :: (?: %(h16)s : ){5} %(ls32)s |
+ %(h16)s :: (?: %(h16)s : ){4} %(ls32)s |
+ (?: %(h16)s : ) %(h16)s :: (?: %(h16)s : ){3} %(ls32)s |
+ (?: %(h16)s : ){2} %(h16)s :: (?: %(h16)s : ){2} %(ls32)s |
+ (?: %(h16)s : ){3} %(h16)s :: %(h16)s : %(ls32)s |
+ (?: %(h16)s : ){4} %(h16)s :: %(ls32)s |
+ (?: %(h16)s : ){5} %(h16)s :: %(h16)s |
+ (?: %(h16)s : ){6} %(h16)s ::
+ )
+""" % locals()
+
+# IPvFuture = "v" 1*HEXDIG "." 1*( unreserved / sub-delims / ":" )
+IPvFuture = r"v %(HEXDIG)s+ \. (?: %(unreserved)s | %(sub_delims)s | : )+" % locals()
+
+# IP-literal = "[" ( IPv6address / IPvFuture ) "]"
+IP_literal = r"\[ (?: %(IPv6address)s | %(IPvFuture)s ) \]" % locals()
+
+# reg-name = *( unreserved / pct-encoded / sub-delims )
+reg_name = r"(?: %(unreserved)s | %(pct_encoded)s | %(sub_delims)s )*" % locals()
+
+# userinfo = *( unreserved / pct-encoded / sub-delims / ":" )
+userinfo = r"(?: %(unreserved)s | %(pct_encoded)s | %(sub_delims)s | : )" % locals()
+
+# host = IP-literal / IPv4address / reg-name
+host = r"(?: %(IP_literal)s | %(IPv4address)s | %(reg_name)s )" % locals()
+
+# port = *DIGIT
+port = r"(?: %(DIGIT)s )*" % locals()
+
+# authority = [ userinfo "@" ] host [ ":" port ]
+authority = r"(?: %(userinfo)s @)? %(host)s (?: : %(port)s)?" % locals()
+
+
+
+### Path
+
+# segment = *pchar
+segment = r"%(pchar)s*" % locals()
+
+# segment-nz = 1*pchar
+segment_nz = r"%(pchar)s+" % locals()
+
+# segment-nz-nc = 1*( unreserved / pct-encoded / sub-delims / "@" )
+# ; non-zero-length segment without any colon ":"
+segment_nz_nc = r"(?: %(unreserved)s | %(pct_encoded)s | %(sub_delims)s | @ )+" % locals()
+
+# path-abempty = *( "/" segment )
+path_abempty = r"(?: / %(segment)s )*" % locals()
+
+# path-absolute = "/" [ segment-nz *( "/" segment ) ]
+path_absolute = r"/ (?: %(segment_nz)s (?: / %(segment)s )* )?" % locals()
+
+# path-noscheme = segment-nz-nc *( "/" segment )
+path_noscheme = r"%(segment_nz_nc)s (?: / %(segment)s )*" % locals()
+
+# path-rootless = segment-nz *( "/" segment )
+path_rootless = r"%(segment_nz)s (?: / %(segment)s )*" % locals()
+
+# path-empty = 0<pchar>
+path_empty = r"%(pchar)s{0}"
+
+# path = path-abempty ; begins with "/" or is empty
+# / path-absolute ; begins with "/" but not "//"
+# / path-noscheme ; begins with a non-colon segment
+# / path-rootless ; begins with a segment
+# / path-empty ; zero characters
+path = r"""(?: %(path_abempty)s |
+ %(path_absolute)s |
+ %(path_noscheme)s |
+ %(path_rootless)s |
+ %(path_empty)s
+ )
+""" % locals()
+
+
+
+### Query and Fragment
+
+# query = *( pchar / "/" / "?" )
+query = r"(?: %(pchar)s | / | \? )*" % locals()
+
+# fragment = *( pchar / "/" / "?" )
+fragment = r"(?: %(pchar)s | / | \? )*" % locals()
+
+
+
+### URIs
+
+# hier-part = "//" authority path-abempty
+# / path-absolute
+# / path-rootless
+# / path-empty
+hier_part = r"""(?: (?: // %(authority)s %(path_abempty)s ) |
+ %(path_absolute)s |
+ %(path_rootless)s |
+ %(path_empty)s
+ )
+""" % locals()
+
+# relative-part = "//" authority path-abempty
+# / path-absolute
+# / path-noscheme
+# / path-empty
+relative_part = r"""(?: (?: // %(authority)s %(path_abempty)s ) |
+ %(path_absolute)s |
+ %(path_noscheme)s |
+ %(path_empty)s
+ )
+""" % locals()
+
+# relative-ref = relative-part [ "?" query ] [ "#" fragment ]
+relative_ref = r"%(relative_part)s (?: \? %(query)s)? (?: \# %(fragment)s)?" % locals()
+
+# URI = scheme ":" hier-part [ "?" query ] [ "#" fragment ]
+URI = r"(?: %(scheme)s : %(hier_part)s (?: \? %(query)s )? (?: \# %(fragment)s )? )" % locals()
+
+# URI-reference = URI / relative-ref
+URI_reference = r"(?: %(URI)s | %(relative_ref)s )" % locals()
+
+# absolute-URI = scheme ":" hier-part [ "?" query ]
+absolute_URI = r"(?: %(scheme)s : %(hier_part)s (?: \? %(query)s )? )" % locals()
+
+
+if "__main__" == __name__:
+ import re
+ import sys
+ try:
+ instr = sys.argv[1]
+ except IndexError:
+ print "usage: %s test-string" % sys.argv[0]
+ sys.exit(1)
+
+ print 'testing: "%s"' % instr
+
+ print "URI:",
+ if re.match("^%s$" % URI, instr, re.VERBOSE):
+ print "yes"
+ else:
+ print "no"
+
+ print "URI reference:",
+ if re.match("^%s$" % URI_reference, instr, re.VERBOSE):
+ print "yes"
+ else:
+ print "no"
+
+ print "Absolute URI:",
+ if re.match("^%s$" % absolute_URI, instr, re.VERBOSE):
+ print "yes"
+ else:
+ print "no"
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/webui.py Mon Jul 26 14:12:44 2010 -0400
@@ -0,0 +1,816 @@
+#!/usr/bin/env python
+
+"""
+A Web UI for RED, the Resource Expert Droid.
+"""
+
+__author__ = "Mark Nottingham <mnot@mnot.net>"
+__copyright__ = """\
+Copyright (c) 2008-2010 Mark Nottingham
+
+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.
+"""
+
+### Configuration ##########################################################
+
+# FIXME: make language configurable/dynamic
+lang = "en"
+charset = "utf-8"
+
+# Where to store exceptions; set to None to disable traceback logging
+logdir = 'exceptions'
+
+# how many seconds to allow it to run for
+max_runtime = 60
+
+# URI root for static assets (absolute or relative, but no trailing '/')
+static_root = 'static'
+
+# directory containing files to append to the front page; None to disable
+extra_dir = "extra"
+
+### End configuration ######################################################
+
+import cgi
+import codecs
+import locale
+import operator
+import os
+import pprint
+import re
+import sys
+import textwrap
+import time
+import urllib
+from functools import partial
+from urlparse import urljoin, urlsplit
+from cgi import escape as e
+
+assert sys.version_info[0] == 2 and sys.version_info[1] >= 5, "Please use Python 2.5 or greater"
+
+import link_parse
+import nbhttp
+import red
+import red_defns
+import red_fetcher
+import red_header
+import red_speak as rs
+from response_analyse import relative_time, f_num
+
+# HTML template for error bodies
+error_template = u"""\
+
+<p class="error">
+ %s
+</p>
+"""
+
+nl = u"\n"
+
+try:
+ locale.setlocale(locale.LC_ALL, locale.normalize(lang))
+except:
+ locale.setlocale(locale.LC_ALL, '')
+
+class RedWebUi(object):
+ """
+ A Web UI for RED.
+
+ Given a URI, run RED on it and present the results to output as HTML.
+ If descend is true, spider the links and present a summary.
+ """
+ def __init__(self, test_uri, req_hdrs, base_uri, output_hdrs, output_body, descend=False):
+ self.base_uri = base_uri
+ self.req_hdrs = req_hdrs
+ self.descend_links = descend
+ self.output = output_body
+ self.start = time.time()
+ timeout = nbhttp.schedule(max_runtime, self.timeoutError)
+ html_header = red_header.__doc__ % {
+ 'static': static_root,
+ 'version': red.__version__,
+ 'html_uri': e(test_uri),
+ 'js_uri': e_js(test_uri),
+ 'js_req_hdrs': ", ".join(['["%s", "%s"]' % (
+ e_js(n), e_js(v)) for n,v in req_hdrs]),
+ 'extra_js': self.presentExtra('.js')
+ }
+ self.links = {} # {type: set(link...)}
+ self.link_count = 0
+ self.link_droids = [] # list of REDs for summary output
+ self.hidden_text = [] # list of things to hide for popups
+ self.body_sample = "" # sample of the response body
+ self.body_sample_size = 1024 * 128 # how big to allow the sample to be
+ self.sample_seen = 0
+ self.sample_complete = True
+ ct_hdr = ("Content-Type", "text/html; charset=utf-8")
+ if test_uri:
+ output_hdrs("200 OK", [ct_hdr, ("Cache-Control", "max-age=60, must-revalidate")])
+ self.output(html_header.encode(charset, 'replace'))
+ self.link_parser = link_parse.HTMLLinkParser(test_uri, self.processLink, self.updateStatus)
+ self.red = red.ResourceExpertDroid(
+ test_uri,
+ req_hdrs=req_hdrs,
+ status_cb=self.updateStatus,
+ body_procs=[self.link_parser.feed, self.storeSample],
+ )
+ self.updateStatus('Done.')
+ if self.red.res_complete:
+ if self.descend_links and self.link_count > 0:
+ self.output(TablePresenter(self, self.red).presentResults())
+ else:
+ self.output(DetailPresenter(self, self.red).presentResults())
+ elapsed = time.time() - self.start
+ self.updateStatus("RED made %(requests)s requests in %(elapsed)2.2f seconds." % {
+ 'requests': red_fetcher.total_requests,
+ 'elapsed': elapsed
+ });
+ else:
+ self.output("<div id='main'>\n")
+ if self.red.res_error['desc'] == nbhttp.error.ERR_CONNECT['desc']:
+ self.output(error_template % "Could not connect to the server (%s)" % \
+ self.red.res_error.get('detail', "unknown"))
+ elif self.red.res_error['desc'] == nbhttp.error.ERR_URL['desc']:
+ self.output(error_template % self.red.res_error.get(
+ 'detail', "RED can't fetch that URL."))
+ elif self.red.res_error['desc'] == nbhttp.error.ERR_READ_TIMEOUT['desc']:
+ self.output(error_template % self.red.res_error['desc'])
+ elif self.red.res_error['desc'] == nbhttp.error.ERR_HTTP_VERSION['desc']:
+ self.output(error_template % "<code>%s</code> isn't HTTP." % e(self.red.res_error.get('detail', '')[:20]))
+ else:
+ raise AssertionError, "Unidentified incomplete response error."
+ self.output(self.presentFooter())
+ self.output("</div>\n")
+ else: # no test_uri
+ output_hdrs("200 OK", [ct_hdr, ("Cache-Control", "max-age=300")])
+ self.output(html_header.encode(charset, 'replace'))
+ self.output(self.presentExtra())
+ self.output(self.presentFooter())
+ self.output("</div>\n")
+ self.output("</body></html>\n")
+ timeout.delete()
+
+ def processLink(self, link, tag, title):
+ "Handle a link from content"
+ self.link_count += 1
+ if not self.links.has_key(tag):
+ self.links[tag] = set()
+ if self.descend_links and tag not in ['a'] and \
+ link not in self.links[tag]:
+ self.link_droids.append((
+ red.ResourceExpertDroid(
+ urljoin(self.link_parser.base, link),
+ req_hdrs=self.req_hdrs,
+ status_cb=self.updateStatus
+ ),
+ tag
+ ))
+ self.links[tag].add(link)
+
+ def storeSample(self, response, chunk):
+ """store the first self.sample_size bytes of the response"""
+ if self.sample_seen + len(chunk) < self.body_sample_size:
+ self.body_sample += chunk
+ self.sample_seen += len(chunk)
+ elif self.sample_seen < self.body_sample_size:
+ max_chunk = self.body_sample_size - self.sample_seen
+ self.body_sample += chunk[:max_chunk]
+ self.sample_seen += len(chunk)
+ self.sample_complete = False
+ else:
+ self.sample_complete = False
+
+ def presentBody(self):
+ """show the stored body sample"""
+ try:
+ uni_sample = unicode(self.body_sample,
+ self.link_parser.doc_enc or self.link_parser.http_enc, 'ignore')
+ except LookupError:
+ uni_sample = unicode(self.body_sample, charset, 'ignore')
+ safe_sample = e(uni_sample)
+ message = ""
+ for tag, link_set in self.links.items():
+ for link in link_set:
+ def link_to(matchobj):
+ return r"%s<a href='%s' class='nocode'>%s</a>%s" % (
+ matchobj.group(1),
+ u"?uri=%s" % e_query_arg(urljoin(self.link_parser.base, link)),
+ e(link),
+ matchobj.group(1)
+ )
+ safe_sample = re.sub(r"(['\"])%s\1" % re.escape(link), link_to, safe_sample)
+ if not self.sample_complete:
+ message = "<p class='note'>RED isn't showing the whole body, because it's so big!</p>"
+ return """<pre class="prettyprint">%s</pre>\n%s""" % (safe_sample, message)
+
+ def presentExtra(self, type='.html'):
+ """
+ Show extra content from the extra_dir, if any. MUST be UTF-8.
+ Type controls the extension included; currently supported:
+ - '.html': shown only on start page, after input block
+ - '.js': javascript block (with script tag surrounding)
+ included on every page view.
+ """
+ o = []
+ if extra_dir and os.path.isdir(extra_dir):
+ extra_files = [p for p in os.listdir(extra_dir) if os.path.splitext(p)[1] == type]
+ for extra_file in extra_files:
+ extra_path = os.path.join(extra_dir, extra_file)
+ try:
+ o.append(codecs.open(extra_path, mode='r', encoding=charset, errors='replace').read())
+ except IOError, why:
+ o.append("<!-- error opening %s: %s -->" % (extra_file, why))
+ return nl.join(o)
+
+ def presentHiddenList(self):
+ "return a list of hidden items to be used by the UI"
+ return "<ul>" + "\n".join(["<li id='%s'>%s</li>" % (id, text) for \
+ (id, text) in self.hidden_text]) + "</ul>"
+
+ def presentFooter(self):
+ "page footer"
+ return """\
+<br clear="all"/> <!-- FIXME -->
+<div class="footer">
+<p class="version">this is RED %(version)s.</p>
+<p class="navigation">
+<a href="http://REDbot.org/about/">about</a> |
+<a href="http://blog.REDbot.org/">blog</a> |
+<a href="http://REDbot.org/project">project</a> |
+<a href="javascript:location%%20=%%20'%(baseuri)s?uri='+escape(location);%%20void%%200"
+title="drag me to your toolbar to use RED any time.">RED</a> bookmarklet
+</p>
+</div>
+
+""" % {
+ 'baseuri': self.base_uri,
+ 'version': red.__version__,
+ }
+
+ def updateStatus(self, message):
+ "Update the status bar of the browser"
+ msg = u"""
+<script>
+<!-- %3.3f
+window.status="%s";
+-->
+</script>
+ """ % (time.time() - self.start, e(message))
+ self.output(msg.encode(charset, 'replace'))
+ sys.stdout.flush()
+
+ def timeoutError(self):
+ """ Max runtime reached."""
+ self.output(error_template % ("RED timeout."))
+ self.output("<!-- Outstanding Connections\n")
+ class DummyStream:
+ @staticmethod
+ def write(s):
+ self.output(s)
+ ds = DummyStream()
+ for conn in red_fetcher.outstanding_requests:
+ self.output("*** %s\n" % conn.uri)
+ pprint.pprint(conn.__dict__, ds)
+ if conn.client:
+ pprint.pprint(conn.client.__dict__, ds)
+ if conn.client._tcp_conn:
+ pprint.pprint(conn.client._tcp_conn.__dict__, ds)
+
+ self.output("-->\n")
+ nbhttp.stop() # FIXME: not appropriate for standalone server
+
+
+class DetailPresenter(object):
+ """
+ Present a single RED response in detail.
+ """
+ # the order of message categories to display
+ msg_categories = [
+ rs.c.GENERAL, rs.c.CONNECTION, rs.c.CONNEG, rs.c.CACHING, rs.c.VALIDATION, rs.c.RANGE
+ ]
+
+ # Media types that browsers can view natively
+ viewable_types = [
+ 'text/plain',
+ 'text/html',
+ 'application/xhtml+xml',
+ 'application/pdf',
+ 'image/gif',
+ 'image/jpeg',
+ 'image/jpg',
+ 'image/png',
+ 'application/javascript',
+ 'application/x-javascript',
+ 'text/javascript',
+ 'text/x-javascript',
+ 'text/css',
+ ]
+
+ # Validator uris, by media type
+ validators = {
+ 'text/html': "http://validator.w3.org/check?uri=%s",
+ 'text/css': "http://jigsaw.w3.org/css-validator/validator?uri=%s&",
+ 'application/xhtml+xml': "http://validator.w3.org/check?uri=%s",
+ 'application/atom+xml': "http://feedvalidator.org/check.cgi?url=%s",
+ 'application/rss+xml': "http://feedvalidator.org/check.cgi?url=%s",
+ }
+
+ # HTML template for the main response body
+ template = u"""\
+ <div id="left_column">
+ <pre id='response'>%(response)s</pre>
+
+ <p class="options">
+ %(options)s
+ </p>
+ </div>
+
+ <div id="right_column">
+ <div id='details'>
+ %(messages)s
+ </div>
+ </div>
+
+ <br clear="all"/> <!-- FIXME -->
+
+ <div id='body'>
+ %(body)s
+ </div>
+
+ %(footer)s
+
+ <div class='hidden' id='hidden_list'>%(hidden_list)s</div>
+
+ """
+
+ def __init__(self, ui, red):
+ self.ui = ui
+ self.red = red
+ self.header_presenter = HeaderPresenter(self.red)
+
+ def presentResults(self):
+ "Fill in the template with RED's results."
+ result_strings = {
+ 'response': self.presentResponse(),
+ 'options': self.presentOptions(),
+ 'messages': nl.join([self.presentCategory(cat) for cat in self.msg_categories]),
+ 'body': self.ui.presentBody(),
+ 'footer': self.ui.presentFooter(),
+ 'hidden_list': self.ui.presentHiddenList(),
+ }
+ return (self.template % result_strings).encode(charset)
+
+ def presentResponse(self):
+ "Return the HTTP response line and headers as HTML"
+ return \
+ u" <span class='status'>HTTP/%s %s %s</span>\n" % (
+ e(str(self.red.res_version)),
+ e(str(self.red.res_status)),
+ e(self.red.res_phrase)
+ ) + \
+ nl.join([self.presentHeader(f,v) for (f,v) in self.red.res_hdrs])
+
+ def presentHeader(self, name, value):
+ "Return an individual HTML header as HTML"
+ token_name = "header-%s" % name.lower()
+ py_name = "HDR_" + name.upper().replace("-", "_")
+ if hasattr(red_defns, py_name) and token_name not in [i[0] for i in self.ui.hidden_text]:
+ defn = getattr(red_defns, py_name)[lang] % {
+ 'field_name': name,
+ }
+ self.ui.hidden_text.append((token_name, defn))
+ return u" <span name='%s' class='hdr'>%s:%s</span>" % (
+ e(token_name), e(name), self.header_presenter.Show(name, value))
+
+ def presentCategory(self, category):
+ "For a given category, return all of the non-detail messages in it as an HTML list"
+ messages = [msg for msg in self.red.messages if msg.category == category]
+ if not messages:
+ return nl
+ out = []
+ if [msg for msg in messages]:
+ out.append(u"<h3>%s</h3>\n<ul>\n" % category)
+ for m in messages:
+ out.append(u"<li class='%s %s msg' name='msgid-%s'><span>%s</span></li>" %
+ (m.level, e(m.subject), id(m), e(m.summary[lang] % m.vars))
+ )
+ self.ui.hidden_text.append(("msgid-%s" % id(m), m.text[lang] % m.vars))
+ smsgs = [msg for msg in getattr(m.subrequest, "messages", []) if msg.level in [rs.l.BAD]]
+ if smsgs:
+ out.append(u"<ul>")
+ for sm in smsgs:
+ out.append(
+ u"<li class='%s %s msg' name='msgid-%s'><span>%s</span></li>" %
+ (sm.level, e(sm.subject), id(sm), e(sm.summary[lang] % sm.vars))
+ )
+ self.ui.hidden_text.append(("msgid-%s" % id(sm), sm.text[lang] % sm.vars))
+ out.append(u"</ul>")
+ out.append(u"</ul>\n")
+ return nl.join(out)
+
+ def presentOptions(self):
+ "Return things that the user can do with the URI as HTML links"
+ options = []
+ media_type = self.red.parsed_hdrs.get('content-type', [""])[0]
+ options.append((u"response headers: %s bytes" % f_num(self.red.client.input_header_length),
+ "how large the response header block is, including the status line"))
+ options.append((u"body: %s bytes" % f_num(self.red.res_body_len),
+ "how large the response body is"))
+ transfer_overhead = self.red.client.input_transfer_length - self.red.res_body_len
+ if transfer_overhead > 0:
+ options.append((u"transfer overhead: %s bytes" % f_num(transfer_overhead),
+ "how much using chunked encoding adds to the response size"))
+ options.append(None)
+ options.append((u"<a href='#' id='body_view'>view body</a>", ""))
+ if self.validators.has_key(media_type):
+ options.append((u"<a href='%s'>validate body</a>" %
+ self.validators[media_type] % e_query_arg(self.red.uri), ""))
+ if self.ui.link_count > 0:
+ options.append((u"<a href='?descend=True&uri=%s'>check assets</a>" %
+ e_query_arg(self.red.uri), "run RED on images, frames and embedded links"))
+ return nl.join([o and "<span class='option' title='%s'>%s</span>" % (o[1], o[0]) or "<br>" for o in options])
+
+
+class HeaderPresenter(object):
+ """
+ Present a HTTP header in the Web UI. By default, it will:
+ - Escape HTML sequences to avoid XSS attacks
+ - Wrap long lines
+ However if a method is present that corresponds to the header's
+ field-name, that method will be run instead to represent the value.
+ """
+
+ def __init__(self, red):
+ self.red = red
+
+ def Show(self, name, value):
+ "Return the given header name/value pair after presentation processing"
+ name = name.lower()
+ name_token = name.replace('-', '_')
+ if name_token[0] != "_" and hasattr(self, name_token):
+ return getattr(self, name_token)(name, value)
+ else:
+ return self.I(e(value), len(name))
+
+ def BARE_URI(self, name, value):
+ "Present a bare URI header value"
+ value = value.rstrip()
+ svalue = value.lstrip()
+ space = len(value) - len(svalue)
+ return u"%s<a href='?uri=%s'>%s</a>" % ( " " * space,
+ e(urljoin(self.red.uri, svalue)), self.I(e(svalue), len(name)))
+ content_location = \
+ location = \
+ x_xrds_location = \
+ BARE_URI
+
+ @staticmethod
+ def I(value, sub_width):
+ "wrap a line to fit in the header box"
+ hdr_sz = 75
+ sw = hdr_sz - min(hdr_sz-1, sub_width)
+ tr = textwrap.TextWrapper(width=sw, subsequent_indent=" "*8, break_long_words=True)
+ return tr.fill(value)
+
+
+class TablePresenter(object):
+ """
+ Present a summary of multiple RED responses.
+ """
+ # HTML template for the main response body
+ template = u"""\
+ </div>
+
+ <table id='summary'>
+ %(table)s
+ </table>
+
+ <div id='details'>
+ %(problems)s
+ </div>
+
+ <div class='hidden' id='hidden_list'>%(hidden_list)s</div>
+
+ %(footer)s
+
+ """
+ def __init__(self, ui, red):
+ self.ui = ui
+ self.red = red
+ self.problems = []
+
+ def presentResults(self):
+ "Fill in the template with RED's results."
+ result_strings = {
+ 'table': self.presentTables(),
+ 'problems': self.presentProblems(),
+ 'footer': self.ui.presentFooter(),
+ 'hidden_list': self.ui.presentHiddenList(),
+ }
+ return (self.template % result_strings).encode(charset)
+
+ link_order = [
+ ('link', 'Head Links'),
+ ('script', 'Script Links'),
+ ('frame', 'Frame Links'),
+ ('iframe', 'IFrame Links'),
+ ('img', 'Image Links'),
+ ]
+ def presentTables(self):
+ out = [self.presentTableHeader()]
+ out.append(self.presentDroid(self.red))
+ for hdr_tag, heading in self.link_order:
+ droids = [d[0] for d in self.ui.link_droids if d[1] == hdr_tag]
+ if droids:
+ droids.sort(key=operator.attrgetter('uri'))
+ out.append(self.presentTableHeader(heading + " (%s)" % len(droids)))
+ out += [self.presentDroid(d) for d in droids]
+ return nl.join(out)
+
+ def presentDroid(self, red):
+ out = [u'<tr class="droid %s">']
+ m = 50
+ if red.parsed_hdrs.get('content-type', [""])[0][:6] == 'image/':
+ cl = " class='preview'"
+ else:
+ cl = ""
+ if len(red.uri) > m:
+ out.append(u"""<td class="uri"><a href="%s" title="%s"%s>
+ %s<span class="fade1">%s</span><span class="fade2">%s</span><span class="fade3">%s</span>
+ </a></td>""" % (
+ u"?uri=%s" % e_query_arg(red.uri), e(red.uri), cl, e(red.uri[:m-2]),
+ e(red.uri[m-2]), e(red.uri[m-1]), e(red.uri[m]),
+ )
+ )
+ else:
+ out.append(u'<td class="uri"><a href="%s" title="%s"%s>%s</a></td>' % (
+ u"?uri=%s" % e(red.uri), e(red.uri), cl, e(red.uri)))
+ if red.res_complete:
+ if red.res_status in ['301', '302', '303', '307'] and \
+ red.parsed_hdrs.has_key('location'):
+ out.append(u'<td><a href="?descend=True&uri=%s">%s</a></td>' % (
+ urljoin(red.uri, red.parsed_hdrs['location']), red.res_status))
+ elif red.res_status in ['400', '404', '410']:
+ out.append(u'<td class="bad">%s</td>' % red.res_status)
+ else:
+ out.append(u'<td>%s</td>' % red.res_status)
+ # pconn
+ out.append(self.presentYesNo(red.store_shared))
+ out.append(self.presentYesNo(red.store_private))
+ out.append(self.presentTime(red.age))
+ out.append(self.presentTime(red.freshness_lifetime))
+ out.append(self.presentYesNo(red.stale_serveable))
+ out.append(self.presentYesNo(red.ims_support))
+ out.append(self.presentYesNo(red.inm_support))
+ if red.gzip_support:
+ out.append(u"<td>%s%%</td>" % red.gzip_savings)
+ else:
+ out.append(self.presentYesNo(red.gzip_support))
+ out.append(self.presentYesNo(red.partial_support))
+ problems = [m for m in red.messages if m.level in [rs.l.WARN, rs.l.BAD]]
+ # TODO: problems += sum([m[2].messages for m in red.messages if m[2] != None], [])
+ out.append(u"<td>")
+ pr_enum = []
+ for problem in problems:
+ if problem not in self.problems:
+ self.problems.append(problem)
+ pr_enum.append(self.problems.index(problem))
+ # add the problem number to the <tr> so we can highlight appropriately
+ out[0] = out[0] % u" ".join(["%d" % p for p in pr_enum])
+ # append the actual problem numbers to the final <td>
+ for p in pr_enum:
+ m = self.problems[p]
+ out.append("<span class='prob_num'> %s <span class='hidden'>%s</span></span>" % (p + 1, e(m.summary[lang] % m.vars)))
+ else:
+ out.append('<td colspan="11">%s' % red.res_error['desc'])
+ out.append(u"</td>")
+ out.append(u'</tr>')
+ return nl.join(out)
+
+ def presentTableHeader(self, heading=None):
+ return u"""
+ <tr>
+ <th title="The URI tested. Click to run a detailed analysis.">%s</th>
+ <th title="The HTTP status code returned.">status</th>
+ <th title="Whether a shared (e.g., proxy) cache can store the response.">shared<br>cache</th>
+ <th title="Whether a private (e.g., browser) cache can store the response.">private<br>cache</th>
+ <th title="How long the response had been cached before RED got it.">age</th>
+ <th title="How long a cache can treat the response as fresh.">fresh</th>
+ <th title="Whether a cache can serve the response once it becomes stale (e.g., when it can't contact the origin server).">serve<br>stale</th>
+ <th title="Whether If-Modified-Since validation is supported, using Last-Modified.">IMS</th>
+ <th title="Whether If-None-Match validation is supported, using ETags.">INM</th>
+ <th title="Whether negotiation for gzip compression is supported; if so, the percent of the original size saved.">gzip</th>
+ <th title="Whether partial responses are supported.">partial<br>content</th>
+ <th title="Issues encountered.">problems</th>
+ </tr>
+ """ % (heading or "URI")
+
+ def presentTime(self, value):
+ if value is None:
+ return u'<td>-</td>'
+ else:
+ return u'<td>%s</td>' % relative_time(value, 0, 0)
+
+ def presentYesNo(self, value):
+ if value is True:
+ return u'<td><img src="static/icon/accept1.png" alt="yes" title="yes"/></td>'
+ elif value is False:
+ return u'<td><img src="static/icon/remove-16.png" alt="no" title="no"/></td>'
+ elif value is None:
+ return u'<td><img src="static/icon/help1.png" alt="?" title="unknown"/></td>'
+ else:
+ raise AssertionError, 'unknown value'
+
+ def presentProblems(self):
+ out = ['<br /><h2>Problems</h2><ol>']
+ for m in self.problems:
+ out.append(u"<li class='%s %s msg' name='msgid-%s'><span>%s</span></li>" %
+ (m.level, e(m.subject), id(m), e(m.summary[lang] % m.vars))
+ )
+ self.ui.hidden_text.append(("msgid-%s" % id(m), m.text[lang] % m.vars))
+ out.append(u"</ol>\n")
+ return nl.join(out)
+
+# Escaping functions.
+uri_gen_delims = r":/?#[]@"
+uri_sub_delims = r"!$&'()*+,;="
+def unicode_url_escape(url, safe):
+ "URL esape a unicode string. Assume that anything already encoded is to be left alone."
+ # also include "~" because it doesn't need to be encoded, but Python does anyway :/
+ return urllib.quote(url.encode(charset, 'replace'), safe + '%~')
+e_url = partial(unicode_url_escape, safe=uri_gen_delims + uri_sub_delims)
+e_authority = partial(unicode_url_escape, safe=uri_sub_delims + r"[]:@")
+e_path = partial(unicode_url_escape, safe=uri_sub_delims + r":@/")
+e_path_seg = partial(unicode_url_escape, safe=uri_sub_delims + r":@")
+e_query = partial(unicode_url_escape, safe=uri_sub_delims + r":@/?")
+e_query_arg = partial(unicode_url_escape, safe=r"!$'()*+,;:@/?")
+
+def e_js(instr):
+ "Make sure instr is safe for writing into a double-quoted JavaScript string."
+ if not instr: return ""
+ instr = instr.replace('"', r'\"')
+ if instr[-1] == '\\':
+ instr += '\\'
+ return instr
+
+
+# adapted from cgitb.Hook
+def except_handler(etype, evalue, etb):
+ """
+ Log uncaught exceptions and display a friendly error.
+ Assumes output to STDOUT (i.e., CGI only).
+ """
+ import cgitb
+ print cgitb.reset()
+ if logdir is None:
+ print error_template % """
+A problem has occurred, but it probably isn't your fault.
+"""
+ else:
+ import stat
+ import tempfile
+ import traceback
+ try:
+ doc = cgitb.html((etype, evalue, etb), 5)
+ except: # just in case something goes wrong
+ doc = ''.join(traceback.format_exception(etype, evalue, etb))
+ try:
+ while etb.tb_next != None:
+ etb = etb.tb_next
+ e_file = etb.tb_frame.f_code.co_filename
+ e_line = etb.tb_frame.f_lineno
+ dir = os.path.join(logdir, os.path.split(e_file)[-1])
+ if not os.path.exists(dir):
+ os.umask(0000)
+ os.makedirs(dir)
+ (fd, path) = tempfile.mkstemp(prefix="%s_" % e_line, suffix='.html', dir=dir)
+ fh = os.fdopen(fd, 'w')
+ fh.write(doc)
+ fh.write("<h2>Outstanding Connections</h2>\n<pre>")
+ for conn in red_fetcher.outstanding_requests:
+ fh.write("*** %s - %s\n" % (conn.uri, hex(id(conn))))
+ pprint.pprint(conn.__dict__, fh)
+ if conn.client:
+ pprint.pprint(conn.client.__dict__, fh)
+ if conn.client._tcp_conn:
+ pprint.pprint(conn.client._tcp_conn.__dict__, fh)
+ fh.write("</pre>\n")
+ fh.close()
+ os.chmod(path, stat.S_IROTH)
+ print error_template % """
+A problem has occurred, but it probably isn't your fault.
+RED has remembered it, and we'll try to fix it soon."""
+ except:
+ print error_template % """\
+A problem has occurred, but it probably isn't your fault.
+RED tried to save it, but it couldn't! Oops.<br>
+Please e-mail the information below to
+<a href='mailto:red@redbot.org'>red@redbot.org</a>
+and we'll look into it."""
+ print "<h3>Original Error</h3>"
+ print "<pre>"
+ print ''.join(traceback.format_exception(etype, evalue, etb))
+ print "</pre>"
+ print "<h3>Write Error</h3>"
+ print "<pre>"
+ type, value, tb = sys.exc_info()
+ print ''.join(traceback.format_exception(type, value, tb))
+ print "</pre>"
+ sys.stdout.flush()
+
+def cgi_main():
+ """Run RED as a CGI Script."""
+ sys.excepthook = except_handler
+ sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0)
+ form = cgi.FieldStorage()
+ test_uri = form.getfirst("uri", "").decode(charset, 'replace')
+ req_hdrs = [tuple(rh.split(":", 1))
+ for rh in form.getlist("req_hdr")
+ if rh.find(":") > 0
+ ]
+ descend = form.getfirst('descend', False)
+ base_uri = "http://%s%s%s" % ( # FIXME: only supports HTTP
+ os.environ.get('HTTP_HOST'),
+ os.environ.get('SCRIPT_NAME'),
+ os.environ.get('PATH_INFO', '')
+ )
+ def output_hdrs(status, res_hdrs):
+ sys.stdout.write("Status: %s\n" % status)
+ for k,v in res_hdrs:
+ sys.stdout.write("%s: %s\n" % (k,v))
+ sys.stdout.write("\n")
+ def output_body(o):
+ sys.stdout.write(o)
+ RedWebUi(test_uri, req_hdrs, base_uri, output_hdrs, output_body, descend)
+
+def standalone_main(port, static_dir):
+ """Run RED as a standalone Web server."""
+ static_files = {}
+ for file in os.listdir(static_dir):
+ sys.stderr.write("Loading %s...\n" % file)
+ try:
+ # FIXME: need to load icons
+ static_files["/static/%s" % file] = open(os.path.join(static_dir, file)).read()
+ except IOError:
+ sys.stderr.write("failed.\n")
+ def red_handler(method, uri, req_hdrs, res_start, req_pause):
+ p_uri = urlsplit(uri)
+ if static_files.has_key(p_uri.path):
+ res_body, res_done = res_start("200", "OK", [], nbhttp.dummy)
+ res_body(static_files[p_uri.path])
+ res_done(None)
+ elif p_uri.path == "/":
+ query = cgi.parse_qs(p_uri.query)
+ test_uri = query.get('uri', [""])[0]
+ test_hdrs = [] #FIXME
+ base_uri = "/"
+ descend = query.has_key('descend')
+ res_hdrs = [('Content-Type', 'text/html; charset=utf-8')] #FIXME: need to send proper content-type back, caching headers
+ res_body, res_done = res_start("200", "OK", res_hdrs, nbhttp.dummy)
+ sys.stderr.write("%s %s %s\n" % (str(descend), test_uri, test_hdrs))
+ RedWebUi(test_uri, test_hdrs, base_uri, res_body, output_hdr, descend)
+ res_done(None)
+ else:
+ res_body, res_done = res_start("404", "Not Found", [], nbhttp.dummy)
+ res_done(None)
+ return nbhttp.dummy, nbhttp.dummy
+ nbhttp.Server("", port, red_handler)
+ nbhttp.run() # FIXME: catch errors
+ # FIXME: catch interrupts
+ # FIXME: run/stop in red_fetcher
+ # FIXME: logging
+
+def standalone_monitor(port, static_dir):
+ """Fork a process as a standalone Web server and watch it."""
+ from multiprocessing import Process
+ while True:
+ p = Process(target=standalone_main, args=(port, static_dir))
+ sys.stderr.write("* Starting RED server...\n")
+ p.start()
+ p.join()
+ # TODO: listen to socket and drop privs
+
+if __name__ == "__main__":
+ try:
+ # FIXME: usage
+ port = sys.argv[1]
+ static_dir = sys.argv[2]
+ standalone_monitor(int(port), static_dir)
+ except IndexError:
+ cgi_main()
\ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/web/Makefile Mon Jul 26 14:12:44 2010 -0400
@@ -0,0 +1,14 @@
+JSFILES = jquery.js jquery.hoverIntent.js prettify.js
+CSSFILES = red_style.css prettify.css
+YUI_COMPRESSOR_JAR = ~/Applications/yuicompressor-2.4.3.jar
+
+all: script.js style.css
+
+clean:
+ rm script.js style.css
+
+script.js:
+ cat $(JSFILES) > script.js
+
+style.css:
+ cat $(CSSFILES) | java -jar $(YUI_COMPRESSOR_JAR) --type css -o style.css
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/web/icon/README-ICONS Mon Jul 26 14:12:44 2010 -0400
@@ -0,0 +1,1 @@
+Icons courtesy Momenticon <http://momenticon.com/>.
\ No newline at end of file
Binary file web/icon/accept1.png has changed
Binary file web/icon/help1.png has changed
Binary file web/icon/infomation-16.png has changed
Binary file web/icon/remove-16.png has changed
Binary file web/icon/yellowflag1.png has changed
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/web/jquery.hoverIntent.js Mon Jul 26 14:12:44 2010 -0400
@@ -0,0 +1,9 @@
+/**
+* hoverIntent r5 // 2007.03.27 // jQuery 1.1.2+
+* <http://cherne.net/brian/resources/jquery.hoverIntent.html>
+*
+* @param f onMouseOver function || An object with configuration options
+* @param g onMouseOut function || Nothing (use configuration options object)
+* @author Brian Cherne <brian@cherne.net>
+*/
+(function($){$.fn.hoverIntent=function(f,g){var cfg={sensitivity:7,interval:100,timeout:0};cfg=$.extend(cfg,g?{over:f,out:g}:f);var cX,cY,pX,pY;var track=function(ev){cX=ev.pageX;cY=ev.pageY;};var compare=function(ev,ob){ob.hoverIntent_t=clearTimeout(ob.hoverIntent_t);if((Math.abs(pX-cX)+Math.abs(pY-cY))<cfg.sensitivity){$(ob).unbind("mousemove",track);ob.hoverIntent_s=1;return cfg.over.apply(ob,[ev]);}else{pX=cX;pY=cY;ob.hoverIntent_t=setTimeout(function(){compare(ev,ob);},cfg.interval);}};var delay=function(ev,ob){ob.hoverIntent_t=clearTimeout(ob.hoverIntent_t);ob.hoverIntent_s=0;return cfg.out.apply(ob,[ev]);};var handleHover=function(e){var p=(e.type=="mouseover"?e.fromElement:e.toElement)||e.relatedTarget;while(p&&p!=this){try{p=p.parentNode;}catch(e){p=this;}}if(p==this){return false;}var ev=jQuery.extend({},e);var ob=this;if(ob.hoverIntent_t){ob.hoverIntent_t=clearTimeout(ob.hoverIntent_t);}if(e.type=="mouseover"){pX=ev.pageX;pY=ev.pageY;$(ob).bind("mousemove",track);if(ob.hoverIntent_s!=1){ob.hoverIntent_t=setTimeout(function(){compare(ev,ob);},cfg.interval);}}else{$(ob).unbind("mousemove",track);if(ob.hoverIntent_s==1){ob.hoverIntent_t=setTimeout(function(){delay(ev,ob);},cfg.timeout);}}};return this.mouseover(handleHover).mouseout(handleHover);};})(jQuery);
\ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/web/jquery.js Mon Jul 26 14:12:44 2010 -0400
@@ -0,0 +1,19 @@
+/*
+ * jQuery JavaScript Library v1.3.1
+ * http://jquery.com/
+ *
+ * Copyright (c) 2009 John Resig
+ * Dual licensed under the MIT and GPL licenses.
+ * http://docs.jquery.com/License
+ *
+ * Date: 2009-01-21 20:42:16 -0500 (Wed, 21 Jan 2009)
+ * Revision: 6158
+ */
+(function(){var l=this,g,y=l.jQuery,p=l.$,o=l.jQuery=l.$=function(E,F){return new o.fn.init(E,F)},D=/^[^<]*(<(.|\s)+>)[^>]*$|^#([\w-]+)$/,f=/^.[^:#\[\.,]*$/;o.fn=o.prototype={init:function(E,H){E=E||document;if(E.nodeType){this[0]=E;this.length=1;this.context=E;return this}if(typeof E==="string"){var G=D.exec(E);if(G&&(G[1]||!H)){if(G[1]){E=o.clean([G[1]],H)}else{var I=document.getElementById(G[3]);if(I&&I.id!=G[3]){return o().find(E)}var F=o(I||[]);F.context=document;F.selector=E;return F}}else{return o(H).find(E)}}else{if(o.isFunction(E)){return o(document).ready(E)}}if(E.selector&&E.context){this.selector=E.selector;this.context=E.context}return this.setArray(o.makeArray(E))},selector:"",jquery:"1.3.1",size:function(){return this.length},get:function(E){return E===g?o.makeArray(this):this[E]},pushStack:function(F,H,E){var G=o(F);G.prevObject=this;G.context=this.context;if(H==="find"){G.selector=this.selector+(this.selector?" ":"")+E}else{if(H){G.selector=this.selector+"."+H+"("+E+")"}}return G},setArray:function(E){this.length=0;Array.prototype.push.apply(this,E);return this},each:function(F,E){return o.each(this,F,E)},index:function(E){return o.inArray(E&&E.jquery?E[0]:E,this)},attr:function(F,H,G){var E=F;if(typeof F==="string"){if(H===g){return this[0]&&o[G||"attr"](this[0],F)}else{E={};E[F]=H}}return this.each(function(I){for(F in E){o.attr(G?this.style:this,F,o.prop(this,E[F],G,I,F))}})},css:function(E,F){if((E=="width"||E=="height")&&parseFloat(F)<0){F=g}return this.attr(E,F,"curCSS")},text:function(F){if(typeof F!=="object"&&F!=null){return this.empty().append((this[0]&&this[0].ownerDocument||document).createTextNode(F))}var E="";o.each(F||this,function(){o.each(this.childNodes,function(){if(this.nodeType!=8){E+=this.nodeType!=1?this.nodeValue:o.fn.text([this])}})});return E},wrapAll:function(E){if(this[0]){var F=o(E,this[0].ownerDocument).clone();if(this[0].parentNode){F.insertBefore(this[0])}F.map(function(){var G=this;while(G.firstChild){G=G.firstChild}return G}).append(this)}return this},wrapInner:function(E){return this.each(function(){o(this).contents().wrapAll(E)})},wrap:function(E){return this.each(function(){o(this).wrapAll(E)})},append:function(){return this.domManip(arguments,true,function(E){if(this.nodeType==1){this.appendChild(E)}})},prepend:function(){return this.domManip(arguments,true,function(E){if(this.nodeType==1){this.insertBefore(E,this.firstChild)}})},before:function(){return this.domManip(arguments,false,function(E){this.parentNode.insertBefore(E,this)})},after:function(){return this.domManip(arguments,false,function(E){this.parentNode.insertBefore(E,this.nextSibling)})},end:function(){return this.prevObject||o([])},push:[].push,find:function(E){if(this.length===1&&!/,/.test(E)){var G=this.pushStack([],"find",E);G.length=0;o.find(E,this[0],G);return G}else{var F=o.map(this,function(H){return o.find(E,H)});return this.pushStack(/[^+>] [^+>]/.test(E)?o.unique(F):F,"find",E)}},clone:function(F){var E=this.map(function(){if(!o.support.noCloneEvent&&!o.isXMLDoc(this)){var I=this.cloneNode(true),H=document.createElement("div");H.appendChild(I);return o.clean([H.innerHTML])[0]}else{return this.cloneNode(true)}});var G=E.find("*").andSelf().each(function(){if(this[h]!==g){this[h]=null}});if(F===true){this.find("*").andSelf().each(function(I){if(this.nodeType==3){return}var H=o.data(this,"events");for(var K in H){for(var J in H[K]){o.event.add(G[I],K,H[K][J],H[K][J].data)}}})}return E},filter:function(E){return this.pushStack(o.isFunction(E)&&o.grep(this,function(G,F){return E.call(G,F)})||o.multiFilter(E,o.grep(this,function(F){return F.nodeType===1})),"filter",E)},closest:function(E){var F=o.expr.match.POS.test(E)?o(E):null;return this.map(function(){var G=this;while(G&&G.ownerDocument){if(F?F.index(G)>-1:o(G).is(E)){return G}G=G.parentNode}})},not:function(E){if(typeof E==="string"){if(f.test(E)){return this.pushStack(o.multiFilter(E,this,true),"not",E)}else{E=o.multiFilter(E,this)}}var F=E.length&&E[E.length-1]!==g&&!E.nodeType;return this.filter(function(){return F?o.inArray(this,E)<0:this!=E})},add:function(E){return this.pushStack(o.unique(o.merge(this.get(),typeof E==="string"?o(E):o.makeArray(E))))},is:function(E){return !!E&&o.multiFilter(E,this).length>0},hasClass:function(E){return !!E&&this.is("."+E)},val:function(K){if(K===g){var E=this[0];if(E){if(o.nodeName(E,"option")){return(E.attributes.value||{}).specified?E.value:E.text}if(o.nodeName(E,"select")){var I=E.selectedIndex,L=[],M=E.options,H=E.type=="select-one";if(I<0){return null}for(var F=H?I:0,J=H?I+1:M.length;F<J;F++){var G=M[F];if(G.selected){K=o(G).val();if(H){return K}L.push(K)}}return L}return(E.value||"").replace(/\r/g,"")}return g}if(typeof K==="number"){K+=""}return this.each(function(){if(this.nodeType!=1){return}if(o.isArray(K)&&/radio|checkbox/.test(this.type)){this.checked=(o.inArray(this.value,K)>=0||o.inArray(this.name,K)>=0)}else{if(o.nodeName(this,"select")){var N=o.makeArray(K);o("option",this).each(function(){this.selected=(o.inArray(this.value,N)>=0||o.inArray(this.text,N)>=0)});if(!N.length){this.selectedIndex=-1}}else{this.value=K}}})},html:function(E){return E===g?(this[0]?this[0].innerHTML:null):this.empty().append(E)},replaceWith:function(E){return this.after(E).remove()},eq:function(E){return this.slice(E,+E+1)},slice:function(){return this.pushStack(Array.prototype.slice.apply(this,arguments),"slice",Array.prototype.slice.call(arguments).join(","))},map:function(E){return this.pushStack(o.map(this,function(G,F){return E.call(G,F,G)}))},andSelf:function(){return this.add(this.prevObject)},domManip:function(K,N,M){if(this[0]){var J=(this[0].ownerDocument||this[0]).createDocumentFragment(),G=o.clean(K,(this[0].ownerDocument||this[0]),J),I=J.firstChild,E=this.length>1?J.cloneNode(true):J;if(I){for(var H=0,F=this.length;H<F;H++){M.call(L(this[H],I),H>0?E.cloneNode(true):J)}}if(G){o.each(G,z)}}return this;function L(O,P){return N&&o.nodeName(O,"table")&&o.nodeName(P,"tr")?(O.getElementsByTagName("tbody")[0]||O.appendChild(O.ownerDocument.createElement("tbody"))):O}}};o.fn.init.prototype=o.fn;function z(E,F){if(F.src){o.ajax({url:F.src,async:false,dataType:"script"})}else{o.globalEval(F.text||F.textContent||F.innerHTML||"")}if(F.parentNode){F.parentNode.removeChild(F)}}function e(){return +new Date}o.extend=o.fn.extend=function(){var J=arguments[0]||{},H=1,I=arguments.length,E=false,G;if(typeof J==="boolean"){E=J;J=arguments[1]||{};H=2}if(typeof J!=="object"&&!o.isFunction(J)){J={}}if(I==H){J=this;--H}for(;H<I;H++){if((G=arguments[H])!=null){for(var F in G){var K=J[F],L=G[F];if(J===L){continue}if(E&&L&&typeof L==="object"&&!L.nodeType){J[F]=o.extend(E,K||(L.length!=null?[]:{}),L)}else{if(L!==g){J[F]=L}}}}}return J};var b=/z-?index|font-?weight|opacity|zoom|line-?height/i,q=document.defaultView||{},s=Object.prototype.toString;o.extend({noConflict:function(E){l.$=p;if(E){l.jQuery=y}return o},isFunction:function(E){return s.call(E)==="[object Function]"},isArray:function(E){return s.call(E)==="[object Array]"},isXMLDoc:function(E){return E.nodeType===9&&E.documentElement.nodeName!=="HTML"||!!E.ownerDocument&&o.isXMLDoc(E.ownerDocument)},globalEval:function(G){G=o.trim(G);if(G){var F=document.getElementsByTagName("head")[0]||document.documentElement,E=document.createElement("script");E.type="text/javascript";if(o.support.scriptEval){E.appendChild(document.createTextNode(G))}else{E.text=G}F.insertBefore(E,F.firstChild);F.removeChild(E)}},nodeName:function(F,E){return F.nodeName&&F.nodeName.toUpperCase()==E.toUpperCase()},each:function(G,K,F){var E,H=0,I=G.length;if(F){if(I===g){for(E in G){if(K.apply(G[E],F)===false){break}}}else{for(;H<I;){if(K.apply(G[H++],F)===false){break}}}}else{if(I===g){for(E in G){if(K.call(G[E],E,G[E])===false){break}}}else{for(var J=G[0];H<I&&K.call(J,H,J)!==false;J=G[++H]){}}}return G},prop:function(H,I,G,F,E){if(o.isFunction(I)){I=I.call(H,F)}return typeof I==="number"&&G=="curCSS"&&!b.test(E)?I+"px":I},className:{add:function(E,F){o.each((F||"").split(/\s+/),function(G,H){if(E.nodeType==1&&!o.className.has(E.className,H)){E.className+=(E.className?" ":"")+H}})},remove:function(E,F){if(E.nodeType==1){E.className=F!==g?o.grep(E.className.split(/\s+/),function(G){return !o.className.has(F,G)}).join(" "):""}},has:function(F,E){return F&&o.inArray(E,(F.className||F).toString().split(/\s+/))>-1}},swap:function(H,G,I){var E={};for(var F in G){E[F]=H.style[F];H.style[F]=G[F]}I.call(H);for(var F in G){H.style[F]=E[F]}},css:function(G,E,I){if(E=="width"||E=="height"){var K,F={position:"absolute",visibility:"hidden",display:"block"},J=E=="width"?["Left","Right"]:["Top","Bottom"];function H(){K=E=="width"?G.offsetWidth:G.offsetHeight;var M=0,L=0;o.each(J,function(){M+=parseFloat(o.curCSS(G,"padding"+this,true))||0;L+=parseFloat(o.curCSS(G,"border"+this+"Width",true))||0});K-=Math.round(M+L)}if(o(G).is(":visible")){H()}else{o.swap(G,F,H)}return Math.max(0,K)}return o.curCSS(G,E,I)},curCSS:function(I,F,G){var L,E=I.style;if(F=="opacity"&&!o.support.opacity){L=o.attr(E,"opacity");return L==""?"1":L}if(F.match(/float/i)){F=w}if(!G&&E&&E[F]){L=E[F]}else{if(q.getComputedStyle){if(F.match(/float/i)){F="float"}F=F.replace(/([A-Z])/g,"-$1").toLowerCase();var M=q.getComputedStyle(I,null);if(M){L=M.getPropertyValue(F)}if(F=="opacity"&&L==""){L="1"}}else{if(I.currentStyle){var J=F.replace(/\-(\w)/g,function(N,O){return O.toUpperCase()});L=I.currentStyle[F]||I.currentStyle[J];if(!/^\d+(px)?$/i.test(L)&&/^\d/.test(L)){var H=E.left,K=I.runtimeStyle.left;I.runtimeStyle.left=I.currentStyle.left;E.left=L||0;L=E.pixelLeft+"px";E.left=H;I.runtimeStyle.left=K}}}}return L},clean:function(F,K,I){K=K||document;if(typeof K.createElement==="undefined"){K=K.ownerDocument||K[0]&&K[0].ownerDocument||document}if(!I&&F.length===1&&typeof F[0]==="string"){var H=/^<(\w+)\s*\/?>$/.exec(F[0]);if(H){return[K.createElement(H[1])]}}var G=[],E=[],L=K.createElement("div");o.each(F,function(P,R){if(typeof R==="number"){R+=""}if(!R){return}if(typeof R==="string"){R=R.replace(/(<(\w+)[^>]*?)\/>/g,function(T,U,S){return S.match(/^(abbr|br|col|img|input|link|meta|param|hr|area|embed)$/i)?T:U+"></"+S+">"});var O=o.trim(R).toLowerCase();var Q=!O.indexOf("<opt")&&[1,"<select multiple='multiple'>","</select>"]||!O.indexOf("<leg")&&[1,"<fieldset>","</fieldset>"]||O.match(/^<(thead|tbody|tfoot|colg|cap)/)&&[1,"<table>","</table>"]||!O.indexOf("<tr")&&[2,"<table><tbody>","</tbody></table>"]||(!O.indexOf("<td")||!O.indexOf("<th"))&&[3,"<table><tbody><tr>","</tr></tbody></table>"]||!O.indexOf("<col")&&[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"]||!o.support.htmlSerialize&&[1,"div<div>","</div>"]||[0,"",""];L.innerHTML=Q[1]+R+Q[2];while(Q[0]--){L=L.lastChild}if(!o.support.tbody){var N=!O.indexOf("<table")&&O.indexOf("<tbody")<0?L.firstChild&&L.firstChild.childNodes:Q[1]=="<table>"&&O.indexOf("<tbody")<0?L.childNodes:[];for(var M=N.length-1;M>=0;--M){if(o.nodeName(N[M],"tbody")&&!N[M].childNodes.length){N[M].parentNode.removeChild(N[M])}}}if(!o.support.leadingWhitespace&&/^\s/.test(R)){L.insertBefore(K.createTextNode(R.match(/^\s*/)[0]),L.firstChild)}R=o.makeArray(L.childNodes)}if(R.nodeType){G.push(R)}else{G=o.merge(G,R)}});if(I){for(var J=0;G[J];J++){if(o.nodeName(G[J],"script")&&(!G[J].type||G[J].type.toLowerCase()==="text/javascript")){E.push(G[J].parentNode?G[J].parentNode.removeChild(G[J]):G[J])}else{if(G[J].nodeType===1){G.splice.apply(G,[J+1,0].concat(o.makeArray(G[J].getElementsByTagName("script"))))}I.appendChild(G[J])}}return E}return G},attr:function(J,G,K){if(!J||J.nodeType==3||J.nodeType==8){return g}var H=!o.isXMLDoc(J),L=K!==g;G=H&&o.props[G]||G;if(J.tagName){var F=/href|src|style/.test(G);if(G=="selected"&&J.parentNode){J.parentNode.selectedIndex}if(G in J&&H&&!F){if(L){if(G=="type"&&o.nodeName(J,"input")&&J.parentNode){throw"type property can't be changed"}J[G]=K}if(o.nodeName(J,"form")&&J.getAttributeNode(G)){return J.getAttributeNode(G).nodeValue}if(G=="tabIndex"){var I=J.getAttributeNode("tabIndex");return I&&I.specified?I.value:J.nodeName.match(/(button|input|object|select|textarea)/i)?0:J.nodeName.match(/^(a|area)$/i)&&J.href?0:g}return J[G]}if(!o.support.style&&H&&G=="style"){return o.attr(J.style,"cssText",K)}if(L){J.setAttribute(G,""+K)}var E=!o.support.hrefNormalized&&H&&F?J.getAttribute(G,2):J.getAttribute(G);return E===null?g:E}if(!o.support.opacity&&G=="opacity"){if(L){J.zoom=1;J.filter=(J.filter||"").replace(/alpha\([^)]*\)/,"")+(parseInt(K)+""=="NaN"?"":"alpha(opacity="+K*100+")")}return J.filter&&J.filter.indexOf("opacity=")>=0?(parseFloat(J.filter.match(/opacity=([^)]*)/)[1])/100)+"":""}G=G.replace(/-([a-z])/ig,function(M,N){return N.toUpperCase()});if(L){J[G]=K}return J[G]},trim:function(E){return(E||"").replace(/^\s+|\s+$/g,"")},makeArray:function(G){var E=[];if(G!=null){var F=G.length;if(F==null||typeof G==="string"||o.isFunction(G)||G.setInterval){E[0]=G}else{while(F){E[--F]=G[F]}}}return E},inArray:function(G,H){for(var E=0,F=H.length;E<F;E++){if(H[E]===G){return E}}return -1},merge:function(H,E){var F=0,G,I=H.length;if(!o.support.getAll){while((G=E[F++])!=null){if(G.nodeType!=8){H[I++]=G}}}else{while((G=E[F++])!=null){H[I++]=G}}return H},unique:function(K){var F=[],E={};try{for(var G=0,H=K.length;G<H;G++){var J=o.data(K[G]);if(!E[J]){E[J]=true;F.push(K[G])}}}catch(I){F=K}return F},grep:function(F,J,E){var G=[];for(var H=0,I=F.length;H<I;H++){if(!E!=!J(F[H],H)){G.push(F[H])}}return G},map:function(E,J){var F=[];for(var G=0,H=E.length;G<H;G++){var I=J(E[G],G);if(I!=null){F[F.length]=I}}return F.concat.apply([],F)}});var C=navigator.userAgent.toLowerCase();o.browser={version:(C.match(/.+(?:rv|it|ra|ie)[\/: ]([\d.]+)/)||[0,"0"])[1],safari:/webkit/.test(C),opera:/opera/.test(C),msie:/msie/.test(C)&&!/opera/.test(C),mozilla:/mozilla/.test(C)&&!/(compatible|webkit)/.test(C)};o.each({parent:function(E){return E.parentNode},parents:function(E){return o.dir(E,"parentNode")},next:function(E){return o.nth(E,2,"nextSibling")},prev:function(E){return o.nth(E,2,"previousSibling")},nextAll:function(E){return o.dir(E,"nextSibling")},prevAll:function(E){return o.dir(E,"previousSibling")},siblings:function(E){return o.sibling(E.parentNode.firstChild,E)},children:function(E){return o.sibling(E.firstChild)},contents:function(E){return o.nodeName(E,"iframe")?E.contentDocument||E.contentWindow.document:o.makeArray(E.childNodes)}},function(E,F){o.fn[E]=function(G){var H=o.map(this,F);if(G&&typeof G=="string"){H=o.multiFilter(G,H)}return this.pushStack(o.unique(H),E,G)}});o.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(E,F){o.fn[E]=function(){var G=arguments;return this.each(function(){for(var H=0,I=G.length;H<I;H++){o(G[H])[F](this)}})}});o.each({removeAttr:function(E){o.attr(this,E,"");if(this.nodeType==1){this.removeAttribute(E)}},addClass:function(E){o.className.add(this,E)},removeClass:function(E){o.className.remove(this,E)},toggleClass:function(F,E){if(typeof E!=="boolean"){E=!o.className.has(this,F)}o.className[E?"add":"remove"](this,F)},remove:function(E){if(!E||o.filter(E,[this]).length){o("*",this).add([this]).each(function(){o.event.remove(this);o.removeData(this)});if(this.parentNode){this.parentNode.removeChild(this)}}},empty:function(){o(">*",this).remove();while(this.firstChild){this.removeChild(this.firstChild)}}},function(E,F){o.fn[E]=function(){return this.each(F,arguments)}});function j(E,F){return E[0]&&parseInt(o.curCSS(E[0],F,true),10)||0}var h="jQuery"+e(),v=0,A={};o.extend({cache:{},data:function(F,E,G){F=F==l?A:F;var H=F[h];if(!H){H=F[h]=++v}if(E&&!o.cache[H]){o.cache[H]={}}if(G!==g){o.cache[H][E]=G}return E?o.cache[H][E]:H},removeData:function(F,E){F=F==l?A:F;var H=F[h];if(E){if(o.cache[H]){delete o.cache[H][E];E="";for(E in o.cache[H]){break}if(!E){o.removeData(F)}}}else{try{delete F[h]}catch(G){if(F.removeAttribute){F.removeAttribute(h)}}delete o.cache[H]}},queue:function(F,E,H){if(F){E=(E||"fx")+"queue";var G=o.data(F,E);if(!G||o.isArray(H)){G=o.data(F,E,o.makeArray(H))}else{if(H){G.push(H)}}}return G},dequeue:function(H,G){var E=o.queue(H,G),F=E.shift();if(!G||G==="fx"){F=E[0]}if(F!==g){F.call(H)}}});o.fn.extend({data:function(E,G){var H=E.split(".");H[1]=H[1]?"."+H[1]:"";if(G===g){var F=this.triggerHandler("getData"+H[1]+"!",[H[0]]);if(F===g&&this.length){F=o.data(this[0],E)}return F===g&&H[1]?this.data(H[0]):F}else{return this.trigger("setData"+H[1]+"!",[H[0],G]).each(function(){o.data(this,E,G)})}},removeData:function(E){return this.each(function(){o.removeData(this,E)})},queue:function(E,F){if(typeof E!=="string"){F=E;E="fx"}if(F===g){return o.queue(this[0],E)}return this.each(function(){var G=o.queue(this,E,F);if(E=="fx"&&G.length==1){G[0].call(this)}})},dequeue:function(E){return this.each(function(){o.dequeue(this,E)})}});
+/*
+ * Sizzle CSS Selector Engine - v0.9.3
+ * Copyright 2009, The Dojo Foundation
+ * Released under the MIT, BSD, and GPL Licenses.
+ * More information: http://sizzlejs.com/
+ */
+(function(){var Q=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]+['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[]+)+|[>+~])(\s*,\s*)?/g,K=0,G=Object.prototype.toString;var F=function(X,T,aa,ab){aa=aa||[];T=T||document;if(T.nodeType!==1&&T.nodeType!==9){return[]}if(!X||typeof X!=="string"){return aa}var Y=[],V,ae,ah,S,ac,U,W=true;Q.lastIndex=0;while((V=Q.exec(X))!==null){Y.push(V[1]);if(V[2]){U=RegExp.rightContext;break}}if(Y.length>1&&L.exec(X)){if(Y.length===2&&H.relative[Y[0]]){ae=I(Y[0]+Y[1],T)}else{ae=H.relative[Y[0]]?[T]:F(Y.shift(),T);while(Y.length){X=Y.shift();if(H.relative[X]){X+=Y.shift()}ae=I(X,ae)}}}else{var ad=ab?{expr:Y.pop(),set:E(ab)}:F.find(Y.pop(),Y.length===1&&T.parentNode?T.parentNode:T,P(T));ae=F.filter(ad.expr,ad.set);if(Y.length>0){ah=E(ae)}else{W=false}while(Y.length){var ag=Y.pop(),af=ag;if(!H.relative[ag]){ag=""}else{af=Y.pop()}if(af==null){af=T}H.relative[ag](ah,af,P(T))}}if(!ah){ah=ae}if(!ah){throw"Syntax error, unrecognized expression: "+(ag||X)}if(G.call(ah)==="[object Array]"){if(!W){aa.push.apply(aa,ah)}else{if(T.nodeType===1){for(var Z=0;ah[Z]!=null;Z++){if(ah[Z]&&(ah[Z]===true||ah[Z].nodeType===1&&J(T,ah[Z]))){aa.push(ae[Z])}}}else{for(var Z=0;ah[Z]!=null;Z++){if(ah[Z]&&ah[Z].nodeType===1){aa.push(ae[Z])}}}}}else{E(ah,aa)}if(U){F(U,T,aa,ab)}return aa};F.matches=function(S,T){return F(S,null,null,T)};F.find=function(Z,S,aa){var Y,W;if(!Z){return[]}for(var V=0,U=H.order.length;V<U;V++){var X=H.order[V],W;if((W=H.match[X].exec(Z))){var T=RegExp.leftContext;if(T.substr(T.length-1)!=="\\"){W[1]=(W[1]||"").replace(/\\/g,"");Y=H.find[X](W,S,aa);if(Y!=null){Z=Z.replace(H.match[X],"");break}}}}if(!Y){Y=S.getElementsByTagName("*")}return{set:Y,expr:Z}};F.filter=function(ab,aa,ae,V){var U=ab,ag=[],Y=aa,X,S;while(ab&&aa.length){for(var Z in H.filter){if((X=H.match[Z].exec(ab))!=null){var T=H.filter[Z],af,ad;S=false;if(Y==ag){ag=[]}if(H.preFilter[Z]){X=H.preFilter[Z](X,Y,ae,ag,V);if(!X){S=af=true}else{if(X===true){continue}}}if(X){for(var W=0;(ad=Y[W])!=null;W++){if(ad){af=T(ad,X,W,Y);var ac=V^!!af;if(ae&&af!=null){if(ac){S=true}else{Y[W]=false}}else{if(ac){ag.push(ad);S=true}}}}}if(af!==g){if(!ae){Y=ag}ab=ab.replace(H.match[Z],"");if(!S){return[]}break}}}ab=ab.replace(/\s*,\s*/,"");if(ab==U){if(S==null){throw"Syntax error, unrecognized expression: "+ab}else{break}}U=ab}return Y};var H=F.selectors={order:["ID","NAME","TAG"],match:{ID:/#((?:[\w\u00c0-\uFFFF_-]|\\.)+)/,CLASS:/\.((?:[\w\u00c0-\uFFFF_-]|\\.)+)/,NAME:/\[name=['"]*((?:[\w\u00c0-\uFFFF_-]|\\.)+)['"]*\]/,ATTR:/\[\s*((?:[\w\u00c0-\uFFFF_-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/,TAG:/^((?:[\w\u00c0-\uFFFF\*_-]|\\.)+)/,CHILD:/:(only|nth|last|first)-child(?:\((even|odd|[\dn+-]*)\))?/,POS:/:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^-]|$)/,PSEUDO:/:((?:[\w\u00c0-\uFFFF_-]|\\.)+)(?:\((['"]*)((?:\([^\)]+\)|[^\2\(\)]*)+)\2\))?/},attrMap:{"class":"className","for":"htmlFor"},attrHandle:{href:function(S){return S.getAttribute("href")}},relative:{"+":function(W,T){for(var U=0,S=W.length;U<S;U++){var V=W[U];if(V){var X=V.previousSibling;while(X&&X.nodeType!==1){X=X.previousSibling}W[U]=typeof T==="string"?X||false:X===T}}if(typeof T==="string"){F.filter(T,W,true)}},">":function(X,T,Y){if(typeof T==="string"&&!/\W/.test(T)){T=Y?T:T.toUpperCase();for(var U=0,S=X.length;U<S;U++){var W=X[U];if(W){var V=W.parentNode;X[U]=V.nodeName===T?V:false}}}else{for(var U=0,S=X.length;U<S;U++){var W=X[U];if(W){X[U]=typeof T==="string"?W.parentNode:W.parentNode===T}}if(typeof T==="string"){F.filter(T,X,true)}}},"":function(V,T,X){var U="done"+(K++),S=R;if(!T.match(/\W/)){var W=T=X?T:T.toUpperCase();S=O}S("parentNode",T,U,V,W,X)},"~":function(V,T,X){var U="done"+(K++),S=R;if(typeof T==="string"&&!T.match(/\W/)){var W=T=X?T:T.toUpperCase();S=O}S("previousSibling",T,U,V,W,X)}},find:{ID:function(T,U,V){if(typeof U.getElementById!=="undefined"&&!V){var S=U.getElementById(T[1]);return S?[S]:[]}},NAME:function(S,T,U){if(typeof T.getElementsByName!=="undefined"&&!U){return T.getElementsByName(S[1])}},TAG:function(S,T){return T.getElementsByTagName(S[1])}},preFilter:{CLASS:function(V,T,U,S,Y){V=" "+V[1].replace(/\\/g,"")+" ";var X;for(var W=0;(X=T[W])!=null;W++){if(X){if(Y^(" "+X.className+" ").indexOf(V)>=0){if(!U){S.push(X)}}else{if(U){T[W]=false}}}}return false},ID:function(S){return S[1].replace(/\\/g,"")},TAG:function(T,S){for(var U=0;S[U]===false;U++){}return S[U]&&P(S[U])?T[1]:T[1].toUpperCase()},CHILD:function(S){if(S[1]=="nth"){var T=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(S[2]=="even"&&"2n"||S[2]=="odd"&&"2n+1"||!/\D/.test(S[2])&&"0n+"+S[2]||S[2]);S[2]=(T[1]+(T[2]||1))-0;S[3]=T[3]-0}S[0]="done"+(K++);return S},ATTR:function(T){var S=T[1].replace(/\\/g,"");if(H.attrMap[S]){T[1]=H.attrMap[S]}if(T[2]==="~="){T[4]=" "+T[4]+" "}return T},PSEUDO:function(W,T,U,S,X){if(W[1]==="not"){if(W[3].match(Q).length>1){W[3]=F(W[3],null,null,T)}else{var V=F.filter(W[3],T,U,true^X);if(!U){S.push.apply(S,V)}return false}}else{if(H.match.POS.test(W[0])){return true}}return W},POS:function(S){S.unshift(true);return S}},filters:{enabled:function(S){return S.disabled===false&&S.type!=="hidden"},disabled:function(S){return S.disabled===true},checked:function(S){return S.checked===true},selected:function(S){S.parentNode.selectedIndex;return S.selected===true},parent:function(S){return !!S.firstChild},empty:function(S){return !S.firstChild},has:function(U,T,S){return !!F(S[3],U).length},header:function(S){return/h\d/i.test(S.nodeName)},text:function(S){return"text"===S.type},radio:function(S){return"radio"===S.type},checkbox:function(S){return"checkbox"===S.type},file:function(S){return"file"===S.type},password:function(S){return"password"===S.type},submit:function(S){return"submit"===S.type},image:function(S){return"image"===S.type},reset:function(S){return"reset"===S.type},button:function(S){return"button"===S.type||S.nodeName.toUpperCase()==="BUTTON"},input:function(S){return/input|select|textarea|button/i.test(S.nodeName)}},setFilters:{first:function(T,S){return S===0},last:function(U,T,S,V){return T===V.length-1},even:function(T,S){return S%2===0},odd:function(T,S){return S%2===1},lt:function(U,T,S){return T<S[3]-0},gt:function(U,T,S){return T>S[3]-0},nth:function(U,T,S){return S[3]-0==T},eq:function(U,T,S){return S[3]-0==T}},filter:{CHILD:function(S,V){var Y=V[1],Z=S.parentNode;var X=V[0];if(Z&&(!Z[X]||!S.nodeIndex)){var W=1;for(var T=Z.firstChild;T;T=T.nextSibling){if(T.nodeType==1){T.nodeIndex=W++}}Z[X]=W-1}if(Y=="first"){return S.nodeIndex==1}else{if(Y=="last"){return S.nodeIndex==Z[X]}else{if(Y=="only"){return Z[X]==1}else{if(Y=="nth"){var ab=false,U=V[2],aa=V[3];if(U==1&&aa==0){return true}if(U==0){if(S.nodeIndex==aa){ab=true}}else{if((S.nodeIndex-aa)%U==0&&(S.nodeIndex-aa)/U>=0){ab=true}}return ab}}}}},PSEUDO:function(Y,U,V,Z){var T=U[1],W=H.filters[T];if(W){return W(Y,V,U,Z)}else{if(T==="contains"){return(Y.textContent||Y.innerText||"").indexOf(U[3])>=0}else{if(T==="not"){var X=U[3];for(var V=0,S=X.length;V<S;V++){if(X[V]===Y){return false}}return true}}}},ID:function(T,S){return T.nodeType===1&&T.getAttribute("id")===S},TAG:function(T,S){return(S==="*"&&T.nodeType===1)||T.nodeName===S},CLASS:function(T,S){return S.test(T.className)},ATTR:function(W,U){var S=H.attrHandle[U[1]]?H.attrHandle[U[1]](W):W[U[1]]||W.getAttribute(U[1]),X=S+"",V=U[2],T=U[4];return S==null?V==="!=":V==="="?X===T:V==="*="?X.indexOf(T)>=0:V==="~="?(" "+X+" ").indexOf(T)>=0:!U[4]?S:V==="!="?X!=T:V==="^="?X.indexOf(T)===0:V==="$="?X.substr(X.length-T.length)===T:V==="|="?X===T||X.substr(0,T.length+1)===T+"-":false},POS:function(W,T,U,X){var S=T[2],V=H.setFilters[S];if(V){return V(W,U,T,X)}}}};var L=H.match.POS;for(var N in H.match){H.match[N]=RegExp(H.match[N].source+/(?![^\[]*\])(?![^\(]*\))/.source)}var E=function(T,S){T=Array.prototype.slice.call(T);if(S){S.push.apply(S,T);return S}return T};try{Array.prototype.slice.call(document.documentElement.childNodes)}catch(M){E=function(W,V){var T=V||[];if(G.call(W)==="[object Array]"){Array.prototype.push.apply(T,W)}else{if(typeof W.length==="number"){for(var U=0,S=W.length;U<S;U++){T.push(W[U])}}else{for(var U=0;W[U];U++){T.push(W[U])}}}return T}}(function(){var T=document.createElement("form"),U="script"+(new Date).getTime();T.innerHTML="<input name='"+U+"'/>";var S=document.documentElement;S.insertBefore(T,S.firstChild);if(!!document.getElementById(U)){H.find.ID=function(W,X,Y){if(typeof X.getElementById!=="undefined"&&!Y){var V=X.getElementById(W[1]);return V?V.id===W[1]||typeof V.getAttributeNode!=="undefined"&&V.getAttributeNode("id").nodeValue===W[1]?[V]:g:[]}};H.filter.ID=function(X,V){var W=typeof X.getAttributeNode!=="undefined"&&X.getAttributeNode("id");return X.nodeType===1&&W&&W.nodeValue===V}}S.removeChild(T)})();(function(){var S=document.createElement("div");S.appendChild(document.createComment(""));if(S.getElementsByTagName("*").length>0){H.find.TAG=function(T,X){var W=X.getElementsByTagName(T[1]);if(T[1]==="*"){var V=[];for(var U=0;W[U];U++){if(W[U].nodeType===1){V.push(W[U])}}W=V}return W}}S.innerHTML="<a href='#'></a>";if(S.firstChild&&S.firstChild.getAttribute("href")!=="#"){H.attrHandle.href=function(T){return T.getAttribute("href",2)}}})();if(document.querySelectorAll){(function(){var S=F,T=document.createElement("div");T.innerHTML="<p class='TEST'></p>";if(T.querySelectorAll&&T.querySelectorAll(".TEST").length===0){return}F=function(X,W,U,V){W=W||document;if(!V&&W.nodeType===9&&!P(W)){try{return E(W.querySelectorAll(X),U)}catch(Y){}}return S(X,W,U,V)};F.find=S.find;F.filter=S.filter;F.selectors=S.selectors;F.matches=S.matches})()}if(document.getElementsByClassName&&document.documentElement.getElementsByClassName){H.order.splice(1,0,"CLASS");H.find.CLASS=function(S,T){return T.getElementsByClassName(S[1])}}function O(T,Z,Y,ac,aa,ab){for(var W=0,U=ac.length;W<U;W++){var S=ac[W];if(S){S=S[T];var X=false;while(S&&S.nodeType){var V=S[Y];if(V){X=ac[V];break}if(S.nodeType===1&&!ab){S[Y]=W}if(S.nodeName===Z){X=S;break}S=S[T]}ac[W]=X}}}function R(T,Y,X,ab,Z,aa){for(var V=0,U=ab.length;V<U;V++){var S=ab[V];if(S){S=S[T];var W=false;while(S&&S.nodeType){if(S[X]){W=ab[S[X]];break}if(S.nodeType===1){if(!aa){S[X]=V}if(typeof Y!=="string"){if(S===Y){W=true;break}}else{if(F.filter(Y,[S]).length>0){W=S;break}}}S=S[T]}ab[V]=W}}}var J=document.compareDocumentPosition?function(T,S){return T.compareDocumentPosition(S)&16}:function(T,S){return T!==S&&(T.contains?T.contains(S):true)};var P=function(S){return S.nodeType===9&&S.documentElement.nodeName!=="HTML"||!!S.ownerDocument&&P(S.ownerDocument)};var I=function(S,Z){var V=[],W="",X,U=Z.nodeType?[Z]:Z;while((X=H.match.PSEUDO.exec(S))){W+=X[0];S=S.replace(H.match.PSEUDO,"")}S=H.relative[S]?S+"*":S;for(var Y=0,T=U.length;Y<T;Y++){F(S,U[Y],V)}return F.filter(W,V)};o.find=F;o.filter=F.filter;o.expr=F.selectors;o.expr[":"]=o.expr.filters;F.selectors.filters.hidden=function(S){return"hidden"===S.type||o.css(S,"display")==="none"||o.css(S,"visibility")==="hidden"};F.selectors.filters.visible=function(S){return"hidden"!==S.type&&o.css(S,"display")!=="none"&&o.css(S,"visibility")!=="hidden"};F.selectors.filters.animated=function(S){return o.grep(o.timers,function(T){return S===T.elem}).length};o.multiFilter=function(U,S,T){if(T){U=":not("+U+")"}return F.matches(U,S)};o.dir=function(U,T){var S=[],V=U[T];while(V&&V!=document){if(V.nodeType==1){S.push(V)}V=V[T]}return S};o.nth=function(W,S,U,V){S=S||1;var T=0;for(;W;W=W[U]){if(W.nodeType==1&&++T==S){break}}return W};o.sibling=function(U,T){var S=[];for(;U;U=U.nextSibling){if(U.nodeType==1&&U!=T){S.push(U)}}return S};return;l.Sizzle=F})();o.event={add:function(I,F,H,K){if(I.nodeType==3||I.nodeType==8){return}if(I.setInterval&&I!=l){I=l}if(!H.guid){H.guid=this.guid++}if(K!==g){var G=H;H=this.proxy(G);H.data=K}var E=o.data(I,"events")||o.data(I,"events",{}),J=o.data(I,"handle")||o.data(I,"handle",function(){return typeof o!=="undefined"&&!o.event.triggered?o.event.handle.apply(arguments.callee.elem,arguments):g});J.elem=I;o.each(F.split(/\s+/),function(M,N){var O=N.split(".");N=O.shift();H.type=O.slice().sort().join(".");var L=E[N];if(o.event.specialAll[N]){o.event.specialAll[N].setup.call(I,K,O)}if(!L){L=E[N]={};if(!o.event.special[N]||o.event.special[N].setup.call(I,K,O)===false){if(I.addEventListener){I.addEventListener(N,J,false)}else{if(I.attachEvent){I.attachEvent("on"+N,J)}}}}L[H.guid]=H;o.event.global[N]=true});I=null},guid:1,global:{},remove:function(K,H,J){if(K.nodeType==3||K.nodeType==8){return}var G=o.data(K,"events"),F,E;if(G){if(H===g||(typeof H==="string"&&H.charAt(0)==".")){for(var I in G){this.remove(K,I+(H||""))}}else{if(H.type){J=H.handler;H=H.type}o.each(H.split(/\s+/),function(M,O){var Q=O.split(".");O=Q.shift();var N=RegExp("(^|\\.)"+Q.slice().sort().join(".*\\.")+"(\\.|$)");if(G[O]){if(J){delete G[O][J.guid]}else{for(var P in G[O]){if(N.test(G[O][P].type)){delete G[O][P]}}}if(o.event.specialAll[O]){o.event.specialAll[O].teardown.call(K,Q)}for(F in G[O]){break}if(!F){if(!o.event.special[O]||o.event.special[O].teardown.call(K,Q)===false){if(K.removeEventListener){K.removeEventListener(O,o.data(K,"handle"),false)}else{if(K.detachEvent){K.detachEvent("on"+O,o.data(K,"handle"))}}}F=null;delete G[O]}}})}for(F in G){break}if(!F){var L=o.data(K,"handle");if(L){L.elem=null}o.removeData(K,"events");o.removeData(K,"handle")}}},trigger:function(I,K,H,E){var G=I.type||I;if(!E){I=typeof I==="object"?I[h]?I:o.extend(o.Event(G),I):o.Event(G);if(G.indexOf("!")>=0){I.type=G=G.slice(0,-1);I.exclusive=true}if(!H){I.stopPropagation();if(this.global[G]){o.each(o.cache,function(){if(this.events&&this.events[G]){o.event.trigger(I,K,this.handle.elem)}})}}if(!H||H.nodeType==3||H.nodeType==8){return g}I.result=g;I.target=H;K=o.makeArray(K);K.unshift(I)}I.currentTarget=H;var J=o.data(H,"handle");if(J){J.apply(H,K)}if((!H[G]||(o.nodeName(H,"a")&&G=="click"))&&H["on"+G]&&H["on"+G].apply(H,K)===false){I.result=false}if(!E&&H[G]&&!I.isDefaultPrevented()&&!(o.nodeName(H,"a")&&G=="click")){this.triggered=true;try{H[G]()}catch(L){}}this.triggered=false;if(!I.isPropagationStopped()){var F=H.parentNode||H.ownerDocument;if(F){o.event.trigger(I,K,F,true)}}},handle:function(K){var J,E;K=arguments[0]=o.event.fix(K||l.event);var L=K.type.split(".");K.type=L.shift();J=!L.length&&!K.exclusive;var I=RegExp("(^|\\.)"+L.slice().sort().join(".*\\.")+"(\\.|$)");E=(o.data(this,"events")||{})[K.type];for(var G in E){var H=E[G];if(J||I.test(H.type)){K.handler=H;K.data=H.data;var F=H.apply(this,arguments);if(F!==g){K.result=F;if(F===false){K.preventDefault();K.stopPropagation()}}if(K.isImmediatePropagationStopped()){break}}}},props:"altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode metaKey newValue originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),fix:function(H){if(H[h]){return H}var F=H;H=o.Event(F);for(var G=this.props.length,J;G;){J=this.props[--G];H[J]=F[J]}if(!H.target){H.target=H.srcElement||document}if(H.target.nodeType==3){H.target=H.target.parentNode}if(!H.relatedTarget&&H.fromElement){H.relatedTarget=H.fromElement==H.target?H.toElement:H.fromElement}if(H.pageX==null&&H.clientX!=null){var I=document.documentElement,E=document.body;H.pageX=H.clientX+(I&&I.scrollLeft||E&&E.scrollLeft||0)-(I.clientLeft||0);H.pageY=H.clientY+(I&&I.scrollTop||E&&E.scrollTop||0)-(I.clientTop||0)}if(!H.which&&((H.charCode||H.charCode===0)?H.charCode:H.keyCode)){H.which=H.charCode||H.keyCode}if(!H.metaKey&&H.ctrlKey){H.metaKey=H.ctrlKey}if(!H.which&&H.button){H.which=(H.button&1?1:(H.button&2?3:(H.button&4?2:0)))}return H},proxy:function(F,E){E=E||function(){return F.apply(this,arguments)};E.guid=F.guid=F.guid||E.guid||this.guid++;return E},special:{ready:{setup:B,teardown:function(){}}},specialAll:{live:{setup:function(E,F){o.event.add(this,F[0],c)},teardown:function(G){if(G.length){var E=0,F=RegExp("(^|\\.)"+G[0]+"(\\.|$)");o.each((o.data(this,"events").live||{}),function(){if(F.test(this.type)){E++}});if(E<1){o.event.remove(this,G[0],c)}}}}}};o.Event=function(E){if(!this.preventDefault){return new o.Event(E)}if(E&&E.type){this.originalEvent=E;this.type=E.type}else{this.type=E}this.timeStamp=e();this[h]=true};function k(){return false}function u(){return true}o.Event.prototype={preventDefault:function(){this.isDefaultPrevented=u;var E=this.originalEvent;if(!E){return}if(E.preventDefault){E.preventDefault()}E.returnValue=false},stopPropagation:function(){this.isPropagationStopped=u;var E=this.originalEvent;if(!E){return}if(E.stopPropagation){E.stopPropagation()}E.cancelBubble=true},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=u;this.stopPropagation()},isDefaultPrevented:k,isPropagationStopped:k,isImmediatePropagationStopped:k};var a=function(F){var E=F.relatedTarget;while(E&&E!=this){try{E=E.parentNode}catch(G){E=this}}if(E!=this){F.type=F.data;o.event.handle.apply(this,arguments)}};o.each({mouseover:"mouseenter",mouseout:"mouseleave"},function(F,E){o.event.special[E]={setup:function(){o.event.add(this,F,a,E)},teardown:function(){o.event.remove(this,F,a)}}});o.fn.extend({bind:function(F,G,E){return F=="unload"?this.one(F,G,E):this.each(function(){o.event.add(this,F,E||G,E&&G)})},one:function(G,H,F){var E=o.event.proxy(F||H,function(I){o(this).unbind(I,E);return(F||H).apply(this,arguments)});return this.each(function(){o.event.add(this,G,E,F&&H)})},unbind:function(F,E){return this.each(function(){o.event.remove(this,F,E)})},trigger:function(E,F){return this.each(function(){o.event.trigger(E,F,this)})},triggerHandler:function(E,G){if(this[0]){var F=o.Event(E);F.preventDefault();F.stopPropagation();o.event.trigger(F,G,this[0]);return F.result}},toggle:function(G){var E=arguments,F=1;while(F<E.length){o.event.proxy(G,E[F++])}return this.click(o.event.proxy(G,function(H){this.lastToggle=(this.lastToggle||0)%F;H.preventDefault();return E[this.lastToggle++].apply(this,arguments)||false}))},hover:function(E,F){return this.mouseenter(E).mouseleave(F)},ready:function(E){B();if(o.isReady){E.call(document,o)}else{o.readyList.push(E)}return this},live:function(G,F){var E=o.event.proxy(F);E.guid+=this.selector+G;o(document).bind(i(G,this.selector),this.selector,E);return this},die:function(F,E){o(document).unbind(i(F,this.selector),E?{guid:E.guid+this.selector+F}:null);return this}});function c(H){var E=RegExp("(^|\\.)"+H.type+"(\\.|$)"),G=true,F=[];o.each(o.data(this,"events").live||[],function(I,J){if(E.test(J.type)){var K=o(H.target).closest(J.data)[0];if(K){F.push({elem:K,fn:J})}}});o.each(F,function(){if(this.fn.call(this.elem,H,this.fn.data)===false){G=false}});return G}function i(F,E){return["live",F,E.replace(/\./g,"`").replace(/ /g,"|")].join(".")}o.extend({isReady:false,readyList:[],ready:function(){if(!o.isReady){o.isReady=true;if(o.readyList){o.each(o.readyList,function(){this.call(document,o)});o.readyList=null}o(document).triggerHandler("ready")}}});var x=false;function B(){if(x){return}x=true;if(document.addEventListener){document.addEventListener("DOMContentLoaded",function(){document.removeEventListener("DOMContentLoaded",arguments.callee,false);o.ready()},false)}else{if(document.attachEvent){document.attachEvent("onreadystatechange",function(){if(document.readyState==="complete"){document.detachEvent("onreadystatechange",arguments.callee);o.ready()}});if(document.documentElement.doScroll&&typeof l.frameElement==="undefined"){(function(){if(o.isReady){return}try{document.documentElement.doScroll("left")}catch(E){setTimeout(arguments.callee,0);return}o.ready()})()}}}o.event.add(l,"load",o.ready)}o.each(("blur,focus,load,resize,scroll,unload,click,dblclick,mousedown,mouseup,mousemove,mouseover,mouseout,mouseenter,mouseleave,change,select,submit,keydown,keypress,keyup,error").split(","),function(F,E){o.fn[E]=function(G){return G?this.bind(E,G):this.trigger(E)}});o(l).bind("unload",function(){for(var E in o.cache){if(E!=1&&o.cache[E].handle){o.event.remove(o.cache[E].handle.elem)}}});(function(){o.support={};var F=document.documentElement,G=document.createElement("script"),K=document.createElement("div"),J="script"+(new Date).getTime();K.style.display="none";K.innerHTML=' <link/><table></table><a href="/a" style="color:red;float:left;opacity:.5;">a</a><select><option>text</option></select><object><param/></object>';var H=K.getElementsByTagName("*"),E=K.getElementsByTagName("a")[0];if(!H||!H.length||!E){return}o.support={leadingWhitespace:K.firstChild.nodeType==3,tbody:!K.getElementsByTagName("tbody").length,objectAll:!!K.getElementsByTagName("object")[0].getElementsByTagName("*").length,htmlSerialize:!!K.getElementsByTagName("link").length,style:/red/.test(E.getAttribute("style")),hrefNormalized:E.getAttribute("href")==="/a",opacity:E.style.opacity==="0.5",cssFloat:!!E.style.cssFloat,scriptEval:false,noCloneEvent:true,boxModel:null};G.type="text/javascript";try{G.appendChild(document.createTextNode("window."+J+"=1;"))}catch(I){}F.insertBefore(G,F.firstChild);if(l[J]){o.support.scriptEval=true;delete l[J]}F.removeChild(G);if(K.attachEvent&&K.fireEvent){K.attachEvent("onclick",function(){o.support.noCloneEvent=false;K.detachEvent("onclick",arguments.callee)});K.cloneNode(true).fireEvent("onclick")}o(function(){var L=document.createElement("div");L.style.width="1px";L.style.paddingLeft="1px";document.body.appendChild(L);o.boxModel=o.support.boxModel=L.offsetWidth===2;document.body.removeChild(L)})})();var w=o.support.cssFloat?"cssFloat":"styleFloat";o.props={"for":"htmlFor","class":"className","float":w,cssFloat:w,styleFloat:w,readonly:"readOnly",maxlength:"maxLength",cellspacing:"cellSpacing",rowspan:"rowSpan",tabindex:"tabIndex"};o.fn.extend({_load:o.fn.load,load:function(G,J,K){if(typeof G!=="string"){return this._load(G)}var I=G.indexOf(" ");if(I>=0){var E=G.slice(I,G.length);G=G.slice(0,I)}var H="GET";if(J){if(o.isFunction(J)){K=J;J=null}else{if(typeof J==="object"){J=o.param(J);H="POST"}}}var F=this;o.ajax({url:G,type:H,dataType:"html",data:J,complete:function(M,L){if(L=="success"||L=="notmodified"){F.html(E?o("<div/>").append(M.responseText.replace(/<script(.|\s)*?\/script>/g,"")).find(E):M.responseText)}if(K){F.each(K,[M.responseText,L,M])}}});return this},serialize:function(){return o.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?o.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||/select|textarea/i.test(this.nodeName)||/text|hidden|password/i.test(this.type))}).map(function(E,F){var G=o(this).val();return G==null?null:o.isArray(G)?o.map(G,function(I,H){return{name:F.name,value:I}}):{name:F.name,value:G}}).get()}});o.each("ajaxStart,ajaxStop,ajaxComplete,ajaxError,ajaxSuccess,ajaxSend".split(","),function(E,F){o.fn[F]=function(G){return this.bind(F,G)}});var r=e();o.extend({get:function(E,G,H,F){if(o.isFunction(G)){H=G;G=null}return o.ajax({type:"GET",url:E,data:G,success:H,dataType:F})},getScript:function(E,F){return o.get(E,null,F,"script")},getJSON:function(E,F,G){return o.get(E,F,G,"json")},post:function(E,G,H,F){if(o.isFunction(G)){H=G;G={}}return o.ajax({type:"POST",url:E,data:G,success:H,dataType:F})},ajaxSetup:function(E){o.extend(o.ajaxSettings,E)},ajaxSettings:{url:location.href,global:true,type:"GET",contentType:"application/x-www-form-urlencoded",processData:true,async:true,xhr:function(){return l.ActiveXObject?new ActiveXObject("Microsoft.XMLHTTP"):new XMLHttpRequest()},accepts:{xml:"application/xml, text/xml",html:"text/html",script:"text/javascript, application/javascript",json:"application/json, text/javascript",text:"text/plain",_default:"*/*"}},lastModified:{},ajax:function(M){M=o.extend(true,M,o.extend(true,{},o.ajaxSettings,M));var W,F=/=\?(&|$)/g,R,V,G=M.type.toUpperCase();if(M.data&&M.processData&&typeof M.data!=="string"){M.data=o.param(M.data)}if(M.dataType=="jsonp"){if(G=="GET"){if(!M.url.match(F)){M.url+=(M.url.match(/\?/)?"&":"?")+(M.jsonp||"callback")+"=?"}}else{if(!M.data||!M.data.match(F)){M.data=(M.data?M.data+"&":"")+(M.jsonp||"callback")+"=?"}}M.dataType="json"}if(M.dataType=="json"&&(M.data&&M.data.match(F)||M.url.match(F))){W="jsonp"+r++;if(M.data){M.data=(M.data+"").replace(F,"="+W+"$1")}M.url=M.url.replace(F,"="+W+"$1");M.dataType="script";l[W]=function(X){V=X;I();L();l[W]=g;try{delete l[W]}catch(Y){}if(H){H.removeChild(T)}}}if(M.dataType=="script"&&M.cache==null){M.cache=false}if(M.cache===false&&G=="GET"){var E=e();var U=M.url.replace(/(\?|&)_=.*?(&|$)/,"$1_="+E+"$2");M.url=U+((U==M.url)?(M.url.match(/\?/)?"&":"?")+"_="+E:"")}if(M.data&&G=="GET"){M.url+=(M.url.match(/\?/)?"&":"?")+M.data;M.data=null}if(M.global&&!o.active++){o.event.trigger("ajaxStart")}var Q=/^(\w+:)?\/\/([^\/?#]+)/.exec(M.url);if(M.dataType=="script"&&G=="GET"&&Q&&(Q[1]&&Q[1]!=location.protocol||Q[2]!=location.host)){var H=document.getElementsByTagName("head")[0];var T=document.createElement("script");T.src=M.url;if(M.scriptCharset){T.charset=M.scriptCharset}if(!W){var O=false;T.onload=T.onreadystatechange=function(){if(!O&&(!this.readyState||this.readyState=="loaded"||this.readyState=="complete")){O=true;I();L();H.removeChild(T)}}}H.appendChild(T);return g}var K=false;var J=M.xhr();if(M.username){J.open(G,M.url,M.async,M.username,M.password)}else{J.open(G,M.url,M.async)}try{if(M.data){J.setRequestHeader("Content-Type",M.contentType)}if(M.ifModified){J.setRequestHeader("If-Modified-Since",o.lastModified[M.url]||"Thu, 01 Jan 1970 00:00:00 GMT")}J.setRequestHeader("X-Requested-With","XMLHttpRequest");J.setRequestHeader("Accept",M.dataType&&M.accepts[M.dataType]?M.accepts[M.dataType]+", */*":M.accepts._default)}catch(S){}if(M.beforeSend&&M.beforeSend(J,M)===false){if(M.global&&!--o.active){o.event.trigger("ajaxStop")}J.abort();return false}if(M.global){o.event.trigger("ajaxSend",[J,M])}var N=function(X){if(J.readyState==0){if(P){clearInterval(P);P=null;if(M.global&&!--o.active){o.event.trigger("ajaxStop")}}}else{if(!K&&J&&(J.readyState==4||X=="timeout")){K=true;if(P){clearInterval(P);P=null}R=X=="timeout"?"timeout":!o.httpSuccess(J)?"error":M.ifModified&&o.httpNotModified(J,M.url)?"notmodified":"success";if(R=="success"){try{V=o.httpData(J,M.dataType,M)}catch(Z){R="parsererror"}}if(R=="success"){var Y;try{Y=J.getResponseHeader("Last-Modified")}catch(Z){}if(M.ifModified&&Y){o.lastModified[M.url]=Y}if(!W){I()}}else{o.handleError(M,J,R)}L();if(X){J.abort()}if(M.async){J=null}}}};if(M.async){var P=setInterval(N,13);if(M.timeout>0){setTimeout(function(){if(J&&!K){N("timeout")}},M.timeout)}}try{J.send(M.data)}catch(S){o.handleError(M,J,null,S)}if(!M.async){N()}function I(){if(M.success){M.success(V,R)}if(M.global){o.event.trigger("ajaxSuccess",[J,M])}}function L(){if(M.complete){M.complete(J,R)}if(M.global){o.event.trigger("ajaxComplete",[J,M])}if(M.global&&!--o.active){o.event.trigger("ajaxStop")}}return J},handleError:function(F,H,E,G){if(F.error){F.error(H,E,G)}if(F.global){o.event.trigger("ajaxError",[H,F,G])}},active:0,httpSuccess:function(F){try{return !F.status&&location.protocol=="file:"||(F.status>=200&&F.status<300)||F.status==304||F.status==1223}catch(E){}return false},httpNotModified:function(G,E){try{var H=G.getResponseHeader("Last-Modified");return G.status==304||H==o.lastModified[E]}catch(F){}return false},httpData:function(J,H,G){var F=J.getResponseHeader("content-type"),E=H=="xml"||!H&&F&&F.indexOf("xml")>=0,I=E?J.responseXML:J.responseText;if(E&&I.documentElement.tagName=="parsererror"){throw"parsererror"}if(G&&G.dataFilter){I=G.dataFilter(I,H)}if(typeof I==="string"){if(H=="script"){o.globalEval(I)}if(H=="json"){I=l["eval"]("("+I+")")}}return I},param:function(E){var G=[];function H(I,J){G[G.length]=encodeURIComponent(I)+"="+encodeURIComponent(J)}if(o.isArray(E)||E.jquery){o.each(E,function(){H(this.name,this.value)})}else{for(var F in E){if(o.isArray(E[F])){o.each(E[F],function(){H(F,this)})}else{H(F,o.isFunction(E[F])?E[F]():E[F])}}}return G.join("&").replace(/%20/g,"+")}});var m={},n,d=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]];function t(F,E){var G={};o.each(d.concat.apply([],d.slice(0,E)),function(){G[this]=F});return G}o.fn.extend({show:function(J,L){if(J){return this.animate(t("show",3),J,L)}else{for(var H=0,F=this.length;H<F;H++){var E=o.data(this[H],"olddisplay");this[H].style.display=E||"";if(o.css(this[H],"display")==="none"){var G=this[H].tagName,K;if(m[G]){K=m[G]}else{var I=o("<"+G+" />").appendTo("body");K=I.css("display");if(K==="none"){K="block"}I.remove();m[G]=K}this[H].style.display=o.data(this[H],"olddisplay",K)}}return this}},hide:function(H,I){if(H){return this.animate(t("hide",3),H,I)}else{for(var G=0,F=this.length;G<F;G++){var E=o.data(this[G],"olddisplay");if(!E&&E!=="none"){o.data(this[G],"olddisplay",o.css(this[G],"display"))}this[G].style.display="none"}return this}},_toggle:o.fn.toggle,toggle:function(G,F){var E=typeof G==="boolean";return o.isFunction(G)&&o.isFunction(F)?this._toggle.apply(this,arguments):G==null||E?this.each(function(){var H=E?G:o(this).is(":hidden");o(this)[H?"show":"hide"]()}):this.animate(t("toggle",3),G,F)},fadeTo:function(E,G,F){return this.animate({opacity:G},E,F)},animate:function(I,F,H,G){var E=o.speed(F,H,G);return this[E.queue===false?"each":"queue"](function(){var K=o.extend({},E),M,L=this.nodeType==1&&o(this).is(":hidden"),J=this;for(M in I){if(I[M]=="hide"&&L||I[M]=="show"&&!L){return K.complete.call(this)}if((M=="height"||M=="width")&&this.style){K.display=o.css(this,"display");K.overflow=this.style.overflow}}if(K.overflow!=null){this.style.overflow="hidden"}K.curAnim=o.extend({},I);o.each(I,function(O,S){var R=new o.fx(J,K,O);if(/toggle|show|hide/.test(S)){R[S=="toggle"?L?"show":"hide":S](I)}else{var Q=S.toString().match(/^([+-]=)?([\d+-.]+)(.*)$/),T=R.cur(true)||0;if(Q){var N=parseFloat(Q[2]),P=Q[3]||"px";if(P!="px"){J.style[O]=(N||1)+P;T=((N||1)/R.cur(true))*T;J.style[O]=T+P}if(Q[1]){N=((Q[1]=="-="?-1:1)*N)+T}R.custom(T,N,P)}else{R.custom(T,S,"")}}});return true})},stop:function(F,E){var G=o.timers;if(F){this.queue([])}this.each(function(){for(var H=G.length-1;H>=0;H--){if(G[H].elem==this){if(E){G[H](true)}G.splice(H,1)}}});if(!E){this.dequeue()}return this}});o.each({slideDown:t("show",1),slideUp:t("hide",1),slideToggle:t("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"}},function(E,F){o.fn[E]=function(G,H){return this.animate(F,G,H)}});o.extend({speed:function(G,H,F){var E=typeof G==="object"?G:{complete:F||!F&&H||o.isFunction(G)&&G,duration:G,easing:F&&H||H&&!o.isFunction(H)&&H};E.duration=o.fx.off?0:typeof E.duration==="number"?E.duration:o.fx.speeds[E.duration]||o.fx.speeds._default;E.old=E.complete;E.complete=function(){if(E.queue!==false){o(this).dequeue()}if(o.isFunction(E.old)){E.old.call(this)}};return E},easing:{linear:function(G,H,E,F){return E+F*G},swing:function(G,H,E,F){return((-Math.cos(G*Math.PI)/2)+0.5)*F+E}},timers:[],fx:function(F,E,G){this.options=E;this.elem=F;this.prop=G;if(!E.orig){E.orig={}}}});o.fx.prototype={update:function(){if(this.options.step){this.options.step.call(this.elem,this.now,this)}(o.fx.step[this.prop]||o.fx.step._default)(this);if((this.prop=="height"||this.prop=="width")&&this.elem.style){this.elem.style.display="block"}},cur:function(F){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null)){return this.elem[this.prop]}var E=parseFloat(o.css(this.elem,this.prop,F));return E&&E>-10000?E:parseFloat(o.curCSS(this.elem,this.prop))||0},custom:function(I,H,G){this.startTime=e();this.start=I;this.end=H;this.unit=G||this.unit||"px";this.now=this.start;this.pos=this.state=0;var E=this;function F(J){return E.step(J)}F.elem=this.elem;if(F()&&o.timers.push(F)==1){n=setInterval(function(){var K=o.timers;for(var J=0;J<K.length;J++){if(!K[J]()){K.splice(J--,1)}}if(!K.length){clearInterval(n)}},13)}},show:function(){this.options.orig[this.prop]=o.attr(this.elem.style,this.prop);this.options.show=true;this.custom(this.prop=="width"||this.prop=="height"?1:0,this.cur());o(this.elem).show()},hide:function(){this.options.orig[this.prop]=o.attr(this.elem.style,this.prop);this.options.hide=true;this.custom(this.cur(),0)},step:function(H){var G=e();if(H||G>=this.options.duration+this.startTime){this.now=this.end;this.pos=this.state=1;this.update();this.options.curAnim[this.prop]=true;var E=true;for(var F in this.options.curAnim){if(this.options.curAnim[F]!==true){E=false}}if(E){if(this.options.display!=null){this.elem.style.overflow=this.options.overflow;this.elem.style.display=this.options.display;if(o.css(this.elem,"display")=="none"){this.elem.style.display="block"}}if(this.options.hide){o(this.elem).hide()}if(this.options.hide||this.options.show){for(var I in this.options.curAnim){o.attr(this.elem.style,I,this.options.orig[I])}}this.options.complete.call(this.elem)}return false}else{var J=G-this.startTime;this.state=J/this.options.duration;this.pos=o.easing[this.options.easing||(o.easing.swing?"swing":"linear")](this.state,J,0,1,this.options.duration);this.now=this.start+((this.end-this.start)*this.pos);this.update()}return true}};o.extend(o.fx,{speeds:{slow:600,fast:200,_default:400},step:{opacity:function(E){o.attr(E.elem.style,"opacity",E.now)},_default:function(E){if(E.elem.style&&E.elem.style[E.prop]!=null){E.elem.style[E.prop]=E.now+E.unit}else{E.elem[E.prop]=E.now}}}});if(document.documentElement.getBoundingClientRect){o.fn.offset=function(){if(!this[0]){return{top:0,left:0}}if(this[0]===this[0].ownerDocument.body){return o.offset.bodyOffset(this[0])}var G=this[0].getBoundingClientRect(),J=this[0].ownerDocument,F=J.body,E=J.documentElement,L=E.clientTop||F.clientTop||0,K=E.clientLeft||F.clientLeft||0,I=G.top+(self.pageYOffset||o.boxModel&&E.scrollTop||F.scrollTop)-L,H=G.left+(self.pageXOffset||o.boxModel&&E.scrollLeft||F.scrollLeft)-K;return{top:I,left:H}}}else{o.fn.offset=function(){if(!this[0]){return{top:0,left:0}}if(this[0]===this[0].ownerDocument.body){return o.offset.bodyOffset(this[0])}o.offset.initialized||o.offset.initialize();var J=this[0],G=J.offsetParent,F=J,O=J.ownerDocument,M,H=O.documentElement,K=O.body,L=O.defaultView,E=L.getComputedStyle(J,null),N=J.offsetTop,I=J.offsetLeft;while((J=J.parentNode)&&J!==K&&J!==H){M=L.getComputedStyle(J,null);N-=J.scrollTop,I-=J.scrollLeft;if(J===G){N+=J.offsetTop,I+=J.offsetLeft;if(o.offset.doesNotAddBorder&&!(o.offset.doesAddBorderForTableAndCells&&/^t(able|d|h)$/i.test(J.tagName))){N+=parseInt(M.borderTopWidth,10)||0,I+=parseInt(M.borderLeftWidth,10)||0}F=G,G=J.offsetParent}if(o.offset.subtractsBorderForOverflowNotVisible&&M.overflow!=="visible"){N+=parseInt(M.borderTopWidth,10)||0,I+=parseInt(M.borderLeftWidth,10)||0}E=M}if(E.position==="relative"||E.position==="static"){N+=K.offsetTop,I+=K.offsetLeft}if(E.position==="fixed"){N+=Math.max(H.scrollTop,K.scrollTop),I+=Math.max(H.scrollLeft,K.scrollLeft)}return{top:N,left:I}}}o.offset={initialize:function(){if(this.initialized){return}var L=document.body,F=document.createElement("div"),H,G,N,I,M,E,J=L.style.marginTop,K='<div style="position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;"><div></div></div><table style="position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;" cellpadding="0" cellspacing="0"><tr><td></td></tr></table>';M={position:"absolute",top:0,left:0,margin:0,border:0,width:"1px",height:"1px",visibility:"hidden"};for(E in M){F.style[E]=M[E]}F.innerHTML=K;L.insertBefore(F,L.firstChild);H=F.firstChild,G=H.firstChild,I=H.nextSibling.firstChild.firstChild;this.doesNotAddBorder=(G.offsetTop!==5);this.doesAddBorderForTableAndCells=(I.offsetTop===5);H.style.overflow="hidden",H.style.position="relative";this.subtractsBorderForOverflowNotVisible=(G.offsetTop===-5);L.style.marginTop="1px";this.doesNotIncludeMarginInBodyOffset=(L.offsetTop===0);L.style.marginTop=J;L.removeChild(F);this.initialized=true},bodyOffset:function(E){o.offset.initialized||o.offset.initialize();var G=E.offsetTop,F=E.offsetLeft;if(o.offset.doesNotIncludeMarginInBodyOffset){G+=parseInt(o.curCSS(E,"marginTop",true),10)||0,F+=parseInt(o.curCSS(E,"marginLeft",true),10)||0}return{top:G,left:F}}};o.fn.extend({position:function(){var I=0,H=0,F;if(this[0]){var G=this.offsetParent(),J=this.offset(),E=/^body|html$/i.test(G[0].tagName)?{top:0,left:0}:G.offset();J.top-=j(this,"marginTop");J.left-=j(this,"marginLeft");E.top+=j(G,"borderTopWidth");E.left+=j(G,"borderLeftWidth");F={top:J.top-E.top,left:J.left-E.left}}return F},offsetParent:function(){var E=this[0].offsetParent||document.body;while(E&&(!/^body|html$/i.test(E.tagName)&&o.css(E,"position")=="static")){E=E.offsetParent}return o(E)}});o.each(["Left","Top"],function(F,E){var G="scroll"+E;o.fn[G]=function(H){if(!this[0]){return null}return H!==g?this.each(function(){this==l||this==document?l.scrollTo(!F?H:o(l).scrollLeft(),F?H:o(l).scrollTop()):this[G]=H}):this[0]==l||this[0]==document?self[F?"pageYOffset":"pageXOffset"]||o.boxModel&&document.documentElement[G]||document.body[G]:this[0][G]}});o.each(["Height","Width"],function(H,F){var E=H?"Left":"Top",G=H?"Right":"Bottom";o.fn["inner"+F]=function(){return this[F.toLowerCase()]()+j(this,"padding"+E)+j(this,"padding"+G)};o.fn["outer"+F]=function(J){return this["inner"+F]()+j(this,"border"+E+"Width")+j(this,"border"+G+"Width")+(J?j(this,"margin"+E)+j(this,"margin"+G):0)};var I=F.toLowerCase();o.fn[I]=function(J){return this[0]==l?document.compatMode=="CSS1Compat"&&document.documentElement["client"+F]||document.body["client"+F]:this[0]==document?Math.max(document.documentElement["client"+F],document.body["scroll"+F],document.documentElement["scroll"+F],document.body["offset"+F],document.documentElement["offset"+F]):J===g?(this.length?o.css(this[0],I):null):this.css(I,typeof J==="string"?J:J+"px")}})})();
\ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/web/lang-css.js Mon Jul 26 14:12:44 2010 -0400
@@ -0,0 +1,2 @@
+PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[ \t\r\n\f]+/,null," \t\r\n\u000c"]],[["str",/^\"(?:[^\n\r\f\\\"]|\\(?:\r\n?|\n|\f)|\\[\s\S])*\"/,null],["str",/^\'(?:[^\n\r\f\\\']|\\(?:\r\n?|\n|\f)|\\[\s\S])*\'/,null],["lang-css-str",/^url\(([^\)\"\']*)\)/i],["kwd",/^(?:url|rgb|\!important|@import|@page|@media|@charset|inherit)(?=[^\-\w]|$)/i,null],["lang-css-kw",/^(-?(?:[_a-z]|(?:\\[0-9a-f]+ ?))(?:[_a-z0-9\-]|\\(?:\\[0-9a-f]+ ?))*)\s*:/i],["com",/^\/\*[^*]*\*+(?:[^\/*][^*]*\*+)*\//],["com",
+/^(?:<!--|--\>)/],["lit",/^(?:\d+|\d*\.\d+)(?:%|[a-z]+)?/i],["lit",/^#(?:[0-9a-f]{3}){1,2}/i],["pln",/^-?(?:[_a-z]|(?:\\[\da-f]+ ?))(?:[_a-z\d\-]|\\(?:\\[\da-f]+ ?))*/i],["pun",/^[^\s\w\'\"]+/]]),["css"]);PR.registerLangHandler(PR.createSimpleLexer([],[["kwd",/^-?(?:[_a-z]|(?:\\[\da-f]+ ?))(?:[_a-z\d\-]|\\(?:\\[\da-f]+ ?))*/i]]),["css-kw"]);PR.registerLangHandler(PR.createSimpleLexer([],[["str",/^[^\)\"\']+/]]),["css-str"]);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/web/lang-hs.js Mon Jul 26 14:12:44 2010 -0400
@@ -0,0 +1,2 @@
+PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t\n\x0B\x0C\r ]+/,null,"\t\n\u000b\u000c\r "],["str",/^\"(?:[^\"\\\n\x0C\r]|\\[\s\S])*(?:\"|$)/,null,'"'],["str",/^\'(?:[^\'\\\n\x0C\r]|\\[^&])\'?/,null,"'"],["lit",/^(?:0o[0-7]+|0x[\da-f]+|\d+(?:\.\d+)?(?:e[+\-]?\d+)?)/i,null,"0123456789"]],[["com",/^(?:(?:--+(?:[^\r\n\x0C]*)?)|(?:\{-(?:[^-]|-+[^-\}])*-\}))/],["kwd",/^(?:case|class|data|default|deriving|do|else|if|import|in|infix|infixl|infixr|instance|let|module|newtype|of|then|type|where|_)(?=[^a-zA-Z0-9\']|$)/,
+null],["pln",/^(?:[A-Z][\w\']*\.)*[a-zA-Z][\w\']*/],["pun",/^[^\t\n\x0B\x0C\r a-zA-Z0-9\'\"]+/]]),["hs"]);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/web/lang-lisp.js Mon Jul 26 14:12:44 2010 -0400
@@ -0,0 +1,3 @@
+(function(){var a=null;
+PR.registerLangHandler(PR.createSimpleLexer([["opn",/^\(/,a,"("],["clo",/^\)/,a,")"],["com",/^;[^\r\n]*/,a,";"],["pln",/^[\t\n\r \xA0]+/,a,"\t\n\r \u00a0"],["str",/^\"(?:[^\"\\]|\\[\s\S])*(?:\"|$)/,a,'"']],[["kwd",/^(?:block|c[ad]+r|catch|cons|defun|do|eq|eql|equal|equalp|eval-when|flet|format|go|if|labels|lambda|let|load-time-value|locally|macrolet|multiple-value-call|nil|progn|progv|quote|require|return-from|setq|symbol-macrolet|t|tagbody|the|throw|unwind)\b/,a],["lit",/^[+\-]?(?:0x[0-9a-f]+|\d+\/\d+|(?:\.\d+|\d+(?:\.\d*)?)(?:[ed][+\-]?\d+)?)/i],["lit",
+/^\'(?:-*(?:\w|\\[\x21-\x7e])(?:[\w-]*|\\[\x21-\x7e])[=!?]?)?/],["pln",/^-*(?:[a-z_]|\\[\x21-\x7e])(?:[\w-]*|\\[\x21-\x7e])[=!?]?/i],["pun",/^[^\w\t\n\r \xA0()\"\\\';]+/]]),["cl","el","lisp","scm"])})()
\ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/web/lang-lua.js Mon Jul 26 14:12:44 2010 -0400
@@ -0,0 +1,2 @@
+PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t\n\r \xA0]+/,null,"\t\n\r \u00a0"],["str",/^(?:\"(?:[^\"\\]|\\[\s\S])*(?:\"|$)|\'(?:[^\'\\]|\\[\s\S])*(?:\'|$))/,null,"\"'"]],[["com",/^--(?:\[(=*)\[[\s\S]*?(?:\]\1\]|$)|[^\r\n]*)/],["str",/^\[(=*)\[[\s\S]*?(?:\]\1\]|$)/],["kwd",/^(?:and|break|do|else|elseif|end|false|for|function|if|in|local|nil|not|or|repeat|return|then|true|until|while)\b/,null],["lit",/^[+-]?(?:0x[\da-f]+|(?:(?:\.\d+|\d+(?:\.\d*)?)(?:e[+\-]?\d+)?))/i],["pln",/^[a-z_]\w*/i],
+["pun",/^[^\w\t\n\r \xA0][^\w\t\n\r \xA0\"\-\+=]*/]]),["lua"]);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/web/lang-ml.js Mon Jul 26 14:12:44 2010 -0400
@@ -0,0 +1,2 @@
+PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t\n\r \xA0]+/,null,"\t\n\r \u00a0"],["com",/^#(?:if[\t\n\r \xA0]+(?:[a-z_$][\w\']*|``[^\r\n\t`]*(?:``|$))|else|endif|light)/i,null,"#"],["str",/^(?:\"(?:[^\"\\]|\\[\s\S])*(?:\"|$)|\'(?:[^\'\\]|\\[\s\S])*(?:\'|$))/,null,"\"'"]],[["com",/^(?:\/\/[^\r\n]*|\(\*[\s\S]*?\*\))/],["kwd",/^(?:abstract|and|as|assert|begin|class|default|delegate|do|done|downcast|downto|elif|else|end|exception|extern|false|finally|for|fun|function|if|in|inherit|inline|interface|internal|lazy|let|match|member|module|mutable|namespace|new|null|of|open|or|override|private|public|rec|return|static|struct|then|to|true|try|type|upcast|use|val|void|when|while|with|yield|asr|land|lor|lsl|lsr|lxor|mod|sig|atomic|break|checked|component|const|constraint|constructor|continue|eager|event|external|fixed|functor|global|include|method|mixin|object|parallel|process|protected|pure|sealed|trait|virtual|volatile)\b/],
+["lit",/^[+\-]?(?:0x[\da-f]+|(?:(?:\.\d+|\d+(?:\.\d*)?)(?:e[+\-]?\d+)?))/i],["pln",/^(?:[a-z_]\w*[!?#]?|``[^\r\n\t`]*(?:``|$))/i],["pun",/^[^\t\n\r \xA0\"\'\w]+/]]),["fs","ml"]);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/web/lang-proto.js Mon Jul 26 14:12:44 2010 -0400
@@ -0,0 +1,1 @@
+PR.registerLangHandler(PR.sourceDecorator({keywords:"bool bytes default double enum extend extensions false fixed32 fixed64 float group import int32 int64 max message option optional package repeated required returns rpc service sfixed32 sfixed64 sint32 sint64 string syntax to true uint32 uint64",cStyleComments:true}),["proto"]);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/web/lang-sql.js Mon Jul 26 14:12:44 2010 -0400
@@ -0,0 +1,2 @@
+PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t\n\r \xA0]+/,null,"\t\n\r \u00a0"],["str",/^(?:"(?:[^\"\\]|\\.)*"|'(?:[^\'\\]|\\.)*')/,null,"\"'"]],[["com",/^(?:--[^\r\n]*|\/\*[\s\S]*?(?:\*\/|$))/],["kwd",/^(?:ADD|ALL|ALTER|AND|ANY|AS|ASC|AUTHORIZATION|BACKUP|BEGIN|BETWEEN|BREAK|BROWSE|BULK|BY|CASCADE|CASE|CHECK|CHECKPOINT|CLOSE|CLUSTERED|COALESCE|COLLATE|COLUMN|COMMIT|COMPUTE|CONSTRAINT|CONTAINS|CONTAINSTABLE|CONTINUE|CONVERT|CREATE|CROSS|CURRENT|CURRENT_DATE|CURRENT_TIME|CURRENT_TIMESTAMP|CURRENT_USER|CURSOR|DATABASE|DBCC|DEALLOCATE|DECLARE|DEFAULT|DELETE|DENY|DESC|DISK|DISTINCT|DISTRIBUTED|DOUBLE|DROP|DUMMY|DUMP|ELSE|END|ERRLVL|ESCAPE|EXCEPT|EXEC|EXECUTE|EXISTS|EXIT|FETCH|FILE|FILLFACTOR|FOR|FOREIGN|FREETEXT|FREETEXTTABLE|FROM|FULL|FUNCTION|GOTO|GRANT|GROUP|HAVING|HOLDLOCK|IDENTITY|IDENTITYCOL|IDENTITY_INSERT|IF|IN|INDEX|INNER|INSERT|INTERSECT|INTO|IS|JOIN|KEY|KILL|LEFT|LIKE|LINENO|LOAD|NATIONAL|NOCHECK|NONCLUSTERED|NOT|NULL|NULLIF|OF|OFF|OFFSETS|ON|OPEN|OPENDATASOURCE|OPENQUERY|OPENROWSET|OPENXML|OPTION|OR|ORDER|OUTER|OVER|PERCENT|PLAN|PRECISION|PRIMARY|PRINT|PROC|PROCEDURE|PUBLIC|RAISERROR|READ|READTEXT|RECONFIGURE|REFERENCES|REPLICATION|RESTORE|RESTRICT|RETURN|REVOKE|RIGHT|ROLLBACK|ROWCOUNT|ROWGUIDCOL|RULE|SAVE|SCHEMA|SELECT|SESSION_USER|SET|SETUSER|SHUTDOWN|SOME|STATISTICS|SYSTEM_USER|TABLE|TEXTSIZE|THEN|TO|TOP|TRAN|TRANSACTION|TRIGGER|TRUNCATE|TSEQUAL|UNION|UNIQUE|UPDATE|UPDATETEXT|USE|USER|VALUES|VARYING|VIEW|WAITFOR|WHEN|WHERE|WHILE|WITH|WRITETEXT)(?=[^\w-]|$)/i,
+null],["lit",/^[+-]?(?:0x[\da-f]+|(?:(?:\.\d+|\d+(?:\.\d*)?)(?:e[+\-]?\d+)?))/i],["pln",/^[a-z_][\w-]*/i],["pun",/^[^\w\t\n\r \xA0]+/]]),["sql"]);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/web/lang-vb.js Mon Jul 26 14:12:44 2010 -0400
@@ -0,0 +1,2 @@
+PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t\n\r \xA0\u2028\u2029]+/,null,"\t\n\r \u00a0\u2028\u2029"],["str",/^(?:[\"\u201C\u201D](?:[^\"\u201C\u201D]|[\"\u201C\u201D]{2})(?:[\"\u201C\u201D]c|$)|[\"\u201C\u201D](?:[^\"\u201C\u201D]|[\"\u201C\u201D]{2})*(?:[\"\u201C\u201D]|$))/i,null,'"\u201c\u201d'],["com",/^[\'\u2018\u2019][^\r\n\u2028\u2029]*/,null,"'\u2018\u2019"]],[["kwd",/^(?:AddHandler|AddressOf|Alias|And|AndAlso|Ansi|As|Assembly|Auto|Boolean|ByRef|Byte|ByVal|Call|Case|Catch|CBool|CByte|CChar|CDate|CDbl|CDec|Char|CInt|Class|CLng|CObj|Const|CShort|CSng|CStr|CType|Date|Decimal|Declare|Default|Delegate|Dim|DirectCast|Do|Double|Each|Else|ElseIf|End|EndIf|Enum|Erase|Error|Event|Exit|Finally|For|Friend|Function|Get|GetType|GoSub|GoTo|Handles|If|Implements|Imports|In|Inherits|Integer|Interface|Is|Let|Lib|Like|Long|Loop|Me|Mod|Module|MustInherit|MustOverride|MyBase|MyClass|Namespace|New|Next|Not|NotInheritable|NotOverridable|Object|On|Option|Optional|Or|OrElse|Overloads|Overridable|Overrides|ParamArray|Preserve|Private|Property|Protected|Public|RaiseEvent|ReadOnly|ReDim|RemoveHandler|Resume|Return|Select|Set|Shadows|Shared|Short|Single|Static|Step|Stop|String|Structure|Sub|SyncLock|Then|Throw|To|Try|TypeOf|Unicode|Until|Variant|Wend|When|While|With|WithEvents|WriteOnly|Xor|EndIf|GoSub|Let|Variant|Wend)\b/i,
+null],["com",/^REM[^\r\n\u2028\u2029]*/i],["lit",/^(?:True\b|False\b|Nothing\b|\d+(?:E[+\-]?\d+[FRD]?|[FRDSIL])?|(?:&H[0-9A-F]+|&O[0-7]+)[SIL]?|\d*\.\d+(?:E[+\-]?\d+)?[FRD]?|#\s+(?:\d+[\-\/]\d+[\-\/]\d+(?:\s+\d+:\d+(?::\d+)?(\s*(?:AM|PM))?)?|\d+:\d+(?::\d+)?(\s*(?:AM|PM))?)\s+#)/i],["pln",/^(?:(?:[a-z]|_\w)\w*|\[(?:[a-z]|_\w)\w*\])/i],["pun",/^[^\w\t\n\r \"\'\[\]\xA0\u2018\u2019\u201C\u201D\u2028\u2029]+/],["pun",/^(?:\[|\])/]]),["vb","vbs"]);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/web/lang-wiki.js Mon Jul 26 14:12:44 2010 -0400
@@ -0,0 +1,2 @@
+PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t \xA0a-gi-z0-9]+/,null,"\t \u00a0abcdefgijklmnopqrstuvwxyz0123456789"],["pun",/^[=*~\^\[\]]+/,null,"=*~^[]"]],[["lang-wiki.meta",/(?:^^|\r\n?|\n)(#[a-z]+)\b/],["lit",/^(?:[A-Z][a-z][a-z0-9]+[A-Z][a-z][a-zA-Z0-9]+)\b/],["lang-",/^\{\{\{([\s\S]+?)\}\}\}/],["lang-",/^`([^\r\n`]+)`/],["str",/^https?:\/\/[^\/?#\s]*(?:\/[^?#\s]*)?(?:\?[^#\s]*)?(?:#\S*)?/i],["pln",/^(?:\r\n|[\s\S])[^#=*~^A-Zh\{`\[\r\n]*/]]),["wiki"]);
+PR.registerLangHandler(PR.createSimpleLexer([["kwd",/^#[a-z]+/i,null,"#"]],[]),["wiki.meta"]);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/web/prettify.css Mon Jul 26 14:12:44 2010 -0400
@@ -0,0 +1,27 @@
+/* Pretty printing styles. Used with prettify.js. */
+
+.str { color: #080; }
+.kwd { color: #008; }
+.com { color: #800; }
+.typ { color: #606; }
+.lit { color: #066; }
+.pun { color: #660; }
+.pln { color: #000; }
+.tag { color: #008; }
+.atn { color: #606; }
+.atv { color: #080; }
+.dec { color: #606; }
+pre.prettyprint { padding: 2px; border: 1px solid #888; }
+
+@media print {
+ .str { color: #060; }
+ .kwd { color: #006; font-weight: bold; }
+ .com { color: #600; font-style: italic; }
+ .typ { color: #404; font-weight: bold; }
+ .lit { color: #044; }
+ .pun { color: #440; }
+ .pln { color: #000; }
+ .tag { color: #006; font-weight: bold; }
+ .atn { color: #404; }
+ .atv { color: #060; }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/web/prettify.js Mon Jul 26 14:12:44 2010 -0400
@@ -0,0 +1,33 @@
+(function(){
+var o=true,r=null,z=false;window.PR_SHOULD_USE_CONTINUATION=o;window.PR_TAB_WIDTH=8;window.PR_normalizedHtml=window.PR=window.prettyPrintOne=window.prettyPrint=void 0;window._pr_isIE6=function(){var N=navigator&&navigator.userAgent&&/\bMSIE 6\./.test(navigator.userAgent);window._pr_isIE6=function(){return N};return N};
+var aa="!",ba="!=",ca="!==",F="#",da="%",ea="%=",G="&",fa="&&",ja="&&=",ka="&=",H="(",la="*",ma="*=",na="+=",oa=",",pa="-=",qa="->",ra="/",sa="/=",ta=":",ua="::",va=";",I="<",wa="<<",xa="<<=",ya="<=",za="=",Aa="==",Ba="===",J=">",Ca=">=",Da=">>",Ea=">>=",Fa=">>>",Ga=">>>=",Ha="?",Ia="@",L="[",M="^",Ta="^=",Ua="^^",Va="^^=",Wa="{",O="|",Xa="|=",Ya="||",Za="||=",$a="~",ab="break",bb="case",cb="continue",db="delete",eb="do",fb="else",gb="finally",hb="instanceof",ib="return",jb="throw",kb="try",lb="typeof",
+mb="(?:^^|[+-]",nb="\\$1",ob=")\\s*",pb="&",qb="<",rb=">",sb=""",tb="&#",ub="x",vb="'",wb='"',xb=" ",yb="XMP",zb="</",Ab='="',P="",Q="\\",Bb="b",Cb="t",Db="n",Eb="v",Fb="f",Gb="r",Hb="u",Ib="0",Jb="1",Kb="2",Lb="3",Mb="4",Nb="5",Ob="6",Pb="7",Qb="\\x0",Rb="\\x",Sb="-",Tb="]",Ub="\\\\u[0-9A-Fa-f]{4}|\\\\x[0-9A-Fa-f]{2}|\\\\[0-3][0-7]{0,2}|\\\\[0-7]{1,2}|\\\\[\\s\\S]|-|[^-\\\\]",R="g",Vb="\\B",Wb="\\b",Xb="\\D",Yb="\\d",Zb="\\S",$b="\\s",ac="\\W",bc="\\w",cc="(?:\\[(?:[^\\x5C\\x5D]|\\\\[\\s\\S])*\\]|\\\\u[A-Fa-f0-9]{4}|\\\\x[A-Fa-f0-9]{2}|\\\\[0-9]+|\\\\[^ux0-9]|\\(\\?[:!=]|[\\(\\)\\^]|[^\\x5B\\x5C\\(\\)\\^]+)",
+dc="(?:",ec=")",fc="gi",gc="PRE",hc='<!DOCTYPE foo PUBLIC "foo bar">\n<foo />',ic="\t",jc="\n",kc="[^<]+|<!--[\\s\\S]*?--\>|<!\\[CDATA\\[[\\s\\S]*?\\]\\]>|</?[a-zA-Z][^>]*>|<",lc="nocode",mc=' $1="$2$3$4"',S="pln",nc="string",T="lang-",oc="src",U="str",pc="'\"",qc="'\"`",rc="\"'",V="com",sc="lang-regex",tc="(/(?=[^/*])(?:[^/\\x5B\\x5C]|\\x5C[\\s\\S]|\\x5B(?:[^\\x5C\\x5D]|\\x5C[\\s\\S])*(?:\\x5D|$))+/)",uc="kwd",vc="^(?:",wc=")\\b",xc=" \r\n\t\u00a0",yc="lit",zc="typ",Ac="0123456789",Y="pun",Bc="break continue do else for if return while auto case char const default double enum extern float goto int long register short signed sizeof static struct switch typedef union unsigned void volatile catch class delete false import new operator private protected public this throw true try alignof align_union asm axiom bool concept concept_map const_cast constexpr decltype dynamic_cast explicit export friend inline late_check mutable namespace nullptr reinterpret_cast static_assert static_cast template typeid typename typeof using virtual wchar_t where break continue do else for if return while auto case char const default double enum extern float goto int long register short signed sizeof static struct switch typedef union unsigned void volatile catch class delete false import new operator private protected public this throw true try boolean byte extends final finally implements import instanceof null native package strictfp super synchronized throws transient as base by checked decimal delegate descending event fixed foreach from group implicit in interface internal into is lock object out override orderby params partial readonly ref sbyte sealed stackalloc string select uint ulong unchecked unsafe ushort var break continue do else for if return while auto case char const default double enum extern float goto int long register short signed sizeof static struct switch typedef union unsigned void volatile catch class delete false import new operator private protected public this throw true try debugger eval export function get null set undefined var with Infinity NaN caller delete die do dump elsif eval exit foreach for goto if import last local my next no our print package redo require sub undef unless until use wantarray while BEGIN END break continue do else for if return while and as assert class def del elif except exec finally from global import in is lambda nonlocal not or pass print raise try with yield False True None break continue do else for if return while alias and begin case class def defined elsif end ensure false in module next nil not or redo rescue retry self super then true undef unless until when yield BEGIN END break continue do else for if return while case done elif esac eval fi function in local set then until ",
+Cc="</span>",Dc='<span class="',Ec='">',Fc="$1 ",Gc=" <br />",Hc="<br />",Ic="console",Jc="cannot override language handler %s",Kc="default-markup",Lc="default-code",Mc="dec",Z="lang-js",$="lang-css",Nc="lang-in.tag",Oc="htm",Pc="html",Qc="mxml",Rc="xhtml",Sc="xml",Tc="xsl",Uc=" \t\r\n",Vc="atv",Wc="tag",Xc="atn",Yc="lang-uq.val",Zc="in.tag",$c="uq.val",ad="break continue do else for if return while auto case char const default double enum extern float goto int long register short signed sizeof static struct switch typedef union unsigned void volatile catch class delete false import new operator private protected public this throw true try alignof align_union asm axiom bool concept concept_map const_cast constexpr decltype dynamic_cast explicit export friend inline late_check mutable namespace nullptr reinterpret_cast static_assert static_cast template typeid typename typeof using virtual wchar_t where ",
+bd="c",cd="cc",dd="cpp",ed="cxx",fd="cyc",gd="m",hd="null true false",id="json",jd="break continue do else for if return while auto case char const default double enum extern float goto int long register short signed sizeof static struct switch typedef union unsigned void volatile catch class delete false import new operator private protected public this throw true try boolean byte extends final finally implements import instanceof null native package strictfp super synchronized throws transient as base by checked decimal delegate descending event fixed foreach from group implicit in interface internal into is lock object out override orderby params partial readonly ref sbyte sealed stackalloc string select uint ulong unchecked unsafe ushort var ",
+kd="cs",ld="break continue do else for if return while auto case char const default double enum extern float goto int long register short signed sizeof static struct switch typedef union unsigned void volatile catch class delete false import new operator private protected public this throw true try boolean byte extends final finally implements import instanceof null native package strictfp super synchronized throws transient ",md="java",nd="break continue do else for if return while case done elif esac eval fi function in local set then until ",
+od="bsh",pd="csh",qd="sh",rd="break continue do else for if return while and as assert class def del elif except exec finally from global import in is lambda nonlocal not or pass print raise try with yield False True None ",sd="cv",td="py",ud="caller delete die do dump elsif eval exit foreach for goto if import last local my next no our print package redo require sub undef unless until use wantarray while BEGIN END ",vd="perl",wd="pl",xd="pm",yd="break continue do else for if return while alias and begin case class def defined elsif end ensure false in module next nil not or redo rescue retry self super then true undef unless until when yield BEGIN END ",
+zd="rb",Ad="break continue do else for if return while auto case char const default double enum extern float goto int long register short signed sizeof static struct switch typedef union unsigned void volatile catch class delete false import new operator private protected public this throw true try debugger eval export function get null set undefined var with Infinity NaN ",Bd="js",Cd="regex",Dd="pre",Ed="code",Fd="xmp",Gd="prettyprint",Hd="class",Id="br",Jd="\r";
+(function(){var N=function(){for(var a=[aa,ba,ca,F,da,ea,G,fa,ja,ka,H,la,ma,na,oa,pa,qa,ra,sa,ta,ua,va,I,wa,xa,ya,za,Aa,Ba,J,Ca,Da,Ea,Fa,Ga,Ha,Ia,L,M,Ta,Ua,Va,Wa,O,Xa,Ya,Za,$a,ab,bb,cb,db,eb,fb,gb,hb,ib,jb,kb,lb],b=mb,c=0;c<a.length;++c)b+=O+a[c].replace(/([^=<>:&a-z])/g,nb);b+=ob;return b}(),Ja=/&/g,Ka=/</g,La=/>/g,Kd=/\"/g;function Ld(a){return a.replace(Ja,pb).replace(Ka,qb).replace(La,rb).replace(Kd,sb)}function ga(a){return a.replace(Ja,pb).replace(Ka,qb).replace(La,rb)}var Md=/</g,Nd=/>/g,
+Od=/'/g,Pd=/"/g,Qd=/&/g,Rd=/ /g;function Sd(a){var b=a.indexOf(G);if(b<0)return a;for(--b;(b=a.indexOf(tb,b+1))>=0;){var c=a.indexOf(va,b);if(c>=0){var d=a.substring(b+3,c),g=10;if(d&&d.charAt(0)===ub){d=d.substring(1);g=16}var i=parseInt(d,g);isNaN(i)||(a=a.substring(0,b)+String.fromCharCode(i)+a.substring(c+1))}}return a.replace(Md,I).replace(Nd,J).replace(Od,vb).replace(Pd,wb).replace(Qd,G).replace(Rd,xb)}function Ma(a){return yb===a.tagName}function W(a,b){switch(a.nodeType){case 1:var c=
+a.tagName.toLowerCase();b.push(I,c);for(var d=0;d<a.attributes.length;++d){var g=a.attributes[d];if(g.specified){b.push(xb);W(g,b)}}b.push(J);for(var i=a.firstChild;i;i=i.nextSibling)W(i,b);if(a.firstChild||!/^(?:br|link|img)$/.test(c))b.push(zb,c,J);break;case 2:b.push(a.name.toLowerCase(),Ab,Ld(a.value),wb);break;case 3:case 4:b.push(ga(a.nodeValue));break}}function Na(a){for(var b=0,c=z,d=z,g=0,i=a.length;g<i;++g){var m=a[g];if(m.ignoreCase)d=o;else if(/[a-z]/i.test(m.source.replace(/\\u[0-9a-f]{4}|\\x[0-9a-f]{2}|\\[^ux]/gi,
+P))){c=o;d=z;break}}function l(j){if(j.charAt(0)!==Q)return j.charCodeAt(0);switch(j.charAt(1)){case Bb:return 8;case Cb:return 9;case Db:return 10;case Eb:return 11;case Fb:return 12;case Gb:return 13;case Hb:case ub:return parseInt(j.substring(2),16)||j.charCodeAt(1);case Ib:case Jb:case Kb:case Lb:case Mb:case Nb:case Ob:case Pb:return parseInt(j.substring(1),8);default:return j.charCodeAt(1)}}function n(j){if(j<32)return(j<16?Qb:Rb)+j.toString(16);var f=String.fromCharCode(j);if(f===Q||f===Sb||
+f===L||f===Tb)f=Q+f;return f}function q(j){for(var f=j.substring(1,j.length-1).match(new RegExp(Ub,R)),s=[],k=[],h=f[0]===M,e=h?1:0,p=f.length;e<p;++e){var t=f[e];switch(t){case Vb:case Wb:case Xb:case Yb:case Zb:case $b:case ac:case bc:s.push(t);continue}var u=l(t),x;if(e+2<p&&Sb===f[e+1]){x=l(f[e+2]);e+=2}else x=u;k.push([u,x]);if(!(x<65||u>122)){x<65||u>90||k.push([Math.max(65,u)|32,Math.min(x,90)|32]);x<97||u>122||k.push([Math.max(97,u)&-33,Math.min(x,122)&-33])}}k.sort(function(Oa,Pa){return Oa[0]-
+Pa[0]||Pa[1]-Oa[1]});var B=[],E=[NaN,NaN];for(e=0;e<k.length;++e){var A=k[e];if(A[0]<=E[1]+1)E[1]=Math.max(E[1],A[1]);else B.push(E=A)}var D=[L];h&&D.push(M);D.push.apply(D,s);for(e=0;e<B.length;++e){A=B[e];D.push(n(A[0]));if(A[1]>A[0]){A[1]+1>A[0]&&D.push(Sb);D.push(n(A[1]))}}D.push(Tb);return D.join(P)}function v(j){var f=j.source.match(new RegExp(cc,R)),s=f.length,k=[],h,e=0;for(h=0;e<s;++e){var p=f[e];if(p===H)++h;else if(Q===p.charAt(0)){var t=+p.substring(1);if(t&&t<=h)k[t]=-1}}for(e=1;e<k.length;++e)if(-1===
+k[e])k[e]=++b;for(h=e=0;e<s;++e){p=f[e];if(p===H){++h;if(k[h]===undefined)f[e]=dc}else if(Q===p.charAt(0))if((t=+p.substring(1))&&t<=h)f[e]=Q+k[h]}for(h=e=0;e<s;++e)if(M===f[e]&&M!==f[e+1])f[e]=P;if(j.ignoreCase&&c)for(e=0;e<s;++e){p=f[e];var u=p.charAt(0);if(p.length>=2&&u===L)f[e]=q(p);else if(u!==Q)f[e]=p.replace(/[a-zA-Z]/g,function(x){var B=x.charCodeAt(0);return L+String.fromCharCode(B&-33,B|32)+Tb})}return f.join(P)}var w=[];g=0;for(i=a.length;g<i;++g){m=a[g];if(m.global||m.multiline)throw new Error(P+
+m);w.push(dc+v(m)+ec)}return new RegExp(w.join(O),d?fc:R)}var ha=r;function Td(a){if(r===ha){var b=document.createElement(gc);b.appendChild(document.createTextNode(hc));ha=!/</.test(b.innerHTML)}if(ha){var c=a.innerHTML;if(Ma(a))c=ga(c);return c}for(var d=[],g=a.firstChild;g;g=g.nextSibling)W(g,d);return d.join(P)}function Ud(a){var b=0;return function(c){for(var d=r,g=0,i=0,m=c.length;i<m;++i){var l=c.charAt(i);switch(l){case ic:d||(d=[]);d.push(c.substring(g,i));var n=a-b%a;for(b+=n;n>=0;n-=" ".length)d.push(" ".substring(0,
+n));g=i+1;break;case jc:b=0;break;default:++b}}if(!d)return c;d.push(c.substring(g));return d.join(P)}}var Vd=new RegExp(kc,R),Wd=/^<\!--/,Xd=/^<\[CDATA\[/,Yd=/^<br\b/i,Qa=/^<(\/?)([a-zA-Z]+)/;function Zd(a){var b=a.match(Vd),c=[],d=0,g=[];if(b)for(var i=0,m=b.length;i<m;++i){var l=b[i];if(l.length>1&&l.charAt(0)===I){if(!Wd.test(l))if(Xd.test(l)){c.push(l.substring(9,l.length-3));d+=l.length-12}else if(Yd.test(l)){c.push(jc);++d}else if(l.indexOf(lc)>=0&&$d(l)){var n=l.match(Qa)[2],q=1,v;v=i+1;a:for(;v<
+m;++v){var w=b[v].match(Qa);if(w&&w[2]===n)if(w[1]===ra){if(--q===0)break a}else++q}if(v<m){g.push(d,b.slice(i,v+1).join(P));i=v}else g.push(d,l)}else g.push(d,l)}else{var j=Sd(l);c.push(j);d+=j.length}}return{source:c.join(P),tags:g}}function $d(a){return!!a.replace(/\s(\w+)\s*=\s*(?:\"([^\"]*)\"|'([^\']*)'|(\S+))/g,mc).match(/[cC][lL][aA][sS][sS]=\"[^\"]*\bnocode\b/)}function ia(a,b,c,d){if(b){var g={source:b,b:a};c(g);d.push.apply(d,g.c)}}function K(a,b){var c={},d;(function(){for(var m=a.concat(b),
+l=[],n={},q=0,v=m.length;q<v;++q){var w=m[q],j=w[3];if(j)for(var f=j.length;--f>=0;)c[j.charAt(f)]=w;var s=w[1],k=P+s;if(!n.hasOwnProperty(k)){l.push(s);n[k]=r}}l.push(/[\0-\uffff]/);d=Na(l)})();var g=b.length,i=function(m){for(var l=m.source,n=m.b,q=[n,S],v=0,w=l.match(d)||[],j={},f=0,s=w.length;f<s;++f){var k=w[f],h=j[k],e,p;if(typeof h===nc)p=z;else{var t=c[k.charAt(0)];if(t){e=k.match(t[1]);h=t[0]}else{for(var u=0;u<g;++u){t=b[u];if(e=k.match(t[1])){h=t[0];break}}e||(h=S)}if((p=h.length>=5&&T===
+h.substring(0,5))&&!(e&&e[1])){p=z;h=oc}p||(j[k]=h)}var x=v;v+=k.length;if(p){var B=e[1],E=k.indexOf(B),A=E+B.length,D=h.substring(5);ia(n+x,k.substring(0,E),i,q);ia(n+x+E,B,Ra(D,B),q);ia(n+x+A,k.substring(A),i,q)}else q.push(n+x,h)}m.c=q};return i}function C(a){var b=[],c=[];if(a.tripleQuotedStrings)b.push([U,/^(?:\'\'\'(?:[^\'\\]|\\[\s\S]|\'{1,2}(?=[^\']))*(?:\'\'\'|$)|\"\"\"(?:[^\"\\]|\\[\s\S]|\"{1,2}(?=[^\"]))*(?:\"\"\"|$)|\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$))/,r,pc]);
+else a.multiLineStrings?b.push([U,/^(?:\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$)|\`(?:[^\\\`]|\\[\s\S])*(?:\`|$))/,r,qc]):b.push([U,/^(?:\'(?:[^\\\'\r\n]|\\.)*(?:\'|$)|\"(?:[^\\\"\r\n]|\\.)*(?:\"|$))/,r,rc]);if(a.hashComments)a.cStyleComments?b.push([V,/^#(?:[^\r\n\/]|\/(?!\*)|\/\*[^\r\n]*?\*\/)*/,r,F]):b.push([V,/^#[^\r\n]*/,r,F]);if(a.cStyleComments){c.push([V,/^\/\/[^\r\n]*/,r]);c.push([V,/^\/\*[\s\S]*?(?:\*\/|$)/,r])}a.regexLiterals&&c.push([sc,new RegExp(M+N+tc)]);var d=
+a.keywords.replace(/^\s+|\s+$/g,P);d.length&&c.push([uc,new RegExp(vc+d.replace(/\s+/g,O)+wc),r]);b.push([S,/^\s+/,r,xc]);c.push([yc,/^@[a-z_$][a-z_$@0-9]*/i,r,Ia],[zc,/^@?[A-Z]+[a-z][A-Za-z_$@0-9]*/,r],[S,/^[a-z_$][a-z_$@0-9]*/i,r],[yc,/^(?:0x[a-f0-9]+|(?:\d(?:_\d+)*\d*(?:\.\d*)?|\.\d\+)(?:e[+\-]?\d+)?)[a-z]*/i,r,Ac],[Y,/^.[^\s\w\.$@\'\"\`\/\#]*/,r]);return K(b,c)}var ae=C({keywords:Bc,hashComments:o,cStyleComments:o,multiLineStrings:o,regexLiterals:o});function be(a){var b=a.source,c=a.f,d=a.c,
+g=[],i=0,m=r,l=r,n=0,q=0,v=Ud(window.PR_TAB_WIDTH),w=/([\r\n ]) /g,j=/(^| ) /gm,f=/\r\n?|\n/g,s=/[ \r\n]$/,k=o;function h(p){if(p>i){if(m&&m!==l){g.push(Cc);m=r}if(!m&&l){m=l;g.push(Dc,m,Ec)}var t=ga(v(b.substring(i,p))).replace(k?j:w,Fc);k=s.test(t);var u=window._pr_isIE6()?Gc:Hc;g.push(t.replace(f,u));i=p}}for(;1;){var e;if(e=n<c.length?q<d.length?c[n]<=d[q]:o:z){h(c[n]);if(m){g.push(Cc);m=r}g.push(c[n+1]);n+=2}else if(q<d.length){h(d[q]);l=d[q+1];q+=2}else break}h(b.length);m&&g.push(Cc);a.a=g.join(P)}
+var X={};function y(a,b){for(var c=b.length;--c>=0;){var d=b[c];if(X.hasOwnProperty(d))Ic in window&&console.i(Jc,d);else X[d]=a}}function Ra(a,b){a&&X.hasOwnProperty(a)||(a=/^\s*</.test(b)?Kc:Lc);return X[a]}y(ae,[Lc]);y(K([],[[S,/^[^<?]+/],[Mc,/^<!\w[^>]*(?:>|$)/],[V,/^<\!--[\s\S]*?(?:-\->|$)/],[T,/^<\?([\s\S]+?)(?:\?>|$)/],[T,/^<%([\s\S]+?)(?:%>|$)/],[Y,/^(?:<[%?]|[%?]>)/],[T,/^<xmp\b[^>]*>([\s\S]+?)<\/xmp\b[^>]*>/i],[Z,/^<script\b[^>]*>([\s\S]+?)<\/script\b[^>]*>/i],[$,/^<style\b[^>]*>([\s\S]+?)<\/style\b[^>]*>/i],
+[Nc,/^(<\/?[a-z][^<>]*>)/i]]),[Kc,Oc,Pc,Qc,Rc,Sc,Tc]);y(K([[S,/^[\s]+/,r,Uc],[Vc,/^(?:\"[^\"]*\"?|\'[^\']*\'?)/,r,rc]],[[Wc,/^^<\/?[a-z](?:[\w:-]*\w)?|\/?>$/],[Xc,/^(?!style\b|on)[a-z](?:[\w:-]*\w)?/],[Yc,/^=\s*([^>\'\"\s]*(?:[^>\'\"\s\/]|\/(?=\s)))/],[Y,/^[=<>\/]+/],[Z,/^on\w+\s*=\s*\"([^\"]+)\"/i],[Z,/^on\w+\s*=\s*\'([^\']+)\'/i],[Z,/^on\w+\s*=\s*([^\"\'>\s]+)/i],[$,/^sty\w+\s*=\s*\"([^\"]+)\"/i],[$,/^sty\w+\s*=\s*\'([^\']+)\'/i],[$,/^sty\w+\s*=\s*([^\"\'>\s]+)/i]]),[Zc]);y(K([],[[Vc,/^[\s\S]+/]]),
+[$c]);y(C({keywords:ad,hashComments:o,cStyleComments:o}),[bd,cd,dd,ed,fd,gd]);y(C({keywords:hd}),[id]);y(C({keywords:jd,hashComments:o,cStyleComments:o}),[kd]);y(C({keywords:ld,cStyleComments:o}),[md]);y(C({keywords:nd,hashComments:o,multiLineStrings:o}),[od,pd,qd]);y(C({keywords:rd,hashComments:o,multiLineStrings:o,tripleQuotedStrings:o}),[sd,td]);y(C({keywords:ud,hashComments:o,multiLineStrings:o,regexLiterals:o}),[vd,wd,xd]);y(C({keywords:yd,hashComments:o,multiLineStrings:o,regexLiterals:o}),
+[zd]);y(C({keywords:Ad,cStyleComments:o,regexLiterals:o}),[Bd]);y(K([],[[U,/^[\s\S]+/]]),[Cd]);function Sa(a){var b=a.e,c=a.d;a.a=b;try{var d=Zd(b),g=d.source;a.source=g;a.b=0;a.f=d.tags;Ra(c,g)(a);be(a)}catch(i){if(Ic in window){console.log(i);console.h()}}}function ce(a,b){var c={e:a,d:b};Sa(c);return c.a}function de(a){for(var b=window._pr_isIE6(),c=[document.getElementsByTagName(Dd),document.getElementsByTagName(Ed),document.getElementsByTagName(Fd)],d=[],g=0;g<c.length;++g)for(var i=0,m=c[g].length;i<
+m;++i)d.push(c[g][i]);c=r;var l=Date;l.now||(l={now:function(){return(new Date).getTime()}});var n=0,q;function v(){for(var j=window.PR_SHOULD_USE_CONTINUATION?l.now()+250:Infinity;n<d.length&&l.now()<j;n++){var f=d[n];if(f.className&&f.className.indexOf(Gd)>=0){var s=f.className.match(/\blang-(\w+)\b/);if(s)s=s[1];for(var k=z,h=f.parentNode;h;h=h.parentNode)if((h.tagName===Dd||h.tagName===Ed||h.tagName===Fd)&&h.className&&h.className.indexOf(Gd)>=0){k=o;break}if(!k){var e=Td(f);e=e.replace(/(?:\r\n?|\n)$/,
+P);q={e:e,d:s,g:f};Sa(q);w()}}}if(n<d.length)setTimeout(v,250);else a&&a()}function w(){var j=q.a;if(j){var f=q.g;if(Ma(f)){for(var s=document.createElement(gc),k=0;k<f.attributes.length;++k){var h=f.attributes[k];if(h.specified){var e=h.name.toLowerCase();if(e===Hd)s.className=h.value;else s.setAttribute(h.name,h.value)}}s.innerHTML=j;f.parentNode.replaceChild(s,f);f=s}else f.innerHTML=j;if(b&&f.tagName===gc)for(var p=f.getElementsByTagName(Id),t=p.length;--t>=0;){var u=p[t];u.parentNode.replaceChild(document.createTextNode(Jd),
+u)}}}v()}window.PR_normalizedHtml=W;window.prettyPrintOne=ce;window.prettyPrint=de;window.PR={combinePrefixPatterns:Na,createSimpleLexer:K,registerLangHandler:y,sourceDecorator:C,PR_ATTRIB_NAME:Xc,PR_ATTRIB_VALUE:Vc,PR_COMMENT:V,PR_DECLARATION:Mc,PR_KEYWORD:uc,PR_LITERAL:yc,PR_NOCODE:lc,PR_PLAIN:S,PR_PUNCTUATION:Y,PR_SOURCE:oc,PR_STRING:U,PR_TAG:Wc,PR_TYPE:zc}})();
+})()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/web/red_style.css Mon Jul 26 14:12:44 2010 -0400
@@ -0,0 +1,291 @@
+
+/* layout, major elements */
+
+body {
+ background-color: #f9f9f4;
+ color: #111;
+ font: 12pt/14pt Arial, "Helvetica Neue", Helvetica, sans-serif;
+ margin: 1em 50px;
+ max-width: 1100px;
+}
+#left_column {
+ width: 650px;
+ float: left;
+ margin: 0em;
+ padding: 0em;
+}
+#right_column {
+ width: 400px;
+ float: right;
+ margin: 1em 0em 0em 40px;
+ padding: 0em;
+}
+@media screen and (max-width: 1200px) {
+ #left_column, #right_column {
+ width: 650px;
+ margin: 0em;
+ padding: 0em;
+ float: left;
+ }
+}
+h1 {
+ color: #444;
+ padding: 0;
+ margin: 8px 0px;
+ font-size: 1.5em;
+ width: 100%;
+}
+h1 a {
+ color: #444;
+ text-decoration: none;
+}
+.hilight {
+ color: #d33;
+}
+h2 {
+ font-size: 1.1em;
+}
+h3 {
+ font-size: 0.9em;
+ margin: 0;
+ padding: 0;
+}
+.hidden {
+ display: none;
+}
+.version {
+ padding-top: 5em;
+ text-align: center;
+ color: #aaa;
+ font-size: 0.6em;
+}
+.navigation {
+ text-align: center;
+ color: #aaa;
+ font-size: 0.8em;
+}
+.note {
+ text-align: center;
+ font-size: 0.8em;
+ color: #999;
+ font-style: italic;
+}
+
+
+/* message lists */
+
+ul {
+ margin-top: 0.2em;
+ margin-left: 12pt;
+}
+ul ul {
+ padding-left: 9pt;
+ margin-bottom: 0.4em;
+}
+li {
+ padding-left: 4pt;
+}
+
+/* request */
+
+input#uri {
+ width: 645px;
+ font-size: 1.1em;
+}
+.add_req_hdr {
+ -webkit-border-bottom-right-radius: 1em;
+ -webkit-border-bottom-left-radius: 1em;
+ -moz-border-radius-bottomleft: 1em;
+ -moz-border-radius-bottomright: 1em;
+ background-color: #a9a9a4;
+ width: 650px;
+ margin: 0;
+}
+#add_req_hdr {
+ font-size: 0.65em;
+ margin-left: 2em;
+ color: white;
+ text-decoration: none;
+}
+.delete_req_hdr {
+ font-size: 0.65em;
+ margin-left: 0.5em;
+ color: white;
+ text-decoration: none;
+}
+.req_hdr {
+ background-color: #a9a9a4;
+ width: 650px;
+ white-space: nowrap;
+}
+.hdr_name, .hdr_val {
+ font-size: 0.7em;
+}
+input.hdr_val {
+ width: 200px;
+ max-width: 200px;
+}
+option[value="other..."] {
+ font-style: italic;
+ color: #999;
+}
+
+
+/* response detail */
+
+#response {
+ font-size: 10pt;
+ font-family: Courier, monospace;
+ background-color: #111;
+ color: #ddd;
+ border-radius: 1em;
+ -moz-border-radius: 1em;
+ -webkit-border-radius: 1em;
+ border: 1px solid black;
+ padding: 1em 0em;
+ margin-bottom: 0em;
+}
+
+#response a {
+ color: #ccf;
+}
+.options {
+ margin-top: 0.2em;
+ margin-bottom: 2em;
+ padding-top: 0em;
+ text-align: center;
+ font-size: 0.6em;
+}
+.options > * {
+ padding-right: 2em;
+}
+
+
+/* summary table */
+
+table#summary {
+ font-size: 0.8em;
+ width: 100%;
+ border-spacing: 0;
+ background-color: #111;
+ color: #ddd;
+ border-radius: 1em;
+ -moz-border-radius: 1em;
+ -webkit-border-radius: 1em;
+ border: 1px solid black;
+ padding: 1em;
+ margin-top: 1em;
+}
+table#summary th {
+ text-align: left;
+ padding: 2em 0.5em 0.25em 0.5em;
+ border-bottom: 1px solid #666;
+ vertical-align: bottom;
+}
+table#summary td {
+ text-align: center;
+ padding: 0.25em 0.4em;
+ white-space: nowrap;
+}
+table#summary tr:hover td {
+ background-color: #333;
+}
+table#summary td.uri {
+ text-align: left;
+ font-family: monospace;
+}
+table#summary td a {
+ color: #ccf;
+}
+.fade1 { opacity: 0.75; }
+.fade2 { opacity: 0.5; }
+.fade3 { opacity: 0.25; }
+
+
+/* response body */
+
+#body {
+ display: none;
+ position: absolute;
+ right: 3em;
+ left: 3em;
+}
+
+#body .prettyprint {
+ font: 0.80em/1.00em Consolas, "Lucida Console", Monaco, monospace;
+ overflow-x: auto;
+ overflow-y: hidden;
+ background-color: #fff;
+ border: 1px solid #ccc;
+ border-radius: 1em;
+ -moz-border-radius: 1em;
+ -webkit-border-radius: 1em;
+ padding: 9px;
+}
+
+/* message levels */
+
+.good {
+ color: #060;
+ list-style-image: url(icon/accept1.png);
+}
+.warning {
+ color: #660;
+}
+ul li.warning {
+ list-style-image: url(icon/yellowflag1.png);
+}
+.bad {
+ color: #800;
+}
+td.bad {
+ color: #e66;
+ font-style: bold;
+}
+ul li.bad {
+ list-style-image: url(icon/remove-16.png);
+}
+.info {
+ color: #111;
+ list-style-image: url(icon/infomation-16.png);
+}
+
+
+/* popups */
+
+#popup {
+ position: absolute;
+ border-radius: 0.5em;
+ -moz-border-radius: 0.5em;
+ -webkit-border-radius: 0.5em;
+ background-color: #679;
+ opacity: 0.95;
+ padding: 9px;
+ color: white;
+ font-size: 0.85em;
+ z-index: 10;
+ display: none;
+}
+
+#popup img {
+ max-width: 440px;
+}
+
+
+/* footer */
+
+.footer {
+ max-width: 650px;
+}
+
+/* news */
+
+.news {
+ text-align: center;
+ font-size: 1.0em;
+ font-weight: normal;
+ width: 650px;
+}
+.news-banner {
+ color: #999;
+}
\ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/web/script.js Mon Jul 26 14:12:44 2010 -0400
@@ -0,0 +1,59 @@
+/*
+ * jQuery JavaScript Library v1.3.1
+ * http://jquery.com/
+ *
+ * Copyright (c) 2009 John Resig
+ * Dual licensed under the MIT and GPL licenses.
+ * http://docs.jquery.com/License
+ *
+ * Date: 2009-01-21 20:42:16 -0500 (Wed, 21 Jan 2009)
+ * Revision: 6158
+ */
+(function(){var l=this,g,y=l.jQuery,p=l.$,o=l.jQuery=l.$=function(E,F){return new o.fn.init(E,F)},D=/^[^<]*(<(.|\s)+>)[^>]*$|^#([\w-]+)$/,f=/^.[^:#\[\.,]*$/;o.fn=o.prototype={init:function(E,H){E=E||document;if(E.nodeType){this[0]=E;this.length=1;this.context=E;return this}if(typeof E==="string"){var G=D.exec(E);if(G&&(G[1]||!H)){if(G[1]){E=o.clean([G[1]],H)}else{var I=document.getElementById(G[3]);if(I&&I.id!=G[3]){return o().find(E)}var F=o(I||[]);F.context=document;F.selector=E;return F}}else{return o(H).find(E)}}else{if(o.isFunction(E)){return o(document).ready(E)}}if(E.selector&&E.context){this.selector=E.selector;this.context=E.context}return this.setArray(o.makeArray(E))},selector:"",jquery:"1.3.1",size:function(){return this.length},get:function(E){return E===g?o.makeArray(this):this[E]},pushStack:function(F,H,E){var G=o(F);G.prevObject=this;G.context=this.context;if(H==="find"){G.selector=this.selector+(this.selector?" ":"")+E}else{if(H){G.selector=this.selector+"."+H+"("+E+")"}}return G},setArray:function(E){this.length=0;Array.prototype.push.apply(this,E);return this},each:function(F,E){return o.each(this,F,E)},index:function(E){return o.inArray(E&&E.jquery?E[0]:E,this)},attr:function(F,H,G){var E=F;if(typeof F==="string"){if(H===g){return this[0]&&o[G||"attr"](this[0],F)}else{E={};E[F]=H}}return this.each(function(I){for(F in E){o.attr(G?this.style:this,F,o.prop(this,E[F],G,I,F))}})},css:function(E,F){if((E=="width"||E=="height")&&parseFloat(F)<0){F=g}return this.attr(E,F,"curCSS")},text:function(F){if(typeof F!=="object"&&F!=null){return this.empty().append((this[0]&&this[0].ownerDocument||document).createTextNode(F))}var E="";o.each(F||this,function(){o.each(this.childNodes,function(){if(this.nodeType!=8){E+=this.nodeType!=1?this.nodeValue:o.fn.text([this])}})});return E},wrapAll:function(E){if(this[0]){var F=o(E,this[0].ownerDocument).clone();if(this[0].parentNode){F.insertBefore(this[0])}F.map(function(){var G=this;while(G.firstChild){G=G.firstChild}return G}).append(this)}return this},wrapInner:function(E){return this.each(function(){o(this).contents().wrapAll(E)})},wrap:function(E){return this.each(function(){o(this).wrapAll(E)})},append:function(){return this.domManip(arguments,true,function(E){if(this.nodeType==1){this.appendChild(E)}})},prepend:function(){return this.domManip(arguments,true,function(E){if(this.nodeType==1){this.insertBefore(E,this.firstChild)}})},before:function(){return this.domManip(arguments,false,function(E){this.parentNode.insertBefore(E,this)})},after:function(){return this.domManip(arguments,false,function(E){this.parentNode.insertBefore(E,this.nextSibling)})},end:function(){return this.prevObject||o([])},push:[].push,find:function(E){if(this.length===1&&!/,/.test(E)){var G=this.pushStack([],"find",E);G.length=0;o.find(E,this[0],G);return G}else{var F=o.map(this,function(H){return o.find(E,H)});return this.pushStack(/[^+>] [^+>]/.test(E)?o.unique(F):F,"find",E)}},clone:function(F){var E=this.map(function(){if(!o.support.noCloneEvent&&!o.isXMLDoc(this)){var I=this.cloneNode(true),H=document.createElement("div");H.appendChild(I);return o.clean([H.innerHTML])[0]}else{return this.cloneNode(true)}});var G=E.find("*").andSelf().each(function(){if(this[h]!==g){this[h]=null}});if(F===true){this.find("*").andSelf().each(function(I){if(this.nodeType==3){return}var H=o.data(this,"events");for(var K in H){for(var J in H[K]){o.event.add(G[I],K,H[K][J],H[K][J].data)}}})}return E},filter:function(E){return this.pushStack(o.isFunction(E)&&o.grep(this,function(G,F){return E.call(G,F)})||o.multiFilter(E,o.grep(this,function(F){return F.nodeType===1})),"filter",E)},closest:function(E){var F=o.expr.match.POS.test(E)?o(E):null;return this.map(function(){var G=this;while(G&&G.ownerDocument){if(F?F.index(G)>-1:o(G).is(E)){return G}G=G.parentNode}})},not:function(E){if(typeof E==="string"){if(f.test(E)){return this.pushStack(o.multiFilter(E,this,true),"not",E)}else{E=o.multiFilter(E,this)}}var F=E.length&&E[E.length-1]!==g&&!E.nodeType;return this.filter(function(){return F?o.inArray(this,E)<0:this!=E})},add:function(E){return this.pushStack(o.unique(o.merge(this.get(),typeof E==="string"?o(E):o.makeArray(E))))},is:function(E){return !!E&&o.multiFilter(E,this).length>0},hasClass:function(E){return !!E&&this.is("."+E)},val:function(K){if(K===g){var E=this[0];if(E){if(o.nodeName(E,"option")){return(E.attributes.value||{}).specified?E.value:E.text}if(o.nodeName(E,"select")){var I=E.selectedIndex,L=[],M=E.options,H=E.type=="select-one";if(I<0){return null}for(var F=H?I:0,J=H?I+1:M.length;F<J;F++){var G=M[F];if(G.selected){K=o(G).val();if(H){return K}L.push(K)}}return L}return(E.value||"").replace(/\r/g,"")}return g}if(typeof K==="number"){K+=""}return this.each(function(){if(this.nodeType!=1){return}if(o.isArray(K)&&/radio|checkbox/.test(this.type)){this.checked=(o.inArray(this.value,K)>=0||o.inArray(this.name,K)>=0)}else{if(o.nodeName(this,"select")){var N=o.makeArray(K);o("option",this).each(function(){this.selected=(o.inArray(this.value,N)>=0||o.inArray(this.text,N)>=0)});if(!N.length){this.selectedIndex=-1}}else{this.value=K}}})},html:function(E){return E===g?(this[0]?this[0].innerHTML:null):this.empty().append(E)},replaceWith:function(E){return this.after(E).remove()},eq:function(E){return this.slice(E,+E+1)},slice:function(){return this.pushStack(Array.prototype.slice.apply(this,arguments),"slice",Array.prototype.slice.call(arguments).join(","))},map:function(E){return this.pushStack(o.map(this,function(G,F){return E.call(G,F,G)}))},andSelf:function(){return this.add(this.prevObject)},domManip:function(K,N,M){if(this[0]){var J=(this[0].ownerDocument||this[0]).createDocumentFragment(),G=o.clean(K,(this[0].ownerDocument||this[0]),J),I=J.firstChild,E=this.length>1?J.cloneNode(true):J;if(I){for(var H=0,F=this.length;H<F;H++){M.call(L(this[H],I),H>0?E.cloneNode(true):J)}}if(G){o.each(G,z)}}return this;function L(O,P){return N&&o.nodeName(O,"table")&&o.nodeName(P,"tr")?(O.getElementsByTagName("tbody")[0]||O.appendChild(O.ownerDocument.createElement("tbody"))):O}}};o.fn.init.prototype=o.fn;function z(E,F){if(F.src){o.ajax({url:F.src,async:false,dataType:"script"})}else{o.globalEval(F.text||F.textContent||F.innerHTML||"")}if(F.parentNode){F.parentNode.removeChild(F)}}function e(){return +new Date}o.extend=o.fn.extend=function(){var J=arguments[0]||{},H=1,I=arguments.length,E=false,G;if(typeof J==="boolean"){E=J;J=arguments[1]||{};H=2}if(typeof J!=="object"&&!o.isFunction(J)){J={}}if(I==H){J=this;--H}for(;H<I;H++){if((G=arguments[H])!=null){for(var F in G){var K=J[F],L=G[F];if(J===L){continue}if(E&&L&&typeof L==="object"&&!L.nodeType){J[F]=o.extend(E,K||(L.length!=null?[]:{}),L)}else{if(L!==g){J[F]=L}}}}}return J};var b=/z-?index|font-?weight|opacity|zoom|line-?height/i,q=document.defaultView||{},s=Object.prototype.toString;o.extend({noConflict:function(E){l.$=p;if(E){l.jQuery=y}return o},isFunction:function(E){return s.call(E)==="[object Function]"},isArray:function(E){return s.call(E)==="[object Array]"},isXMLDoc:function(E){return E.nodeType===9&&E.documentElement.nodeName!=="HTML"||!!E.ownerDocument&&o.isXMLDoc(E.ownerDocument)},globalEval:function(G){G=o.trim(G);if(G){var F=document.getElementsByTagName("head")[0]||document.documentElement,E=document.createElement("script");E.type="text/javascript";if(o.support.scriptEval){E.appendChild(document.createTextNode(G))}else{E.text=G}F.insertBefore(E,F.firstChild);F.removeChild(E)}},nodeName:function(F,E){return F.nodeName&&F.nodeName.toUpperCase()==E.toUpperCase()},each:function(G,K,F){var E,H=0,I=G.length;if(F){if(I===g){for(E in G){if(K.apply(G[E],F)===false){break}}}else{for(;H<I;){if(K.apply(G[H++],F)===false){break}}}}else{if(I===g){for(E in G){if(K.call(G[E],E,G[E])===false){break}}}else{for(var J=G[0];H<I&&K.call(J,H,J)!==false;J=G[++H]){}}}return G},prop:function(H,I,G,F,E){if(o.isFunction(I)){I=I.call(H,F)}return typeof I==="number"&&G=="curCSS"&&!b.test(E)?I+"px":I},className:{add:function(E,F){o.each((F||"").split(/\s+/),function(G,H){if(E.nodeType==1&&!o.className.has(E.className,H)){E.className+=(E.className?" ":"")+H}})},remove:function(E,F){if(E.nodeType==1){E.className=F!==g?o.grep(E.className.split(/\s+/),function(G){return !o.className.has(F,G)}).join(" "):""}},has:function(F,E){return F&&o.inArray(E,(F.className||F).toString().split(/\s+/))>-1}},swap:function(H,G,I){var E={};for(var F in G){E[F]=H.style[F];H.style[F]=G[F]}I.call(H);for(var F in G){H.style[F]=E[F]}},css:function(G,E,I){if(E=="width"||E=="height"){var K,F={position:"absolute",visibility:"hidden",display:"block"},J=E=="width"?["Left","Right"]:["Top","Bottom"];function H(){K=E=="width"?G.offsetWidth:G.offsetHeight;var M=0,L=0;o.each(J,function(){M+=parseFloat(o.curCSS(G,"padding"+this,true))||0;L+=parseFloat(o.curCSS(G,"border"+this+"Width",true))||0});K-=Math.round(M+L)}if(o(G).is(":visible")){H()}else{o.swap(G,F,H)}return Math.max(0,K)}return o.curCSS(G,E,I)},curCSS:function(I,F,G){var L,E=I.style;if(F=="opacity"&&!o.support.opacity){L=o.attr(E,"opacity");return L==""?"1":L}if(F.match(/float/i)){F=w}if(!G&&E&&E[F]){L=E[F]}else{if(q.getComputedStyle){if(F.match(/float/i)){F="float"}F=F.replace(/([A-Z])/g,"-$1").toLowerCase();var M=q.getComputedStyle(I,null);if(M){L=M.getPropertyValue(F)}if(F=="opacity"&&L==""){L="1"}}else{if(I.currentStyle){var J=F.replace(/\-(\w)/g,function(N,O){return O.toUpperCase()});L=I.currentStyle[F]||I.currentStyle[J];if(!/^\d+(px)?$/i.test(L)&&/^\d/.test(L)){var H=E.left,K=I.runtimeStyle.left;I.runtimeStyle.left=I.currentStyle.left;E.left=L||0;L=E.pixelLeft+"px";E.left=H;I.runtimeStyle.left=K}}}}return L},clean:function(F,K,I){K=K||document;if(typeof K.createElement==="undefined"){K=K.ownerDocument||K[0]&&K[0].ownerDocument||document}if(!I&&F.length===1&&typeof F[0]==="string"){var H=/^<(\w+)\s*\/?>$/.exec(F[0]);if(H){return[K.createElement(H[1])]}}var G=[],E=[],L=K.createElement("div");o.each(F,function(P,R){if(typeof R==="number"){R+=""}if(!R){return}if(typeof R==="string"){R=R.replace(/(<(\w+)[^>]*?)\/>/g,function(T,U,S){return S.match(/^(abbr|br|col|img|input|link|meta|param|hr|area|embed)$/i)?T:U+"></"+S+">"});var O=o.trim(R).toLowerCase();var Q=!O.indexOf("<opt")&&[1,"<select multiple='multiple'>","</select>"]||!O.indexOf("<leg")&&[1,"<fieldset>","</fieldset>"]||O.match(/^<(thead|tbody|tfoot|colg|cap)/)&&[1,"<table>","</table>"]||!O.indexOf("<tr")&&[2,"<table><tbody>","</tbody></table>"]||(!O.indexOf("<td")||!O.indexOf("<th"))&&[3,"<table><tbody><tr>","</tr></tbody></table>"]||!O.indexOf("<col")&&[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"]||!o.support.htmlSerialize&&[1,"div<div>","</div>"]||[0,"",""];L.innerHTML=Q[1]+R+Q[2];while(Q[0]--){L=L.lastChild}if(!o.support.tbody){var N=!O.indexOf("<table")&&O.indexOf("<tbody")<0?L.firstChild&&L.firstChild.childNodes:Q[1]=="<table>"&&O.indexOf("<tbody")<0?L.childNodes:[];for(var M=N.length-1;M>=0;--M){if(o.nodeName(N[M],"tbody")&&!N[M].childNodes.length){N[M].parentNode.removeChild(N[M])}}}if(!o.support.leadingWhitespace&&/^\s/.test(R)){L.insertBefore(K.createTextNode(R.match(/^\s*/)[0]),L.firstChild)}R=o.makeArray(L.childNodes)}if(R.nodeType){G.push(R)}else{G=o.merge(G,R)}});if(I){for(var J=0;G[J];J++){if(o.nodeName(G[J],"script")&&(!G[J].type||G[J].type.toLowerCase()==="text/javascript")){E.push(G[J].parentNode?G[J].parentNode.removeChild(G[J]):G[J])}else{if(G[J].nodeType===1){G.splice.apply(G,[J+1,0].concat(o.makeArray(G[J].getElementsByTagName("script"))))}I.appendChild(G[J])}}return E}return G},attr:function(J,G,K){if(!J||J.nodeType==3||J.nodeType==8){return g}var H=!o.isXMLDoc(J),L=K!==g;G=H&&o.props[G]||G;if(J.tagName){var F=/href|src|style/.test(G);if(G=="selected"&&J.parentNode){J.parentNode.selectedIndex}if(G in J&&H&&!F){if(L){if(G=="type"&&o.nodeName(J,"input")&&J.parentNode){throw"type property can't be changed"}J[G]=K}if(o.nodeName(J,"form")&&J.getAttributeNode(G)){return J.getAttributeNode(G).nodeValue}if(G=="tabIndex"){var I=J.getAttributeNode("tabIndex");return I&&I.specified?I.value:J.nodeName.match(/(button|input|object|select|textarea)/i)?0:J.nodeName.match(/^(a|area)$/i)&&J.href?0:g}return J[G]}if(!o.support.style&&H&&G=="style"){return o.attr(J.style,"cssText",K)}if(L){J.setAttribute(G,""+K)}var E=!o.support.hrefNormalized&&H&&F?J.getAttribute(G,2):J.getAttribute(G);return E===null?g:E}if(!o.support.opacity&&G=="opacity"){if(L){J.zoom=1;J.filter=(J.filter||"").replace(/alpha\([^)]*\)/,"")+(parseInt(K)+""=="NaN"?"":"alpha(opacity="+K*100+")")}return J.filter&&J.filter.indexOf("opacity=")>=0?(parseFloat(J.filter.match(/opacity=([^)]*)/)[1])/100)+"":""}G=G.replace(/-([a-z])/ig,function(M,N){return N.toUpperCase()});if(L){J[G]=K}return J[G]},trim:function(E){return(E||"").replace(/^\s+|\s+$/g,"")},makeArray:function(G){var E=[];if(G!=null){var F=G.length;if(F==null||typeof G==="string"||o.isFunction(G)||G.setInterval){E[0]=G}else{while(F){E[--F]=G[F]}}}return E},inArray:function(G,H){for(var E=0,F=H.length;E<F;E++){if(H[E]===G){return E}}return -1},merge:function(H,E){var F=0,G,I=H.length;if(!o.support.getAll){while((G=E[F++])!=null){if(G.nodeType!=8){H[I++]=G}}}else{while((G=E[F++])!=null){H[I++]=G}}return H},unique:function(K){var F=[],E={};try{for(var G=0,H=K.length;G<H;G++){var J=o.data(K[G]);if(!E[J]){E[J]=true;F.push(K[G])}}}catch(I){F=K}return F},grep:function(F,J,E){var G=[];for(var H=0,I=F.length;H<I;H++){if(!E!=!J(F[H],H)){G.push(F[H])}}return G},map:function(E,J){var F=[];for(var G=0,H=E.length;G<H;G++){var I=J(E[G],G);if(I!=null){F[F.length]=I}}return F.concat.apply([],F)}});var C=navigator.userAgent.toLowerCase();o.browser={version:(C.match(/.+(?:rv|it|ra|ie)[\/: ]([\d.]+)/)||[0,"0"])[1],safari:/webkit/.test(C),opera:/opera/.test(C),msie:/msie/.test(C)&&!/opera/.test(C),mozilla:/mozilla/.test(C)&&!/(compatible|webkit)/.test(C)};o.each({parent:function(E){return E.parentNode},parents:function(E){return o.dir(E,"parentNode")},next:function(E){return o.nth(E,2,"nextSibling")},prev:function(E){return o.nth(E,2,"previousSibling")},nextAll:function(E){return o.dir(E,"nextSibling")},prevAll:function(E){return o.dir(E,"previousSibling")},siblings:function(E){return o.sibling(E.parentNode.firstChild,E)},children:function(E){return o.sibling(E.firstChild)},contents:function(E){return o.nodeName(E,"iframe")?E.contentDocument||E.contentWindow.document:o.makeArray(E.childNodes)}},function(E,F){o.fn[E]=function(G){var H=o.map(this,F);if(G&&typeof G=="string"){H=o.multiFilter(G,H)}return this.pushStack(o.unique(H),E,G)}});o.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(E,F){o.fn[E]=function(){var G=arguments;return this.each(function(){for(var H=0,I=G.length;H<I;H++){o(G[H])[F](this)}})}});o.each({removeAttr:function(E){o.attr(this,E,"");if(this.nodeType==1){this.removeAttribute(E)}},addClass:function(E){o.className.add(this,E)},removeClass:function(E){o.className.remove(this,E)},toggleClass:function(F,E){if(typeof E!=="boolean"){E=!o.className.has(this,F)}o.className[E?"add":"remove"](this,F)},remove:function(E){if(!E||o.filter(E,[this]).length){o("*",this).add([this]).each(function(){o.event.remove(this);o.removeData(this)});if(this.parentNode){this.parentNode.removeChild(this)}}},empty:function(){o(">*",this).remove();while(this.firstChild){this.removeChild(this.firstChild)}}},function(E,F){o.fn[E]=function(){return this.each(F,arguments)}});function j(E,F){return E[0]&&parseInt(o.curCSS(E[0],F,true),10)||0}var h="jQuery"+e(),v=0,A={};o.extend({cache:{},data:function(F,E,G){F=F==l?A:F;var H=F[h];if(!H){H=F[h]=++v}if(E&&!o.cache[H]){o.cache[H]={}}if(G!==g){o.cache[H][E]=G}return E?o.cache[H][E]:H},removeData:function(F,E){F=F==l?A:F;var H=F[h];if(E){if(o.cache[H]){delete o.cache[H][E];E="";for(E in o.cache[H]){break}if(!E){o.removeData(F)}}}else{try{delete F[h]}catch(G){if(F.removeAttribute){F.removeAttribute(h)}}delete o.cache[H]}},queue:function(F,E,H){if(F){E=(E||"fx")+"queue";var G=o.data(F,E);if(!G||o.isArray(H)){G=o.data(F,E,o.makeArray(H))}else{if(H){G.push(H)}}}return G},dequeue:function(H,G){var E=o.queue(H,G),F=E.shift();if(!G||G==="fx"){F=E[0]}if(F!==g){F.call(H)}}});o.fn.extend({data:function(E,G){var H=E.split(".");H[1]=H[1]?"."+H[1]:"";if(G===g){var F=this.triggerHandler("getData"+H[1]+"!",[H[0]]);if(F===g&&this.length){F=o.data(this[0],E)}return F===g&&H[1]?this.data(H[0]):F}else{return this.trigger("setData"+H[1]+"!",[H[0],G]).each(function(){o.data(this,E,G)})}},removeData:function(E){return this.each(function(){o.removeData(this,E)})},queue:function(E,F){if(typeof E!=="string"){F=E;E="fx"}if(F===g){return o.queue(this[0],E)}return this.each(function(){var G=o.queue(this,E,F);if(E=="fx"&&G.length==1){G[0].call(this)}})},dequeue:function(E){return this.each(function(){o.dequeue(this,E)})}});
+/*
+ * Sizzle CSS Selector Engine - v0.9.3
+ * Copyright 2009, The Dojo Foundation
+ * Released under the MIT, BSD, and GPL Licenses.
+ * More information: http://sizzlejs.com/
+ */
+(function(){var Q=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]+['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[]+)+|[>+~])(\s*,\s*)?/g,K=0,G=Object.prototype.toString;var F=function(X,T,aa,ab){aa=aa||[];T=T||document;if(T.nodeType!==1&&T.nodeType!==9){return[]}if(!X||typeof X!=="string"){return aa}var Y=[],V,ae,ah,S,ac,U,W=true;Q.lastIndex=0;while((V=Q.exec(X))!==null){Y.push(V[1]);if(V[2]){U=RegExp.rightContext;break}}if(Y.length>1&&L.exec(X)){if(Y.length===2&&H.relative[Y[0]]){ae=I(Y[0]+Y[1],T)}else{ae=H.relative[Y[0]]?[T]:F(Y.shift(),T);while(Y.length){X=Y.shift();if(H.relative[X]){X+=Y.shift()}ae=I(X,ae)}}}else{var ad=ab?{expr:Y.pop(),set:E(ab)}:F.find(Y.pop(),Y.length===1&&T.parentNode?T.parentNode:T,P(T));ae=F.filter(ad.expr,ad.set);if(Y.length>0){ah=E(ae)}else{W=false}while(Y.length){var ag=Y.pop(),af=ag;if(!H.relative[ag]){ag=""}else{af=Y.pop()}if(af==null){af=T}H.relative[ag](ah,af,P(T))}}if(!ah){ah=ae}if(!ah){throw"Syntax error, unrecognized expression: "+(ag||X)}if(G.call(ah)==="[object Array]"){if(!W){aa.push.apply(aa,ah)}else{if(T.nodeType===1){for(var Z=0;ah[Z]!=null;Z++){if(ah[Z]&&(ah[Z]===true||ah[Z].nodeType===1&&J(T,ah[Z]))){aa.push(ae[Z])}}}else{for(var Z=0;ah[Z]!=null;Z++){if(ah[Z]&&ah[Z].nodeType===1){aa.push(ae[Z])}}}}}else{E(ah,aa)}if(U){F(U,T,aa,ab)}return aa};F.matches=function(S,T){return F(S,null,null,T)};F.find=function(Z,S,aa){var Y,W;if(!Z){return[]}for(var V=0,U=H.order.length;V<U;V++){var X=H.order[V],W;if((W=H.match[X].exec(Z))){var T=RegExp.leftContext;if(T.substr(T.length-1)!=="\\"){W[1]=(W[1]||"").replace(/\\/g,"");Y=H.find[X](W,S,aa);if(Y!=null){Z=Z.replace(H.match[X],"");break}}}}if(!Y){Y=S.getElementsByTagName("*")}return{set:Y,expr:Z}};F.filter=function(ab,aa,ae,V){var U=ab,ag=[],Y=aa,X,S;while(ab&&aa.length){for(var Z in H.filter){if((X=H.match[Z].exec(ab))!=null){var T=H.filter[Z],af,ad;S=false;if(Y==ag){ag=[]}if(H.preFilter[Z]){X=H.preFilter[Z](X,Y,ae,ag,V);if(!X){S=af=true}else{if(X===true){continue}}}if(X){for(var W=0;(ad=Y[W])!=null;W++){if(ad){af=T(ad,X,W,Y);var ac=V^!!af;if(ae&&af!=null){if(ac){S=true}else{Y[W]=false}}else{if(ac){ag.push(ad);S=true}}}}}if(af!==g){if(!ae){Y=ag}ab=ab.replace(H.match[Z],"");if(!S){return[]}break}}}ab=ab.replace(/\s*,\s*/,"");if(ab==U){if(S==null){throw"Syntax error, unrecognized expression: "+ab}else{break}}U=ab}return Y};var H=F.selectors={order:["ID","NAME","TAG"],match:{ID:/#((?:[\w\u00c0-\uFFFF_-]|\\.)+)/,CLASS:/\.((?:[\w\u00c0-\uFFFF_-]|\\.)+)/,NAME:/\[name=['"]*((?:[\w\u00c0-\uFFFF_-]|\\.)+)['"]*\]/,ATTR:/\[\s*((?:[\w\u00c0-\uFFFF_-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/,TAG:/^((?:[\w\u00c0-\uFFFF\*_-]|\\.)+)/,CHILD:/:(only|nth|last|first)-child(?:\((even|odd|[\dn+-]*)\))?/,POS:/:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^-]|$)/,PSEUDO:/:((?:[\w\u00c0-\uFFFF_-]|\\.)+)(?:\((['"]*)((?:\([^\)]+\)|[^\2\(\)]*)+)\2\))?/},attrMap:{"class":"className","for":"htmlFor"},attrHandle:{href:function(S){return S.getAttribute("href")}},relative:{"+":function(W,T){for(var U=0,S=W.length;U<S;U++){var V=W[U];if(V){var X=V.previousSibling;while(X&&X.nodeType!==1){X=X.previousSibling}W[U]=typeof T==="string"?X||false:X===T}}if(typeof T==="string"){F.filter(T,W,true)}},">":function(X,T,Y){if(typeof T==="string"&&!/\W/.test(T)){T=Y?T:T.toUpperCase();for(var U=0,S=X.length;U<S;U++){var W=X[U];if(W){var V=W.parentNode;X[U]=V.nodeName===T?V:false}}}else{for(var U=0,S=X.length;U<S;U++){var W=X[U];if(W){X[U]=typeof T==="string"?W.parentNode:W.parentNode===T}}if(typeof T==="string"){F.filter(T,X,true)}}},"":function(V,T,X){var U="done"+(K++),S=R;if(!T.match(/\W/)){var W=T=X?T:T.toUpperCase();S=O}S("parentNode",T,U,V,W,X)},"~":function(V,T,X){var U="done"+(K++),S=R;if(typeof T==="string"&&!T.match(/\W/)){var W=T=X?T:T.toUpperCase();S=O}S("previousSibling",T,U,V,W,X)}},find:{ID:function(T,U,V){if(typeof U.getElementById!=="undefined"&&!V){var S=U.getElementById(T[1]);return S?[S]:[]}},NAME:function(S,T,U){if(typeof T.getElementsByName!=="undefined"&&!U){return T.getElementsByName(S[1])}},TAG:function(S,T){return T.getElementsByTagName(S[1])}},preFilter:{CLASS:function(V,T,U,S,Y){V=" "+V[1].replace(/\\/g,"")+" ";var X;for(var W=0;(X=T[W])!=null;W++){if(X){if(Y^(" "+X.className+" ").indexOf(V)>=0){if(!U){S.push(X)}}else{if(U){T[W]=false}}}}return false},ID:function(S){return S[1].replace(/\\/g,"")},TAG:function(T,S){for(var U=0;S[U]===false;U++){}return S[U]&&P(S[U])?T[1]:T[1].toUpperCase()},CHILD:function(S){if(S[1]=="nth"){var T=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(S[2]=="even"&&"2n"||S[2]=="odd"&&"2n+1"||!/\D/.test(S[2])&&"0n+"+S[2]||S[2]);S[2]=(T[1]+(T[2]||1))-0;S[3]=T[3]-0}S[0]="done"+(K++);return S},ATTR:function(T){var S=T[1].replace(/\\/g,"");if(H.attrMap[S]){T[1]=H.attrMap[S]}if(T[2]==="~="){T[4]=" "+T[4]+" "}return T},PSEUDO:function(W,T,U,S,X){if(W[1]==="not"){if(W[3].match(Q).length>1){W[3]=F(W[3],null,null,T)}else{var V=F.filter(W[3],T,U,true^X);if(!U){S.push.apply(S,V)}return false}}else{if(H.match.POS.test(W[0])){return true}}return W},POS:function(S){S.unshift(true);return S}},filters:{enabled:function(S){return S.disabled===false&&S.type!=="hidden"},disabled:function(S){return S.disabled===true},checked:function(S){return S.checked===true},selected:function(S){S.parentNode.selectedIndex;return S.selected===true},parent:function(S){return !!S.firstChild},empty:function(S){return !S.firstChild},has:function(U,T,S){return !!F(S[3],U).length},header:function(S){return/h\d/i.test(S.nodeName)},text:function(S){return"text"===S.type},radio:function(S){return"radio"===S.type},checkbox:function(S){return"checkbox"===S.type},file:function(S){return"file"===S.type},password:function(S){return"password"===S.type},submit:function(S){return"submit"===S.type},image:function(S){return"image"===S.type},reset:function(S){return"reset"===S.type},button:function(S){return"button"===S.type||S.nodeName.toUpperCase()==="BUTTON"},input:function(S){return/input|select|textarea|button/i.test(S.nodeName)}},setFilters:{first:function(T,S){return S===0},last:function(U,T,S,V){return T===V.length-1},even:function(T,S){return S%2===0},odd:function(T,S){return S%2===1},lt:function(U,T,S){return T<S[3]-0},gt:function(U,T,S){return T>S[3]-0},nth:function(U,T,S){return S[3]-0==T},eq:function(U,T,S){return S[3]-0==T}},filter:{CHILD:function(S,V){var Y=V[1],Z=S.parentNode;var X=V[0];if(Z&&(!Z[X]||!S.nodeIndex)){var W=1;for(var T=Z.firstChild;T;T=T.nextSibling){if(T.nodeType==1){T.nodeIndex=W++}}Z[X]=W-1}if(Y=="first"){return S.nodeIndex==1}else{if(Y=="last"){return S.nodeIndex==Z[X]}else{if(Y=="only"){return Z[X]==1}else{if(Y=="nth"){var ab=false,U=V[2],aa=V[3];if(U==1&&aa==0){return true}if(U==0){if(S.nodeIndex==aa){ab=true}}else{if((S.nodeIndex-aa)%U==0&&(S.nodeIndex-aa)/U>=0){ab=true}}return ab}}}}},PSEUDO:function(Y,U,V,Z){var T=U[1],W=H.filters[T];if(W){return W(Y,V,U,Z)}else{if(T==="contains"){return(Y.textContent||Y.innerText||"").indexOf(U[3])>=0}else{if(T==="not"){var X=U[3];for(var V=0,S=X.length;V<S;V++){if(X[V]===Y){return false}}return true}}}},ID:function(T,S){return T.nodeType===1&&T.getAttribute("id")===S},TAG:function(T,S){return(S==="*"&&T.nodeType===1)||T.nodeName===S},CLASS:function(T,S){return S.test(T.className)},ATTR:function(W,U){var S=H.attrHandle[U[1]]?H.attrHandle[U[1]](W):W[U[1]]||W.getAttribute(U[1]),X=S+"",V=U[2],T=U[4];return S==null?V==="!=":V==="="?X===T:V==="*="?X.indexOf(T)>=0:V==="~="?(" "+X+" ").indexOf(T)>=0:!U[4]?S:V==="!="?X!=T:V==="^="?X.indexOf(T)===0:V==="$="?X.substr(X.length-T.length)===T:V==="|="?X===T||X.substr(0,T.length+1)===T+"-":false},POS:function(W,T,U,X){var S=T[2],V=H.setFilters[S];if(V){return V(W,U,T,X)}}}};var L=H.match.POS;for(var N in H.match){H.match[N]=RegExp(H.match[N].source+/(?![^\[]*\])(?![^\(]*\))/.source)}var E=function(T,S){T=Array.prototype.slice.call(T);if(S){S.push.apply(S,T);return S}return T};try{Array.prototype.slice.call(document.documentElement.childNodes)}catch(M){E=function(W,V){var T=V||[];if(G.call(W)==="[object Array]"){Array.prototype.push.apply(T,W)}else{if(typeof W.length==="number"){for(var U=0,S=W.length;U<S;U++){T.push(W[U])}}else{for(var U=0;W[U];U++){T.push(W[U])}}}return T}}(function(){var T=document.createElement("form"),U="script"+(new Date).getTime();T.innerHTML="<input name='"+U+"'/>";var S=document.documentElement;S.insertBefore(T,S.firstChild);if(!!document.getElementById(U)){H.find.ID=function(W,X,Y){if(typeof X.getElementById!=="undefined"&&!Y){var V=X.getElementById(W[1]);return V?V.id===W[1]||typeof V.getAttributeNode!=="undefined"&&V.getAttributeNode("id").nodeValue===W[1]?[V]:g:[]}};H.filter.ID=function(X,V){var W=typeof X.getAttributeNode!=="undefined"&&X.getAttributeNode("id");return X.nodeType===1&&W&&W.nodeValue===V}}S.removeChild(T)})();(function(){var S=document.createElement("div");S.appendChild(document.createComment(""));if(S.getElementsByTagName("*").length>0){H.find.TAG=function(T,X){var W=X.getElementsByTagName(T[1]);if(T[1]==="*"){var V=[];for(var U=0;W[U];U++){if(W[U].nodeType===1){V.push(W[U])}}W=V}return W}}S.innerHTML="<a href='#'></a>";if(S.firstChild&&S.firstChild.getAttribute("href")!=="#"){H.attrHandle.href=function(T){return T.getAttribute("href",2)}}})();if(document.querySelectorAll){(function(){var S=F,T=document.createElement("div");T.innerHTML="<p class='TEST'></p>";if(T.querySelectorAll&&T.querySelectorAll(".TEST").length===0){return}F=function(X,W,U,V){W=W||document;if(!V&&W.nodeType===9&&!P(W)){try{return E(W.querySelectorAll(X),U)}catch(Y){}}return S(X,W,U,V)};F.find=S.find;F.filter=S.filter;F.selectors=S.selectors;F.matches=S.matches})()}if(document.getElementsByClassName&&document.documentElement.getElementsByClassName){H.order.splice(1,0,"CLASS");H.find.CLASS=function(S,T){return T.getElementsByClassName(S[1])}}function O(T,Z,Y,ac,aa,ab){for(var W=0,U=ac.length;W<U;W++){var S=ac[W];if(S){S=S[T];var X=false;while(S&&S.nodeType){var V=S[Y];if(V){X=ac[V];break}if(S.nodeType===1&&!ab){S[Y]=W}if(S.nodeName===Z){X=S;break}S=S[T]}ac[W]=X}}}function R(T,Y,X,ab,Z,aa){for(var V=0,U=ab.length;V<U;V++){var S=ab[V];if(S){S=S[T];var W=false;while(S&&S.nodeType){if(S[X]){W=ab[S[X]];break}if(S.nodeType===1){if(!aa){S[X]=V}if(typeof Y!=="string"){if(S===Y){W=true;break}}else{if(F.filter(Y,[S]).length>0){W=S;break}}}S=S[T]}ab[V]=W}}}var J=document.compareDocumentPosition?function(T,S){return T.compareDocumentPosition(S)&16}:function(T,S){return T!==S&&(T.contains?T.contains(S):true)};var P=function(S){return S.nodeType===9&&S.documentElement.nodeName!=="HTML"||!!S.ownerDocument&&P(S.ownerDocument)};var I=function(S,Z){var V=[],W="",X,U=Z.nodeType?[Z]:Z;while((X=H.match.PSEUDO.exec(S))){W+=X[0];S=S.replace(H.match.PSEUDO,"")}S=H.relative[S]?S+"*":S;for(var Y=0,T=U.length;Y<T;Y++){F(S,U[Y],V)}return F.filter(W,V)};o.find=F;o.filter=F.filter;o.expr=F.selectors;o.expr[":"]=o.expr.filters;F.selectors.filters.hidden=function(S){return"hidden"===S.type||o.css(S,"display")==="none"||o.css(S,"visibility")==="hidden"};F.selectors.filters.visible=function(S){return"hidden"!==S.type&&o.css(S,"display")!=="none"&&o.css(S,"visibility")!=="hidden"};F.selectors.filters.animated=function(S){return o.grep(o.timers,function(T){return S===T.elem}).length};o.multiFilter=function(U,S,T){if(T){U=":not("+U+")"}return F.matches(U,S)};o.dir=function(U,T){var S=[],V=U[T];while(V&&V!=document){if(V.nodeType==1){S.push(V)}V=V[T]}return S};o.nth=function(W,S,U,V){S=S||1;var T=0;for(;W;W=W[U]){if(W.nodeType==1&&++T==S){break}}return W};o.sibling=function(U,T){var S=[];for(;U;U=U.nextSibling){if(U.nodeType==1&&U!=T){S.push(U)}}return S};return;l.Sizzle=F})();o.event={add:function(I,F,H,K){if(I.nodeType==3||I.nodeType==8){return}if(I.setInterval&&I!=l){I=l}if(!H.guid){H.guid=this.guid++}if(K!==g){var G=H;H=this.proxy(G);H.data=K}var E=o.data(I,"events")||o.data(I,"events",{}),J=o.data(I,"handle")||o.data(I,"handle",function(){return typeof o!=="undefined"&&!o.event.triggered?o.event.handle.apply(arguments.callee.elem,arguments):g});J.elem=I;o.each(F.split(/\s+/),function(M,N){var O=N.split(".");N=O.shift();H.type=O.slice().sort().join(".");var L=E[N];if(o.event.specialAll[N]){o.event.specialAll[N].setup.call(I,K,O)}if(!L){L=E[N]={};if(!o.event.special[N]||o.event.special[N].setup.call(I,K,O)===false){if(I.addEventListener){I.addEventListener(N,J,false)}else{if(I.attachEvent){I.attachEvent("on"+N,J)}}}}L[H.guid]=H;o.event.global[N]=true});I=null},guid:1,global:{},remove:function(K,H,J){if(K.nodeType==3||K.nodeType==8){return}var G=o.data(K,"events"),F,E;if(G){if(H===g||(typeof H==="string"&&H.charAt(0)==".")){for(var I in G){this.remove(K,I+(H||""))}}else{if(H.type){J=H.handler;H=H.type}o.each(H.split(/\s+/),function(M,O){var Q=O.split(".");O=Q.shift();var N=RegExp("(^|\\.)"+Q.slice().sort().join(".*\\.")+"(\\.|$)");if(G[O]){if(J){delete G[O][J.guid]}else{for(var P in G[O]){if(N.test(G[O][P].type)){delete G[O][P]}}}if(o.event.specialAll[O]){o.event.specialAll[O].teardown.call(K,Q)}for(F in G[O]){break}if(!F){if(!o.event.special[O]||o.event.special[O].teardown.call(K,Q)===false){if(K.removeEventListener){K.removeEventListener(O,o.data(K,"handle"),false)}else{if(K.detachEvent){K.detachEvent("on"+O,o.data(K,"handle"))}}}F=null;delete G[O]}}})}for(F in G){break}if(!F){var L=o.data(K,"handle");if(L){L.elem=null}o.removeData(K,"events");o.removeData(K,"handle")}}},trigger:function(I,K,H,E){var G=I.type||I;if(!E){I=typeof I==="object"?I[h]?I:o.extend(o.Event(G),I):o.Event(G);if(G.indexOf("!")>=0){I.type=G=G.slice(0,-1);I.exclusive=true}if(!H){I.stopPropagation();if(this.global[G]){o.each(o.cache,function(){if(this.events&&this.events[G]){o.event.trigger(I,K,this.handle.elem)}})}}if(!H||H.nodeType==3||H.nodeType==8){return g}I.result=g;I.target=H;K=o.makeArray(K);K.unshift(I)}I.currentTarget=H;var J=o.data(H,"handle");if(J){J.apply(H,K)}if((!H[G]||(o.nodeName(H,"a")&&G=="click"))&&H["on"+G]&&H["on"+G].apply(H,K)===false){I.result=false}if(!E&&H[G]&&!I.isDefaultPrevented()&&!(o.nodeName(H,"a")&&G=="click")){this.triggered=true;try{H[G]()}catch(L){}}this.triggered=false;if(!I.isPropagationStopped()){var F=H.parentNode||H.ownerDocument;if(F){o.event.trigger(I,K,F,true)}}},handle:function(K){var J,E;K=arguments[0]=o.event.fix(K||l.event);var L=K.type.split(".");K.type=L.shift();J=!L.length&&!K.exclusive;var I=RegExp("(^|\\.)"+L.slice().sort().join(".*\\.")+"(\\.|$)");E=(o.data(this,"events")||{})[K.type];for(var G in E){var H=E[G];if(J||I.test(H.type)){K.handler=H;K.data=H.data;var F=H.apply(this,arguments);if(F!==g){K.result=F;if(F===false){K.preventDefault();K.stopPropagation()}}if(K.isImmediatePropagationStopped()){break}}}},props:"altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode metaKey newValue originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),fix:function(H){if(H[h]){return H}var F=H;H=o.Event(F);for(var G=this.props.length,J;G;){J=this.props[--G];H[J]=F[J]}if(!H.target){H.target=H.srcElement||document}if(H.target.nodeType==3){H.target=H.target.parentNode}if(!H.relatedTarget&&H.fromElement){H.relatedTarget=H.fromElement==H.target?H.toElement:H.fromElement}if(H.pageX==null&&H.clientX!=null){var I=document.documentElement,E=document.body;H.pageX=H.clientX+(I&&I.scrollLeft||E&&E.scrollLeft||0)-(I.clientLeft||0);H.pageY=H.clientY+(I&&I.scrollTop||E&&E.scrollTop||0)-(I.clientTop||0)}if(!H.which&&((H.charCode||H.charCode===0)?H.charCode:H.keyCode)){H.which=H.charCode||H.keyCode}if(!H.metaKey&&H.ctrlKey){H.metaKey=H.ctrlKey}if(!H.which&&H.button){H.which=(H.button&1?1:(H.button&2?3:(H.button&4?2:0)))}return H},proxy:function(F,E){E=E||function(){return F.apply(this,arguments)};E.guid=F.guid=F.guid||E.guid||this.guid++;return E},special:{ready:{setup:B,teardown:function(){}}},specialAll:{live:{setup:function(E,F){o.event.add(this,F[0],c)},teardown:function(G){if(G.length){var E=0,F=RegExp("(^|\\.)"+G[0]+"(\\.|$)");o.each((o.data(this,"events").live||{}),function(){if(F.test(this.type)){E++}});if(E<1){o.event.remove(this,G[0],c)}}}}}};o.Event=function(E){if(!this.preventDefault){return new o.Event(E)}if(E&&E.type){this.originalEvent=E;this.type=E.type}else{this.type=E}this.timeStamp=e();this[h]=true};function k(){return false}function u(){return true}o.Event.prototype={preventDefault:function(){this.isDefaultPrevented=u;var E=this.originalEvent;if(!E){return}if(E.preventDefault){E.preventDefault()}E.returnValue=false},stopPropagation:function(){this.isPropagationStopped=u;var E=this.originalEvent;if(!E){return}if(E.stopPropagation){E.stopPropagation()}E.cancelBubble=true},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=u;this.stopPropagation()},isDefaultPrevented:k,isPropagationStopped:k,isImmediatePropagationStopped:k};var a=function(F){var E=F.relatedTarget;while(E&&E!=this){try{E=E.parentNode}catch(G){E=this}}if(E!=this){F.type=F.data;o.event.handle.apply(this,arguments)}};o.each({mouseover:"mouseenter",mouseout:"mouseleave"},function(F,E){o.event.special[E]={setup:function(){o.event.add(this,F,a,E)},teardown:function(){o.event.remove(this,F,a)}}});o.fn.extend({bind:function(F,G,E){return F=="unload"?this.one(F,G,E):this.each(function(){o.event.add(this,F,E||G,E&&G)})},one:function(G,H,F){var E=o.event.proxy(F||H,function(I){o(this).unbind(I,E);return(F||H).apply(this,arguments)});return this.each(function(){o.event.add(this,G,E,F&&H)})},unbind:function(F,E){return this.each(function(){o.event.remove(this,F,E)})},trigger:function(E,F){return this.each(function(){o.event.trigger(E,F,this)})},triggerHandler:function(E,G){if(this[0]){var F=o.Event(E);F.preventDefault();F.stopPropagation();o.event.trigger(F,G,this[0]);return F.result}},toggle:function(G){var E=arguments,F=1;while(F<E.length){o.event.proxy(G,E[F++])}return this.click(o.event.proxy(G,function(H){this.lastToggle=(this.lastToggle||0)%F;H.preventDefault();return E[this.lastToggle++].apply(this,arguments)||false}))},hover:function(E,F){return this.mouseenter(E).mouseleave(F)},ready:function(E){B();if(o.isReady){E.call(document,o)}else{o.readyList.push(E)}return this},live:function(G,F){var E=o.event.proxy(F);E.guid+=this.selector+G;o(document).bind(i(G,this.selector),this.selector,E);return this},die:function(F,E){o(document).unbind(i(F,this.selector),E?{guid:E.guid+this.selector+F}:null);return this}});function c(H){var E=RegExp("(^|\\.)"+H.type+"(\\.|$)"),G=true,F=[];o.each(o.data(this,"events").live||[],function(I,J){if(E.test(J.type)){var K=o(H.target).closest(J.data)[0];if(K){F.push({elem:K,fn:J})}}});o.each(F,function(){if(this.fn.call(this.elem,H,this.fn.data)===false){G=false}});return G}function i(F,E){return["live",F,E.replace(/\./g,"`").replace(/ /g,"|")].join(".")}o.extend({isReady:false,readyList:[],ready:function(){if(!o.isReady){o.isReady=true;if(o.readyList){o.each(o.readyList,function(){this.call(document,o)});o.readyList=null}o(document).triggerHandler("ready")}}});var x=false;function B(){if(x){return}x=true;if(document.addEventListener){document.addEventListener("DOMContentLoaded",function(){document.removeEventListener("DOMContentLoaded",arguments.callee,false);o.ready()},false)}else{if(document.attachEvent){document.attachEvent("onreadystatechange",function(){if(document.readyState==="complete"){document.detachEvent("onreadystatechange",arguments.callee);o.ready()}});if(document.documentElement.doScroll&&typeof l.frameElement==="undefined"){(function(){if(o.isReady){return}try{document.documentElement.doScroll("left")}catch(E){setTimeout(arguments.callee,0);return}o.ready()})()}}}o.event.add(l,"load",o.ready)}o.each(("blur,focus,load,resize,scroll,unload,click,dblclick,mousedown,mouseup,mousemove,mouseover,mouseout,mouseenter,mouseleave,change,select,submit,keydown,keypress,keyup,error").split(","),function(F,E){o.fn[E]=function(G){return G?this.bind(E,G):this.trigger(E)}});o(l).bind("unload",function(){for(var E in o.cache){if(E!=1&&o.cache[E].handle){o.event.remove(o.cache[E].handle.elem)}}});(function(){o.support={};var F=document.documentElement,G=document.createElement("script"),K=document.createElement("div"),J="script"+(new Date).getTime();K.style.display="none";K.innerHTML=' <link/><table></table><a href="/a" style="color:red;float:left;opacity:.5;">a</a><select><option>text</option></select><object><param/></object>';var H=K.getElementsByTagName("*"),E=K.getElementsByTagName("a")[0];if(!H||!H.length||!E){return}o.support={leadingWhitespace:K.firstChild.nodeType==3,tbody:!K.getElementsByTagName("tbody").length,objectAll:!!K.getElementsByTagName("object")[0].getElementsByTagName("*").length,htmlSerialize:!!K.getElementsByTagName("link").length,style:/red/.test(E.getAttribute("style")),hrefNormalized:E.getAttribute("href")==="/a",opacity:E.style.opacity==="0.5",cssFloat:!!E.style.cssFloat,scriptEval:false,noCloneEvent:true,boxModel:null};G.type="text/javascript";try{G.appendChild(document.createTextNode("window."+J+"=1;"))}catch(I){}F.insertBefore(G,F.firstChild);if(l[J]){o.support.scriptEval=true;delete l[J]}F.removeChild(G);if(K.attachEvent&&K.fireEvent){K.attachEvent("onclick",function(){o.support.noCloneEvent=false;K.detachEvent("onclick",arguments.callee)});K.cloneNode(true).fireEvent("onclick")}o(function(){var L=document.createElement("div");L.style.width="1px";L.style.paddingLeft="1px";document.body.appendChild(L);o.boxModel=o.support.boxModel=L.offsetWidth===2;document.body.removeChild(L)})})();var w=o.support.cssFloat?"cssFloat":"styleFloat";o.props={"for":"htmlFor","class":"className","float":w,cssFloat:w,styleFloat:w,readonly:"readOnly",maxlength:"maxLength",cellspacing:"cellSpacing",rowspan:"rowSpan",tabindex:"tabIndex"};o.fn.extend({_load:o.fn.load,load:function(G,J,K){if(typeof G!=="string"){return this._load(G)}var I=G.indexOf(" ");if(I>=0){var E=G.slice(I,G.length);G=G.slice(0,I)}var H="GET";if(J){if(o.isFunction(J)){K=J;J=null}else{if(typeof J==="object"){J=o.param(J);H="POST"}}}var F=this;o.ajax({url:G,type:H,dataType:"html",data:J,complete:function(M,L){if(L=="success"||L=="notmodified"){F.html(E?o("<div/>").append(M.responseText.replace(/<script(.|\s)*?\/script>/g,"")).find(E):M.responseText)}if(K){F.each(K,[M.responseText,L,M])}}});return this},serialize:function(){return o.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?o.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||/select|textarea/i.test(this.nodeName)||/text|hidden|password/i.test(this.type))}).map(function(E,F){var G=o(this).val();return G==null?null:o.isArray(G)?o.map(G,function(I,H){return{name:F.name,value:I}}):{name:F.name,value:G}}).get()}});o.each("ajaxStart,ajaxStop,ajaxComplete,ajaxError,ajaxSuccess,ajaxSend".split(","),function(E,F){o.fn[F]=function(G){return this.bind(F,G)}});var r=e();o.extend({get:function(E,G,H,F){if(o.isFunction(G)){H=G;G=null}return o.ajax({type:"GET",url:E,data:G,success:H,dataType:F})},getScript:function(E,F){return o.get(E,null,F,"script")},getJSON:function(E,F,G){return o.get(E,F,G,"json")},post:function(E,G,H,F){if(o.isFunction(G)){H=G;G={}}return o.ajax({type:"POST",url:E,data:G,success:H,dataType:F})},ajaxSetup:function(E){o.extend(o.ajaxSettings,E)},ajaxSettings:{url:location.href,global:true,type:"GET",contentType:"application/x-www-form-urlencoded",processData:true,async:true,xhr:function(){return l.ActiveXObject?new ActiveXObject("Microsoft.XMLHTTP"):new XMLHttpRequest()},accepts:{xml:"application/xml, text/xml",html:"text/html",script:"text/javascript, application/javascript",json:"application/json, text/javascript",text:"text/plain",_default:"*/*"}},lastModified:{},ajax:function(M){M=o.extend(true,M,o.extend(true,{},o.ajaxSettings,M));var W,F=/=\?(&|$)/g,R,V,G=M.type.toUpperCase();if(M.data&&M.processData&&typeof M.data!=="string"){M.data=o.param(M.data)}if(M.dataType=="jsonp"){if(G=="GET"){if(!M.url.match(F)){M.url+=(M.url.match(/\?/)?"&":"?")+(M.jsonp||"callback")+"=?"}}else{if(!M.data||!M.data.match(F)){M.data=(M.data?M.data+"&":"")+(M.jsonp||"callback")+"=?"}}M.dataType="json"}if(M.dataType=="json"&&(M.data&&M.data.match(F)||M.url.match(F))){W="jsonp"+r++;if(M.data){M.data=(M.data+"").replace(F,"="+W+"$1")}M.url=M.url.replace(F,"="+W+"$1");M.dataType="script";l[W]=function(X){V=X;I();L();l[W]=g;try{delete l[W]}catch(Y){}if(H){H.removeChild(T)}}}if(M.dataType=="script"&&M.cache==null){M.cache=false}if(M.cache===false&&G=="GET"){var E=e();var U=M.url.replace(/(\?|&)_=.*?(&|$)/,"$1_="+E+"$2");M.url=U+((U==M.url)?(M.url.match(/\?/)?"&":"?")+"_="+E:"")}if(M.data&&G=="GET"){M.url+=(M.url.match(/\?/)?"&":"?")+M.data;M.data=null}if(M.global&&!o.active++){o.event.trigger("ajaxStart")}var Q=/^(\w+:)?\/\/([^\/?#]+)/.exec(M.url);if(M.dataType=="script"&&G=="GET"&&Q&&(Q[1]&&Q[1]!=location.protocol||Q[2]!=location.host)){var H=document.getElementsByTagName("head")[0];var T=document.createElement("script");T.src=M.url;if(M.scriptCharset){T.charset=M.scriptCharset}if(!W){var O=false;T.onload=T.onreadystatechange=function(){if(!O&&(!this.readyState||this.readyState=="loaded"||this.readyState=="complete")){O=true;I();L();H.removeChild(T)}}}H.appendChild(T);return g}var K=false;var J=M.xhr();if(M.username){J.open(G,M.url,M.async,M.username,M.password)}else{J.open(G,M.url,M.async)}try{if(M.data){J.setRequestHeader("Content-Type",M.contentType)}if(M.ifModified){J.setRequestHeader("If-Modified-Since",o.lastModified[M.url]||"Thu, 01 Jan 1970 00:00:00 GMT")}J.setRequestHeader("X-Requested-With","XMLHttpRequest");J.setRequestHeader("Accept",M.dataType&&M.accepts[M.dataType]?M.accepts[M.dataType]+", */*":M.accepts._default)}catch(S){}if(M.beforeSend&&M.beforeSend(J,M)===false){if(M.global&&!--o.active){o.event.trigger("ajaxStop")}J.abort();return false}if(M.global){o.event.trigger("ajaxSend",[J,M])}var N=function(X){if(J.readyState==0){if(P){clearInterval(P);P=null;if(M.global&&!--o.active){o.event.trigger("ajaxStop")}}}else{if(!K&&J&&(J.readyState==4||X=="timeout")){K=true;if(P){clearInterval(P);P=null}R=X=="timeout"?"timeout":!o.httpSuccess(J)?"error":M.ifModified&&o.httpNotModified(J,M.url)?"notmodified":"success";if(R=="success"){try{V=o.httpData(J,M.dataType,M)}catch(Z){R="parsererror"}}if(R=="success"){var Y;try{Y=J.getResponseHeader("Last-Modified")}catch(Z){}if(M.ifModified&&Y){o.lastModified[M.url]=Y}if(!W){I()}}else{o.handleError(M,J,R)}L();if(X){J.abort()}if(M.async){J=null}}}};if(M.async){var P=setInterval(N,13);if(M.timeout>0){setTimeout(function(){if(J&&!K){N("timeout")}},M.timeout)}}try{J.send(M.data)}catch(S){o.handleError(M,J,null,S)}if(!M.async){N()}function I(){if(M.success){M.success(V,R)}if(M.global){o.event.trigger("ajaxSuccess",[J,M])}}function L(){if(M.complete){M.complete(J,R)}if(M.global){o.event.trigger("ajaxComplete",[J,M])}if(M.global&&!--o.active){o.event.trigger("ajaxStop")}}return J},handleError:function(F,H,E,G){if(F.error){F.error(H,E,G)}if(F.global){o.event.trigger("ajaxError",[H,F,G])}},active:0,httpSuccess:function(F){try{return !F.status&&location.protocol=="file:"||(F.status>=200&&F.status<300)||F.status==304||F.status==1223}catch(E){}return false},httpNotModified:function(G,E){try{var H=G.getResponseHeader("Last-Modified");return G.status==304||H==o.lastModified[E]}catch(F){}return false},httpData:function(J,H,G){var F=J.getResponseHeader("content-type"),E=H=="xml"||!H&&F&&F.indexOf("xml")>=0,I=E?J.responseXML:J.responseText;if(E&&I.documentElement.tagName=="parsererror"){throw"parsererror"}if(G&&G.dataFilter){I=G.dataFilter(I,H)}if(typeof I==="string"){if(H=="script"){o.globalEval(I)}if(H=="json"){I=l["eval"]("("+I+")")}}return I},param:function(E){var G=[];function H(I,J){G[G.length]=encodeURIComponent(I)+"="+encodeURIComponent(J)}if(o.isArray(E)||E.jquery){o.each(E,function(){H(this.name,this.value)})}else{for(var F in E){if(o.isArray(E[F])){o.each(E[F],function(){H(F,this)})}else{H(F,o.isFunction(E[F])?E[F]():E[F])}}}return G.join("&").replace(/%20/g,"+")}});var m={},n,d=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]];function t(F,E){var G={};o.each(d.concat.apply([],d.slice(0,E)),function(){G[this]=F});return G}o.fn.extend({show:function(J,L){if(J){return this.animate(t("show",3),J,L)}else{for(var H=0,F=this.length;H<F;H++){var E=o.data(this[H],"olddisplay");this[H].style.display=E||"";if(o.css(this[H],"display")==="none"){var G=this[H].tagName,K;if(m[G]){K=m[G]}else{var I=o("<"+G+" />").appendTo("body");K=I.css("display");if(K==="none"){K="block"}I.remove();m[G]=K}this[H].style.display=o.data(this[H],"olddisplay",K)}}return this}},hide:function(H,I){if(H){return this.animate(t("hide",3),H,I)}else{for(var G=0,F=this.length;G<F;G++){var E=o.data(this[G],"olddisplay");if(!E&&E!=="none"){o.data(this[G],"olddisplay",o.css(this[G],"display"))}this[G].style.display="none"}return this}},_toggle:o.fn.toggle,toggle:function(G,F){var E=typeof G==="boolean";return o.isFunction(G)&&o.isFunction(F)?this._toggle.apply(this,arguments):G==null||E?this.each(function(){var H=E?G:o(this).is(":hidden");o(this)[H?"show":"hide"]()}):this.animate(t("toggle",3),G,F)},fadeTo:function(E,G,F){return this.animate({opacity:G},E,F)},animate:function(I,F,H,G){var E=o.speed(F,H,G);return this[E.queue===false?"each":"queue"](function(){var K=o.extend({},E),M,L=this.nodeType==1&&o(this).is(":hidden"),J=this;for(M in I){if(I[M]=="hide"&&L||I[M]=="show"&&!L){return K.complete.call(this)}if((M=="height"||M=="width")&&this.style){K.display=o.css(this,"display");K.overflow=this.style.overflow}}if(K.overflow!=null){this.style.overflow="hidden"}K.curAnim=o.extend({},I);o.each(I,function(O,S){var R=new o.fx(J,K,O);if(/toggle|show|hide/.test(S)){R[S=="toggle"?L?"show":"hide":S](I)}else{var Q=S.toString().match(/^([+-]=)?([\d+-.]+)(.*)$/),T=R.cur(true)||0;if(Q){var N=parseFloat(Q[2]),P=Q[3]||"px";if(P!="px"){J.style[O]=(N||1)+P;T=((N||1)/R.cur(true))*T;J.style[O]=T+P}if(Q[1]){N=((Q[1]=="-="?-1:1)*N)+T}R.custom(T,N,P)}else{R.custom(T,S,"")}}});return true})},stop:function(F,E){var G=o.timers;if(F){this.queue([])}this.each(function(){for(var H=G.length-1;H>=0;H--){if(G[H].elem==this){if(E){G[H](true)}G.splice(H,1)}}});if(!E){this.dequeue()}return this}});o.each({slideDown:t("show",1),slideUp:t("hide",1),slideToggle:t("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"}},function(E,F){o.fn[E]=function(G,H){return this.animate(F,G,H)}});o.extend({speed:function(G,H,F){var E=typeof G==="object"?G:{complete:F||!F&&H||o.isFunction(G)&&G,duration:G,easing:F&&H||H&&!o.isFunction(H)&&H};E.duration=o.fx.off?0:typeof E.duration==="number"?E.duration:o.fx.speeds[E.duration]||o.fx.speeds._default;E.old=E.complete;E.complete=function(){if(E.queue!==false){o(this).dequeue()}if(o.isFunction(E.old)){E.old.call(this)}};return E},easing:{linear:function(G,H,E,F){return E+F*G},swing:function(G,H,E,F){return((-Math.cos(G*Math.PI)/2)+0.5)*F+E}},timers:[],fx:function(F,E,G){this.options=E;this.elem=F;this.prop=G;if(!E.orig){E.orig={}}}});o.fx.prototype={update:function(){if(this.options.step){this.options.step.call(this.elem,this.now,this)}(o.fx.step[this.prop]||o.fx.step._default)(this);if((this.prop=="height"||this.prop=="width")&&this.elem.style){this.elem.style.display="block"}},cur:function(F){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null)){return this.elem[this.prop]}var E=parseFloat(o.css(this.elem,this.prop,F));return E&&E>-10000?E:parseFloat(o.curCSS(this.elem,this.prop))||0},custom:function(I,H,G){this.startTime=e();this.start=I;this.end=H;this.unit=G||this.unit||"px";this.now=this.start;this.pos=this.state=0;var E=this;function F(J){return E.step(J)}F.elem=this.elem;if(F()&&o.timers.push(F)==1){n=setInterval(function(){var K=o.timers;for(var J=0;J<K.length;J++){if(!K[J]()){K.splice(J--,1)}}if(!K.length){clearInterval(n)}},13)}},show:function(){this.options.orig[this.prop]=o.attr(this.elem.style,this.prop);this.options.show=true;this.custom(this.prop=="width"||this.prop=="height"?1:0,this.cur());o(this.elem).show()},hide:function(){this.options.orig[this.prop]=o.attr(this.elem.style,this.prop);this.options.hide=true;this.custom(this.cur(),0)},step:function(H){var G=e();if(H||G>=this.options.duration+this.startTime){this.now=this.end;this.pos=this.state=1;this.update();this.options.curAnim[this.prop]=true;var E=true;for(var F in this.options.curAnim){if(this.options.curAnim[F]!==true){E=false}}if(E){if(this.options.display!=null){this.elem.style.overflow=this.options.overflow;this.elem.style.display=this.options.display;if(o.css(this.elem,"display")=="none"){this.elem.style.display="block"}}if(this.options.hide){o(this.elem).hide()}if(this.options.hide||this.options.show){for(var I in this.options.curAnim){o.attr(this.elem.style,I,this.options.orig[I])}}this.options.complete.call(this.elem)}return false}else{var J=G-this.startTime;this.state=J/this.options.duration;this.pos=o.easing[this.options.easing||(o.easing.swing?"swing":"linear")](this.state,J,0,1,this.options.duration);this.now=this.start+((this.end-this.start)*this.pos);this.update()}return true}};o.extend(o.fx,{speeds:{slow:600,fast:200,_default:400},step:{opacity:function(E){o.attr(E.elem.style,"opacity",E.now)},_default:function(E){if(E.elem.style&&E.elem.style[E.prop]!=null){E.elem.style[E.prop]=E.now+E.unit}else{E.elem[E.prop]=E.now}}}});if(document.documentElement.getBoundingClientRect){o.fn.offset=function(){if(!this[0]){return{top:0,left:0}}if(this[0]===this[0].ownerDocument.body){return o.offset.bodyOffset(this[0])}var G=this[0].getBoundingClientRect(),J=this[0].ownerDocument,F=J.body,E=J.documentElement,L=E.clientTop||F.clientTop||0,K=E.clientLeft||F.clientLeft||0,I=G.top+(self.pageYOffset||o.boxModel&&E.scrollTop||F.scrollTop)-L,H=G.left+(self.pageXOffset||o.boxModel&&E.scrollLeft||F.scrollLeft)-K;return{top:I,left:H}}}else{o.fn.offset=function(){if(!this[0]){return{top:0,left:0}}if(this[0]===this[0].ownerDocument.body){return o.offset.bodyOffset(this[0])}o.offset.initialized||o.offset.initialize();var J=this[0],G=J.offsetParent,F=J,O=J.ownerDocument,M,H=O.documentElement,K=O.body,L=O.defaultView,E=L.getComputedStyle(J,null),N=J.offsetTop,I=J.offsetLeft;while((J=J.parentNode)&&J!==K&&J!==H){M=L.getComputedStyle(J,null);N-=J.scrollTop,I-=J.scrollLeft;if(J===G){N+=J.offsetTop,I+=J.offsetLeft;if(o.offset.doesNotAddBorder&&!(o.offset.doesAddBorderForTableAndCells&&/^t(able|d|h)$/i.test(J.tagName))){N+=parseInt(M.borderTopWidth,10)||0,I+=parseInt(M.borderLeftWidth,10)||0}F=G,G=J.offsetParent}if(o.offset.subtractsBorderForOverflowNotVisible&&M.overflow!=="visible"){N+=parseInt(M.borderTopWidth,10)||0,I+=parseInt(M.borderLeftWidth,10)||0}E=M}if(E.position==="relative"||E.position==="static"){N+=K.offsetTop,I+=K.offsetLeft}if(E.position==="fixed"){N+=Math.max(H.scrollTop,K.scrollTop),I+=Math.max(H.scrollLeft,K.scrollLeft)}return{top:N,left:I}}}o.offset={initialize:function(){if(this.initialized){return}var L=document.body,F=document.createElement("div"),H,G,N,I,M,E,J=L.style.marginTop,K='<div style="position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;"><div></div></div><table style="position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;" cellpadding="0" cellspacing="0"><tr><td></td></tr></table>';M={position:"absolute",top:0,left:0,margin:0,border:0,width:"1px",height:"1px",visibility:"hidden"};for(E in M){F.style[E]=M[E]}F.innerHTML=K;L.insertBefore(F,L.firstChild);H=F.firstChild,G=H.firstChild,I=H.nextSibling.firstChild.firstChild;this.doesNotAddBorder=(G.offsetTop!==5);this.doesAddBorderForTableAndCells=(I.offsetTop===5);H.style.overflow="hidden",H.style.position="relative";this.subtractsBorderForOverflowNotVisible=(G.offsetTop===-5);L.style.marginTop="1px";this.doesNotIncludeMarginInBodyOffset=(L.offsetTop===0);L.style.marginTop=J;L.removeChild(F);this.initialized=true},bodyOffset:function(E){o.offset.initialized||o.offset.initialize();var G=E.offsetTop,F=E.offsetLeft;if(o.offset.doesNotIncludeMarginInBodyOffset){G+=parseInt(o.curCSS(E,"marginTop",true),10)||0,F+=parseInt(o.curCSS(E,"marginLeft",true),10)||0}return{top:G,left:F}}};o.fn.extend({position:function(){var I=0,H=0,F;if(this[0]){var G=this.offsetParent(),J=this.offset(),E=/^body|html$/i.test(G[0].tagName)?{top:0,left:0}:G.offset();J.top-=j(this,"marginTop");J.left-=j(this,"marginLeft");E.top+=j(G,"borderTopWidth");E.left+=j(G,"borderLeftWidth");F={top:J.top-E.top,left:J.left-E.left}}return F},offsetParent:function(){var E=this[0].offsetParent||document.body;while(E&&(!/^body|html$/i.test(E.tagName)&&o.css(E,"position")=="static")){E=E.offsetParent}return o(E)}});o.each(["Left","Top"],function(F,E){var G="scroll"+E;o.fn[G]=function(H){if(!this[0]){return null}return H!==g?this.each(function(){this==l||this==document?l.scrollTo(!F?H:o(l).scrollLeft(),F?H:o(l).scrollTop()):this[G]=H}):this[0]==l||this[0]==document?self[F?"pageYOffset":"pageXOffset"]||o.boxModel&&document.documentElement[G]||document.body[G]:this[0][G]}});o.each(["Height","Width"],function(H,F){var E=H?"Left":"Top",G=H?"Right":"Bottom";o.fn["inner"+F]=function(){return this[F.toLowerCase()]()+j(this,"padding"+E)+j(this,"padding"+G)};o.fn["outer"+F]=function(J){return this["inner"+F]()+j(this,"border"+E+"Width")+j(this,"border"+G+"Width")+(J?j(this,"margin"+E)+j(this,"margin"+G):0)};var I=F.toLowerCase();o.fn[I]=function(J){return this[0]==l?document.compatMode=="CSS1Compat"&&document.documentElement["client"+F]||document.body["client"+F]:this[0]==document?Math.max(document.documentElement["client"+F],document.body["scroll"+F],document.documentElement["scroll"+F],document.body["offset"+F],document.documentElement["offset"+F]):J===g?(this.length?o.css(this[0],I):null):this.css(I,typeof J==="string"?J:J+"px")}})})();/**
+* hoverIntent r5 // 2007.03.27 // jQuery 1.1.2+
+* <http://cherne.net/brian/resources/jquery.hoverIntent.html>
+*
+* @param f onMouseOver function || An object with configuration options
+* @param g onMouseOut function || Nothing (use configuration options object)
+* @author Brian Cherne <brian@cherne.net>
+*/
+(function($){$.fn.hoverIntent=function(f,g){var cfg={sensitivity:7,interval:100,timeout:0};cfg=$.extend(cfg,g?{over:f,out:g}:f);var cX,cY,pX,pY;var track=function(ev){cX=ev.pageX;cY=ev.pageY;};var compare=function(ev,ob){ob.hoverIntent_t=clearTimeout(ob.hoverIntent_t);if((Math.abs(pX-cX)+Math.abs(pY-cY))<cfg.sensitivity){$(ob).unbind("mousemove",track);ob.hoverIntent_s=1;return cfg.over.apply(ob,[ev]);}else{pX=cX;pY=cY;ob.hoverIntent_t=setTimeout(function(){compare(ev,ob);},cfg.interval);}};var delay=function(ev,ob){ob.hoverIntent_t=clearTimeout(ob.hoverIntent_t);ob.hoverIntent_s=0;return cfg.out.apply(ob,[ev]);};var handleHover=function(e){var p=(e.type=="mouseover"?e.fromElement:e.toElement)||e.relatedTarget;while(p&&p!=this){try{p=p.parentNode;}catch(e){p=this;}}if(p==this){return false;}var ev=jQuery.extend({},e);var ob=this;if(ob.hoverIntent_t){ob.hoverIntent_t=clearTimeout(ob.hoverIntent_t);}if(e.type=="mouseover"){pX=ev.pageX;pY=ev.pageY;$(ob).bind("mousemove",track);if(ob.hoverIntent_s!=1){ob.hoverIntent_t=setTimeout(function(){compare(ev,ob);},cfg.interval);}}else{$(ob).unbind("mousemove",track);if(ob.hoverIntent_s==1){ob.hoverIntent_t=setTimeout(function(){delay(ev,ob);},cfg.timeout);}}};return this.mouseover(handleHover).mouseout(handleHover);};})(jQuery);(function(){
+var o=true,r=null,z=false;window.PR_SHOULD_USE_CONTINUATION=o;window.PR_TAB_WIDTH=8;window.PR_normalizedHtml=window.PR=window.prettyPrintOne=window.prettyPrint=void 0;window._pr_isIE6=function(){var N=navigator&&navigator.userAgent&&/\bMSIE 6\./.test(navigator.userAgent);window._pr_isIE6=function(){return N};return N};
+var aa="!",ba="!=",ca="!==",F="#",da="%",ea="%=",G="&",fa="&&",ja="&&=",ka="&=",H="(",la="*",ma="*=",na="+=",oa=",",pa="-=",qa="->",ra="/",sa="/=",ta=":",ua="::",va=";",I="<",wa="<<",xa="<<=",ya="<=",za="=",Aa="==",Ba="===",J=">",Ca=">=",Da=">>",Ea=">>=",Fa=">>>",Ga=">>>=",Ha="?",Ia="@",L="[",M="^",Ta="^=",Ua="^^",Va="^^=",Wa="{",O="|",Xa="|=",Ya="||",Za="||=",$a="~",ab="break",bb="case",cb="continue",db="delete",eb="do",fb="else",gb="finally",hb="instanceof",ib="return",jb="throw",kb="try",lb="typeof",
+mb="(?:^^|[+-]",nb="\\$1",ob=")\\s*",pb="&",qb="<",rb=">",sb=""",tb="&#",ub="x",vb="'",wb='"',xb=" ",yb="XMP",zb="</",Ab='="',P="",Q="\\",Bb="b",Cb="t",Db="n",Eb="v",Fb="f",Gb="r",Hb="u",Ib="0",Jb="1",Kb="2",Lb="3",Mb="4",Nb="5",Ob="6",Pb="7",Qb="\\x0",Rb="\\x",Sb="-",Tb="]",Ub="\\\\u[0-9A-Fa-f]{4}|\\\\x[0-9A-Fa-f]{2}|\\\\[0-3][0-7]{0,2}|\\\\[0-7]{1,2}|\\\\[\\s\\S]|-|[^-\\\\]",R="g",Vb="\\B",Wb="\\b",Xb="\\D",Yb="\\d",Zb="\\S",$b="\\s",ac="\\W",bc="\\w",cc="(?:\\[(?:[^\\x5C\\x5D]|\\\\[\\s\\S])*\\]|\\\\u[A-Fa-f0-9]{4}|\\\\x[A-Fa-f0-9]{2}|\\\\[0-9]+|\\\\[^ux0-9]|\\(\\?[:!=]|[\\(\\)\\^]|[^\\x5B\\x5C\\(\\)\\^]+)",
+dc="(?:",ec=")",fc="gi",gc="PRE",hc='<!DOCTYPE foo PUBLIC "foo bar">\n<foo />',ic="\t",jc="\n",kc="[^<]+|<!--[\\s\\S]*?--\>|<!\\[CDATA\\[[\\s\\S]*?\\]\\]>|</?[a-zA-Z][^>]*>|<",lc="nocode",mc=' $1="$2$3$4"',S="pln",nc="string",T="lang-",oc="src",U="str",pc="'\"",qc="'\"`",rc="\"'",V="com",sc="lang-regex",tc="(/(?=[^/*])(?:[^/\\x5B\\x5C]|\\x5C[\\s\\S]|\\x5B(?:[^\\x5C\\x5D]|\\x5C[\\s\\S])*(?:\\x5D|$))+/)",uc="kwd",vc="^(?:",wc=")\\b",xc=" \r\n\t\u00a0",yc="lit",zc="typ",Ac="0123456789",Y="pun",Bc="break continue do else for if return while auto case char const default double enum extern float goto int long register short signed sizeof static struct switch typedef union unsigned void volatile catch class delete false import new operator private protected public this throw true try alignof align_union asm axiom bool concept concept_map const_cast constexpr decltype dynamic_cast explicit export friend inline late_check mutable namespace nullptr reinterpret_cast static_assert static_cast template typeid typename typeof using virtual wchar_t where break continue do else for if return while auto case char const default double enum extern float goto int long register short signed sizeof static struct switch typedef union unsigned void volatile catch class delete false import new operator private protected public this throw true try boolean byte extends final finally implements import instanceof null native package strictfp super synchronized throws transient as base by checked decimal delegate descending event fixed foreach from group implicit in interface internal into is lock object out override orderby params partial readonly ref sbyte sealed stackalloc string select uint ulong unchecked unsafe ushort var break continue do else for if return while auto case char const default double enum extern float goto int long register short signed sizeof static struct switch typedef union unsigned void volatile catch class delete false import new operator private protected public this throw true try debugger eval export function get null set undefined var with Infinity NaN caller delete die do dump elsif eval exit foreach for goto if import last local my next no our print package redo require sub undef unless until use wantarray while BEGIN END break continue do else for if return while and as assert class def del elif except exec finally from global import in is lambda nonlocal not or pass print raise try with yield False True None break continue do else for if return while alias and begin case class def defined elsif end ensure false in module next nil not or redo rescue retry self super then true undef unless until when yield BEGIN END break continue do else for if return while case done elif esac eval fi function in local set then until ",
+Cc="</span>",Dc='<span class="',Ec='">',Fc="$1 ",Gc=" <br />",Hc="<br />",Ic="console",Jc="cannot override language handler %s",Kc="default-markup",Lc="default-code",Mc="dec",Z="lang-js",$="lang-css",Nc="lang-in.tag",Oc="htm",Pc="html",Qc="mxml",Rc="xhtml",Sc="xml",Tc="xsl",Uc=" \t\r\n",Vc="atv",Wc="tag",Xc="atn",Yc="lang-uq.val",Zc="in.tag",$c="uq.val",ad="break continue do else for if return while auto case char const default double enum extern float goto int long register short signed sizeof static struct switch typedef union unsigned void volatile catch class delete false import new operator private protected public this throw true try alignof align_union asm axiom bool concept concept_map const_cast constexpr decltype dynamic_cast explicit export friend inline late_check mutable namespace nullptr reinterpret_cast static_assert static_cast template typeid typename typeof using virtual wchar_t where ",
+bd="c",cd="cc",dd="cpp",ed="cxx",fd="cyc",gd="m",hd="null true false",id="json",jd="break continue do else for if return while auto case char const default double enum extern float goto int long register short signed sizeof static struct switch typedef union unsigned void volatile catch class delete false import new operator private protected public this throw true try boolean byte extends final finally implements import instanceof null native package strictfp super synchronized throws transient as base by checked decimal delegate descending event fixed foreach from group implicit in interface internal into is lock object out override orderby params partial readonly ref sbyte sealed stackalloc string select uint ulong unchecked unsafe ushort var ",
+kd="cs",ld="break continue do else for if return while auto case char const default double enum extern float goto int long register short signed sizeof static struct switch typedef union unsigned void volatile catch class delete false import new operator private protected public this throw true try boolean byte extends final finally implements import instanceof null native package strictfp super synchronized throws transient ",md="java",nd="break continue do else for if return while case done elif esac eval fi function in local set then until ",
+od="bsh",pd="csh",qd="sh",rd="break continue do else for if return while and as assert class def del elif except exec finally from global import in is lambda nonlocal not or pass print raise try with yield False True None ",sd="cv",td="py",ud="caller delete die do dump elsif eval exit foreach for goto if import last local my next no our print package redo require sub undef unless until use wantarray while BEGIN END ",vd="perl",wd="pl",xd="pm",yd="break continue do else for if return while alias and begin case class def defined elsif end ensure false in module next nil not or redo rescue retry self super then true undef unless until when yield BEGIN END ",
+zd="rb",Ad="break continue do else for if return while auto case char const default double enum extern float goto int long register short signed sizeof static struct switch typedef union unsigned void volatile catch class delete false import new operator private protected public this throw true try debugger eval export function get null set undefined var with Infinity NaN ",Bd="js",Cd="regex",Dd="pre",Ed="code",Fd="xmp",Gd="prettyprint",Hd="class",Id="br",Jd="\r";
+(function(){var N=function(){for(var a=[aa,ba,ca,F,da,ea,G,fa,ja,ka,H,la,ma,na,oa,pa,qa,ra,sa,ta,ua,va,I,wa,xa,ya,za,Aa,Ba,J,Ca,Da,Ea,Fa,Ga,Ha,Ia,L,M,Ta,Ua,Va,Wa,O,Xa,Ya,Za,$a,ab,bb,cb,db,eb,fb,gb,hb,ib,jb,kb,lb],b=mb,c=0;c<a.length;++c)b+=O+a[c].replace(/([^=<>:&a-z])/g,nb);b+=ob;return b}(),Ja=/&/g,Ka=/</g,La=/>/g,Kd=/\"/g;function Ld(a){return a.replace(Ja,pb).replace(Ka,qb).replace(La,rb).replace(Kd,sb)}function ga(a){return a.replace(Ja,pb).replace(Ka,qb).replace(La,rb)}var Md=/</g,Nd=/>/g,
+Od=/'/g,Pd=/"/g,Qd=/&/g,Rd=/ /g;function Sd(a){var b=a.indexOf(G);if(b<0)return a;for(--b;(b=a.indexOf(tb,b+1))>=0;){var c=a.indexOf(va,b);if(c>=0){var d=a.substring(b+3,c),g=10;if(d&&d.charAt(0)===ub){d=d.substring(1);g=16}var i=parseInt(d,g);isNaN(i)||(a=a.substring(0,b)+String.fromCharCode(i)+a.substring(c+1))}}return a.replace(Md,I).replace(Nd,J).replace(Od,vb).replace(Pd,wb).replace(Qd,G).replace(Rd,xb)}function Ma(a){return yb===a.tagName}function W(a,b){switch(a.nodeType){case 1:var c=
+a.tagName.toLowerCase();b.push(I,c);for(var d=0;d<a.attributes.length;++d){var g=a.attributes[d];if(g.specified){b.push(xb);W(g,b)}}b.push(J);for(var i=a.firstChild;i;i=i.nextSibling)W(i,b);if(a.firstChild||!/^(?:br|link|img)$/.test(c))b.push(zb,c,J);break;case 2:b.push(a.name.toLowerCase(),Ab,Ld(a.value),wb);break;case 3:case 4:b.push(ga(a.nodeValue));break}}function Na(a){for(var b=0,c=z,d=z,g=0,i=a.length;g<i;++g){var m=a[g];if(m.ignoreCase)d=o;else if(/[a-z]/i.test(m.source.replace(/\\u[0-9a-f]{4}|\\x[0-9a-f]{2}|\\[^ux]/gi,
+P))){c=o;d=z;break}}function l(j){if(j.charAt(0)!==Q)return j.charCodeAt(0);switch(j.charAt(1)){case Bb:return 8;case Cb:return 9;case Db:return 10;case Eb:return 11;case Fb:return 12;case Gb:return 13;case Hb:case ub:return parseInt(j.substring(2),16)||j.charCodeAt(1);case Ib:case Jb:case Kb:case Lb:case Mb:case Nb:case Ob:case Pb:return parseInt(j.substring(1),8);default:return j.charCodeAt(1)}}function n(j){if(j<32)return(j<16?Qb:Rb)+j.toString(16);var f=String.fromCharCode(j);if(f===Q||f===Sb||
+f===L||f===Tb)f=Q+f;return f}function q(j){for(var f=j.substring(1,j.length-1).match(new RegExp(Ub,R)),s=[],k=[],h=f[0]===M,e=h?1:0,p=f.length;e<p;++e){var t=f[e];switch(t){case Vb:case Wb:case Xb:case Yb:case Zb:case $b:case ac:case bc:s.push(t);continue}var u=l(t),x;if(e+2<p&&Sb===f[e+1]){x=l(f[e+2]);e+=2}else x=u;k.push([u,x]);if(!(x<65||u>122)){x<65||u>90||k.push([Math.max(65,u)|32,Math.min(x,90)|32]);x<97||u>122||k.push([Math.max(97,u)&-33,Math.min(x,122)&-33])}}k.sort(function(Oa,Pa){return Oa[0]-
+Pa[0]||Pa[1]-Oa[1]});var B=[],E=[NaN,NaN];for(e=0;e<k.length;++e){var A=k[e];if(A[0]<=E[1]+1)E[1]=Math.max(E[1],A[1]);else B.push(E=A)}var D=[L];h&&D.push(M);D.push.apply(D,s);for(e=0;e<B.length;++e){A=B[e];D.push(n(A[0]));if(A[1]>A[0]){A[1]+1>A[0]&&D.push(Sb);D.push(n(A[1]))}}D.push(Tb);return D.join(P)}function v(j){var f=j.source.match(new RegExp(cc,R)),s=f.length,k=[],h,e=0;for(h=0;e<s;++e){var p=f[e];if(p===H)++h;else if(Q===p.charAt(0)){var t=+p.substring(1);if(t&&t<=h)k[t]=-1}}for(e=1;e<k.length;++e)if(-1===
+k[e])k[e]=++b;for(h=e=0;e<s;++e){p=f[e];if(p===H){++h;if(k[h]===undefined)f[e]=dc}else if(Q===p.charAt(0))if((t=+p.substring(1))&&t<=h)f[e]=Q+k[h]}for(h=e=0;e<s;++e)if(M===f[e]&&M!==f[e+1])f[e]=P;if(j.ignoreCase&&c)for(e=0;e<s;++e){p=f[e];var u=p.charAt(0);if(p.length>=2&&u===L)f[e]=q(p);else if(u!==Q)f[e]=p.replace(/[a-zA-Z]/g,function(x){var B=x.charCodeAt(0);return L+String.fromCharCode(B&-33,B|32)+Tb})}return f.join(P)}var w=[];g=0;for(i=a.length;g<i;++g){m=a[g];if(m.global||m.multiline)throw new Error(P+
+m);w.push(dc+v(m)+ec)}return new RegExp(w.join(O),d?fc:R)}var ha=r;function Td(a){if(r===ha){var b=document.createElement(gc);b.appendChild(document.createTextNode(hc));ha=!/</.test(b.innerHTML)}if(ha){var c=a.innerHTML;if(Ma(a))c=ga(c);return c}for(var d=[],g=a.firstChild;g;g=g.nextSibling)W(g,d);return d.join(P)}function Ud(a){var b=0;return function(c){for(var d=r,g=0,i=0,m=c.length;i<m;++i){var l=c.charAt(i);switch(l){case ic:d||(d=[]);d.push(c.substring(g,i));var n=a-b%a;for(b+=n;n>=0;n-=" ".length)d.push(" ".substring(0,
+n));g=i+1;break;case jc:b=0;break;default:++b}}if(!d)return c;d.push(c.substring(g));return d.join(P)}}var Vd=new RegExp(kc,R),Wd=/^<\!--/,Xd=/^<\[CDATA\[/,Yd=/^<br\b/i,Qa=/^<(\/?)([a-zA-Z]+)/;function Zd(a){var b=a.match(Vd),c=[],d=0,g=[];if(b)for(var i=0,m=b.length;i<m;++i){var l=b[i];if(l.length>1&&l.charAt(0)===I){if(!Wd.test(l))if(Xd.test(l)){c.push(l.substring(9,l.length-3));d+=l.length-12}else if(Yd.test(l)){c.push(jc);++d}else if(l.indexOf(lc)>=0&&$d(l)){var n=l.match(Qa)[2],q=1,v;v=i+1;a:for(;v<
+m;++v){var w=b[v].match(Qa);if(w&&w[2]===n)if(w[1]===ra){if(--q===0)break a}else++q}if(v<m){g.push(d,b.slice(i,v+1).join(P));i=v}else g.push(d,l)}else g.push(d,l)}else{var j=Sd(l);c.push(j);d+=j.length}}return{source:c.join(P),tags:g}}function $d(a){return!!a.replace(/\s(\w+)\s*=\s*(?:\"([^\"]*)\"|'([^\']*)'|(\S+))/g,mc).match(/[cC][lL][aA][sS][sS]=\"[^\"]*\bnocode\b/)}function ia(a,b,c,d){if(b){var g={source:b,b:a};c(g);d.push.apply(d,g.c)}}function K(a,b){var c={},d;(function(){for(var m=a.concat(b),
+l=[],n={},q=0,v=m.length;q<v;++q){var w=m[q],j=w[3];if(j)for(var f=j.length;--f>=0;)c[j.charAt(f)]=w;var s=w[1],k=P+s;if(!n.hasOwnProperty(k)){l.push(s);n[k]=r}}l.push(/[\0-\uffff]/);d=Na(l)})();var g=b.length,i=function(m){for(var l=m.source,n=m.b,q=[n,S],v=0,w=l.match(d)||[],j={},f=0,s=w.length;f<s;++f){var k=w[f],h=j[k],e,p;if(typeof h===nc)p=z;else{var t=c[k.charAt(0)];if(t){e=k.match(t[1]);h=t[0]}else{for(var u=0;u<g;++u){t=b[u];if(e=k.match(t[1])){h=t[0];break}}e||(h=S)}if((p=h.length>=5&&T===
+h.substring(0,5))&&!(e&&e[1])){p=z;h=oc}p||(j[k]=h)}var x=v;v+=k.length;if(p){var B=e[1],E=k.indexOf(B),A=E+B.length,D=h.substring(5);ia(n+x,k.substring(0,E),i,q);ia(n+x+E,B,Ra(D,B),q);ia(n+x+A,k.substring(A),i,q)}else q.push(n+x,h)}m.c=q};return i}function C(a){var b=[],c=[];if(a.tripleQuotedStrings)b.push([U,/^(?:\'\'\'(?:[^\'\\]|\\[\s\S]|\'{1,2}(?=[^\']))*(?:\'\'\'|$)|\"\"\"(?:[^\"\\]|\\[\s\S]|\"{1,2}(?=[^\"]))*(?:\"\"\"|$)|\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$))/,r,pc]);
+else a.multiLineStrings?b.push([U,/^(?:\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$)|\`(?:[^\\\`]|\\[\s\S])*(?:\`|$))/,r,qc]):b.push([U,/^(?:\'(?:[^\\\'\r\n]|\\.)*(?:\'|$)|\"(?:[^\\\"\r\n]|\\.)*(?:\"|$))/,r,rc]);if(a.hashComments)a.cStyleComments?b.push([V,/^#(?:[^\r\n\/]|\/(?!\*)|\/\*[^\r\n]*?\*\/)*/,r,F]):b.push([V,/^#[^\r\n]*/,r,F]);if(a.cStyleComments){c.push([V,/^\/\/[^\r\n]*/,r]);c.push([V,/^\/\*[\s\S]*?(?:\*\/|$)/,r])}a.regexLiterals&&c.push([sc,new RegExp(M+N+tc)]);var d=
+a.keywords.replace(/^\s+|\s+$/g,P);d.length&&c.push([uc,new RegExp(vc+d.replace(/\s+/g,O)+wc),r]);b.push([S,/^\s+/,r,xc]);c.push([yc,/^@[a-z_$][a-z_$@0-9]*/i,r,Ia],[zc,/^@?[A-Z]+[a-z][A-Za-z_$@0-9]*/,r],[S,/^[a-z_$][a-z_$@0-9]*/i,r],[yc,/^(?:0x[a-f0-9]+|(?:\d(?:_\d+)*\d*(?:\.\d*)?|\.\d\+)(?:e[+\-]?\d+)?)[a-z]*/i,r,Ac],[Y,/^.[^\s\w\.$@\'\"\`\/\#]*/,r]);return K(b,c)}var ae=C({keywords:Bc,hashComments:o,cStyleComments:o,multiLineStrings:o,regexLiterals:o});function be(a){var b=a.source,c=a.f,d=a.c,
+g=[],i=0,m=r,l=r,n=0,q=0,v=Ud(window.PR_TAB_WIDTH),w=/([\r\n ]) /g,j=/(^| ) /gm,f=/\r\n?|\n/g,s=/[ \r\n]$/,k=o;function h(p){if(p>i){if(m&&m!==l){g.push(Cc);m=r}if(!m&&l){m=l;g.push(Dc,m,Ec)}var t=ga(v(b.substring(i,p))).replace(k?j:w,Fc);k=s.test(t);var u=window._pr_isIE6()?Gc:Hc;g.push(t.replace(f,u));i=p}}for(;1;){var e;if(e=n<c.length?q<d.length?c[n]<=d[q]:o:z){h(c[n]);if(m){g.push(Cc);m=r}g.push(c[n+1]);n+=2}else if(q<d.length){h(d[q]);l=d[q+1];q+=2}else break}h(b.length);m&&g.push(Cc);a.a=g.join(P)}
+var X={};function y(a,b){for(var c=b.length;--c>=0;){var d=b[c];if(X.hasOwnProperty(d))Ic in window&&console.i(Jc,d);else X[d]=a}}function Ra(a,b){a&&X.hasOwnProperty(a)||(a=/^\s*</.test(b)?Kc:Lc);return X[a]}y(ae,[Lc]);y(K([],[[S,/^[^<?]+/],[Mc,/^<!\w[^>]*(?:>|$)/],[V,/^<\!--[\s\S]*?(?:-\->|$)/],[T,/^<\?([\s\S]+?)(?:\?>|$)/],[T,/^<%([\s\S]+?)(?:%>|$)/],[Y,/^(?:<[%?]|[%?]>)/],[T,/^<xmp\b[^>]*>([\s\S]+?)<\/xmp\b[^>]*>/i],[Z,/^<script\b[^>]*>([\s\S]+?)<\/script\b[^>]*>/i],[$,/^<style\b[^>]*>([\s\S]+?)<\/style\b[^>]*>/i],
+[Nc,/^(<\/?[a-z][^<>]*>)/i]]),[Kc,Oc,Pc,Qc,Rc,Sc,Tc]);y(K([[S,/^[\s]+/,r,Uc],[Vc,/^(?:\"[^\"]*\"?|\'[^\']*\'?)/,r,rc]],[[Wc,/^^<\/?[a-z](?:[\w:-]*\w)?|\/?>$/],[Xc,/^(?!style\b|on)[a-z](?:[\w:-]*\w)?/],[Yc,/^=\s*([^>\'\"\s]*(?:[^>\'\"\s\/]|\/(?=\s)))/],[Y,/^[=<>\/]+/],[Z,/^on\w+\s*=\s*\"([^\"]+)\"/i],[Z,/^on\w+\s*=\s*\'([^\']+)\'/i],[Z,/^on\w+\s*=\s*([^\"\'>\s]+)/i],[$,/^sty\w+\s*=\s*\"([^\"]+)\"/i],[$,/^sty\w+\s*=\s*\'([^\']+)\'/i],[$,/^sty\w+\s*=\s*([^\"\'>\s]+)/i]]),[Zc]);y(K([],[[Vc,/^[\s\S]+/]]),
+[$c]);y(C({keywords:ad,hashComments:o,cStyleComments:o}),[bd,cd,dd,ed,fd,gd]);y(C({keywords:hd}),[id]);y(C({keywords:jd,hashComments:o,cStyleComments:o}),[kd]);y(C({keywords:ld,cStyleComments:o}),[md]);y(C({keywords:nd,hashComments:o,multiLineStrings:o}),[od,pd,qd]);y(C({keywords:rd,hashComments:o,multiLineStrings:o,tripleQuotedStrings:o}),[sd,td]);y(C({keywords:ud,hashComments:o,multiLineStrings:o,regexLiterals:o}),[vd,wd,xd]);y(C({keywords:yd,hashComments:o,multiLineStrings:o,regexLiterals:o}),
+[zd]);y(C({keywords:Ad,cStyleComments:o,regexLiterals:o}),[Bd]);y(K([],[[U,/^[\s\S]+/]]),[Cd]);function Sa(a){var b=a.e,c=a.d;a.a=b;try{var d=Zd(b),g=d.source;a.source=g;a.b=0;a.f=d.tags;Ra(c,g)(a);be(a)}catch(i){if(Ic in window){console.log(i);console.h()}}}function ce(a,b){var c={e:a,d:b};Sa(c);return c.a}function de(a){for(var b=window._pr_isIE6(),c=[document.getElementsByTagName(Dd),document.getElementsByTagName(Ed),document.getElementsByTagName(Fd)],d=[],g=0;g<c.length;++g)for(var i=0,m=c[g].length;i<
+m;++i)d.push(c[g][i]);c=r;var l=Date;l.now||(l={now:function(){return(new Date).getTime()}});var n=0,q;function v(){for(var j=window.PR_SHOULD_USE_CONTINUATION?l.now()+250:Infinity;n<d.length&&l.now()<j;n++){var f=d[n];if(f.className&&f.className.indexOf(Gd)>=0){var s=f.className.match(/\blang-(\w+)\b/);if(s)s=s[1];for(var k=z,h=f.parentNode;h;h=h.parentNode)if((h.tagName===Dd||h.tagName===Ed||h.tagName===Fd)&&h.className&&h.className.indexOf(Gd)>=0){k=o;break}if(!k){var e=Td(f);e=e.replace(/(?:\r\n?|\n)$/,
+P);q={e:e,d:s,g:f};Sa(q);w()}}}if(n<d.length)setTimeout(v,250);else a&&a()}function w(){var j=q.a;if(j){var f=q.g;if(Ma(f)){for(var s=document.createElement(gc),k=0;k<f.attributes.length;++k){var h=f.attributes[k];if(h.specified){var e=h.name.toLowerCase();if(e===Hd)s.className=h.value;else s.setAttribute(h.name,h.value)}}s.innerHTML=j;f.parentNode.replaceChild(s,f);f=s}else f.innerHTML=j;if(b&&f.tagName===gc)for(var p=f.getElementsByTagName(Id),t=p.length;--t>=0;){var u=p[t];u.parentNode.replaceChild(document.createTextNode(Jd),
+u)}}}v()}window.PR_normalizedHtml=W;window.prettyPrintOne=ce;window.prettyPrint=de;window.PR={combinePrefixPatterns:Na,createSimpleLexer:K,registerLangHandler:y,sourceDecorator:C,PR_ATTRIB_NAME:Xc,PR_ATTRIB_VALUE:Vc,PR_COMMENT:V,PR_DECLARATION:Mc,PR_KEYWORD:uc,PR_LITERAL:yc,PR_NOCODE:lc,PR_PLAIN:S,PR_PUNCTUATION:Y,PR_SOURCE:oc,PR_STRING:U,PR_TAG:Wc,PR_TYPE:zc}})();
+})()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/web/style.css Mon Jul 26 14:12:44 2010 -0400
@@ -0,0 +1,1 @@
+body{background-color:#f9f9f4;color:#111;font:12pt/14pt Arial,"Helvetica Neue",Helvetica,sans-serif;margin:1em 50px;max-width:1100px}#left_column{width:650px;float:left;margin:0;padding:0}#right_column{width:400px;float:right;margin:1em 0 0 40px;padding:0}@media screen and (max-width:1200px){#left_column,#right_column{width:650px;margin:0;padding:0;float:left}}h1{color:#444;padding:0;margin:8px 0;font-size:1.5em;width:100%}h1 a{color:#444;text-decoration:none}.hilight{color:#d33}h2{font-size:1.1em}h3{font-size:.9em;margin:0;padding:0}.hidden{display:none}.version{padding-top:5em;text-align:center;color:#aaa;font-size:.6em}.navigation{text-align:center;color:#aaa;font-size:.8em}.note{text-align:center;font-size:.8em;color:#999;font-style:italic}ul{margin-top:.2em;margin-left:12pt}ul ul{padding-left:9pt;margin-bottom:.4em}li{padding-left:4pt}input#uri{width:645px;font-size:1.1em}.add_req_hdr{-webkit-border-bottom-right-radius:1em;-webkit-border-bottom-left-radius:1em;-moz-border-radius-bottomleft:1em;-moz-border-radius-bottomright:1em;background-color:#a9a9a4;width:650px;margin:0}#add_req_hdr{font-size:.65em;margin-left:2em;color:white;text-decoration:none}.delete_req_hdr{font-size:.65em;margin-left:.5em;color:white;text-decoration:none}.req_hdr{background-color:#a9a9a4;width:650px;white-space:nowrap}.hdr_name,.hdr_val{font-size:.7em}input.hdr_val{width:200px;max-width:200px}option[value="other..."]{font-style:italic;color:#999}#response{font-size:10pt;font-family:Courier,monospace;background-color:#111;color:#ddd;border-radius:1em;-moz-border-radius:1em;-webkit-border-radius:1em;border:1px solid black;padding:1em 0;margin-bottom:0}#response a{color:#ccf}.options{margin-top:.2em;margin-bottom:2em;padding-top:0;text-align:center;font-size:.6em}.options>*{padding-right:2em}table#summary{font-size:.8em;width:100%;border-spacing:0;background-color:#111;color:#ddd;border-radius:1em;-moz-border-radius:1em;-webkit-border-radius:1em;border:1px solid black;padding:1em;margin-top:1em}table#summary th{text-align:left;padding:2em .5em .25em .5em;border-bottom:1px solid #666;vertical-align:bottom}table#summary td{text-align:center;padding:.25em .4em;white-space:nowrap}table#summary tr:hover td{background-color:#333}table#summary td.uri{text-align:left;font-family:monospace}table#summary td a{color:#ccf}.fade1{opacity:.75}.fade2{opacity:.5}.fade3{opacity:.25}#body{display:none;position:absolute;right:3em;left:3em}#body .prettyprint{font:.80em/1.00em Consolas,"Lucida Console",Monaco,monospace;overflow-x:auto;overflow-y:hidden;background-color:#fff;border:1px solid #ccc;border-radius:1em;-moz-border-radius:1em;-webkit-border-radius:1em;padding:9px}.good{color:#060;list-style-image:url(icon/accept1.png)}.warning{color:#660}ul li.warning{list-style-image:url(icon/yellowflag1.png)}.bad{color:#800}td.bad{color:#e66;font-style:bold}ul li.bad{list-style-image:url(icon/remove-16.png)}.info{color:#111;list-style-image:url(icon/infomation-16.png)}#popup{position:absolute;border-radius:.5em;-moz-border-radius:.5em;-webkit-border-radius:.5em;background-color:#679;opacity:.95;padding:9px;color:white;font-size:.85em;z-index:10;display:none}#popup img{max-width:440px}.footer{max-width:650px}.news{text-align:center;font-size:1.0em;font-weight:normal;width:650px}.news-banner{color:#999}.str{color:#080}.kwd{color:#008}.com{color:#800}.typ{color:#606}.lit{color:#066}.pun{color:#660}.pln{color:#000}.tag{color:#008}.atn{color:#606}.atv{color:#080}.dec{color:#606}pre.prettyprint{padding:2px;border:1px solid #888}@media print{.str{color:#060}.kwd{color:#006;font-weight:bold}.com{color:#600;font-style:italic}.typ{color:#404;font-weight:bold}.lit{color:#044}.pun{color:#440}.pln{color:#000}.tag{color:#006;font-weight:bold}.atn{color:#404}.atv{color:#060}}
\ No newline at end of file