Commits

Lakin Wecker  committed ee04f75

trunk - Porting the cherrypy.lib.httputil name change. It'll be easier to maintain two released branches if the modules are named the same. However, in the 2.6 branch we'll leave cherrypy.lib.http working and deprecate it for 3.3.

  • Participants
  • Parent commits dafcfbb

Comments (0)

Files changed (20)

File cherrypy/__init__.py

 Tool = _cptools.Tool
 
 from cherrypy import _cprequest
-from cherrypy.lib import http as _http
+from cherrypy.lib import httputil as _httputil
 
 from cherrypy import _cptree
 tree = _cptree.Tree()
     
     __metaclass__ = _AttributeDocstrings
     
-    request = _cprequest.Request(_http.Host("127.0.0.1", 80),
-                                 _http.Host("127.0.0.1", 1111))
+    request = _cprequest.Request(_httputil.Host("127.0.0.1", 80),
+                                 _httputil.Host("127.0.0.1", 1111))
     request__doc = """
     The request object for the current thread. In the main thread,
     and any threads which are not receiving HTTP requests, this is None."""

File cherrypy/_cpdispatch.py

         for "example.com" and "www.example.com". In addition, "Host"
         headers may contain the port number.
     """
-    from cherrypy.lib import http
+    from cherrypy.lib import httputil
     def vhost_dispatch(path_info):
         header = cherrypy.request.headers.get
         
         
         prefix = domains.get(domain, "")
         if prefix:
-            path_info = http.urljoin(prefix, path_info)
+            path_info = httputil.urljoin(prefix, path_info)
         
         result = next_dispatcher(path_info)
         

File cherrypy/_cperror.py

 from sys import exc_info as _exc_info
 from traceback import format_exception as _format_exception
 from urlparse import urljoin as _urljoin
-from cherrypy.lib import http as _http
+from cherrypy.lib import httputil as _httputil
 
 
 class CherryPyException(Exception):
     def __init__(self, status=500, message=None):
         self.status = status
         try:
-            self.code, self.reason, defaultmsg = _http.valid_status(status)
+            self.code, self.reason, defaultmsg = _httputil.valid_status(status)
         except ValueError, x:
             raise cherrypy.HTTPError(500, x.args[0])
         
     import cherrypy
     
     try:
-        code, reason, message = _http.valid_status(status)
+        code, reason, message = _httputil.valid_status(status)
     except ValueError, x:
         raise cherrypy.HTTPError(500, x.args[0])
     

File cherrypy/_cpmodpy.py

 
 import cherrypy
 from cherrypy._cperror import format_exc, bare_error
-from cherrypy.lib import http
+from cherrypy.lib import httputil
 
 
 
         
         # Obtain a Request object from CherryPy
         local = req.connection.local_addr
-        local = http.Host(local[0], local[1], req.connection.local_host or "")
+        local = httputil.Host(local[0], local[1], req.connection.local_host or "")
         remote = req.connection.remote_addr
-        remote = http.Host(remote[0], remote[1], req.connection.remote_host or "")
+        remote = httputil.Host(remote[0], remote[1], req.connection.remote_host or "")
         
         scheme = req.parsed_uri[0] or 'http'
         req.get_basic_auth_pw()

File cherrypy/_cprequest.py

 import cherrypy
 from cherrypy import _cpcgifs, _cpconfig
 from cherrypy._cperror import format_exc, bare_error
-from cherrypy.lib import http, file_generator
+from cherrypy.lib import httputil, file_generator
 
 
 class Hook(object):
     unless we are processing an InternalRedirect."""
     
     # Conversation/connection attributes
-    local = http.Host("127.0.0.1", 80)
+    local = httputil.Host("127.0.0.1", 80)
     local__doc = \
-        "An http.Host(ip, port, hostname) object for the server socket."
+        "An httputil.Host(ip, port, hostname) object for the server socket."
     
-    remote = http.Host("127.0.0.1", 1111)
+    remote = httputil.Host("127.0.0.1", 1111)
     remote__doc = \
-        "An http.Host(ip, port, hostname) object for the client socket."
+        "An httputil.Host(ip, port, hostname) object for the client socket."
     
     scheme = "http"
     scheme__doc = """
     A list of the HTTP request headers as (name, value) tuples.
     In general, you should use request.headers (a dict) instead."""
     
-    headers = http.HeaderMap()
+    headers = httputil.HeaderMap()
     headers__doc = """
     A dict-like object containing the request headers. Keys are header
     names (in Title-Case format); however, you may get and set them in
     a case-insensitive manner. That is, headers['Content-Type'] and
     headers['content-type'] refer to the same value. Values are header
     values (decoded according to RFC 2047 if necessary). See also:
-    http.HeaderMap, http.HeaderElement."""
+    httputil.HeaderMap, httputil.HeaderElement."""
     
     cookie = Cookie.SimpleCookie()
     cookie__doc = """See help(Cookie)."""
                  server_protocol="HTTP/1.1"):
         """Populate a new Request object.
         
-        local_host should be an http.Host object with the server info.
-        remote_host should be an http.Host object with the client info.
+        local_host should be an httputil.Host object with the server info.
+        remote_host should be an httputil.Host object with the client info.
         scheme should be a string, either "http" or "https".
         """
         self.local = local_host
             
             self.header_list = list(headers)
             self.rfile = rfile
-            self.headers = http.HeaderMap()
+            self.headers = httputil.HeaderMap()
             self.cookie = Cookie.SimpleCookie()
             self.handler = None
             
     
     def process_headers(self):
         """Parse HTTP header data into Python structures. (Core)"""
-        self.params = http.parse_query_string(self.query_string)
+        self.params = httputil.parse_query_string(self.query_string)
         
         # Process the headers into self.headers
         headers = self.headers
             # only Konqueror does that), only the last one will remain in headers
             # (but they will be correctly stored in request.cookie).
             if "=?" in value:
-                dict.__setitem__(headers, name, http.decode_TEXT(value))
+                dict.__setitem__(headers, name, httputil.decode_TEXT(value))
             else:
                 dict.__setitem__(headers, name, value)
             
         # won't parse the request body for params if the client
         # didn't provide a "Content-Type" header.
         if 'Content-Type' not in self.headers:
-            h = http.HeaderMap(self.headers.items())
+            h = httputil.HeaderMap(self.headers.items())
             h['Content-Type'] = ''
         else:
             h = self.headers
             # request body was a content-type other than multipart.
             self.body = forms.file
         else:
-            self.body_params = p = http.params_from_CGI_form(forms)
+            self.body_params = p = httputil.params_from_CGI_form(forms)
             self.params.update(p)
     
     def handle_error(self):
     A list of the HTTP response headers as (name, value) tuples.
     In general, you should use response.headers (a dict) instead."""
     
-    headers = http.HeaderMap()
+    headers = httputil.HeaderMap()
     headers__doc = """
     A dict-like object containing the response headers. Keys are header
     names (in Title-Case format); however, you may get and set them in
     a case-insensitive manner. That is, headers['Content-Type'] and
     headers['content-type'] refer to the same value. Values are header
     values (decoded according to RFC 2047 if necessary). See also:
-    http.HeaderMap, http.HeaderElement."""
+    httputil.HeaderMap, httputil.HeaderElement."""
     
     cookie = Cookie.SimpleCookie()
     cookie__doc = """See help(Cookie)."""
         self._body = []
         self.time = time.time()
         
-        self.headers = http.HeaderMap()
+        self.headers = httputil.HeaderMap()
         # Since we know all our keys are titled strings, we can
         # bypass HeaderMap.update and get a big speed boost.
         dict.update(self.headers, {
             "Content-Type": 'text/html',
             "Server": "CherryPy/" + cherrypy.__version__,
-            "Date": http.HTTPDate(self.time),
+            "Date": httputil.HTTPDate(self.time),
         })
         self.cookie = Cookie.SimpleCookie()
     
     def finalize(self):
         """Transform headers (and cookies) into self.header_list. (Core)"""
         try:
-            code, reason, _ = http.valid_status(self.status)
+            code, reason, _ = httputil.valid_status(self.status)
         except ValueError, x:
             raise cherrypy.HTTPError(500, x.args[0])
         

File cherrypy/_cptree.py

 import os
 import cherrypy
 from cherrypy import _cpconfig, _cplogging, _cprequest, _cpwsgi, tools
-from cherrypy.lib import http as _http
+from cherrypy.lib import httputil as _httputil
 
 
 class Application(object):
         
         if path is None:
             try:
-                path = _http.urljoin(cherrypy.request.script_name,
+                path = _httputil.urljoin(cherrypy.request.script_name,
                                      cherrypy.request.path_info)
             except AttributeError:
                 return None
         # If you're calling this, then you're probably setting SCRIPT_NAME
         # to '' (some WSGI servers always set SCRIPT_NAME to '').
         # Try to look up the app using the full path.
-        path = _http.urljoin(environ.get('SCRIPT_NAME', ''),
+        path = _httputil.urljoin(environ.get('SCRIPT_NAME', ''),
                              environ.get('PATH_INFO', ''))
         sn = self.script_name(path or "/")
         if sn is None:

File cherrypy/_cpwsgi.py

 
 import cherrypy as _cherrypy
 from cherrypy import _cperror
-from cherrypy.lib import http as _http
+from cherrypy.lib import httputil as _httputil
 
 
 class VirtualHost(object):
     def get_response(self):
         """Run self.request and return its response."""
         meth = self.environ['REQUEST_METHOD']
-        path = _http.urljoin(self.environ.get('SCRIPT_NAME', ''),
+        path = _httputil.urljoin(self.environ.get('SCRIPT_NAME', ''),
                              self.environ.get('PATH_INFO', ''))
         qs = self.environ.get('QUERY_STRING', '')
         rproto = self.environ.get('SERVER_PROTOCOL')
         """Create a Request object using environ."""
         env = self.environ.get
         
-        local = _http.Host('', int(env('SERVER_PORT', 80)),
+        local = _httputil.Host('', int(env('SERVER_PORT', 80)),
                            env('SERVER_NAME', ''))
-        remote = _http.Host(env('REMOTE_ADDR', ''),
+        remote = _httputil.Host(env('REMOTE_ADDR', ''),
                             int(env('REMOTE_PORT', -1)),
                             env('REMOTE_HOST', ''))
         scheme = env('wsgi.url_scheme')

File cherrypy/lib/caching.py

 import time
 
 import cherrypy
-from cherrypy.lib import cptools, http
+from cherrypy.lib import cptools, httputil
 
 class VaryHeaderAwareStore():
     """
         s, h, b, create_time, original_req_headers = cache_data
         
         # Copy the response headers. See http://www.cherrypy.org/ticket/721.
-        response.headers = rh = http.HeaderMap()
+        response.headers = rh = httputil.HeaderMap()
         for k in h:
             dict.__setitem__(rh, k, dict.__getitem__(h, k))
         
                 if force or "Cache-Control" not in headers:
                     headers["Cache-Control"] = "no-cache, must-revalidate"
             # Set an explicit Expires date in the past.
-            expiry = http.HTTPDate(1169942400.0)
+            expiry = httputil.HTTPDate(1169942400.0)
         else:
-            expiry = http.HTTPDate(response.time + secs)
+            expiry = httputil.HTTPDate(response.time + secs)
         if force or "Expires" not in headers:
             headers["Expires"] = expiry

File cherrypy/lib/cptools.py

     from sets import Set as set
 
 import cherrypy
-from cherrypy.lib import http as _http
+from cherrypy.lib import httputil as _httputil
 
 
 #                     Conditional HTTP request support                     #
     if hasattr(response, "ETag"):
         return
     
-    status, reason, msg = _http.valid_status(response.status)
+    status, reason, msg = _httputil.valid_status(response.status)
     
     etag = response.headers.get('ETag')
     
     response = cherrypy.response
     lastmod = response.headers.get('Last-Modified')
     if lastmod:
-        status, reason, msg = _http.valid_status(response.status)
+        status, reason, msg = _httputil.valid_status(response.status)
         
         request = cherrypy.request
         
     raise cherrypy.HTTPError(406, msg)
 
 
-class MonitoredHeaderMap(_http.HeaderMap):
+class MonitoredHeaderMap(_httputil.HeaderMap):
     
     def __init__(self):
         self.accessed_headers = set()
     
     def __getitem__(self, key):
         self.accessed_headers.add(key)
-        return _http.HeaderMap.__getitem__(self, key)
+        return _httputil.HeaderMap.__getitem__(self, key)
     
     def __contains__(self, key):
         self.accessed_headers.add(key)
-        return _http.HeaderMap.__contains__(self, key)
+        return _httputil.HeaderMap.__contains__(self, key)
     
     def get(self, key, default=None):
         self.accessed_headers.add(key)
-        return _http.HeaderMap.get(self, key, default=default)
+        return _httputil.HeaderMap.get(self, key, default=default)
     
     def has_key(self, key):
         self.accessed_headers.add(key)
-        return _http.HeaderMap.has_key(self, key)
+        return _httputil.HeaderMap.has_key(self, key)
 
 
 def autovary():

File cherrypy/lib/http.py

-"""HTTP library functions."""
+import warnings
+warnings.warn('cherrypy.lib.http has been deprecated and will be removed '
+              'in CherryPy 3.3 use cherrypy.lib.httputil instead.',
+              DeprecationWarning)
 
-# This module contains functions for building an HTTP application
-# framework: any one, not just one whose name starts with "Ch". ;) If you
-# reference any modules from some popular framework inside *this* module,
-# FuManChu will personally hang you up by your thumbs and submit you
-# to a public caning.
+from cherrypy.lib.httputil import *
 
-from BaseHTTPServer import BaseHTTPRequestHandler
-response_codes = BaseHTTPRequestHandler.responses.copy()
-
-# From http://www.cherrypy.org/ticket/361
-response_codes[500] = ('Internal Server Error',
-                      'The server encountered an unexpected condition '
-                      'which prevented it from fulfilling the request.')
-response_codes[503] = ('Service Unavailable',
-                      'The server is currently unable to handle the '
-                      'request due to a temporary overloading or '
-                      'maintenance of the server.')
-
-
-import cgi
-import re
-from rfc822 import formatdate as HTTPDate
-
-
-def urljoin(*atoms):
-    """Return the given path *atoms, joined into a single URL.
-    
-    This will correctly join a SCRIPT_NAME and PATH_INFO into the
-    original URL, even if either atom is blank.
-    """
-    url = "/".join([x for x in atoms if x])
-    while "//" in url:
-        url = url.replace("//", "/")
-    # Special-case the final url of "", and return "/" instead.
-    return url or "/"
-
-def protocol_from_http(protocol_str):
-    """Return a protocol tuple from the given 'HTTP/x.y' string."""
-    return int(protocol_str[5]), int(protocol_str[7])
-
-def get_ranges(headervalue, content_length):
-    """Return a list of (start, stop) indices from a Range header, or None.
-    
-    Each (start, stop) tuple will be composed of two ints, which are suitable
-    for use in a slicing operation. That is, the header "Range: bytes=3-6",
-    if applied against a Python string, is requesting resource[3:7]. This
-    function will return the list [(3, 7)].
-    
-    If this function returns an empty list, you should return HTTP 416.
-    """
-    
-    if not headervalue:
-        return None
-    
-    result = []
-    bytesunit, byteranges = headervalue.split("=", 1)
-    for brange in byteranges.split(","):
-        start, stop = [x.strip() for x in brange.split("-", 1)]
-        if start:
-            if not stop:
-                stop = content_length - 1
-            start, stop = map(int, (start, stop))
-            if start >= content_length:
-                # From rfc 2616 sec 14.16:
-                # "If the server receives a request (other than one
-                # including an If-Range request-header field) with an
-                # unsatisfiable Range request-header field (that is,
-                # all of whose byte-range-spec values have a first-byte-pos
-                # value greater than the current length of the selected
-                # resource), it SHOULD return a response code of 416
-                # (Requested range not satisfiable)."
-                continue
-            if stop < start:
-                # From rfc 2616 sec 14.16:
-                # "If the server ignores a byte-range-spec because it
-                # is syntactically invalid, the server SHOULD treat
-                # the request as if the invalid Range header field
-                # did not exist. (Normally, this means return a 200
-                # response containing the full entity)."
-                return None
-            result.append((start, stop + 1))
-        else:
-            if not stop:
-                # See rfc quote above.
-                return None
-            # Negative subscript (last N bytes)
-            result.append((content_length - int(stop), content_length))
-    
-    return result
-
-
-class HeaderElement(object):
-    """An element (with parameters) from an HTTP header's element list."""
-    
-    def __init__(self, value, params=None):
-        self.value = value
-        if params is None:
-            params = {}
-        self.params = params
-    
-    def __unicode__(self):
-        p = [";%s=%s" % (k, v) for k, v in self.params.iteritems()]
-        return u"%s%s" % (self.value, "".join(p))
-    
-    def __str__(self):
-        return str(self.__unicode__())
-    
-    def parse(elementstr):
-        """Transform 'token;key=val' to ('token', {'key': 'val'})."""
-        # Split the element into a value and parameters. The 'value' may
-        # be of the form, "token=token", but we don't split that here.
-        atoms = [x.strip() for x in elementstr.split(";") if x.strip()]
-        initial_value = atoms.pop(0).strip()
-        params = {}
-        for atom in atoms:
-            atom = [x.strip() for x in atom.split("=", 1) if x.strip()]
-            key = atom.pop(0)
-            if atom:
-                val = atom[0]
-            else:
-                val = ""
-            params[key] = val
-        return initial_value, params
-    parse = staticmethod(parse)
-    
-    def from_str(cls, elementstr):
-        """Construct an instance from a string of the form 'token;key=val'."""
-        ival, params = cls.parse(elementstr)
-        return cls(ival, params)
-    from_str = classmethod(from_str)
-
-
-q_separator = re.compile(r'; *q *=')
-
-class AcceptElement(HeaderElement):
-    """An element (with parameters) from an Accept* header's element list.
-    
-    AcceptElement objects are comparable; the more-preferred object will be
-    "less than" the less-preferred object. They are also therefore sortable;
-    if you sort a list of AcceptElement objects, they will be listed in
-    priority order; the most preferred value will be first. Yes, it should
-    have been the other way around, but it's too late to fix now.
-    """
-    
-    def from_str(cls, elementstr):
-        qvalue = None
-        # The first "q" parameter (if any) separates the initial
-        # media-range parameter(s) (if any) from the accept-params.
-        atoms = q_separator.split(elementstr, 1)
-        media_range = atoms.pop(0).strip()
-        if atoms:
-            # The qvalue for an Accept header can have extensions. The other
-            # headers cannot, but it's easier to parse them as if they did.
-            qvalue = HeaderElement.from_str(atoms[0].strip())
-        
-        media_type, params = cls.parse(media_range)
-        if qvalue is not None:
-            params["q"] = qvalue
-        return cls(media_type, params)
-    from_str = classmethod(from_str)
-    
-    def qvalue(self):
-        val = self.params.get("q", "1")
-        if isinstance(val, HeaderElement):
-            val = val.value
-        return float(val)
-    qvalue = property(qvalue, doc="The qvalue, or priority, of this value.")
-    
-    def __cmp__(self, other):
-        diff = cmp(other.qvalue, self.qvalue)
-        if diff == 0:
-            diff = cmp(str(other), str(self))
-        return diff
-
-
-def header_elements(fieldname, fieldvalue):
-    """Return a HeaderElement list from a comma-separated header str."""
-    
-    if not fieldvalue:
-        return None
-    headername = fieldname.lower()
-    
-    result = []
-    for element in fieldvalue.split(","):
-        if headername.startswith("accept") or headername == 'te':
-            hv = AcceptElement.from_str(element)
-        else:
-            hv = HeaderElement.from_str(element)
-        result.append(hv)
-    
-    result.sort()
-    return result
-
-def decode_TEXT(value):
-    """Decode RFC-2047 TEXT (e.g. "=?utf-8?q?f=C3=BCr?=" -> u"f\xfcr")."""
-    from email.Header import decode_header
-    atoms = decode_header(value)
-    decodedvalue = ""
-    for atom, charset in atoms:
-        if charset is not None:
-            atom = atom.decode(charset)
-        decodedvalue += atom
-    return decodedvalue
-
-def valid_status(status):
-    """Return legal HTTP status Code, Reason-phrase and Message.
-    
-    The status arg must be an int, or a str that begins with an int.
-    
-    If status is an int, or a str and no reason-phrase is supplied,
-    a default reason-phrase will be provided.
-    """
-    
-    if not status:
-        status = 200
-    
-    status = str(status)
-    parts = status.split(" ", 1)
-    if len(parts) == 1:
-        # No reason supplied.
-        code, = parts
-        reason = None
-    else:
-        code, reason = parts
-        reason = reason.strip()
-    
-    try:
-        code = int(code)
-    except ValueError:
-        raise ValueError("Illegal response status from server "
-                         "(%s is non-numeric)." % repr(code))
-    
-    if code < 100 or code > 599:
-        raise ValueError("Illegal response status from server "
-                         "(%s is out of range)." % repr(code))
-    
-    if code not in response_codes:
-        # code is unknown but not illegal
-        default_reason, message = "", ""
-    else:
-        default_reason, message = response_codes[code]
-    
-    if reason is None:
-        reason = default_reason
-    
-    return code, reason, message
-
-
-image_map_pattern = re.compile(r"[0-9]+,[0-9]+")
-
-def parse_query_string(query_string, keep_blank_values=True):
-    """Build a params dictionary from a query_string.
-    
-    Duplicate key/value pairs in the provided query_string will be
-    returned as {'key': [val1, val2, ...]}. Single key/values will
-    be returned as strings: {'key': 'value'}.
-    """
-    if image_map_pattern.match(query_string):
-        # Server-side image map. Map the coords to 'x' and 'y'
-        # (like CGI::Request does).
-        pm = query_string.split(",")
-        pm = {'x': int(pm[0]), 'y': int(pm[1])}
-    else:
-        pm = cgi.parse_qs(query_string, keep_blank_values)
-        for key, val in pm.items():
-            if len(val) == 1:
-                pm[key] = val[0]
-    return pm
-
-def params_from_CGI_form(form):
-    params = {}
-    for key in form.keys():
-        value_list = form[key]
-        if key is None:
-            # multipart/* message parts that have no Content-Disposition
-            # have a .name of None, but Python kwarg keys must be strings.
-            # See http://www.cherrypy.org/ticket/890.
-            key = 'parts'
-        if isinstance(value_list, list):
-            params[key] = []
-            for item in value_list:
-                if item.filename is not None:
-                    value = item # It's a file upload
-                else:
-                    value = item.value # It's a regular field
-                params[key].append(value)
-        else:
-            if value_list.filename is not None:
-                value = value_list # It's a file upload
-            else:
-                value = value_list.value # It's a regular field
-            params[key] = value
-    return params
-
-
-class CaseInsensitiveDict(dict):
-    """A case-insensitive dict subclass.
-    
-    Each key is changed on entry to str(key).title().
-    """
-    
-    def __getitem__(self, key):
-        return dict.__getitem__(self, str(key).title())
-    
-    def __setitem__(self, key, value):
-        dict.__setitem__(self, str(key).title(), value)
-    
-    def __delitem__(self, key):
-        dict.__delitem__(self, str(key).title())
-    
-    def __contains__(self, key):
-        return dict.__contains__(self, str(key).title())
-    
-    def get(self, key, default=None):
-        return dict.get(self, str(key).title(), default)
-    
-    def has_key(self, key):
-        return dict.has_key(self, str(key).title())
-    
-    def update(self, E):
-        for k in E.keys():
-            self[str(k).title()] = E[k]
-    
-    def fromkeys(cls, seq, value=None):
-        newdict = cls()
-        for k in seq:
-            newdict[str(k).title()] = value
-        return newdict
-    fromkeys = classmethod(fromkeys)
-    
-    def setdefault(self, key, x=None):
-        key = str(key).title()
-        try:
-            return self[key]
-        except KeyError:
-            self[key] = x
-            return x
-    
-    def pop(self, key, default):
-        return dict.pop(self, str(key).title(), default)
-
-
-class HeaderMap(CaseInsensitiveDict):
-    """A dict subclass for HTTP request and response headers.
-    
-    Each key is changed on entry to str(key).title(). This allows headers
-    to be case-insensitive and avoid duplicates.
-    
-    Values are header values (decoded according to RFC 2047 if necessary).
-    """
-    
-    def elements(self, key):
-        """Return a list of HeaderElements for the given header (or None)."""
-        key = str(key).title()
-        h = self.get(key)
-        if h is None:
-            return []
-        return header_elements(key, h)
-    
-    def output(self, protocol=(1, 1)):
-        """Transform self into a list of (name, value) tuples."""
-        header_list = []
-        for key, v in self.iteritems():
-            if isinstance(v, unicode):
-                # HTTP/1.0 says, "Words of *TEXT may contain octets
-                # from character sets other than US-ASCII." and
-                # "Recipients of header field TEXT containing octets
-                # outside the US-ASCII character set may assume that
-                # they represent ISO-8859-1 characters."
-                try:
-                    v = v.encode("iso-8859-1")
-                except UnicodeEncodeError:
-                    if protocol >= (1, 1):
-                        # Encode RFC-2047 TEXT
-                        # (e.g. u"\u8200" -> "=?utf-8?b?6IiA?=").
-                        from email.Header import Header
-                        v = Header(v, 'utf-8').encode()
-                    else:
-                        raise
-            else:
-                # This coercion should not take any time at all
-                # if value is already of type "str".
-                v = str(v)
-            header_list.append((key, v))
-        return header_list
-
-
-
-class Host(object):
-    """An internet address.
-    
-    name should be the client's host name. If not available (because no DNS
-        lookup is performed), the IP address should be used instead.
-    """
-    
-    ip = "0.0.0.0"
-    port = 80
-    name = "unknown.tld"
-    
-    def __init__(self, ip, port, name=None):
-        self.ip = ip
-        self.port = port
-        if name is None:
-            name = ip
-        self.name = name
-    
-    def __repr__(self):
-        return "http.Host(%r, %r, %r)" % (self.ip, self.port, self.name)

File cherrypy/lib/httputil.py

+"""HTTP library functions."""
+
+# This module contains functions for building an HTTP application
+# framework: any one, not just one whose name starts with "Ch". ;) If you
+# reference any modules from some popular framework inside *this* module,
+# FuManChu will personally hang you up by your thumbs and submit you
+# to a public caning.
+
+from BaseHTTPServer import BaseHTTPRequestHandler
+response_codes = BaseHTTPRequestHandler.responses.copy()
+
+# From http://www.cherrypy.org/ticket/361
+response_codes[500] = ('Internal Server Error',
+                      'The server encountered an unexpected condition '
+                      'which prevented it from fulfilling the request.')
+response_codes[503] = ('Service Unavailable',
+                      'The server is currently unable to handle the '
+                      'request due to a temporary overloading or '
+                      'maintenance of the server.')
+
+
+import cgi
+import re
+from rfc822 import formatdate as HTTPDate
+
+
+def urljoin(*atoms):
+    """Return the given path *atoms, joined into a single URL.
+    
+    This will correctly join a SCRIPT_NAME and PATH_INFO into the
+    original URL, even if either atom is blank.
+    """
+    url = "/".join([x for x in atoms if x])
+    while "//" in url:
+        url = url.replace("//", "/")
+    # Special-case the final url of "", and return "/" instead.
+    return url or "/"
+
+def protocol_from_http(protocol_str):
+    """Return a protocol tuple from the given 'HTTP/x.y' string."""
+    return int(protocol_str[5]), int(protocol_str[7])
+
+def get_ranges(headervalue, content_length):
+    """Return a list of (start, stop) indices from a Range header, or None.
+    
+    Each (start, stop) tuple will be composed of two ints, which are suitable
+    for use in a slicing operation. That is, the header "Range: bytes=3-6",
+    if applied against a Python string, is requesting resource[3:7]. This
+    function will return the list [(3, 7)].
+    
+    If this function returns an empty list, you should return HTTP 416.
+    """
+    
+    if not headervalue:
+        return None
+    
+    result = []
+    bytesunit, byteranges = headervalue.split("=", 1)
+    for brange in byteranges.split(","):
+        start, stop = [x.strip() for x in brange.split("-", 1)]
+        if start:
+            if not stop:
+                stop = content_length - 1
+            start, stop = map(int, (start, stop))
+            if start >= content_length:
+                # From rfc 2616 sec 14.16:
+                # "If the server receives a request (other than one
+                # including an If-Range request-header field) with an
+                # unsatisfiable Range request-header field (that is,
+                # all of whose byte-range-spec values have a first-byte-pos
+                # value greater than the current length of the selected
+                # resource), it SHOULD return a response code of 416
+                # (Requested range not satisfiable)."
+                continue
+            if stop < start:
+                # From rfc 2616 sec 14.16:
+                # "If the server ignores a byte-range-spec because it
+                # is syntactically invalid, the server SHOULD treat
+                # the request as if the invalid Range header field
+                # did not exist. (Normally, this means return a 200
+                # response containing the full entity)."
+                return None
+            result.append((start, stop + 1))
+        else:
+            if not stop:
+                # See rfc quote above.
+                return None
+            # Negative subscript (last N bytes)
+            result.append((content_length - int(stop), content_length))
+    
+    return result
+
+
+class HeaderElement(object):
+    """An element (with parameters) from an HTTP header's element list."""
+    
+    def __init__(self, value, params=None):
+        self.value = value
+        if params is None:
+            params = {}
+        self.params = params
+    
+    def __unicode__(self):
+        p = [";%s=%s" % (k, v) for k, v in self.params.iteritems()]
+        return u"%s%s" % (self.value, "".join(p))
+    
+    def __str__(self):
+        return str(self.__unicode__())
+    
+    def parse(elementstr):
+        """Transform 'token;key=val' to ('token', {'key': 'val'})."""
+        # Split the element into a value and parameters. The 'value' may
+        # be of the form, "token=token", but we don't split that here.
+        atoms = [x.strip() for x in elementstr.split(";") if x.strip()]
+        initial_value = atoms.pop(0).strip()
+        params = {}
+        for atom in atoms:
+            atom = [x.strip() for x in atom.split("=", 1) if x.strip()]
+            key = atom.pop(0)
+            if atom:
+                val = atom[0]
+            else:
+                val = ""
+            params[key] = val
+        return initial_value, params
+    parse = staticmethod(parse)
+    
+    def from_str(cls, elementstr):
+        """Construct an instance from a string of the form 'token;key=val'."""
+        ival, params = cls.parse(elementstr)
+        return cls(ival, params)
+    from_str = classmethod(from_str)
+
+
+q_separator = re.compile(r'; *q *=')
+
+class AcceptElement(HeaderElement):
+    """An element (with parameters) from an Accept* header's element list.
+    
+    AcceptElement objects are comparable; the more-preferred object will be
+    "less than" the less-preferred object. They are also therefore sortable;
+    if you sort a list of AcceptElement objects, they will be listed in
+    priority order; the most preferred value will be first. Yes, it should
+    have been the other way around, but it's too late to fix now.
+    """
+    
+    def from_str(cls, elementstr):
+        qvalue = None
+        # The first "q" parameter (if any) separates the initial
+        # media-range parameter(s) (if any) from the accept-params.
+        atoms = q_separator.split(elementstr, 1)
+        media_range = atoms.pop(0).strip()
+        if atoms:
+            # The qvalue for an Accept header can have extensions. The other
+            # headers cannot, but it's easier to parse them as if they did.
+            qvalue = HeaderElement.from_str(atoms[0].strip())
+        
+        media_type, params = cls.parse(media_range)
+        if qvalue is not None:
+            params["q"] = qvalue
+        return cls(media_type, params)
+    from_str = classmethod(from_str)
+    
+    def qvalue(self):
+        val = self.params.get("q", "1")
+        if isinstance(val, HeaderElement):
+            val = val.value
+        return float(val)
+    qvalue = property(qvalue, doc="The qvalue, or priority, of this value.")
+    
+    def __cmp__(self, other):
+        diff = cmp(other.qvalue, self.qvalue)
+        if diff == 0:
+            diff = cmp(str(other), str(self))
+        return diff
+
+
+def header_elements(fieldname, fieldvalue):
+    """Return a HeaderElement list from a comma-separated header str."""
+    
+    if not fieldvalue:
+        return None
+    headername = fieldname.lower()
+    
+    result = []
+    for element in fieldvalue.split(","):
+        if headername.startswith("accept") or headername == 'te':
+            hv = AcceptElement.from_str(element)
+        else:
+            hv = HeaderElement.from_str(element)
+        result.append(hv)
+    
+    result.sort()
+    return result
+
+def decode_TEXT(value):
+    """Decode RFC-2047 TEXT (e.g. "=?utf-8?q?f=C3=BCr?=" -> u"f\xfcr")."""
+    from email.Header import decode_header
+    atoms = decode_header(value)
+    decodedvalue = ""
+    for atom, charset in atoms:
+        if charset is not None:
+            atom = atom.decode(charset)
+        decodedvalue += atom
+    return decodedvalue
+
+def valid_status(status):
+    """Return legal HTTP status Code, Reason-phrase and Message.
+    
+    The status arg must be an int, or a str that begins with an int.
+    
+    If status is an int, or a str and no reason-phrase is supplied,
+    a default reason-phrase will be provided.
+    """
+    
+    if not status:
+        status = 200
+    
+    status = str(status)
+    parts = status.split(" ", 1)
+    if len(parts) == 1:
+        # No reason supplied.
+        code, = parts
+        reason = None
+    else:
+        code, reason = parts
+        reason = reason.strip()
+    
+    try:
+        code = int(code)
+    except ValueError:
+        raise ValueError("Illegal response status from server "
+                         "(%s is non-numeric)." % repr(code))
+    
+    if code < 100 or code > 599:
+        raise ValueError("Illegal response status from server "
+                         "(%s is out of range)." % repr(code))
+    
+    if code not in response_codes:
+        # code is unknown but not illegal
+        default_reason, message = "", ""
+    else:
+        default_reason, message = response_codes[code]
+    
+    if reason is None:
+        reason = default_reason
+    
+    return code, reason, message
+
+
+image_map_pattern = re.compile(r"[0-9]+,[0-9]+")
+
+def parse_query_string(query_string, keep_blank_values=True):
+    """Build a params dictionary from a query_string.
+    
+    Duplicate key/value pairs in the provided query_string will be
+    returned as {'key': [val1, val2, ...]}. Single key/values will
+    be returned as strings: {'key': 'value'}.
+    """
+    if image_map_pattern.match(query_string):
+        # Server-side image map. Map the coords to 'x' and 'y'
+        # (like CGI::Request does).
+        pm = query_string.split(",")
+        pm = {'x': int(pm[0]), 'y': int(pm[1])}
+    else:
+        pm = cgi.parse_qs(query_string, keep_blank_values)
+        for key, val in pm.items():
+            if len(val) == 1:
+                pm[key] = val[0]
+    return pm
+
+def params_from_CGI_form(form):
+    params = {}
+    for key in form.keys():
+        value_list = form[key]
+        if key is None:
+            # multipart/* message parts that have no Content-Disposition
+            # have a .name of None, but Python kwarg keys must be strings.
+            # See http://www.cherrypy.org/ticket/890.
+            key = 'parts'
+        if isinstance(value_list, list):
+            params[key] = []
+            for item in value_list:
+                if item.filename is not None:
+                    value = item # It's a file upload
+                else:
+                    value = item.value # It's a regular field
+                params[key].append(value)
+        else:
+            if value_list.filename is not None:
+                value = value_list # It's a file upload
+            else:
+                value = value_list.value # It's a regular field
+            params[key] = value
+    return params
+
+
+class CaseInsensitiveDict(dict):
+    """A case-insensitive dict subclass.
+    
+    Each key is changed on entry to str(key).title().
+    """
+    
+    def __getitem__(self, key):
+        return dict.__getitem__(self, str(key).title())
+    
+    def __setitem__(self, key, value):
+        dict.__setitem__(self, str(key).title(), value)
+    
+    def __delitem__(self, key):
+        dict.__delitem__(self, str(key).title())
+    
+    def __contains__(self, key):
+        return dict.__contains__(self, str(key).title())
+    
+    def get(self, key, default=None):
+        return dict.get(self, str(key).title(), default)
+    
+    def has_key(self, key):
+        return dict.has_key(self, str(key).title())
+    
+    def update(self, E):
+        for k in E.keys():
+            self[str(k).title()] = E[k]
+    
+    def fromkeys(cls, seq, value=None):
+        newdict = cls()
+        for k in seq:
+            newdict[str(k).title()] = value
+        return newdict
+    fromkeys = classmethod(fromkeys)
+    
+    def setdefault(self, key, x=None):
+        key = str(key).title()
+        try:
+            return self[key]
+        except KeyError:
+            self[key] = x
+            return x
+    
+    def pop(self, key, default):
+        return dict.pop(self, str(key).title(), default)
+
+
+class HeaderMap(CaseInsensitiveDict):
+    """A dict subclass for HTTP request and response headers.
+    
+    Each key is changed on entry to str(key).title(). This allows headers
+    to be case-insensitive and avoid duplicates.
+    
+    Values are header values (decoded according to RFC 2047 if necessary).
+    """
+    
+    def elements(self, key):
+        """Return a list of HeaderElements for the given header (or None)."""
+        key = str(key).title()
+        h = self.get(key)
+        if h is None:
+            return []
+        return header_elements(key, h)
+    
+    def output(self, protocol=(1, 1)):
+        """Transform self into a list of (name, value) tuples."""
+        header_list = []
+        for key, v in self.iteritems():
+            if isinstance(v, unicode):
+                # HTTP/1.0 says, "Words of *TEXT may contain octets
+                # from character sets other than US-ASCII." and
+                # "Recipients of header field TEXT containing octets
+                # outside the US-ASCII character set may assume that
+                # they represent ISO-8859-1 characters."
+                try:
+                    v = v.encode("iso-8859-1")
+                except UnicodeEncodeError:
+                    if protocol >= (1, 1):
+                        # Encode RFC-2047 TEXT
+                        # (e.g. u"\u8200" -> "=?utf-8?b?6IiA?=").
+                        from email.Header import Header
+                        v = Header(v, 'utf-8').encode()
+                    else:
+                        raise
+            else:
+                # This coercion should not take any time at all
+                # if value is already of type "str".
+                v = str(v)
+            header_list.append((key, v))
+        return header_list
+
+
+
+class Host(object):
+    """An internet address.
+    
+    name should be the client's host name. If not available (because no DNS
+        lookup is performed), the IP address should be used instead.
+    """
+    
+    ip = "0.0.0.0"
+    port = 80
+    name = "unknown.tld"
+    
+    def __init__(self, ip, port, name=None):
+        self.ip = ip
+        self.port = port
+        if name is None:
+            name = ip
+        self.name = name
+    
+    def __repr__(self):
+        return "httputil.Host(%r, %r, %r)" % (self.ip, self.port, self.name)

File cherrypy/lib/sessions.py

 from warnings import warn
 
 import cherrypy
-from cherrypy.lib import http
+from cherrypy.lib import httputil
 
 
 missing = object()
     # the browser. So we have to use the old "expires" ... sigh ...
 ##    cookie[name]['max-age'] = timeout * 60
     if timeout:
-        cookie[name]['expires'] = http.HTTPDate(time.time() + (timeout * 60))
+        cookie[name]['expires'] = httputil.HTTPDate(time.time() + (timeout * 60))
     if domain is not None:
         cookie[name]['domain'] = domain
     if secure:

File cherrypy/lib/static.py

 import urllib
 
 import cherrypy
-from cherrypy.lib import cptools, http, file_generator_limited
+from cherrypy.lib import cptools, httputil, file_generator_limited
 
 
 def serve_file(path, content_type=None, disposition=None, name=None):
     
     # Set the Last-Modified response header, so that
     # modified-since validation code can work.
-    response.headers['Last-Modified'] = http.HTTPDate(st.st_mtime)
+    response.headers['Last-Modified'] = httputil.HTTPDate(st.st_mtime)
     cptools.validate_since()
     
     if content_type is None:
     # HTTP/1.0 didn't have Range/Accept-Ranges headers, or the 206 code
     if cherrypy.request.protocol >= (1, 1):
         response.headers["Accept-Ranges"] = "bytes"
-        r = http.get_ranges(cherrypy.request.headers.get('Range'), c_len)
+        r = httputil.get_ranges(cherrypy.request.headers.get('Range'), c_len)
         if r == []:
             response.headers['Content-Range'] = "bytes */%s" % c_len
             message = "Invalid Range (first-byte-pos greater than Content-Length)"

File cherrypy/test/benchmark.py

 
 import cherrypy
 from cherrypy import _cperror, _cpmodpy
-from cherrypy.lib import http
+from cherrypy.lib import httputil
 
 
 AB_PATH = ""
         cherrypy.response.status = "204 No Content"
         cherrypy.response.header_list = [("Content-Type", 'text/html'),
                                          ("Server", "Null CherryPy"),
-                                         ("Date", http.HTTPDate()),
+                                         ("Date", httputil.HTTPDate()),
                                          ("Content-Length", "0"),
                                          ]
         cherrypy.response.body = [""]

File cherrypy/test/helper.py

 import warnings
 
 import cherrypy
-from cherrypy.lib import http, profiler
+from cherrypy.lib import httputil, profiler
 from cherrypy.test import webtest
 
 
     def getPage(self, url, headers=None, method="GET", body=None, protocol=None):
         """Open the url. Return status, headers, body."""
         if self.script_name:
-            url = http.urljoin(self.script_name, url)
+            url = httputil.urljoin(self.script_name, url)
         return webtest.WebCase.getPage(self, url, headers, method, body, protocol)
     
     def assertErrorPage(self, status, message=None, pattern=''):

File cherrypy/test/test_caching.py

 from itertools import count
 
 import cherrypy
-from cherrypy.lib import http
+from cherrypy.lib import httputil
 
 gif_bytes = ('GIF89a\x01\x00\x01\x00\x82\x00\x01\x99"\x1e\x00\x00\x00\x00\x00'
              '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
         index.exposed = True
         
         def a_gif(self):
-            cherrypy.response.headers['Last-Modified'] = http.HTTPDate()
+            cherrypy.response.headers['Last-Modified'] = httputil.HTTPDate()
             return gif_bytes
         a_gif.exposed = True
 

File cherrypy/test/test_config.py

 [/]
 neg: -1234
 filename: os.path.join(sys.prefix, "hello.py")
-thing1: cherrypy.lib.http.response_codes[404]
+thing1: cherrypy.lib.httputil.response_codes[404]
 thing2: __import__('cherrypy.tutorial', globals(), locals(), ['']).thing2
 complex: 3+2j
 ones: "11"
         self.assertBody(repr(os.path.join(sys.prefix, "hello.py")))
         
         self.getPage("/repr?key=thing1")
-        self.assertBody(repr(cherrypy.lib.http.response_codes[404]))
+        self.assertBody(repr(cherrypy.lib.httputil.response_codes[404]))
         
         if not getattr(cherrypy.server, "using_apache", False):
             # The object ID's won't match up when using Apache, since the

File cherrypy/test/test_core.py

 
 import cherrypy
 from cherrypy import _cptools, tools
-from cherrypy.lib import http, static
+from cherrypy.lib import httputil, static
 from cherrypy._cpdispatch import test_callable_spec
 
 
     class Ranges(Test):
         
         def get_ranges(self, bytes):
-            return repr(http.get_ranges('bytes=%s' % bytes, 8))
+            return repr(httputil.get_ranges('bytes=%s' % bytes, 8))
         
         def slice_file(self):
             path = os.path.join(os.getcwd(), os.path.dirname(__file__))

File cherrypy/test/test_httplib.py

-"""Tests for cherrypy/lib/http.py."""
+"""Tests for cherrypy/lib/httputil.py."""
 
 from cherrypy.test import test
 test.prefer_parent_path()
 
 import unittest
-from cherrypy.lib import http
+from cherrypy.lib import httputil
 
 
 class UtilityTests(unittest.TestCase):
     
     def test_urljoin(self):
         # Test all slash+atom combinations for SCRIPT_NAME and PATH_INFO
-        self.assertEqual(http.urljoin("/sn/", "/pi/"), "/sn/pi/")
-        self.assertEqual(http.urljoin("/sn/", "/pi"), "/sn/pi")
-        self.assertEqual(http.urljoin("/sn/", "/"), "/sn/")
-        self.assertEqual(http.urljoin("/sn/", ""), "/sn/")
-        self.assertEqual(http.urljoin("/sn", "/pi/"), "/sn/pi/")
-        self.assertEqual(http.urljoin("/sn", "/pi"), "/sn/pi")
-        self.assertEqual(http.urljoin("/sn", "/"), "/sn/")
-        self.assertEqual(http.urljoin("/sn", ""), "/sn")
-        self.assertEqual(http.urljoin("/", "/pi/"), "/pi/")
-        self.assertEqual(http.urljoin("/", "/pi"), "/pi")
-        self.assertEqual(http.urljoin("/", "/"), "/")
-        self.assertEqual(http.urljoin("/", ""), "/")
-        self.assertEqual(http.urljoin("", "/pi/"), "/pi/")
-        self.assertEqual(http.urljoin("", "/pi"), "/pi")
-        self.assertEqual(http.urljoin("", "/"), "/")
-        self.assertEqual(http.urljoin("", ""), "/")
+        self.assertEqual(httputil.urljoin("/sn/", "/pi/"), "/sn/pi/")
+        self.assertEqual(httputil.urljoin("/sn/", "/pi"), "/sn/pi")
+        self.assertEqual(httputil.urljoin("/sn/", "/"), "/sn/")
+        self.assertEqual(httputil.urljoin("/sn/", ""), "/sn/")
+        self.assertEqual(httputil.urljoin("/sn", "/pi/"), "/sn/pi/")
+        self.assertEqual(httputil.urljoin("/sn", "/pi"), "/sn/pi")
+        self.assertEqual(httputil.urljoin("/sn", "/"), "/sn/")
+        self.assertEqual(httputil.urljoin("/sn", ""), "/sn")
+        self.assertEqual(httputil.urljoin("/", "/pi/"), "/pi/")
+        self.assertEqual(httputil.urljoin("/", "/pi"), "/pi")
+        self.assertEqual(httputil.urljoin("/", "/"), "/")
+        self.assertEqual(httputil.urljoin("/", ""), "/")
+        self.assertEqual(httputil.urljoin("", "/pi/"), "/pi/")
+        self.assertEqual(httputil.urljoin("", "/pi"), "/pi")
+        self.assertEqual(httputil.urljoin("", "/"), "/")
+        self.assertEqual(httputil.urljoin("", ""), "/")
 
 if __name__ == '__main__':
     unittest.main()

File cherrypy/test/test_request_obj.py

 
 import cherrypy
 from cherrypy import _cptools, tools
-from cherrypy.lib import http, static
+from cherrypy.lib import static
 from cherrypy._cpdispatch import test_callable_spec
 
 defined_http_methods = ("OPTIONS", "GET", "HEAD", "POST", "PUT", "DELETE",