[Zope-Checkins] CVS: Zope3/lib/python/Zope/Publisher/HTTP - BrowserPayload.py:1.1.2.1 IPayload.py:1.1.2.1 cgi_names.py:1.1.2.1 HTTPRequest.py:1.1.2.4 HTTPResponse.py:1.1.2.3

Shane Hathaway shane@digicool.com
Thu, 15 Nov 2001 18:10:02 -0500


Update of /cvs-repository/Zope3/lib/python/Zope/Publisher/HTTP
In directory cvs.zope.org:/tmp/cvs-serv16729/HTTP

Modified Files:
      Tag: Zope-3x-branch
	HTTPRequest.py HTTPResponse.py 
Added Files:
      Tag: Zope-3x-branch
	BrowserPayload.py IPayload.py cgi_names.py 
Log Message:
- Divided the HTTP protocol from its payload

- Expanded minitest

- Renamed some interface methods


=== Added File Zope3/lib/python/Zope/Publisher/HTTP/BrowserPayload.py === (516/616 lines abridged)

import re
from types import ClassType, ListType, StringType
from cgi import FieldStorage, escape
from cgi_names import isCGI_NAME

from IPayload import IRequestPayload, IResponsePayload
from Zope.Publisher.Converters import get_converter
from Zope.Publisher.Exceptions import Redirect

#from Zope.Publisher.Browser import IBrowserPublish


latin1_alias_match = re.compile(
    r'text/html(\s*;\s*charset=((latin)|(latin[-_]?1)|'
    r'(cp1252)|(cp819)|(csISOLatin1)|(IBM819)|(iso-ir-100)|'
    r'(iso[-_]8859[-_]1(:1987)?)))?$',re.I).match

def is_text_html(content_type):
    return (content_type == 'text/html' or
            latin1_alias_match(content_type) is not None)


class BrowserRequestPayload:
    """
    Works with the body of a browser request.
    """

    __implements__ = IRequestPayload

    def getPreferredPublishingType(self):
        return IBrowserPublish


    def processInputs(
        self, request, fs=None,
        # "static" variables that we want to be local for speed
        SEQUENCE=1,
        DEFAULT=2,
        RECORD=4,
        RECORDS=8,
        REC=12, # RECORD|RECORDS
        EMPTY=16,
        CONVERTED=32,
        hasattr=hasattr,
        getattr=getattr,
        setattr=setattr,
        search_type=re.compile('(:[a-zA-Z][a-zA-Z0-9_]+|\\.[xy])$').search,
        ):
        """

[-=- -=- -=- 516 lines omitted -=- -=- -=-]

        L1.sort()
        return ", ".join(
            map(lambda item: "%s: %s" % (item[0], repr(item[1])), L1)) 



# Flags
SEQUENCE=1
DEFAULT=2
RECORD=4
RECORDS=8
REC=RECORD|RECORDS
EMPTY=16
CONVERTED=32


def format_exception(etype,value,tb,limit=None):
    import traceback
    result=['Traceback (innermost last):']
    if limit is None:
        if hasattr(sys, 'tracebacklimit'):
            limit = sys.tracebacklimit
    n = 0
    while tb is not None and (limit is None or n < limit):
        f = tb.tb_frame
        lineno = tb.tb_lineno
        co = f.f_code
        filename = co.co_filename
        name = co.co_name
        locals = f.f_locals
        globals = f.f_globals
        modname = globals.get('__name__', filename)
        result.append('  Module %s, line %d, in %s'
                      % (modname,lineno,name))
        try: result.append('    (Object: %s)' %
                           locals[co.co_varnames[0]].__name__)
        except: pass
        try: result.append('    (Info: %s)' %
                           str(locals['__traceback_info__']))
        except: pass
        tb = tb.tb_next
        n = n+1
    result.append(' '.join(traceback.format_exception_only(etype, value)))
    return result


def traceback_string(t,v,tb):
    tb=format_exception(t,v,tb,200)
    return '\n'.join(tb)



=== Added File Zope3/lib/python/Zope/Publisher/HTTP/IPayload.py ===


from Interface import Interface


class IRequestPayload (Interface):

    def processInputs(request):
        """
        Processes request inputs.
        """

    def getPreferredPublishingType():
        """
        Returns an interface object.
        """


class IResponsePayload (Interface):

    def setBody(response, body):
        """
        Sets the body of the response.
        """

    def handleException(response, exc_info):
        """
        Calls setBody() with an error response.
        """



=== Added File Zope3/lib/python/Zope/Publisher/HTTP/cgi_names.py ===

#cgi hotfix:
import cgi
if not hasattr(cgi, 'valid_boundary'):
    try: import cgi_hotfix
    except ImportError: pass

isCGI_NAME = {
    # These fields are placed in request.environ instead of request.form.
    'SERVER_SOFTWARE' : 1, 
    'SERVER_NAME' : 1, 
    'GATEWAY_INTERFACE' : 1, 
    'SERVER_PROTOCOL' : 1, 
    'SERVER_PORT' : 1, 
    'REQUEST_METHOD' : 1, 
    'PATH_INFO' : 1, 
    'PATH_TRANSLATED' : 1, 
    'SCRIPT_NAME' : 1, 
    'QUERY_STRING' : 1, 
    'REMOTE_HOST' : 1, 
    'REMOTE_ADDR' : 1, 
    'AUTH_TYPE' : 1, 
    'REMOTE_USER' : 1, 
    'REMOTE_IDENT' : 1, 
    'CONTENT_TYPE' : 1, 
    'CONTENT_LENGTH' : 1,
    'SERVER_URL': 1,
    }.has_key

hide_key={
    'HTTP_AUTHORIZATION':1,
    'HTTP_CGI_AUTHORIZATION': 1,
    }.has_key



=== Zope3/lib/python/Zope/Publisher/HTTP/HTTPRequest.py 1.1.2.3 => 1.1.2.4 === (471/571 lines abridged)
 import re, sys, os, string, time, whrandom, cgi
 from Zope.Publisher.BaseRequest import BaseRequest
-from Zope.Publisher.Converters import get_converter
 from HTTPResponse import HTTPResponse
-from cgi import FieldStorage, escape
+from BrowserPayload import BrowserRequestPayload
+from cgi_names import isCGI_NAME, hide_key
 from urllib import quote, unquote, splittype, splitport
 
-#cgi hotfix:
-if not hasattr(cgi, 'valid_boundary'):
-    try: import cgi_hotfix
-    except ImportError: pass
-
-isCGI_NAME = {
-    # These fields are placed in request.environ instead of request.form.
-    'SERVER_SOFTWARE' : 1, 
-    'SERVER_NAME' : 1, 
-    'GATEWAY_INTERFACE' : 1, 
-    'SERVER_PROTOCOL' : 1, 
-    'SERVER_PORT' : 1, 
-    'REQUEST_METHOD' : 1, 
-    'PATH_INFO' : 1, 
-    'PATH_TRANSLATED' : 1, 
-    'SCRIPT_NAME' : 1, 
-    'QUERY_STRING' : 1, 
-    'REMOTE_HOST' : 1, 
-    'REMOTE_ADDR' : 1, 
-    'AUTH_TYPE' : 1, 
-    'REMOTE_USER' : 1, 
-    'REMOTE_IDENT' : 1, 
-    'CONTENT_TYPE' : 1, 
-    'CONTENT_LENGTH' : 1,
-    'SERVER_URL': 1,
-    }.has_key
-
-hide_key={
-    'HTTP_AUTHORIZATION':1,
-    'HTTP_CGI_AUTHORIZATION': 1,
-    }.has_key
 
 DEFAULT_PORTS = {'http': '80', 'https': '443'}
 
@@ -99,6 +68,8 @@
     _auth = None          # The value of the HTTP_AUTHORIZATION header.
     _computed_urls = ()   # Names of computed URLx variables
     _script = ()          # SERVER_URL + _script + quoted_steps == full URL
+    script = ''           # script + quoted_steps = full URL
+    payload = BrowserRequestPayload()
     

[-=- -=- -=- 471 lines omitted -=- -=- -=-]

+def parse_cookie(
+    text,
+    result=None,
+    qparmre=re.compile(
+    '([\x00- ]*([^\x00- ;,="]+)="([^"]*)"([\x00- ]*[;,])?[\x00- ]*)'),
+    parmre=re.compile(
+    '([\x00- ]*([^\x00- ;,="]+)=([^\x00- ;,"]*)([\x00- ]*[;,])?[\x00- ]*)'),
+    acquire=parse_cookie_lock.acquire,
+    release=parse_cookie_lock.release,
+    ):
 
     if result is None: result={}
     already_have=result.has_key
@@ -973,36 +515,4 @@
 
     return apply(parse_cookie,(text[l:],result))
 
-# add class
-class record:
-
-    # TODO: declare public.
-
-    def __getattr__(self, key, default=None):
-        if key in ('get', 'keys', 'items', 'values', 'copy', 'has_key'):
-            return getattr(self.__dict__, key)
-        raise AttributeError, key
-
-    def __getitem__(self, key):
-        return self.__dict__[key]
-            
-    def __str__(self):
-        L1 = self.__dict__.items()
-        L1.sort()
-        return ", ".join(map(lambda item: "%s: %s" % item, L1)) 
-
-    def __repr__(self):
-        L1 = self.__dict__.items()
-        L1.sort()
-        return ", ".join(
-            map(lambda item: "%s: %s" % (item[0], repr(item[1])), L1)) 
-
-# Flags
-SEQUENCE=1
-DEFAULT=2
-RECORD=4
-RECORDS=8
-REC=RECORD|RECORDS
-EMPTY=16
-CONVERTED=32
 


=== Zope3/lib/python/Zope/Publisher/HTTP/HTTPResponse.py 1.1.2.2 => 1.1.2.3 ===
 from Zope.Publisher.BaseResponse import BaseResponse
 from Zope.Publisher.Exceptions import Redirect
+from BrowserPayload import BrowserResponsePayload
 
 nl2sp = string.maketrans('\n',' ')
 
@@ -78,20 +79,9 @@
 status_codes['redirect']=300
 
 
-start_of_header_search=re.compile('(<head[^>]*>)', re.IGNORECASE).search
-
 accumulate_header={'set-cookie': 1}.has_key
 
 
-latin1_alias_match=re.compile(
-    r'text/html(\s*;\s*charset=((latin)|(latin[-_]?1)|'
-    r'(cp1252)|(cp819)|(csISOLatin1)|(IBM819)|(iso-ir-100)|'
-    r'(iso[-_]8859[-_]1(:1987)?)))?$',re.I).match
-
-def is_text_html(content_type):
-    return (content_type == 'text/html' or
-            latin1_alias_match(content_type) is not None)
-                 
 
 class HTTPResponse (BaseResponse):
     """
@@ -119,6 +109,7 @@
     base = None
     realm = 'Zope'
     _error_format = 'text/html'
+    payload = BrowserResponsePayload()
 
     def __init__(self, outstream, body='', headers=None,
                  status=None, cookies=None):
@@ -185,36 +176,7 @@
             "%s%s: %s\n" % (self.accumulated_headers, name, value))
 
     def setBody(self, body):
-        '''
-        Sets the body of the response
-        
-        Sets the return body equal to the (string) argument "body". Also
-        updates the "content-length" return header.
-
-        If the body is a 2-element tuple, then it will be treated
-        as (title,body)
-        
-        If is_error is true then the HTML will be formatted as a Zope error
-        message instead of a generic HTML page.
-        '''
-        body = str(body)
-
-        if not self.headers.has_key('content-type'):
-            c = (self.isHTML(body) and 'text/html' or 'text/plain')
-            self.setHeader('content-type', c)
-
-        content_type = self.headers['content-type']
-        if is_text_html(content_type):
-            # Some browsers interpret certain characters in Latin 1 as html
-            # special characters. These cannot be removed by html_quote,
-            # because this is not the case for all encodings.
-            body = body.replace('\213', '&lt;')
-            body = body.replace('\233', '&gt;')
-
-        self.body = body
-        self.insertBase()
-        self.updateContentLength()
-        return self
+        return self.payload.setBody(self, body)
 
     def updateContentLength(self):
         blen = str(len(self.body))
@@ -228,26 +190,6 @@
             base=base+'/'
         self.base=base
 
-    def insertBase(self,
-                   base_re_search=re.compile('(<base.*?>)',re.I).search
-                   ):
-
-        # Only insert a base tag if content appears to be html.
-        content_type = self.headers.get('content-type', '').split(';')[0]
-        if content_type and content_type != 'text/html':
-            return
-
-        if self.base:
-            body=self.body
-            if body:
-                match=start_of_header_search(body)
-                if match is not None:
-                    index=match.start(0) + len(match.group(0))
-                    ibase=base_re_search(body)
-                    if ibase is None:
-                        self.body=('%s\n<base href="%s" />\n%s' %
-                                   (body[:index], self.base, body[index:]))
-
     def appendToCookie(self, name, value):
         '''
         Creates an HTTP header that sets a cookie on cookie-enabled
@@ -314,11 +256,6 @@
         else: h=value
         self.setHeader(name,h)
 
-    def isHTML(self, str):
-        s = str.strip()
-        return (s[:6].lower() == '<html>' or
-                s[:14].lower() == '<!doctype html')
-
     def redirect(self, location, status=302):
         """Causes a redirection without raising an error"""
         self.setStatus(status)
@@ -328,22 +265,7 @@
     def handleException(self, exc_info):
         """
         """
-        t, v = exc_info[:2]
-        if isinstance(t, ClassType):
-            if issubclass(t, Redirect):
-                self.redirect(v.getLocation())
-                return
-        self.setStatus(t)
-        body = ("<html><head><title>Site error</title></head>\n"
-                "<body><p>A site error occurred.</p>\n"
-                "<pre>\n"
-                "%s\n"
-                "</pre>\n"
-                "</body></html>\n" %
-                escape(traceback_string(t, v, exc_info[2]))
-                )
-        self.setBody(body)
-
+        self.payload.handleException(self, exc_info)
 
     _wrote=None
 
@@ -370,9 +292,7 @@
         
         return cookie_list
 
-    def __str__(self,
-                html_search=re.compile('<html>',re.I).search,
-                ):
+    def __str__(self):
         """
         Outputs the headers of the response.
         """
@@ -431,39 +351,4 @@
 
         self.outstream.write(data)
 
-
-
-def format_exception(etype,value,tb,limit=None):
-    import traceback
-    result=['Traceback (innermost last):']
-    if limit is None:
-        if hasattr(sys, 'tracebacklimit'):
-            limit = sys.tracebacklimit
-    n = 0
-    while tb is not None and (limit is None or n < limit):
-        f = tb.tb_frame
-        lineno = tb.tb_lineno
-        co = f.f_code
-        filename = co.co_filename
-        name = co.co_name
-        locals = f.f_locals
-        globals = f.f_globals
-        modname = globals.get('__name__', filename)
-        result.append('  Module %s, line %d, in %s'
-                      % (modname,lineno,name))
-        try: result.append('    (Object: %s)' %
-                           locals[co.co_varnames[0]].__name__)
-        except: pass
-        try: result.append('    (Info: %s)' %
-                           str(locals['__traceback_info__']))
-        except: pass
-        tb = tb.tb_next
-        n = n+1
-    result.append(' '.join(traceback.format_exception_only(etype, value)))
-    return result
-
-
-def traceback_string(t,v,tb):
-    tb=format_exception(t,v,tb,200)
-    return '\n'.join(tb)