[Zope-Checkins] CVS: Zope3/lib/python/Zope/Publisher/HTTP - IHTTPApplicationRequest.py:1.1.2.1 BrowserPayload.py:1.1.2.10.4.1 HTTPRequest.py:1.1.2.21.2.1 HTTPResponse.py:1.1.2.13.4.1

Jim Fulton jim@zope.com
Sat, 16 Mar 2002 09:44:32 -0500


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

Modified Files:
      Tag: Zope3-publisher-refactor-branch
	BrowserPayload.py HTTPRequest.py HTTPResponse.py 
Added Files:
      Tag: Zope-3x-branch
	IHTTPApplicationRequest.py 
Log Message:
Checking in partial publisher refactoring on the
Zope3-publisher-refactor-branch branch to facilitate collaboration
with Stephan.



=== Added File Zope3/lib/python/Zope/Publisher/HTTP/IHTTPApplicationRequest.py ===
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
# 
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
# 
##############################################################################
"""

Revision information:
$Id: IHTTPApplicationRequest.py,v 1.1.2.1 2002/03/16 14:44:00 jim Exp $
"""

from Zope.Publisher.IApplicationRequest import IApplicationRequest

class IHTTPApplicationRequest(IApplicationRequest):
    """HTTP request data.
    
    This object provides access to request data.  This includes, the
    input headers, form data, server data, and cookies.

    Request objects are created by the object publisher and will be
    passed to published objects through the argument name, REQUEST.

    The request object is a mapping object that represents a
    collection of variable to value mappings.  In addition, variables
    are divided into four categories:

      - Environment variables

        These variables include input headers, server data, and other
        request-related data.  The variable names are as <a
        href="http://hoohoo.ncsa.uiuc.edu/cgi/env.html">specified</a>
        in the <a
        href="http://hoohoo.ncsa.uiuc.edu/cgi/interface.html">CGI
        specification</a>

      - Cookies

        These are the cookie data, if present.

      - Other

        Data that may be set by an application object.

    The request object may be used as a mapping object, in which case
    values will be looked up in the order: environment variables,
    other variables, form data, and then cookies.    
    """


=== Zope3/lib/python/Zope/Publisher/HTTP/BrowserPayload.py 1.1.2.10 => 1.1.2.10.4.1 ===
     def processInputs(
         self, request, fs=None,
+
         # "static" variables that we want to be local for speed
         SEQUENCE=1,
         DEFAULT=2,
@@ -375,7 +376,7 @@
                      
         other.update(form)
         if meth:
-            request.setRequestDefault(meth)
+            request.setPathSuffix((meth,))
 
 
     def getPublication(self, request):
@@ -384,7 +385,7 @@
 
     def debugInfo(self, request):
         result = "<p>URL: %s</p>" % request.URL
-        result = result + "<p>SERVER_URL: %s</p>" % request.SERVER_URL
+        result = result + "<p>SERVER_URL: %s</p>" % request.getServerURL()
         result = result + "<h3>form</h3><table>"
         row='<tr valign="top" align="left"><th>%s</th><td>%s</td></tr>'
         for k,v in request.form.items():


=== Zope3/lib/python/Zope/Publisher/HTTP/HTTPRequest.py 1.1.2.21 => 1.1.2.21.2.1 ===
 
 
-    _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 is a protocol-specific handler for the body of
-    # the request.
-    payload = None
-
-    SERVER_URL = ''       # The SERVER_URL header
+    __auth = None          # The value of the HTTP_AUTHORIZATION header.
+    __cookies = None
 
     retry_count = 0
     retry_max_count = 3
 
-    def supports_retry(self):
-        if self.retry_count < self.retry_max_count:
-            if STAGGER_RETRIES:
-                time.sleep(whrandom.uniform(0, 2**(self.retry_count)))
-            return 1
+    def __init__(self, body_instream, outstream, environ):
 
-    def retry(self):
-        self.retry_count=self.retry_count+1
-        self.body_instream.seek(0)
-        r=self.__class__(
-            payload=self._orig_payload,
-            response=self.response.retry(),
-            body_instream=self.body_instream,
-            environ=self._orig_env
-            )
-        r.retry_count=self.retry_count
-        return r
+        super(HTTPRequest, self).__init__(body_instream, outstream, environ)
 
-
-    def setServerURL(self, protocol=None, hostname=None, port=None):
-        """ Set the parts of generated URLs. """
-        server_url = self.SERVER_URL
-        if protocol is None and hostname is None and port is None:
-            return server_url
-        oldprotocol, oldhost = splittype(server_url)
-        oldhostname, oldport = splitport(oldhost[2:])
-        if protocol is None: protocol = oldprotocol
-        if hostname is None: hostname = oldhostname
-        if port is None: port = oldport
-        
-        if (not port or DEFAULT_PORTS.get(protocol, 80) == port):
-            host = hostname
-        else:
-            host = hostname + ':' + port
-        server_url = self.SERVER_URL = '%s://%s' % (protocol, host)
-        self._resetURLs()
-        return server_url
-
-    def setVirtualRoot(self, path, hard=0):
-        """ Treat the current publishing object as a VirtualRoot """
-        if isinstance(path, StringType):
-            path = filter(None, path.split('/'))
-        self._script[:] = map(quote, path)
-        del self.quoted_steps[:]
-        traversed = self.traversed
-        if hard:
-            del traversed[:-1]
-        self.other['VirtualRootPhysicalPath'] = traversed[-1].getPhysicalPath()
-        self._resetURLs()
-
-    def physicalPathToVirtualPath(self, path):
-        """ Remove the path to the VirtualRoot from a physical path """
-        if isinstance(path, StringType):
-            path = path.split('/')
-        rpp = self.other.get('VirtualRootPhysicalPath', ('',))
-        i = 0
-        for name in rpp[:len(path)]:
-            if path[i] == name:
-                i = i + 1
-            else:
-                break
-        return path[i:]
-
-    def physicalPathToURL(self, path, relative=0):
-        """ Convert a physical path into a URL in the current context """
-        path = self._script + map(quote, self.physicalPathToVirtualPath(path))
-        if relative:
-            path.insert(0, '')
-        else:
-            path.insert(0, self.SERVER_URL)
-        return '/'.join(path)
-
-    def physicalPathFromURL(self, URL):
-        """ Convert a URL into a physical path in the current context.
-            If the URL makes no sense in light of the current virtual
-            hosting context, a ValueError is raised."""
-        bad_server_url = 0
-        path = filter(None, URL.split('/'))
-
-        if URL.find('://') >= 0:
-            path = path[2:]
-
-        # Check the path against BASEPATH1
-        vhbase = self._script
-        vhbl = len(vhbase)
-        bad_basepath = 0
-        if path[:vhbl] == vhbase:
-            path = path[vhbl:]
-        else:
-            raise ValueError, (
-                'Url does not match virtual hosting context'
-                )
-        vrpp = self.other.get('VirtualRootPhysicalPath', ('',))
-        return list(vrpp) + map(unquote, path)
-
-    def _resetURLs(self):
-        self.URL = '/'.join(
-            [self.SERVER_URL] + self._script + self.quoted_steps)
-        for x in self._computed_urls:
-            del self.other[x]
-        self._computed_urls = ()
-
-    def _deduceServerURL(self):
-        environ = self.environ
-        have_env = environ.has_key
-        if have_env('HTTPS') and (
-            environ['HTTPS'] == "on" or environ['HTTPS'] == "ON"):
-            protocol = 'https'
-        elif (have_env('SERVER_PORT_SECURE') and 
-              environ['SERVER_PORT_SECURE'] == "1"):
-            protocol = 'https'
-        else: protocol = 'http'
-
-        if have_env('HTTP_HOST'):
-            host = environ['HTTP_HOST'].strip()
-            hostname, port = splitport(host)
-
-            # NOTE: some (DAV) clients manage to forget the port. This
-            # can be fixed with the commented code below - the problem
-            # is that it causes problems for virtual hosting. I've left
-            # the commented code here in case we care enough to come
-            # back and do anything with it later.
-            #
-            # if port is None and environ.has_key('SERVER_PORT'):
-            #     s_port=environ['SERVER_PORT']
-            #     if s_port not in ('80', '443'):
-            #         port=s_port
-
-        else:
-            hostname = environ.get('SERVER_NAME', '').strip()
-            port = environ.get('SERVER_PORT', '')
-        self.setServerURL(protocol=protocol, hostname=hostname, port=port)
-        return self.SERVER_URL
-
-
-    def __init__(self, payload, response, body_instream, environ):
-        BaseRequest.__init__(self, response, body_instream)
-        self.payload = payload
-        self._orig_payload = payload
-        self._orig_env = environ
+        self.__orig_env = environ
         environ = sane_environment(environ)
 
         if environ.has_key('HTTP_AUTHORIZATION'):
             self._auth = environ['HTTP_AUTHORIZATION']
             del environ['HTTP_AUTHORIZATION']
 
-        self.environ=environ
-        get_env=environ.get
-        other = self.other
-        self.form={}
+        self._environ=environ
 
-        ################################################################
-        # Get base info first. This isn't likely to cause
-        # errors and might be useful to error handlers.
-        b = script = get_env('SCRIPT_NAME','').strip()
-
-        # _script and the other _names are meant for URL construction
-        self._script = map(quote, filter(None, script.split('/')))
-        
-        while b and b[-1]=='/': b=b[:-1]
-        p = b.rfind('/')
-        if p >= 0: b=b[:p+1]
-        else: b=''
-        while b and b[0]=='/': b=b[1:]
-
-        server_url = get_env('SERVER_URL',None)
-        if server_url is not None:
-            self.SERVER_URL = server_url = server_url.strip()
-        else:
-            server_url = self._deduceServerURL()
-
-        if server_url.endswith('/'):
-            server_url = server_url[:-1]
-
-        if b: self.base="%s/%s" % (server_url,b)
-        else: self.base=server_url
-        while script.startswith('/'): script=script[1:]
-        if script: script="%s/%s" % (server_url,script)
-        else:      script=server_url
-        self.URL = self.script = script
+        self.__getPath()
 
         ################################################################
         # Cookie values should *not* be appended to existing form
         # vars with the same name - they are more like default values
         # for names not otherwise specified in the form.
         cookies={}
-        k=get_env('HTTP_COOKIE','')
+        k=environ.get('HTTP_COOKIE','')
         if k:
             parse_cookie(k, cookies)
             for k,item in cookies.items():
                 if not other.has_key(k):
                     other[k]=item
-        self.cookies=cookies
-    
-    def processInputs(self):
-        return self.payload.processInputs(self)
+        self.__cookies = cookies
+
+    def __getPath(self):
+        path = self.get('PATH_INFO', '').strip()
+        if path.startswith('/'):  path = path[1:] # XXX Why? Not sure
+        clean = []
+        for item in path.split('/'):
+            if not item or item == '.':
+                continue
+            elif item == '..':
+                del clean[-1]
+            else: clean.append(item)
+
+        clean.reverse()
+        self.setTraversalStack(clean)
 
-    def getPublication(self):
-        return self.payload.getPublication(self)
+    def getCookies(self):
+        return self.__cookies
 
-    def get_header(self, name, default=None):
+    def supports_retry(self):
+        if self.retry_count < self.retry_max_count:
+            if STAGGER_RETRIES:
+                time.sleep(whrandom.uniform(0, 2**(self.retry_count)))
+            return 1
+
+    def retry(self):
+        self.retry_count=self.retry_count+1
+        self.body_instream.seek(0)
+        r=self.__class__(
+            body_instream=self.body_instream,
+            response=self.getResponse().getOutputStream(),
+            environ=self.__orig_env
+            )
+        r.retry_count=self.retry_count
+        return r
+
+    def getHeader(self, name, default=None):
         """Return the named HTTP header, or an optional default
         argument or None if the header is not found. Note that
         both original and CGI-ified header names are recognized,
         e.g. 'Content-Type', 'CONTENT_TYPE' and 'HTTP_CONTENT_TYPE'
         should all return the Content-Type header, if available.
         """
-        environ = self.environ
+        environ = self._environ
         name = name.replace('-', '_').upper()
         val = environ.get(name, None)
         if val is not None:
@@ -315,127 +169,87 @@
             name='HTTP_%s' % name
         return environ.get(name, default)
 
+    def __str__(self):
+        return self.payload.debugInfo(self)
 
-    def computeURLn(self, key, n, pathonly):
-        path = self._script + self.quoted_steps
-        n = len(path) - n
-        if n < 0:
-            raise KeyError, key
-        if pathonly:
-            path = [''] + path[:n]
-        else:
-            path = [self.SERVER_URL] + path[:n]
-        self.other[key] = res = '/'.join(path)
-        self._computed_urls = self._computed_urls + (key,)
-        return res
-
-
-    def computeBASEn(self, key, n, pathonly):
-        path = self.quoted_steps
-        if n:
-            n = n - 1
-            if len(path) < n:
-                raise KeyError, key
-            v = self._script + path[:n]
-        else:
-            v = self._script[:-1]
-        if pathonly:
-            v.insert(0, '')
-        else:
-            v.insert(0, self.SERVER_URL)
-        self.other[key] = res = '/'.join(v)
-        self._computed_urls = self._computed_urls + (key,)
-        return res
-
-    _key_handlers = BaseRequest._key_handlers.copy()
-
-    def getServerURL(self, defaut=None):
-        return self.SERVER_URL
-    _key_handlers['SERVER_URL'] = getServerURL
-
-
-    def get(self, key, default=None):
+    def traverse(self, publication, object):
         """
-        Gets a variable value
-
-        Return a value for the required variable name.
-        The value will be looked up from one of the request data
-        categories. The search order is environment variables,
-        other variables, form data, and then cookies. 
-        
+        Traverses to an object and returns it.
+        Private.
         """
-        handler = self._key_handlers.get(key, None)
-        if handler is not None:
-            v = handler(self, _marker)
-            if v is not _marker:
-                return v
-        v = self.other.get(key, _marker)
-        if v is not _marker:
-            return v
-
-        if key.startswith('U'):
-            match = URLmatch(key)
-            if match is not None:
-                pathonly, n = match.groups()
-                return self.computeURLn(key, int(n), pathonly)
-
-        if key.startswith('B'):
-            match = BASEmatch(key)
-            if match is not None:
-                pathonly, n = match.groups()
-                return self.computeBASEn(key, int(n), pathonly)
-
-        if isCGI_NAME(key) or key.startswith('HTTP_'):
-            environ=self.environ
-            if environ.has_key(key) and (not hide_key(key)):
-                return environ[key]
-            return ''
-
-        if v is _marker:
-            v = self.common.get(key, _marker)
-        return default
-    get.__permission__ = 'Zope.Public'
-
-    def __getitem__(self, key):
-        res = self.get(key, _marker)
-        if res is _marker:
-            raise KeyError, key
-        else:
-            return res
-
-    def has_key(self, key):
-        return self.get(key, _marker) is not _marker
-
-    def keys(self):
-        keys = {'URL':1, 'SERVER_URL':1}
-        keys.update(self.common)
-
-        for key in self.environ.keys():
-            if (isCGI_NAME(key) or key.startswith('HTTP_')) and \
-               (not hide_key(key)):
-                    keys[key] = 1
+        
+        traversal_altered = 0 # flag for adding traversal steps
+        add_steps = None
 
-        n=0
-        while 1:
-            n=n+1
-            key = "URL%s" % n
-            if not self.has_key(key): break
+        self.traversed = traversed = []
+        traversed.append(object)
+        steps = self.steps
+        self.quoted_steps = quoted_steps = map(pc_quote, steps)
+        to_traverse.reverse()
+        self.to_traverse = to_traverse
 
-        n=0
+        prev_object = None
         while 1:
-            n=n+1
-            key = "BASE%s" % n
-            if not self.has_key(key): break
+            if object is not prev_object:
+                # Invoke hooks (but not more than once).
+                publication.callTraversalHooks(self, object)
+                # A hook may have called changeTraversalStack().
+                to_traverse = self.to_traverse
+            prev_object = object
+
+            if to_traverse:
+                # Traverse to the next step.
+                entry_name = to_traverse.pop()
+                if entry_name:
+                    qstep = pc_quote(entry_name)
+                    quoted_steps.append(qstep)
+                        
+                    if traversal_altered:
+                        # The effective URL includes the altered traversal.
+                        #import pdb; pdb.set_trace()
+                        e_url = self.effective_url or self.URL
+                        self.effective_url = '%s/%s' % (e_url, qstep)
+                    else:
+                        # Build up the URL to the object, but not
+                        # to the default traversal.                        
+                        self.URL = '%s/%s' % (self.URL, qstep)
+                    subobject = publication.traverseName(
+                        self, object, entry_name)
+                    object = subobject
+                    traversed.append(object)
+                    steps.append(entry_name)
 
-        keys.update(self.other)
-
-        keys=keys.keys()
-        keys.sort()
-
-        return keys
+            else:
+                add_steps = self._request_default
+                
+                if add_steps:
+                    self._request_default = None
+                
+                if add_steps is None:
+                    object, add_steps = publication.getDefaultTraversal(
+                        self, object)
+                    
+                if add_steps:
+                    traversal_altered = 1
+                    to_traverse.extend(add_steps)
+                else:
+                    # Finished traversal.
+                    break
+        
+        if traversal_altered:
+            eurl = self.effective_url
+            l = eurl.rfind('/')
+            if l >= 0: eurl = eurl[:l+1] # XXX Quick bug fix, need better impl
+            self.response.setBase(eurl)
+
+        self.traversed = tuple(traversed)  # No more changes allowed
+        parents = traversed[:]
+        parents.pop()
+        parents.reverse()
+        self.other['PARENTS'] = tuple(parents)
+        self.other['PUBLISHED'] = object
 
-    def __str__(self):
-        return self.payload.debugInfo(self)
+        return object
 
     def text(self):
         result = "URL: %s\n" % self.URL
@@ -486,6 +300,9 @@
     #  to implement IBrowserPublisher
     _viewtype = IBrowserPublisher
 
+    # XXX this doesn't belong here
+    def getEffectiveURL(self):
+        return self.effective_url or self.URL
 
 base64 = None
 


=== Zope3/lib/python/Zope/Publisher/HTTP/HTTPResponse.py 1.1.2.13 => 1.1.2.13.4.1 ===
 
 
-    def __init__(self, payload, outstream, header_output=None):
-        self.payload = payload
-        self._orig_payload = payload
+    def __init__(self, outstream, header_output=None):
         self.header_output = header_output
-        BaseResponse.__init__(self, outstream)
+
+        super(HTTPResponse, self).__init__(outstream)
+        self.headers = {}
+        self.cookies = {}
 
     def retry(self):
         """
         Returns a response object to be used in a retry attempt
         """
-        return self.__class__(self._orig_payload, self.outstream,
+        return self.__class__(self.outstream,
                               self.header_output)
 
     def setStatus(self, status, reason=None):