[Zope-Checkins] CVS: Zope/lib/python/ZServer - AccessLogger.py:1.2 DebugLogger.py:1.10 FCGIServer.py:1.22 FTPRequest.py:1.16 FTPResponse.py:1.13 FTPServer.py:1.26 HTTPResponse.py:1.43 HTTPServer.py:1.45 ICPServer.py:1.5 INSTALL.txt:1.8 PCGIServer.py:1.26 Producers.py:1.9 README.txt:1.12 WebDAVSrcHandler.py:1.10 ZService.py:1.16 __init__.py:1.29 component.xml:1.2 datatypes.py:1.2

Fred L. Drake, Jr. fred@zope.com
Tue, 18 Mar 2003 16:15:47 -0500


Update of /cvs-repository/Zope/lib/python/ZServer
In directory cvs.zope.org:/tmp/cvs-serv23589/ZServer

Added Files:
	AccessLogger.py DebugLogger.py FCGIServer.py FTPRequest.py 
	FTPResponse.py FTPServer.py HTTPResponse.py HTTPServer.py 
	ICPServer.py INSTALL.txt PCGIServer.py Producers.py README.txt 
	WebDAVSrcHandler.py ZService.py __init__.py component.xml 
	datatypes.py 
Log Message:
Move ZServer into new location, including configuration support from the
new-install-branch.


=== Zope/lib/python/ZServer/AccessLogger.py 1.1 => 1.2 ===
--- /dev/null	Tue Mar 18 16:15:46 2003
+++ Zope/lib/python/ZServer/AccessLogger.py	Tue Mar 18 16:15:14 2003
@@ -0,0 +1,31 @@
+##############################################################################
+#
+# 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
+#
+##############################################################################
+"""
+A logging module which handles ZServer access log messages.
+
+This depends on Vinay Sajip's PEP 282 logging module.
+"""
+import logging
+from zLOG.BaseLogger import BaseLogger
+
+class AccessLogger(BaseLogger):
+    logger = logging.getLogger('access')
+    def log(self, message):
+        if not self.logger.handlers: # dont log if we have no handlers
+            return
+        if message.endswith('\n'):
+            message = message[:-1]
+        self.logger.warn(message)
+
+access_logger = AccessLogger()


=== Zope/lib/python/ZServer/DebugLogger.py 1.9 => 1.10 ===
--- /dev/null	Tue Mar 18 16:15:46 2003
+++ Zope/lib/python/ZServer/DebugLogger.py	Tue Mar 18 16:15:14 2003
@@ -0,0 +1,57 @@
+##############################################################################
+#
+# Copyright (c) 2001 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
+#
+##############################################################################
+
+"""
+Logs debugging information about how ZServer is handling requests
+and responses. This log can be used to help locate troublesome requests.
+
+The format of a log message is:
+
+    <code> <request id> <time> <data>
+
+where:
+
+    <code> is B for begin, I for received input, A for received output,
+    E for sent output.
+
+    <request id> is a unique request id.
+
+    <time> is the local time in ISO 6801 format.
+
+    <data> is the HTTP method and the PATH INFO for B, the size of the
+    input for I, the HTTP status code and the size of the output for
+    A, or nothing for E.
+"""
+
+import time
+import logging
+
+from zLOG.BaseLogger import BaseLogger
+
+
+class DebugLogger(BaseLogger):
+
+    logger = logging.getLogger('trace')
+
+    def log(self, code, request_id, data=''):
+        if not self.logger.handlers:
+            return
+        # Omitting the second parameter requires Python 2.2 or newer.
+        t = time.strftime('%Y-%m-%dT%H:%M:%S')
+        message = '%s %s %s %s' % (code, request_id, t, data)
+        self.logger.warn(message)
+
+
+debug_logger = DebugLogger()
+log = debug_logger.log
+reopen = debug_logger.reopen


=== Zope/lib/python/ZServer/FCGIServer.py 1.21 => 1.22 === (669/769 lines abridged)
--- /dev/null	Tue Mar 18 16:15:46 2003
+++ Zope/lib/python/ZServer/FCGIServer.py	Tue Mar 18 16:15:14 2003
@@ -0,0 +1,766 @@
+##############################################################################
+#
+# Copyright (c) 2001 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
+#
+##############################################################################
+
+"""
+ZServer/Medusa FastCGI server, by Robin Dunn.
+
+Accepts connections from a FastCGI enabled webserver, receives request
+info using the FastCGi protocol, and then hands the request off to
+ZPublisher for processing.  The response is then handed back to the
+webserver to send down to the browser.
+
+See http://www.fastcgi.com/fcgi-devkit-2.1/doc/fcgi-spec.html for the
+protocol specificaition.
+"""
+
+__version__ = "1.0"
+
+#----------------------------------------------------------------------
+
+import asynchat, asyncore
+from medusa import logger
+from medusa.counter import counter
+from medusa.http_server import compute_timezone_for_log
+
+from ZServer import CONNECTION_LIMIT, requestCloseOnExec
+
+from PubCore import handle
+from PubCore.ZEvent import Wakeup
+from ZPublisher.HTTPResponse import HTTPResponse
+from ZPublisher.HTTPRequest import HTTPRequest
+from Producers import ShutdownProducer, LoggingProducer, file_part_producer, file_close_producer
+
+import DebugLogger
+
+from cStringIO import StringIO
+from tempfile import TemporaryFile
+import socket, string, os, sys, time

[-=- -=- -=- 669 lines omitted -=- -=- -=-]

+        if t is not None:
+            self.stdout.write((file_close_producer(t), 0))
+        self._tempfile=None
+
+        self.channel.sendStreamTerminator(FCGI_STDOUT)
+        self.channel.sendEndRecord()
+        self.stdout.close()
+        self.stderr.close()
+
+        if not self.channel.closed:
+            self.channel.push_with_producer(LoggingProducer(self.channel,
+                                                            self.stdout.length,
+                                                            'log_request'), 0)
+        if self._shutdownRequested():
+            self.channel.push(ShutdownProducer(), 0)
+            Wakeup(lambda: asyncore.close_all())
+        else:
+            self.channel.push(None,0)
+            Wakeup()
+        self.channel=None
+
+
+
+#----------------------------------------------------------------------
+
+class FCGIPipe:
+    """
+    This class acts like a file and is used to catch stdout/stderr
+    from ZPublisher and create FCGI records out of the data stream to
+    send back to the web server.
+    """
+    def __init__(self, channel, recType):
+        self.channel = channel
+        self.recType = recType
+        self.length  = 0
+
+    def write(self, data):
+        if type(data)==type(''):
+            datalen = len(data)
+        else:
+            p, datalen = data
+        if data:
+            self.channel.sendDataRecord(data, self.recType)
+            self.length = self.length + datalen
+
+    def close(self):
+        self.channel = None
+
+
+#----------------------------------------------------------------------


=== Zope/lib/python/ZServer/FTPRequest.py 1.15 => 1.16 ===
--- /dev/null	Tue Mar 18 16:15:46 2003
+++ Zope/lib/python/ZServer/FTPRequest.py	Tue Mar 18 16:15:14 2003
@@ -0,0 +1,124 @@
+##############################################################################
+#
+# Copyright (c) 2001 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
+#
+##############################################################################
+"""
+FTP Request class for FTP server.
+
+The FTP Request does the dirty work of turning an FTP request into something
+that ZPublisher can understand.
+"""
+
+from ZPublisher.HTTPRequest import HTTPRequest
+
+from cStringIO import StringIO
+import os
+from base64 import encodestring
+import re
+
+class FTPRequest(HTTPRequest):
+
+    def __init__(self, path, command, channel, response, stdin=None,
+                 environ=None,globbing=None,recursive=0):
+
+        # we need to store the globbing information to pass it
+        # to the ZPublisher and the manage_FTPlist function
+        # (ajung)
+        self.globbing = globbing
+        self.recursive= recursive
+
+        if stdin is None: stdin=StringIO()
+        if environ is None:
+            environ=self._get_env(path, command, channel, stdin)
+
+        self._orig_env=environ
+        HTTPRequest.__init__(self, stdin, environ, response, clean=1)
+
+        # support for cookies and cookie authentication
+        self.cookies=channel.cookies
+        if not self.cookies.has_key('__ac') and channel.userid != 'anonymous':
+            self.other['__ac_name']=channel.userid
+            self.other['__ac_password']=channel.password
+        for k,v in self.cookies.items():
+            if not self.other.has_key(k):
+                self.other[k]=v
+
+
+    def retry(self):
+        self.retry_count=self.retry_count+1
+        r=self.__class__(stdin=self.stdin,
+                         environ=self._orig_env,
+                         response=self.response.retry(),
+                         channel=self, # For my cookies
+                         )
+        return r
+
+    def _get_env(self, path, command, channel, stdin):
+        "Returns a CGI style environment"
+        env={}
+        env['SCRIPT_NAME']='/%s' % channel.module
+        env['REQUEST_METHOD']='GET' # XXX what should this be?
+        env['SERVER_SOFTWARE']=channel.server.SERVER_IDENT
+        if channel.userid != 'anonymous':
+            env['HTTP_AUTHORIZATION']='Basic %s' % re.sub('\012','',
+                    encodestring('%s:%s' % (channel.userid, channel.password)))
+        env['SERVER_NAME']=channel.server.hostname
+        env['SERVER_PORT']=str(channel.server.port)
+        env['REMOTE_ADDR']=channel.client_addr[0]
+        env['GATEWAY_INTERFACE']='CGI/1.1' # that's stretching it ;-)
+
+        # FTP commands
+        #
+        if type(command)==type(()):
+            args=command[1:]
+            command=command[0]
+        if command in ('LST','CWD','PASS'):
+            env['PATH_INFO']=self._join_paths(channel.path,
+                                              path, 'manage_FTPlist')
+        elif command in ('MDTM','SIZE'):
+            env['PATH_INFO']=self._join_paths(channel.path,
+                                              path, 'manage_FTPstat')
+        elif command=='RETR':
+            env['PATH_INFO']=self._join_paths(channel.path,
+                                              path, 'manage_FTPget')
+        elif command in ('RMD','DELE'):
+            env['PATH_INFO']=self._join_paths(channel.path,
+                                              path, 'manage_delObjects')
+            env['QUERY_STRING']='ids=%s' % args[0]
+        elif command=='MKD':
+            env['PATH_INFO']=self._join_paths(channel.path,
+                                              path, 'manage_addFolder')
+            env['QUERY_STRING']='id=%s' % args[0]
+
+        elif command=='RNTO':
+            env['PATH_INFO']=self._join_paths(channel.path,
+                                              path, 'manage_renameObject')
+            env['QUERY_STRING']='id=%s&new_id=%s' % (args[0],args[1])
+
+        elif command=='STOR':
+            env['PATH_INFO']=self._join_paths(channel.path, path)
+            env['REQUEST_METHOD']='PUT'
+            env['CONTENT_LENGTH']=len(stdin.getvalue())
+        else:
+            env['PATH_INFO']=self._join_paths(channel.path, path, command)
+
+        # Fake in globbing information
+        env['GLOBBING'] = self.globbing
+        env['FTP_RECURSIVE'] = self.recursive
+
+        return env
+
+    def _join_paths(self,*args):
+        path=apply(os.path.join,args)
+        path=os.path.normpath(path)
+        if os.sep != '/':
+            path=path.replace(os.sep,'/')
+        return path


=== Zope/lib/python/ZServer/FTPResponse.py 1.12 => 1.13 ===
--- /dev/null	Tue Mar 18 16:15:46 2003
+++ Zope/lib/python/ZServer/FTPResponse.py	Tue Mar 18 16:15:14 2003
@@ -0,0 +1,95 @@
+##############################################################################
+#
+# Copyright (c) 2001 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
+#
+##############################################################################
+"""
+Response class for the FTP Server.
+"""
+
+from ZServer.HTTPResponse import ZServerHTTPResponse
+from PubCore.ZEvent import Wakeup
+from cStringIO import StringIO
+import marshal
+
+
+class FTPResponse(ZServerHTTPResponse):
+    """
+    Response to an FTP command
+    """
+
+    def __str__(self):
+#        return ZServerHTTPResponse.__str__(self)
+        # ZServerHTTPResponse.__str__(self) return HTTP headers
+        # Why should be send them to the FTP client ??? (ajung)
+        return ''
+
+    def outputBody(self):
+        pass
+
+    def setCookie(self, name, value, **kw):
+        self.cookies[name]=value
+
+    def appendCookie(self, name, value):
+        self.cookies[name]=self.cookies[name] + value
+
+    def expireCookie(self, name, **kw):
+        if self.cookies.has_key(name):
+            del self.cookies[name]
+
+    def _cookie_list(self):
+        return []
+
+    def _marshalledBody(self):
+        return marshal.loads(self.body)
+
+    def setMessage(self, message):
+        self._message = message
+
+    def getMessage(self):
+        return getattr(self, '_message', '')
+
+class CallbackPipe:
+    """
+    Sends response object to a callback. Doesn't write anything.
+    The callback takes place in Medusa's thread, not the request thread.
+    """
+    def __init__(self, callback, args):
+        self._callback=callback
+        self._args=args
+        self._producers=[]
+
+    def close(self):
+        pass
+
+    def write(self, text, l=None):
+        if text:
+            self._producers.append(text)
+
+    def finish(self, response):
+        self._response=response
+        Wakeup(self.apply) # move callback to medusas thread
+
+    def apply(self):
+        result=apply(self._callback, self._args+(self._response,))
+
+        # break cycles
+        self._callback=None
+        self._response=None
+        self._args=None
+
+        return result
+
+def make_response(channel, callback, *args):
+    # XXX should this be the FTPResponse constructor instead?
+    r=FTPResponse(stdout=CallbackPipe(callback, args), stderr=StringIO())
+    r.setHeader('content-type','text/plain')
+    r.cookies=channel.cookies
+    return r


=== Zope/lib/python/ZServer/FTPServer.py 1.25 => 1.26 === (545/645 lines abridged)
--- /dev/null	Tue Mar 18 16:15:46 2003
+++ Zope/lib/python/ZServer/FTPServer.py	Tue Mar 18 16:15:14 2003
@@ -0,0 +1,642 @@
+##############################################################################
+#
+# Copyright (c) 2001 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
+#
+##############################################################################
+
+"""ZServer FTP Channel for use the medusa's ftp server.
+
+FTP Service for Zope.
+
+  This server allows FTP connections to Zope. In general FTP is used
+  to manage content. You can:
+
+    * Create and delete Folders, Documents, Files, and Images
+
+    * Edit the contents of Documents, Files, Images
+
+  In the future, FTP may be used to edit object properties.
+
+FTP Protocol
+
+  The FTP protocol for Zope gives Zope objects a way to make themselves
+  available to FTP services. See the 'lib/python/OFS/FTPInterface.py' for
+  more details.
+
+FTP Permissions
+
+  FTP access is controlled by one permission: 'FTP access' if bound to a
+  role, users of that role will be able to list directories, and cd to
+  them. Creating and deleting and changing objects are all governed by
+  existing Zope permissions.
+
+  Permissions are to a certain extent reflected in the permission bits
+  listed in FTP file listings.
+
+FTP Authorization
+
+  Zope supports both normal and anonymous logins. It can be difficult
+  to authorize Zope users since they are defined in distributed user
+  databases. Normally, all logins will be accepted and then the user must

[-=- -=- -=- 545 lines omitted -=- -=- -=-]

+class FTPServer(ftp_server):
+    """FTP server for Zope."""
+
+    ftp_channel_class = zope_ftp_channel
+    limiter=FTPLimiter(10,1)
+    shutup=0
+
+    def __init__(self,module,*args,**kw):
+        self.shutup=1
+        apply(ftp_server.__init__, (self, None) + args, kw)
+        self.shutup=0
+        self.module=module
+        self.log_info('FTP server started at %s\n'
+                      '\tHostname: %s\n\tPort: %d' % (
+                        time.ctime(time.time()),
+                        self.hostname,
+                        self.port
+                        ))
+
+    def clean_shutdown_control(self,phase,time_in_this_phase):
+        if phase==2:
+            self.log_info('closing FTP to new connections')
+            self.close()
+
+    def log_info(self, message, type='info'):
+        if self.shutup: return
+        asyncore.dispatcher.log_info(self, message, type)
+
+    def create_socket(self, family, type):
+        asyncore.dispatcher.create_socket(self, family, type)
+        requestCloseOnExec(self.socket)
+
+    def handle_accept (self):
+        try:
+            conn, addr = self.accept()
+        except TypeError:
+            # unpack non-sequence as result of accept
+            # returning None (in case of EWOULDBLOCK)
+            return
+        self.total_sessions.increment()
+        self.log_info('Incoming connection from %s:%d' % (addr[0], addr[1]))
+        self.ftp_channel_class (self, conn, addr, self.module)
+
+    def readable(self):
+        return len(asyncore.socket_map) < CONNECTION_LIMIT
+
+    def listen(self, num):
+        # override asyncore limits for nt's listen queue size
+        self.accepting = 1
+        return self.socket.listen (num)


=== Zope/lib/python/ZServer/HTTPResponse.py 1.42 => 1.43 ===
--- /dev/null	Tue Mar 18 16:15:46 2003
+++ Zope/lib/python/ZServer/HTTPResponse.py	Tue Mar 18 16:15:14 2003
@@ -0,0 +1,310 @@
+##############################################################################
+#
+# Copyright (c) 2001 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
+#
+##############################################################################
+"""
+ZServer HTTPResponse
+
+The HTTPResponse class takes care of server headers, response munging
+and logging duties.
+
+"""
+import time, re,  sys, tempfile
+from cStringIO import StringIO
+import thread
+from ZPublisher.HTTPResponse import HTTPResponse
+from medusa.http_date import build_http_date
+from PubCore.ZEvent import Wakeup
+from medusa.producers import hooked_producer
+from medusa import http_server
+import asyncore
+from Producers import ShutdownProducer, LoggingProducer, CallbackProducer, \
+    file_part_producer, file_close_producer
+from types import LongType
+import DebugLogger
+
+
+class ZServerHTTPResponse(HTTPResponse):
+    "Used to push data into a channel's producer fifo"
+
+    # Set this value to 1 if streaming output in
+    # HTTP/1.1 should use chunked encoding
+    http_chunk=1
+    http_chunk_size=1024
+
+    # defaults
+    _http_version='1.0'
+    _http_connection='close'
+    _server_version='Zope/2.0 ZServer/2.0'
+
+    # using streaming response
+    _streaming=0
+    # using chunking transfer-encoding
+    _chunking=0
+
+    def __str__(self,
+                html_search=re.compile('<html>',re.I).search,
+                ):
+        if self._wrote:
+            if self._chunking:
+                return '0\r\n\r\n'
+            else:
+                return ''
+
+        headers=self.headers
+        body=self.body
+
+        # set 204 (no content) status if 200 and response is empty
+        # and not streaming
+        if not headers.has_key('content-type') and \
+                not headers.has_key('content-length') and \
+                not self._streaming and \
+                self.status == 200:
+            self.setStatus('nocontent')
+
+        # add content length if not streaming
+        if not headers.has_key('content-length') and \
+                not self._streaming:
+            self.setHeader('content-length',len(body))
+
+
+        content_length= headers.get('content-length', None)
+        if content_length>0 :
+            self.setHeader('content-length', content_length)
+
+        headersl=[]
+        append=headersl.append
+
+        status=headers.get('status', '200 OK')
+
+        # status header must come first.
+        append("HTTP/%s %s" % (self._http_version or '1.0' , status))
+        if headers.has_key('status'):
+            del headers['status']
+
+        if not headers.has_key("Etag"):
+            self.setHeader('Etag','')
+
+        # add zserver headers
+        append('Server: %s' % self._server_version)
+        append('Date: %s' % build_http_date(time.time()))
+
+        if self._http_version=='1.0':
+            if self._http_connection=='keep-alive' and \
+                    self.headers.has_key('content-length'):
+                self.setHeader('Connection','Keep-Alive')
+            else:
+                self.setHeader('Connection','close')
+
+        # Close the connection if we have been asked to.
+        # Use chunking if streaming output.
+        if self._http_version=='1.1':
+            if self._http_connection=='close':
+                self.setHeader('Connection','close')
+            elif not self.headers.has_key('content-length'):
+                if self.http_chunk and self._streaming:
+                    self.setHeader('Transfer-Encoding','chunked')
+                    self._chunking=1
+                else:
+                    self.setHeader('Connection','close')
+
+        for key, val in headers.items():
+            if key.lower()==key:
+                # only change non-literal header names
+                key="%s%s" % (key[:1].upper(), key[1:])
+                start=0
+                l=key.find('-',start)
+                while l >= start:
+                    key="%s-%s%s" % (key[:l],key[l+1:l+2].upper(),key[l+2:])
+                    start=l+1
+                    l=key.find('-',start)
+            append("%s: %s" % (key, val))
+        if self.cookies:
+            headersl=headersl+self._cookie_list()
+        headersl[len(headersl):]=[self.accumulated_headers, body]
+        return "\r\n".join(headersl)
+
+    _tempfile=None
+    _templock=None
+    _tempstart=0
+
+    def write(self,data):
+        """\
+        Return data as a stream
+
+        HTML data may be returned using a stream-oriented interface.
+        This allows the browser to display partial results while
+        computation of a response to proceed.
+
+        The published object should first set any output headers or
+        cookies on the response object.
+
+        Note that published objects must not generate any errors
+        after beginning stream-oriented output.
+
+        """
+        stdout=self.stdout
+
+        if not self._wrote:
+            l=self.headers.get('content-length', None)
+            if l is not None:
+                try:
+                    if type(l) is type(''): l=int(l)
+                    if l > 128000:
+                        self._tempfile=tempfile.TemporaryFile()
+                        self._templock=thread.allocate_lock()
+                except: pass
+
+            self._streaming=1
+            stdout.write(str(self))
+            self._wrote=1
+
+        if not data: return
+
+        if self._chunking:
+            data = '%x\r\n%s\r\n' % (len(data),data)
+
+        l=len(data)
+
+        t=self._tempfile
+        if t is None or l<200:
+            stdout.write(data)
+        else:
+            b=self._tempstart
+            e=b+l
+            self._templock.acquire()
+            try:
+                t.seek(b)
+                t.write(data)
+            finally:
+                self._templock.release()
+            self._tempstart=e
+            stdout.write(file_part_producer(t,self._templock,b,e), l)
+
+    _retried_response = None
+
+    def _finish(self):
+        if self._retried_response:
+            try:
+                self._retried_response._finish()
+            finally:
+                self._retried_response = None
+            return
+        stdout=self.stdout
+
+        t=self._tempfile
+        if t is not None:
+            stdout.write(file_close_producer(t), 0)
+            self._tempfile=None
+
+        stdout.finish(self)
+        stdout.close()
+
+        self.stdout=None # need to break cycle?
+        self._request=None
+
+    def retry(self):
+        """Return a request object to be used in a retry attempt
+        """
+        # This implementation is a bit lame, because it assumes that
+        # only stdout stderr were passed to the constructor. OTOH, I
+        # think that that's all that is ever passed.
+
+        response=self.__class__(stdout=self.stdout, stderr=self.stderr)
+        response.headers=self.headers
+        response._http_version=self._http_version
+        response._http_connection=self._http_connection
+        response._server_version=self._server_version
+        self._retried_response = response
+        return response
+
+
+class ChannelPipe:
+    """Experimental pipe from ZPublisher to a ZServer Channel.
+    Should only be used by one thread at a time. Note also that
+    the channel will be being handled by another thread, thus
+    restrict access to channel to the push method only."""
+
+    def __init__(self, request):
+        self._channel=request.channel
+        self._request=request
+        self._shutdown=0
+        self._close=0
+        self._bytes=0
+
+    def write(self, text, l=None):
+        if self._channel.closed:
+            return
+        if l is None: l=len(text)
+        self._bytes=self._bytes + l
+        self._channel.push(text,0)
+        Wakeup()
+
+    def close(self):
+        DebugLogger.log('A', id(self._request),
+                '%s %s' % (self._request.reply_code, self._bytes))
+        if not self._channel.closed:
+            self._channel.push(LoggingProducer(self._request, self._bytes), 0)
+            self._channel.push(CallbackProducer(self._channel.done), 0)
+            self._channel.push(CallbackProducer(
+                lambda t=('E', id(self._request)): apply(DebugLogger.log, t)), 0)
+            if self._shutdown:
+                self._channel.push(ShutdownProducer(), 0)
+                Wakeup()
+            else:
+                if self._close: self._channel.push(None, 0)
+            Wakeup()
+        else:
+            # channel closed too soon
+
+            self._request.log(self._bytes)
+            DebugLogger.log('E', id(self._request))
+
+            if self._shutdown:
+                Wakeup(lambda: asyncore.close_all())
+            else:
+                Wakeup()
+
+        self._channel=None #need to break cycles?
+        self._request=None
+
+    def flush(self): pass # yeah, whatever
+
+    def finish(self, response):
+        if response._shutdownRequested():
+            self._shutdown = 1
+        if response.headers.get('connection','') == 'close' or \
+                response.headers.get('Connection','') == 'close':
+            self._close=1
+        self._request.reply_code=response.status
+
+
+is_proxying_match = re.compile(r'[^ ]* [^ \\]*:').match
+proxying_connection_re = re.compile ('Proxy-Connection: (.*)', re.IGNORECASE)
+
+def make_response(request, headers):
+    "Simple http response factory"
+    # should this be integrated into the HTTPResponse constructor?
+
+    response=ZServerHTTPResponse(stdout=ChannelPipe(request), stderr=StringIO())
+    response._http_version=request.version
+    if request.version=='1.0' and is_proxying_match(request.request):
+        # a request that was made as if this zope was an http 1.0 proxy.
+        # that means we have to use some slightly different http
+        # headers to manage persistent connections.
+        connection_re = proxying_connection_re
+    else:
+        # a normal http request
+        connection_re = http_server.CONNECTION
+    response._http_connection = http_server.get_header(connection_re,
+                                                       request.header).lower()
+    response._server_version=request.channel.server.SERVER_IDENT
+    return response


=== Zope/lib/python/ZServer/HTTPServer.py 1.44 => 1.45 ===
--- /dev/null	Tue Mar 18 16:15:46 2003
+++ Zope/lib/python/ZServer/HTTPServer.py	Tue Mar 18 16:15:14 2003
@@ -0,0 +1,408 @@
+##############################################################################
+#
+# Copyright (c) 2001 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
+#
+##############################################################################
+
+"""
+Medusa HTTP server for Zope
+
+changes from Medusa's http_server
+
+    Request Threads -- Requests are processed by threads from a thread
+    pool.
+
+    Output Handling -- Output is pushed directly into the producer
+    fifo by the request-handling thread. The HTTP server does not do
+    any post-processing such as chunking.
+
+    Pipelineable -- This is needed for protocols such as HTTP/1.1 in
+    which mutiple requests come in on the same channel, before
+    responses are sent back. When requests are pipelined, the client
+    doesn't wait for the response before sending another request. The
+    server must ensure that responses are sent back in the same order
+    as requests are received.
+
+"""
+import sys
+import re
+import os
+import posixpath
+import types
+import thread
+import time
+import socket
+from cStringIO import StringIO
+
+from PubCore import handle
+from HTTPResponse import make_response
+from ZPublisher.HTTPRequest import HTTPRequest
+
+from medusa.http_server import http_server,get_header, http_channel, VERSION_STRING
+import asyncore
+from medusa import counter, producers
+from medusa.test import  max_sockets
+from medusa.default_handler import unquote
+from asyncore import compact_traceback, dispatcher
+
+from ZServer import CONNECTION_LIMIT, ZOPE_VERSION, ZSERVER_VERSION
+from ZServer import requestCloseOnExec
+from zLOG import LOG, register_subsystem, BLATHER, INFO, WARNING, ERROR
+import DebugLogger
+from medusa import logger
+
+register_subsystem('ZServer HTTPServer')
+
+CONTENT_LENGTH  = re.compile('Content-Length: ([0-9]+)',re.I)
+CONNECTION      = re.compile('Connection: (.*)', re.I)
+USER_AGENT      = re.compile('User-Agent: (.*)', re.I)
+
+# maps request some headers to environment variables.
+# (those that don't start with 'HTTP_')
+header2env={'content-length'    : 'CONTENT_LENGTH',
+            'content-type'      : 'CONTENT_TYPE',
+            'connection'        : 'CONNECTION_TYPE',
+            }
+
+
+class zhttp_collector:
+    def __init__(self, handler, request, size):
+        self.handler = handler
+        self.request = request
+        if size > 524288:
+            # write large upload data to a file
+            from tempfile import TemporaryFile
+            self.data = TemporaryFile('w+b')
+        else:
+            self.data = StringIO()
+        request.channel.set_terminator(size)
+        request.collector=self
+
+    # put and post collection methods
+    #
+    def collect_incoming_data (self, data):
+        self.data.write(data)
+
+    def found_terminator(self):
+        # reset collector
+        self.request.channel.set_terminator('\r\n\r\n')
+        self.request.collector=None
+        # finish request
+        self.data.seek(0)
+        r=self.request
+        d=self.data
+        del self.request
+        del self.data
+        self.handler.continue_request(d,r)
+
+class zhttp_handler:
+    "A medusa style handler for zhttp_server"
+
+    _force_connection_close = 0
+
+    def __init__ (self, module, uri_base=None, env=None):
+        """Creates a zope_handler
+
+        module -- string, the name of the module to publish
+        uri_base -- string, the base uri of the published module
+                    defaults to '/<module name>' if not given.
+        env -- dictionary, environment variables to be overridden.
+                    Replaces standard variables with supplied ones.
+        """
+
+        self.module_name=module
+        self.env_override=env or {}
+        self.hits = counter.counter()
+        # if uri_base is unspecified, assume it
+        # starts with the published module name
+        #
+        if uri_base is None:
+            uri_base='/%s' % module
+        elif uri_base == '':
+            uri_base='/'
+        else:
+            if uri_base[0] != '/':
+                uri_base='/'+uri_base
+            if uri_base[-1] == '/':
+                uri_base=uri_base[:-1]
+        self.uri_base=uri_base
+        uri_regex='%s.*' % self.uri_base
+        self.uri_regex = re.compile(uri_regex)
+
+    def match(self, request):
+        uri = request.uri
+        if self.uri_regex.match(uri):
+            return 1
+        else:
+            return 0
+
+    def handle_request(self,request):
+        self.hits.increment()
+
+        DebugLogger.log('B', id(request), '%s %s' % (request.command.upper(), request.uri))
+
+        size=get_header(CONTENT_LENGTH, request.header)
+        if size and size != '0':
+            size=int(size)
+            zhttp_collector(self, request, size)
+        else:
+            sin=StringIO()
+            self.continue_request(sin,request)
+
+    def get_environment(self, request,
+                        # These are strictly performance hackery...
+                        h2ehas=header2env.has_key,
+                        h2eget=header2env.get,
+                        workdir=os.getcwd(),
+                        ospath=os.path,
+                        ):
+
+        (path, params, query, fragment) = request.split_uri()
+
+        if params: path = path + params # undo medusa bug!
+
+        while path and path[0] == '/':
+            path = path[1:]
+        if '%' in path:
+            path = unquote(path)
+        if query:
+            # ZPublisher doesn't want the leading '?'
+            query = query[1:]
+
+        server=request.channel.server
+        env = {}
+        env['REQUEST_METHOD']=request.command.upper()
+        env['SERVER_PORT']=str(server.port)
+        env['SERVER_NAME']=server.server_name
+        env['SERVER_SOFTWARE']=server.SERVER_IDENT
+        env['SERVER_PROTOCOL']="HTTP/"+request.version
+        env['channel.creation_time']=request.channel.creation_time
+        if self.uri_base=='/':
+            env['SCRIPT_NAME']=''
+            env['PATH_INFO']='/' + path
+        else:
+            env['SCRIPT_NAME'] = self.uri_base
+            try:
+                path_info=path.split(self.uri_base[1:],1)[1]
+            except:
+                path_info=''
+            env['PATH_INFO']=path_info
+        env['PATH_TRANSLATED']=ospath.normpath(ospath.join(
+                workdir, env['PATH_INFO']))
+        if query:
+            env['QUERY_STRING'] = query
+        env['GATEWAY_INTERFACE']='CGI/1.1'
+        env['REMOTE_ADDR']=request.channel.addr[0]
+
+
+        # This is a really bad hack to support WebDAV
+        # clients accessing documents through GET
+        # on the HTTP port. We check if your WebDAV magic
+        # machinery is enabled and if the client is recognized
+        # as WebDAV client. If yes, we fake the environment
+        # to pretend the ZPublisher to have a WebDAV request.
+        # This sucks like hell but it works pretty fine ;-)
+
+        if env['REQUEST_METHOD']=='GET' and self._wdav_client_reg:
+            self._munge_webdav_source_port(request, env)
+
+
+        # If we're using a resolving logger, try to get the
+        # remote host from the resolver's cache.
+        if hasattr(server.logger, 'resolver'):
+            dns_cache=server.logger.resolver.cache
+            if dns_cache.has_key(env['REMOTE_ADDR']):
+                remote_host=dns_cache[env['REMOTE_ADDR']][2]
+                if remote_host is not None:
+                    env['REMOTE_HOST']=remote_host
+
+        env_has=env.has_key
+        for header in request.header:
+            key,value=header.split(":",1)
+            key=key.lower()
+            value=value.strip()
+            if h2ehas(key) and value:
+                env[h2eget(key)]=value
+            else:
+                key='HTTP_%s' % ("_".join(key.split( "-"))).upper()
+                if value and not env_has(key):
+                    env[key]=value
+        env.update(self.env_override)
+        return env
+
+    _wdav_client_reg = None
+
+    def _munge_webdav_source_port(self, request, env):
+        agent = get_header(USER_AGENT, request.header)
+        if self._wdav_client_reg(agent):
+            env['WEBDAV_SOURCE_PORT'] = 1
+            path_info = env['PATH_INFO']
+            path_info = posixpath.join(path_info, 'manage_FTPget')
+            path_info = posixpath.normpath(path_info)
+            env['PATH_INFO'] = path_info
+
+    def set_webdav_source_clients(self, regex):
+        self._wdav_client_reg = re.compile(regex).search
+
+    def continue_request(self, sin, request):
+        "continue handling request now that we have the stdin"
+
+        s=get_header(CONTENT_LENGTH, request.header)
+        if s:
+            s=int(s)
+        else:
+            s=0
+        DebugLogger.log('I', id(request), s)
+
+        env=self.get_environment(request)
+        zresponse=make_response(request,env)
+        if self._force_connection_close:
+            zresponse._http_connection = 'close'
+        zrequest=HTTPRequest(sin, env, zresponse)
+        request.channel.current_request=None
+        request.channel.queue.append((self.module_name, zrequest, zresponse))
+        request.channel.work()
+
+    def status(self):
+        return producers.simple_producer("""
+            <li>Zope Handler
+            <ul>
+            <li><b>Published Module:</b> %s
+            <li><b>Hits:</b> %s
+            </ul>""" %(self.module_name, self.hits)
+            )
+
+
+
+class zhttp_channel(http_channel):
+    "http channel"
+
+    closed = 0
+    no_more_requests = 0
+    zombie_timeout=100*60 # 100 minutes
+    max_header_len = 8196
+
+    def __init__(self, server, conn, addr):
+        http_channel.__init__(self, server, conn, addr)
+        requestCloseOnExec(conn)
+        self.queue=[]
+        self.working=0
+
+    def push(self, producer, send=1):
+        # this is thread-safe when send is false
+        # note, that strings are not wrapped in
+        # producers by default
+        if self.closed:
+            return
+        self.producer_fifo.push(producer)
+        if send: self.initiate_send()
+
+    push_with_producer=push
+
+    def clean_shutdown_control(self,phase,time_in_this_phase):
+        if phase==3:
+            # This is the shutdown phase where we are trying to finish processing
+            # outstanding requests, and not accept any more
+            self.no_more_requests = 1
+            if self.working or self.writable():
+                # We are busy working on an old request. Try to stall shutdown
+                return 1
+            else:
+                # We are no longer busy. Close ourself and allow shutdown to proceed
+                self.close()
+                return 0
+
+    def work(self):
+        "try to handle a request"
+        if not self.working:
+            if self.queue and not self.no_more_requests:
+                self.working=1
+                try: module_name, request, response=self.queue.pop(0)
+                except: return
+                handle(module_name, request, response)
+
+    def close(self):
+        self.closed=1
+        while self.queue:
+            self.queue.pop()
+        if self.current_request is not None:
+            self.current_request.channel=None # break circ refs
+            self.current_request=None
+        while self.producer_fifo:
+            p=self.producer_fifo.first()
+            if p is not None and type(p) != types.StringType:
+                p.more() # free up resources held by producer
+            self.producer_fifo.pop()
+        dispatcher.close(self)
+
+    def done(self):
+        "Called when a publishing request is finished"
+        self.working=0
+        self.work()
+
+    def kill_zombies(self):
+        now = int (time.time())
+        for channel in asyncore.socket_map.values():
+            if channel.__class__ == self.__class__:
+                if (now - channel.creation_time) > channel.zombie_timeout:
+                    channel.close()
+
+    def collect_incoming_data (self, data):
+        # Override medusa http_channel implementation to prevent DOS attacks
+        # that send never-ending HTTP headers.
+        if self.current_request:
+                # we are receiving data (probably POST data) for a request
+            self.current_request.collect_incoming_data (data)
+        else:
+                # we are receiving header (request) data
+            self.in_buffer = self.in_buffer + data
+            if len(self.in_buffer) > self.max_header_len:
+                raise ValueError('HTTP headers invalid (too long)')
+
+class zhttp_server(http_server):
+    "http server"
+
+    SERVER_IDENT='Zope/%s ZServer/%s' % (ZOPE_VERSION,ZSERVER_VERSION)
+
+    channel_class = zhttp_channel
+    shutup=0
+
+    def __init__ (self, ip, port, resolver=None, logger_object=None):
+        self.shutup=1
+        http_server.__init__(self, ip, port, resolver, logger_object)
+        self.shutup=0
+        self.log_info('HTTP server started at %s\n'
+                      '\tHostname: %s\n\tPort: %d' % (
+                        time.ctime(time.time()),
+                        self.server_name,
+                        self.server_port
+                        ))
+
+    def clean_shutdown_control(self,phase,time_in_this_phase):
+        if phase==2:
+            self.log_info('closing HTTP to new connections')
+            self.close()
+
+    def log_info(self, message, type='info'):
+        if self.shutup: return
+        dispatcher.log_info(self, message, type)
+
+    def create_socket(self, family, type):
+        dispatcher.create_socket(self, family, type)
+        requestCloseOnExec(self.socket)
+
+    def readable(self):
+        return self.accepting and \
+                len(asyncore.socket_map) < CONNECTION_LIMIT
+
+    def listen(self, num):
+        # override asyncore limits for nt's listen queue size
+        self.accepting = 1
+        return self.socket.listen (num)


=== Zope/lib/python/ZServer/ICPServer.py 1.4 => 1.5 ===
--- /dev/null	Tue Mar 18 16:15:46 2003
+++ Zope/lib/python/ZServer/ICPServer.py	Tue Mar 18 16:15:14 2003
@@ -0,0 +1,128 @@
+##############################################################################
+#
+# Copyright (c) 2001 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
+#
+##############################################################################
+
+# Medusa ICP server
+#
+# Why would you want to use this?
+# see http://www.zope.org/Members/htrd/icp/intro
+
+import sys, string, os, socket, errno, struct
+
+import asyncore
+
+from medusa import counter
+
+
+ICP_OP_QUERY = 1
+ICP_OP_HIT = 2
+ICP_OP_MISS = 3
+ICP_OP_ERR = 4
+ICP_OP_MISS_NOFETCH = 21
+ICP_OP_DENIED = 22
+
+class BaseICPServer(asyncore.dispatcher):
+
+    REQUESTS_PER_LOOP = 4
+    _shutdown = 0
+
+    def __init__ (self,ip,port):
+        asyncore.dispatcher.__init__(self)
+        self.create_socket (socket.AF_INET, socket.SOCK_DGRAM)
+        self.set_reuse_addr()
+        self.bind((ip,port))
+        if ip=='':
+            addr = 'any'
+        else:
+            addr = ip
+        self.log_info('ICP server started\n\tAddress: %s\n\tPort: %s' % (addr,port) )
+
+    def clean_shutdown_control(self,phase,time_in_this_phase):
+        if phase==1:
+            # Stop responding to requests.
+            if not self._shutdown:
+                self._shutdown = 1
+                self.log_info('shutting down ICP')
+            if time_in_this_phase<2.0:
+                # We have not yet been deaf long enough for our front end proxies to notice.
+                # Do not allow shutdown to proceed yet
+                return 1
+            else:
+                # Shutdown can proceed. We dont need a socket any more
+                self.close()
+                return 0
+
+    def handle_read(self):
+        for i in range(self.REQUESTS_PER_LOOP):
+            try:
+                request, whence = self.socket.recvfrom(16384)
+            except socket.error,e:
+                if e[0]==errno.EWOULDBLOCK:
+                    break
+                else:
+                    raise
+            else:
+                if self.check_whence(whence):
+                    reply = self.calc_reply(request)
+                    if reply:
+                        self.socket.sendto(reply,whence)
+
+    def readable(self):
+        return not self._shutdown
+
+    def writable(self):
+        return 0
+
+    def handle_write (self):
+        self.log_info ('unexpected write event', 'warning')
+
+    def handle_error (self):      # don't close the socket on error
+        (file,fun,line), t, v, tbinfo = asyncore.compact_traceback()
+        self.log_info('Problem in ICP (%s:%s %s)' % (t, v, tbinfo),
+                      'error')
+
+    def check_whence(self,whence):
+        return 1
+
+    def calc_reply(self,request):
+        if len(request)>20:
+            opcode,version,length,number,options,opdata,junk = struct.unpack('!BBHIIII',request[:20])
+            if version==2:
+                if opcode==ICP_OP_QUERY:
+                    if len(request)!=length:
+                        out_opcode = ICP_OP_ERR
+                    else:
+                        url = request[24:]
+                        if url[-1:]=='\x00':
+                            url = url[:-1]
+                        out_opcode = self.check_url(url)
+                    return struct.pack('!BBHIIII',out_opcode,2,20,number,0,0,0)
+
+    def check_url(self,url):
+        # derived classes replace this with a more
+        # useful policy
+        return ICP_OP_MISS
+
+
+class ICPServer(BaseICPServer):
+    # Products that want to do special ICP handling should .append their hooks into
+    # this list. Each hook is called in turn with the URL as a parameter, and
+    # they must return an ICP_OP code from above or None. The first
+    # non-None return is used as the ICP response
+    hooks = []
+
+    def check_url(self,url):
+        for hook in self.hooks:
+            r = hook(url)
+            if r is not None:
+                return r
+        return ICP_OP_MISS


=== Zope/lib/python/ZServer/INSTALL.txt 1.7 => 1.8 ===
--- /dev/null	Tue Mar 18 16:15:46 2003
+++ Zope/lib/python/ZServer/INSTALL.txt	Tue Mar 18 16:15:14 2003
@@ -0,0 +1,123 @@
+ZServer Installation
+--------------------
+
+Requirements
+
+  ZServer comes with Zope 2. Though ZServer can be used with earlier
+  versions of Zope, this is not supported and not covered by this
+  document.
+
+Configuration
+
+  To run ZServer you simply execute the z2.py start script which is
+  located in your Zope directory. You can pass commandline arguments
+  to the start script in order to run Zope with different options. In
+  order to see a list of options use the '-h' help option.
+  
+  Here's an example of use::
+  
+    $ python2.1 z2.py -w 8888 -f "" -p "" -m "" &
+    
+  This example starts Zope using a web server on port 8888. It does
+  not start and FTP server, or a PCGI server, or a Monitor server. It
+  also starts the server running in the backaground.
+  
+Shell scripts and batch files  
+  
+  You may also wish to create a shell script (or batch file under
+  win32) to set environment variables (such as ZOPE_DEBUG_MODE and
+  PYTHONHOME) and run the start script. 
+  
+  Here's an example shell script for a binary Zope release::
+  
+    ZOPE_DEBUG_MODE=1
+    export ZOPE_DEBUG_MODE
+    PYTHONHOME=/home/Zope
+    export PYTHONHOME
+    /home/Zope/bin/python /home/Zope/z2.py -w 9673 &
+  
+  Note: If ZServer fails because it can't find some standard Python
+  libaries there's a good bet that you need to set the PYTHONHOME as
+  shown above.
+  
+  Here's an example batch file::
+  
+    set ZOPE_DEBUG_MODE=1
+    "\Program Files\Zope\bin\python"  "\Program Files\Zope\z2.py -w
+    8888 -f 8021"
+  
+  Now you're ready to go.  
+
+Starting ZServer
+
+  To start ZServer run the start script::
+  
+    $ python2.1 z2.py
+    
+  To stop the server type 'control-c'.
+
+  Note: If you've created a shell script or batch file to run ZServer
+  use that instead.
+
+  You should see some Medusa information come up on the screen as Zope
+  starts.
+  
+  A log file will be written to the 'var' directory, named
+  'Z2.log' by default.
+
+Using ZServer
+
+  Once you start ZServer is will publish Zope (or any Python module)
+  on HTTP and/or FTP. To access Zope via HTTP point your browser at
+  the server like so::
+  
+    http://www.example.com:9673/
+    
+  This assumes that you have chosen to put HTTP on port 9673 and that
+  you are publishing a module named whose URL prefix is set to ''.
+  
+  Note: to publish Zope normally you publish the 'lib/python/Zope.py'
+  module.
+
+  To access Zope via FTP you need to FTP to it at the port you set FTP
+  to run on. For example::
+  
+    ftp www.example.com 9221
+
+  This opens a FTP session to your machine on port 9221, ZServer's
+  default FTP port. When you are prompted to log in you should supply
+  a Zope username and password. (Probably you should use an account
+  with the 'Manager' role, unless you have configured Zope to allow
+  FTP access to the 'Anonymous' role.) You can also enter 'anonymous'
+  and any password for anonymous FTP access. Once you have logged in
+  you can start issuing normal FTP commands.
+  
+  Right now ZServer supports most basic FTP commands.
+  
+  Note: When you log in your working directory is set to '/'. If you
+  do not have FTP permissions in this directory, you will need to 'cd'
+  to a directory where you have permissions before you can do
+  anything. See above for more information about logging into FTP.
+
+Advanced Usage: zdaemon.py and the Zope NT service.
+
+  One problem you may notice with ZServer is that once the server is
+  shutdown, either through the web management interface, or by some
+  other means, it will not automatically be restarted.
+  
+  On Unix you can use zdeamon.py to keep Zope running. Specifying
+  the '-Z' switch when starting Zope runs zdaemon.py. Zdeamon
+  will restart Zope when it Zope is restarted through the web, and in
+  case of an unexpected error.
+  
+  On NT, use the Zope service for the same functionality. See ZServer.py
+  for more information on running ZServer as a service.
+
+Where to go from here
+
+  Check out the README.txt file. It contains information on what
+  ZServer can do, how it works and and what you can do if you run into
+  problems.
+  
+  And don't forget to have fun!
+  


=== Zope/lib/python/ZServer/PCGIServer.py 1.25 => 1.26 ===
--- /dev/null	Tue Mar 18 16:15:46 2003
+++ Zope/lib/python/ZServer/PCGIServer.py	Tue Mar 18 16:15:14 2003
@@ -0,0 +1,399 @@
+##############################################################################
+#
+# Copyright (c) 2001 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
+#
+##############################################################################
+
+"""
+Medusa PCGI server.
+
+This server functions as the PCGI publisher--it accepts the request
+from the PCGI wrapper CGI program, services the request, and sends
+back the response.
+
+It should work with both inet and unix domain sockets.
+
+Why would you want to use it? Using PCGI to connect to ZServer from
+another webserver is similar to using the web server as a proxy,
+with the difference, that the web server gets to control the
+environment and headers completely.
+
+Note that ZServer can operate multiple PCGI servers.
+"""
+
+from medusa import logger
+import asynchat, asyncore
+from medusa.counter import counter
+from medusa.http_server import compute_timezone_for_log
+from asyncore import compact_traceback
+
+import ZServer
+from ZServer import CONNECTION_LIMIT, requestCloseOnExec
+
+from PubCore import handle
+from PubCore.ZEvent import Wakeup
+from ZPublisher.HTTPResponse import HTTPResponse
+from ZPublisher.HTTPRequest import HTTPRequest
+from Producers import ShutdownProducer, LoggingProducer, CallbackProducer
+import DebugLogger
+
+from cStringIO import StringIO
+from tempfile import TemporaryFile
+import socket, string, os, sys, time
+from types import StringType, TupleType
+
+tz_for_log=compute_timezone_for_log()
+
+class PCGIChannel(asynchat.async_chat):
+    """Processes a PCGI request by collecting the env and stdin and
+    then passing them to ZPublisher. The result is wrapped in a
+    producer and sent back."""
+
+    closed=0
+
+    def __init__(self,server,sock,addr):
+        self.server = server
+        self.addr = addr
+        asynchat.async_chat.__init__ (self, sock)
+        requestCloseOnExec(sock)
+        self.env={}
+        self.data=StringIO()
+        self.set_terminator(10)
+        self.size=None
+        self.done=None
+
+    def found_terminator(self):
+        if self.size is None:
+            # read the next size header
+            # and prepare to read env or stdin
+            self.data.seek(0)
+            self.size=string.atoi(self.data.read())
+            self.set_terminator(self.size)
+            if self.size==0:
+
+                DebugLogger.log('I', id(self), 0)
+
+                self.set_terminator('\r\n')
+                self.data=StringIO()
+                self.send_response()
+            elif self.size > 1048576:
+                self.data=TemporaryFile('w+b')
+            else:
+                self.data=StringIO()
+        elif not self.env:
+            # read env
+            self.size=None
+            self.data.seek(0)
+            buff=self.data.read()
+            for line in string.split(buff,'\000'):
+                try:
+                    k,v = string.split(line,'=',1)
+                    self.env[k] = v
+                except:
+                    pass
+            # Hack around broken IIS PATH_INFO
+            # maybe, this should go in ZPublisher...
+            if self.env.has_key('SERVER_SOFTWARE') and \
+                    string.find(self.env['SERVER_SOFTWARE'],
+                    'Microsoft-IIS') != -1:
+                script = filter(None,string.split(
+                        string.strip(self.env['SCRIPT_NAME']),'/'))
+                path = filter(None,string.split(
+                        string.strip(self.env['PATH_INFO']),'/'))
+                self.env['PATH_INFO'] = '/' + string.join(path[len(script):],'/')
+            self.data=StringIO()
+
+            DebugLogger.log('B', id(self),
+                '%s %s' % (self.env['REQUEST_METHOD'],
+                           self.env.get('PATH_INFO' ,'/')))
+
+            # now read the next size header
+            self.set_terminator(10)
+        else:
+
+            DebugLogger.log('I', id(self), self.terminator)
+
+            # we're done, we've got both env and stdin
+            self.set_terminator('\r\n')
+            self.data.seek(0)
+            self.send_response()
+
+    def send_response(self):
+        # create an output pipe by passing request to ZPublisher,
+        # and requesting a callback of self.log with the module
+        # name and PATH_INFO as an argument.
+        self.done=1
+        response=PCGIResponse(stdout=PCGIPipe(self), stderr=StringIO())
+        request=HTTPRequest(self.data, self.env, response)
+        handle(self.server.module, request, response)
+
+    def collect_incoming_data(self, data):
+        self.data.write(data)
+
+    def readable(self):
+        if not self.done:
+            return 1
+
+    def log_request(self, bytes):
+        if self.env.has_key('HTTP_USER_AGENT'):
+            user_agent=self.env['HTTP_USER_AGENT']
+        else:
+            user_agent=''
+        if self.env.has_key('HTTP_REFERER'):
+            referer=self.env['HTTP_REFERER']
+        else:
+            referer=''
+
+        if self.env.has_key('PATH_INFO'):
+            path=self.env['PATH_INFO']
+        else:
+            path='%s/' % self.server.module
+        if self.env.has_key('REQUEST_METHOD'):
+            method=self.env['REQUEST_METHOD']
+        else:
+            method="GET"
+        addr=self.addr
+        if addr and type(addr) is TupleType:
+            self.server.logger.log (
+                addr[0],
+                '%d - - [%s] "%s %s" %d %d "%s" "%s"' % (
+                    addr[1],
+                    time.strftime (
+                    '%d/%b/%Y:%H:%M:%S ',
+                    time.localtime(time.time())
+                    ) + tz_for_log,
+                    method, path, self.reply_code, bytes,
+                    referer, user_agent
+                    )
+                )
+        else:
+            self.server.logger.log (
+                '127.0.0.1',
+                ' - - [%s] "%s %s" %d %d "%s" "%s"' % (
+                    time.strftime (
+                    '%d/%b/%Y:%H:%M:%S ',
+                    time.gmtime(time.time())
+                    ) + tz_for_log,
+                    method, path, self.reply_code, bytes,
+                    referer, user_agent
+                    )
+                )
+
+    def push(self, producer, send=1):
+        # this is thread-safe when send is false
+        # note, that strings are not wrapped in
+        # producers by default
+        self.producer_fifo.push(producer)
+        if send: self.initiate_send()
+
+    def __repr__(self):
+        return "<PCGIChannel at %x>" % id(self)
+
+    def close(self):
+        self.closed=1
+        while self.producer_fifo:
+            p=self.producer_fifo.first()
+            if p is not None and type(p) != StringType:
+                p.more() # free up resources held by producer
+            self.producer_fifo.pop()
+        asyncore.dispatcher.close(self)
+
+
+class PCGIServer(asyncore.dispatcher):
+    """Accepts PCGI requests and hands them off to the PCGIChannel for
+    handling.
+
+    PCGIServer can be configured with either a PCGI info file or by
+    directly specifying the module, pid_file, and either port (for
+    inet sockets) or socket_file (for unix domain sockets.)
+
+    For inet sockets, the ip argument specifies the address from which
+    the server will accept connections, '' indicates all addresses. If
+    you only want to accept connections from the localhost, set ip to
+    '127.0.0.1'."""
+
+    channel_class=PCGIChannel
+
+    def __init__ (self,
+            module='Main',
+            ip='127.0.0.1',
+            port=None,
+            socket_file=None,
+            pid_file=None,
+            pcgi_file=None,
+            resolver=None,
+            logger_object=None):
+
+        self.ip = ip
+        asyncore.dispatcher.__init__(self)
+        self.count=counter()
+        if not logger_object:
+            logger_object = logger.file_logger (sys.stdout)
+        if resolver:
+            self.logger = logger.resolving_logger (resolver, logger_object)
+        else:
+            self.logger = logger.unresolving_logger (logger_object)
+
+        # get configuration
+        self.module=module
+        self.port=port
+        self.pid_file=pid_file
+        self.socket_file=socket_file
+        if pcgi_file is not None:
+            self.read_info(pcgi_file)
+
+        # write pid file
+        try:
+            f = open(self.pid_file, 'w')
+            f.write(str(os.getpid()))
+            f.close()
+        except IOError:
+            self.log_info("Cannot write PID file.", 'error')
+
+        # setup sockets
+        if self.port:
+            self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
+            self.set_reuse_addr()
+            self.bind((self.ip, self.port))
+            self.log_info(
+                'PCGI Server started at %s\n'
+                '\tInet socket port: %s' % (time.ctime(time.time()), self.port)
+                )
+        else:
+            try:
+                os.unlink(self.socket_file)
+            except os.error:
+                pass
+            self.create_socket(socket.AF_UNIX, socket.SOCK_STREAM)
+            self.set_reuse_addr()
+            self.bind(self.socket_file)
+            try:
+                os.chmod(self.socket_file,0777)
+            except os.error:
+                pass
+            self.log_info(
+                'PCGI Server started at %s\n'
+                '\tUnix socket: %s' % (time.ctime(time.time()), self.socket_file)
+                )
+        self.listen(256)
+
+    def create_socket(self, family, type):
+        asyncore.dispatcher.create_socket(self, family, type)
+        requestCloseOnExec(self.socket)
+
+    def read_info(self,info_file):
+        "read configuration information from a PCGI info file"
+        lines=open(info_file).readlines()
+        directives={}
+        try:
+            for line in lines:
+                line=string.strip(line)
+                if not len(line) or line[0]=='#':
+                    continue
+                k,v=string.split(line,'=',1)
+                directives[string.strip(k)]=string.strip(v)
+        except:
+            raise 'ParseError', 'Error parsing PCGI info file'
+
+        self.pid_file=directives.get('PCGI_PID_FILE',None)
+        self.socket_file=directives.get('PCGI_SOCKET_FILE',None)
+        if directives.has_key('PCGI_PORT'):
+            self.port=string.atoi(directives['PCGI_PORT'])
+        if directives.has_key('PCGI_MODULE'):
+            self.module=directives['PCGI_MODULE']
+        elif directives.has_key('PCGI_MODULE_PATH'):
+            path=directives['PCGI_MODULE_PATH']
+            path,module=os.path.split(path)
+            module,ext=os.path.splitext(module)
+            self.module=module
+
+    def handle_accept (self):
+        self.count.increment()
+        try:
+            conn, addr = self.accept()
+        except socket.error:
+            self.log_info('Server accept() threw an exception', 'warning')
+            return
+        self.channel_class(self, conn, addr)
+
+    def readable(self):
+        return len(asyncore.socket_map) < CONNECTION_LIMIT
+
+    def writable (self):
+        return 0
+
+    def listen(self, num):
+        # override asyncore limits for nt's listen queue size
+        self.accepting = 1
+        return self.socket.listen (num)
+
+
+class PCGIResponse(HTTPResponse):
+
+    def write(self, data):
+        if not self._wrote:
+            self.stdout.write(str(self))
+            self._wrote=1
+        self.stdout.write(data)
+
+    def _finish(self):
+        self.stdout.finish(self)
+        self.stdout.close()
+
+        self.stdout=None
+        self._request=None
+
+
+class PCGIPipe:
+    """
+    Formats a HTTP response in PCGI format
+
+        10 digits indicating len of STDOUT
+        STDOUT
+        10 digits indicating len of STDERR
+        STDERR
+
+    Note that this implementation never sends STDERR
+    """
+    def __init__(self, channel):
+        self._channel=channel
+        self._data=StringIO()
+        self._shutdown=0
+
+    def write(self,text):
+        self._data.write(text)
+
+    def close(self):
+        if not self._channel.closed:
+            data=self._data.getvalue()
+            l=len(data)
+            DebugLogger.log('A', id(self._channel),
+                '%s %s' % (self._channel.reply_code, l))
+            self._channel.push('%010d%s%010d' % (l, data, 0), 0)
+            self._channel.push(LoggingProducer(self._channel, l, 'log_request'), 0)
+            self._channel.push(CallbackProducer(
+                lambda t=('E', id(self._channel)): apply(DebugLogger.log,t)), 0)
+
+            if self._shutdown:
+                try: r=self._shutdown[0]
+                except: r=0
+                ZServer.exit_code=r
+                self._channel.push(ShutdownProducer(), 0)
+                Wakeup(lambda: asyncore.close_all())
+            else:
+                self._channel.push(None, 0)
+                Wakeup()
+        self._data=None
+        self._channel=None
+
+    def finish(self, response):
+        if response._shutdownRequested():
+            self._shutdown = 1
+        self._channel.reply_code=response.status


=== Zope/lib/python/ZServer/Producers.py 1.8 => 1.9 ===
--- /dev/null	Tue Mar 18 16:15:46 2003
+++ Zope/lib/python/ZServer/Producers.py	Tue Mar 18 16:15:14 2003
@@ -0,0 +1,104 @@
+##############################################################################
+#
+# Copyright (c) 2001 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
+#
+##############################################################################
+"""
+ZServer pipe utils. These producers basically function as callbacks.
+"""
+
+import asyncore
+import sys
+
+class ShutdownProducer:
+    "shuts down medusa"
+
+    def more(self):
+        asyncore.close_all()
+
+
+class LoggingProducer:
+    "logs request"
+
+    def __init__(self, logger, bytes, method='log'):
+        self.logger=logger
+        self.bytes=bytes
+        self.method=method
+
+    def more(self):
+        getattr(self.logger, self.method)(self.bytes)
+        self.logger=None
+        return ''
+
+
+class CallbackProducer:
+    "Performs a callback in the channel's thread"
+
+    def __init__(self, callback):
+        self.callback=callback
+
+    def more(self):
+        self.callback()
+        self.callback=None
+        return ''
+
+
+class file_part_producer:
+    "producer wrapper for part of a file[-like] objects"
+
+    # match http_channel's outgoing buffer size
+    out_buffer_size = 1<<16
+
+    def __init__(self, file, lock, start, end):
+        self.file=file
+        self.lock=lock
+        self.start=start
+        self.end=end
+
+    def more(self):
+        end=self.end
+        if not end: return ''
+        start=self.start
+        if start >= end: return ''
+
+        file=self.file
+        size=end-start
+        bsize=self.out_buffer_size
+        if size > bsize: size=bsize
+
+        self.lock.acquire()
+        try:
+            file.seek(start)
+            data = file.read(size)
+        finally:
+            self.lock.release()
+
+        if data:
+            start=start+len(data)
+            if start < end:
+                self.start=start
+                return data
+
+        self.end=0
+        del self.file
+
+        return data
+
+
+class file_close_producer:
+    def __init__(self, file):
+        self.file=file
+
+    def more(self):
+        file=self.file
+        if file is not None:
+            file.close()
+            self.file=None
+        return ''


=== Zope/lib/python/ZServer/README.txt 1.11 => 1.12 ===
--- /dev/null	Tue Mar 18 16:15:46 2003
+++ Zope/lib/python/ZServer/README.txt	Tue Mar 18 16:15:14 2003
@@ -0,0 +1,236 @@
+ZServer README
+--------------
+
+What is ZServer?
+  
+  ZServer is an integration of the Zope application server and the
+  Medusa information server. See the ZServer architecture document for
+  more information::
+  
+    http://www.zope.org/Documentation/Reference/ZServer
+    
+  ZServer gives you HTTP, FTP, WebDAV, PCGI, and remote interactive
+  Python access. In later releases it will probably offer more
+  protocols such as FastCGI, etc.
+
+What is Medusa?
+
+  Medusa is a Python server framework with uses a single threaded
+  asynchronous sockets approach. For more information see::
+  
+    http://www.nightmare.com/medusa
+  
+  There's also an interesting Medusa tutorial at::
+  
+    http://www.nightmare.com:8080/nm/apps/medusa/docs/programming.html
+
+ZServer HTTP support
+
+  ZServer offers HTTP 1.1 publishing for Zope. It does not support
+  publishing files from the file system. You can specify the HTTP port
+  using the -w command line argument for the z2.py start script. You
+  can also specify CGI environment variables on the command line using
+  z2.py
+
+ZServer FTP support
+
+  What you can do with FTP
+
+    FTP access to Zope allows you to FTP to the Zope object hierarchy
+    in order to perform managerial tasks. You can:
+
+      * Navigate the object hierarchy with 'cd'
+    
+      * Replace the content of Documents, Images, and Files
+    
+      * Create Documents, Images, Files, Folders
+    
+      * Delete objects and Folders.
+
+    So basically you can do more than is possible with HTTP PUT. Also,
+    unlike PUT, FTP gives you access to Document content. So when you
+    download a Document you are getting its content, not what it looks
+    like when it is rendered.
+
+  Using FTP
+  
+    To FTP into Zope, ZServer must be configured to serve FTP. By
+    default ZServer serves FTP on port 9221. So to connect to Zope you
+    would issue a command like so::
+    
+      $ ftp localhost 9221
+      
+    When logging in to FTP, you have some choices. You can connect
+    anonymously by using a username of 'anonymous' and any password.
+    Or you can login as a Zope user. Since Zope users are defined at
+    different locations in the object hierarchy, authentication can be
+    problematic. There are two solutions:
+    
+      * login and then cd to the directory where you are defined.
+      
+      * login with a special name that indicates where you are
+      defined.
+      
+    The format of the special name is '<username>@<path>'. For
+    example::
+    
+      joe@Marketing/Projects
+
+  FTP permissions
+
+    FTP support is provided for Folders, Documents, Images, and Files.
+    You can control access to FTP via the new 'FTP access' permission.
+    This permission controls the ability to 'cd' to a Folder and to
+    download objects. Uploading and deleting and creating objects are
+    controlled by existing permissions.
+
+  FTP limits
+  
+    You can set limits for the number of simultaneous FTP connections.
+    You can separately configure the number of anonymous and
+    authenticated connections. Right now this setting is set in
+    'ZServerFTP.py'. In the future, it may be more easy to configure.
+
+  Properties and FTP: The next step
+
+    The next phase of FTP support will allow you to edit properties of
+    all Zope objects. Probably properties will be exposed via special
+    files which will contain an XML representation of the object's
+    properties. You could then download the file, edit the XML and
+    upload it to change the object's properties.
+
+    We do not currently have a target date for FTP property support.
+
+  How does FTP work?
+
+    The ZServer's FTP channel object translates FTP requests into
+    ZPublisher requests. The FTP channel then analyses the response
+    and formulates an appropriate FTP response. The FTP channel
+    stores some state such as the current working directory and the
+    username and password.
+
+    On the Zope side of things, the 'lib/python/OFS/FTPInterface.py'
+    module defines the Zope FTP interface, for listing sub-items,
+    stating, and getting content. The interface is implemented in
+    'SimpleItem', and in other Zope classes. Programmers will not
+    need to implement the entire interface if they inherit from
+    'SimpleItem'. All the other FTP functions are handled by
+    existing methods like 'manage_delObjects', and 'PUT', etc.
+
+ZServer PCGI support
+
+  ZServer will service PCGI requests with both inet and unix domain
+  sockets. This means you can use ZServer instead of
+  'pcgi_publisher.py' as your long running PCGI server process. In the
+  future, PCGI may be able to activate ZServer.
+  
+  Using PCGI instead of HTTP allows you to forward requests from
+  another web server to ZServer. The CGI environment and HTTP headers
+  are controlled by the web server, so you don't need to worry about
+  managing the ZServer environment. However, this configuration will
+  impose a larger overhead than simply using the web server as an HTTP
+  proxy for ZServer.
+  
+  To use PCGI, configure your PCGI info files to communicate with
+  ZServer by setting the PCGI_PORT, PCGI_SOCKET_FILE, and PCGI_NAME.
+  The other PCGI settings are currently ignored by ZServer.
+
+  ZServer's PCGI support will work with mod_pcgi.
+
+ZServer monitor server
+
+  ZServer now includes the Medusa monitor server. This basically gives
+  you a remote, secure Python prompt. You can interactively access Zope.
+  This is a very powerful, but dangerous tool. Be careful.
+  
+  To use the monitor server specify a monitor port number using the -m
+  option with the z2.py start script. The default port is 9999.
+  
+  To connect to the monitor server use the 'ZServer/medusa/monitor_client.py'
+  or 'ZServer/medusa/monitor_client_win32.py' script. For example::
+  
+    $ python2.1 ZServer/medusa/monitor_client.py localhost 9999
+	
+  You will then be asked to enter a password. This is the Zope super manager
+  password which is stored in the 'access' file.
+  
+  Then you will be greeted with a Python prompt. To access Zope import
+  the Zope module::
+  
+    >>> import Zope
+
+  The Zope top level Zope object is available via the 'Zope.app' function::
+  
+    >>> a=Zope.app()
+
+  From this object you can reach all other Zope objects as subobjects.
+  
+  Remember if you make changes to Zope objects and want those changes to be
+  saved you need to commmit the transaction::
+  
+    >>> get_transaction().commit()
+	
+ZServer WebDAV support
+
+  WebDAV is a new protocol for managing web resources. WebDAV operates
+  over HTTP. Since WebDAV uses HTTP, ZServer doesn't really have to do
+  anything special, except stay out of Zope's way when handling WebDAV
+  requests.
+  
+  The only major WebDAV client at this time is Internet Explorer 5. It
+  works with Zope.
+
+Differences between ZopeHTTPServer and ZServer
+
+  ZopeHTTPServer is old and no longer being actively maintained.
+  
+  Both ZopeHTTPServer and ZServer are Python HTTP servers.
+  ZopeHTTPServer is built on the standard Python SimpleHTTPServer
+  framework. ZServer is built on Medusa.
+
+  ZopeHTTPServer is very limited. It can only publish one module at a
+  time. It can only publish via HTTP. It has no support for thread
+  pools.
+  
+  ZServer on the other hand is more complex and supports publishing
+  multiple modules, thread pools, and it uses a new threaded
+  architecture for accessing ZPublisher.
+  
+Running ZServer as nobody
+
+  Normally ZServer will run with the userid of the user who starts
+  it. However, if ZServer is started by root, it will attempt to
+  become nobody or any userid you specify with the -u argument to the
+  z2.py start script.
+ 
+  ZServer is similar to ZopeHTTPServer in these respects.
+
+  If you run Zope with different userids you must be aware of
+  permission issues. Zope must be able to read and write to the 'var'
+  directory. If you change the userid Zope is running under you will
+  probably need to change the permissions on the 'var' directory
+  and the files in it in order for Zope to run under a different
+  userid.
+
+Support
+
+  Questions and comments should go to 'support@digicool.com'.
+
+  You can report bugs and check on the status of bugs using the Zope
+  bug collector::
+    
+    http://www.zope.org/Resources/Collector/
+
+License
+
+  ZServer is covered by the ZPL despite the fact that it comes with
+  much of the Medusa source code. The portions of Medusa that come
+  with ZServer are licensed under the ZPL.
+
+Outstanding issues
+
+  The FTP interface for Zope objects may be changed.
+  
+  HTTP 1.1 support is ZServer is incomplete, though it should work for
+  most HTTP 1.1 clients.
+  


=== Zope/lib/python/ZServer/WebDAVSrcHandler.py 1.9 => 1.10 ===
--- /dev/null	Tue Mar 18 16:15:46 2003
+++ Zope/lib/python/ZServer/WebDAVSrcHandler.py	Tue Mar 18 16:15:14 2003
@@ -0,0 +1,59 @@
+##############################################################################
+#
+# Copyright (c) 2001 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.
+#
+##############################################################################
+
+"""HTTP handler which forces GET requests to return the document source.
+
+Works around current WebDAV clients' failure to implement the
+'source-link' feature of the specification.  Uses manage_FTPget().
+"""
+
+import os
+import posixpath
+
+from ZServer.HTTPServer import zhttp_handler
+
+__version__ = "1.0"
+
+
+class WebDAVSrcHandler(zhttp_handler):
+
+    def get_environment(self, request):
+        """Munge the request to ensure that we call manage_FTPGet."""
+        env = zhttp_handler.get_environment(self, request)
+
+        # Set a flag to indicate this request came through the WebDAV source
+        # port server.
+        env['WEBDAV_SOURCE_PORT'] = 1
+
+        if env['REQUEST_METHOD'] == 'GET':
+            path_info = env['PATH_INFO']
+            if os.sep != '/':
+                path_info =  path_info.replace(os.sep, '/')
+            path_info = posixpath.join(path_info, 'manage_FTPget')
+            path_info = posixpath.normpath(path_info)
+            env['PATH_INFO'] = path_info
+
+        # Workaround for lousy WebDAV implementation of M$ Office 2K.
+        # Requests for "index_html" are *sometimes* send as "index_html."
+        # We check the user-agent and remove a trailing dot for PATH_INFO
+        # and PATH_TRANSLATED
+
+        if env.get("HTTP_USER_AGENT", "").find(
+            "Microsoft Data Access Internet Publishing Provider") > -1:
+            if env["PATH_INFO"][-1] == '.':
+                env["PATH_INFO"] = env["PATH_INFO"][:-1]
+
+            if env["PATH_TRANSLATED"][-1] == '.':
+                env["PATH_TRANSLATED"] = env["PATH_TRANSLATED"][:-1]
+
+        return env


=== Zope/lib/python/ZServer/ZService.py 1.15 => 1.16 ===
--- /dev/null	Tue Mar 18 16:15:46 2003
+++ Zope/lib/python/ZServer/ZService.py	Tue Mar 18 16:15:14 2003
@@ -0,0 +1,241 @@
+##############################################################################
+#
+# Copyright (c) 2001 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
+#
+##############################################################################
+"""
+ZServer as a NT service.
+
+The serice starts up and monitors a ZServer process.
+
+Features:
+
+  * When you start the service it starts ZServer
+  * When you stop the serivice it stops ZServer
+  * It monitors ZServer and restarts it if it exits abnormally
+  * If ZServer is shutdown from the web, the service stops.
+  * If ZServer cannot be restarted, the service stops.
+
+Usage:
+
+  Installation
+
+    The ZServer service should be installed by the Zope Windows
+    installer. You can manually install, uninstall the service from
+    the commandline.
+
+      ZService.py [options] install|update|remove|start [...]
+          |stop|restart [...]|debug [...]
+
+    Options for 'install' and 'update' commands only:
+
+     --username domain\username : The Username the service is to run
+                                  under
+
+     --password password : The password for the username
+
+     --startup [manual|auto|disabled] : How the service starts,
+                                        default = manual
+
+    Commands
+
+      install : Installs the service
+
+      update : Updates the service, use this when you change
+               ZServer.py
+
+      remove : Removes the service
+
+      start : Starts the service, this can also be done from the
+              services control panel
+
+      stop : Stops the service, this can also be done from the
+             services control panel
+
+      restart : Restarts the service
+
+      debug : Runs the service in debug mode
+
+    You can view the usage options by running ZServer.py without any
+    arguments.
+
+    Note: you may have to register the Python service program first,
+
+      win32\pythonservice.exe /register
+
+  Starting Zope
+
+    Start Zope by clicking the 'start' button in the services control
+    panel. You can set Zope to automatically start at boot time by
+    choosing 'Auto' startup by clicking the 'statup' button.
+
+  Stopping Zope
+
+    Stop Zope by clicking the 'stop' button in the services control
+    panel. You can also stop Zope through the web by going to the
+    Zope control panel and by clicking 'Shutdown'.
+
+  Event logging
+
+    Zope events are logged to the NT application event log. Use the
+    event viewer to keep track of Zope events.
+
+  Registry Settings
+
+    You can change how the service starts ZServer by editing a registry
+    key.
+
+      HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\
+        <Service Name>\Parameters\start
+
+    The value of this key is the command which the service uses to
+    start ZServer. For example:
+
+      "C:\Program Files\Zope\bin\python.exe"
+        "C:\Program Files\Zope\z2.py" -w 8888
+
+
+TODO:
+
+  * Integrate it into the Windows installer.
+  * Add ZLOG logging in addition to event log logging.
+  * Make it easier to run multiple Zope services with one Zope install
+
+This script does for NT the same sort of thing zdaemon.py does for UNIX.
+Requires Python win32api extensions.
+"""
+import sys, os,  time, imp
+
+# Some fancy path footwork is required here because we
+# may be run from python.exe or lib/win32/PythonService.exe
+
+home=os.path.split(os.path.split(sys.executable)[0])[0]
+if sys.executable[-10:]!='python.exe':
+    home=os.path.split(home)[0]
+    home=os.path.split(home)[0]
+sys.path.append(os.path.join(home, 'bin'))
+sys.path.append(os.path.join(home, 'ZServer'))
+sys.path.append(os.path.join(home, 'bin', 'lib', 'win32'))
+sys.path.append(os.path.join(home, 'bin', 'lib', 'win32', 'lib'))
+
+
+# pythoncom and pywintypes are special, and require these hacks when
+# we dont have a standard Python installation around.
+
+import win32api
+def magic_import(modulename, filename):
+    # by Mark Hammond
+    try:
+        # See if it does import first!
+        return __import__(modulename)
+    except ImportError:
+        pass
+    # win32 can find the DLL name.
+    h = win32api.LoadLibrary(filename)
+    found = win32api.GetModuleFileName(h)
+    # Python can load the module
+    mod = imp.load_module(modulename, None, found, ('.dll', 'rb', imp.C_EXTENSION))
+    # inject it into the global module list.
+    sys.modules[modulename] = mod
+    # And finally inject it into the namespace.
+    globals()[modulename] = mod
+    win32api.FreeLibrary(h)
+
+magic_import('pywintypes','pywintypes21.dll')
+
+import win32serviceutil, win32service, win32event, win32process
+try: import servicemanager
+except: pass
+
+
+
+class ZServerService(win32serviceutil.ServiceFramework):
+
+    # Some trickery to determine the service name. The WISE
+    # installer will write an svcname.txt to the ZServer dir
+    # that we can use to figure out our service name.
+
+    path=os.path.join(home, 'ZServer', 'svcname.txt')
+    file=open(path, 'r')
+    _svc_name_=file.readline().strip()
+    file.close()
+
+    _svc_display_name_ = "Zope (%s)" % _svc_name_
+
+    restart_min_time=5 # if ZServer restarts before this many
+                       # seconds then we have a problem, and
+                       # need to stop the service.
+
+    def __init__(self, args):
+        win32serviceutil.ServiceFramework.__init__(self, args)
+        self.hWaitStop = win32event.CreateEvent(None, 0, 0, None)
+
+    def SvcDoRun(self):
+        self.start_zserver()
+        while 1:
+            rc=win32event.WaitForMultipleObjects(
+                    (self.hWaitStop, self.hZServer), 0, win32event.INFINITE)
+            if rc - win32event.WAIT_OBJECT_0 == 0:
+                break
+            else:
+                self.restart_zserver()
+        self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING, 5000)
+
+    def SvcStop(self):
+        servicemanager.LogInfoMsg('Stopping Zope.')
+        try:
+            self.stop_zserver()
+        except:
+            pass
+        self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
+        win32event.SetEvent(self.hWaitStop)
+
+    def start_zserver(self):
+        sc=self.get_start_command()
+        result=win32process.CreateProcess(None, self.get_start_command(),
+                None, None, 0, 0, None, None, win32process.STARTUPINFO())
+        self.hZServer=result[0]
+        self.last_start_time=time.time()
+        servicemanager.LogInfoMsg('Starting Zope.')
+
+    def stop_zserver(self):
+        win32process.TerminateProcess(self.hZServer,0)
+
+    def restart_zserver(self):
+        if time.time() - self.last_start_time < self.restart_min_time:
+            servicemanager.LogErrorMsg('Zope died and could not be restarted.')
+            self.SvcStop()
+        code=win32process.GetExitCodeProcess(self.hZServer)
+        if code == 0:
+            # Exited with a normal status code,
+            # assume that shutdown is intentional.
+            self.SvcStop()
+        else:
+            servicemanager.LogWarningMsg('Restarting Zope.')
+            self.start_zserver()
+
+    def get_start_command(self):
+        return win32serviceutil.GetServiceCustomOption(self,'start')
+
+
+def set_start_command(value):
+    "sets the ZServer start command if the start command is not already set"
+    current=win32serviceutil.GetServiceCustomOption(ZServerService,
+                                                    'start', None)
+    if current is None:
+        win32serviceutil.SetServiceCustomOption(ZServerService,'start',value)
+
+
+if __name__=='__main__':
+    win32serviceutil.HandleCommandLine(ZServerService)
+    if 'install' in sys.argv:
+        command='"%s" "%s" -S' % (sys.executable, os.path.join(home,'z2.py'))
+        set_start_command(command)
+        print "Setting ZServer start command to:", command


=== Zope/lib/python/ZServer/__init__.py 1.28 => 1.29 ===
--- /dev/null	Tue Mar 18 16:15:46 2003
+++ Zope/lib/python/ZServer/__init__.py	Tue Mar 18 16:15:14 2003
@@ -0,0 +1,78 @@
+##############################################################################
+#
+# Copyright (c) 2001 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.
+#
+##############################################################################
+
+import sys
+
+from medusa.test import max_sockets
+CONNECTION_LIMIT=max_sockets.max_select_sockets()
+
+ZSERVER_VERSION='1.1'
+try:
+    import App.version_txt
+    ZOPE_VERSION=App.version_txt.version_txt()
+except:
+    ZOPE_VERSION='experimental'
+
+exit_code = 0
+
+# Try to poke zLOG default logging into asyncore
+# XXX We should probably should do a better job of this,
+#     however that would mean that ZServer required zLOG.
+#     (Is that really a bad thing?)
+try:
+    from zLOG import LOG, register_subsystem, BLATHER, INFO, WARNING, ERROR
+except ImportError:
+    pass
+else:
+    register_subsystem('ZServer')
+    severity={'info':INFO, 'warning':WARNING, 'error': ERROR}
+
+    def log_info(self, message, type='info'):
+        if message[:14]=='adding channel' or \
+           message[:15]=='closing channel' or \
+           message == 'Computing default hostname':
+            LOG('ZServer', BLATHER, message)
+        else:
+            LOG('ZServer', severity[type], message)
+
+    import asyncore
+    asyncore.dispatcher.log_info=log_info
+
+# A routine to try to arrange for request sockets to be closed
+# on exec. This makes it easier for folks who spawn long running
+# processes from Zope code. Thanks to Dieter Maurer for this.
+try:
+    import fcntl
+
+    def requestCloseOnExec(sock):
+        try:
+            fcntl.fcntl(sock.fileno(), fcntl.F_SETFD, fcntl.FD_CLOEXEC)
+        except: # XXX What was this supposed to catch?
+            pass
+
+except (ImportError, AttributeError):
+
+    def requestCloseOnExec(sock):
+        pass
+
+import asyncore
+from medusa import resolver, logger
+from HTTPServer import zhttp_server, zhttp_handler
+from PCGIServer import PCGIServer
+from FCGIServer import FCGIServer
+from FTPServer import FTPServer
+from PubCore import setNumberOfThreads
+from medusa.monitor import secure_monitor_server
+
+# override the service name in logger.syslog_logger
+logger.syslog_logger.svc_name='ZServer'


=== Zope/lib/python/ZServer/component.xml 1.1 => 1.2 ===
--- /dev/null	Tue Mar 18 16:15:46 2003
+++ Zope/lib/python/ZServer/component.xml	Tue Mar 18 16:15:14 2003
@@ -0,0 +1,61 @@
+<component prefix="ZServer.datatypes">
+
+  <abstracttype name="server">
+    <description>
+      The "server" type is used to describe a single type of server
+      instance.  The value for a server section is an object with the
+      ServerFactory interface.
+    </description>
+  </abstracttype>
+
+  <sectiontype name="http-server"
+               datatype=".HTTPServerFactory"
+               implements="server">
+     <key name="address" datatype="inet-address"/>
+     <key name="force-connection-close" datatype="boolean" default="off"/>
+     <key name="webdav-source-clients">
+       <description>
+         Regular expression used to identify clients who should
+         receive WebDAV source responses to GET requests.
+       </description>
+     </key>
+  </sectiontype>
+
+  <sectiontype name="webdav-source-server"
+               datatype=".WebDAVSourceServerFactory"
+               implements="server">
+     <key name="address" datatype="inet-address"/>
+     <key name="force-connection-close" datatype="boolean" default="off"/>
+  </sectiontype>
+
+  <sectiontype name="persistent-cgi"
+               datatype=".PCGIServerFactory"
+               implements="server">
+    <key name="path" datatype="existing-file"/>
+  </sectiontype>
+
+  <sectiontype name="fast-cgi"
+               datatype=".FCGIServerFactory"
+               implements="server">
+    <key name="address" datatype="socket-address"/>
+  </sectiontype>
+
+  <sectiontype name="ftp-server"
+               datatype=".FTPServerFactory"
+               implements="server">
+     <key name="address" datatype="inet-address"/>
+  </sectiontype>
+
+  <sectiontype name="monitor-server"
+               datatype=".MonitorServerFactory"
+               implements="server">
+     <key name="address" datatype="inet-address"/>
+  </sectiontype>
+
+  <sectiontype name="icp-server"
+               datatype=".ICPServerFactory"
+               implements="server">
+     <key name="address" datatype="inet-address"/>
+  </sectiontype>
+
+</component>


=== Zope/lib/python/ZServer/datatypes.py 1.1 => 1.2 ===
--- /dev/null	Tue Mar 18 16:15:47 2003
+++ Zope/lib/python/ZServer/datatypes.py	Tue Mar 18 16:15:14 2003
@@ -0,0 +1,167 @@
+##############################################################################
+#
+# Copyright (c) 2003 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.
+#
+##############################################################################
+"""ZConfig datatype support for ZServer.
+
+Each server type is represented by a ServerFactory instance.
+"""
+
+class ServerFactory:
+    def __init__(self, address=None):
+        if address is None:
+            self.host = None
+            self.port = None
+        else:
+            self.host, self.port = address
+
+    def prepare(self, defaulthost=None, dnsresolver=None,
+                module=None, env=None, portbase=None):
+        if defaulthost is not None:
+            self._set_default_host(defaulthost)
+        self.dnsresolver = dnsresolver
+        self.module = module
+        self.cgienv = env
+        if portbase and self.port is not None:
+            self.port += portbase
+
+    def _set_default_host(self, host):
+        if not self.host:
+            self.host = host
+
+    def servertype(self):
+        s = self.__class__.__name__
+        if s.endswith("Factory"):
+            s = s[:-7]
+        return s
+
+    def create(self):
+        raise NotImplementedError(
+            "Concrete ServerFactory classes must implement create().")
+
+
+class HTTPServerFactory(ServerFactory):
+    def __init__(self, section):
+        ServerFactory.__init__(self, section.address)
+        self.force_connection_close = section.force_connection_close
+        # webdav-source-server sections won't have webdav_source_clients:
+        webdav_clients = getattr(section, "webdav_source_clients", None)
+        self.webdav_source_clients = webdav_clients
+
+    def create(self):
+        from ZServer import HTTPServer
+        from ZServer.AccessLogger import access_logger
+        handler = self.createHandler()
+        handler._force_connection_close = self.force_connection_close
+        if self.webdav_source_clients:
+            handler.set_webdav_source_clients(self.webdav_source_clients)
+        server = HTTPServer.zhttp_server(ip=self.host, port=self.port,
+                                         resolver=self.dnsresolver,
+                                         logger_object=access_logger)
+        server.install_handler(handler)
+        return server
+
+    def createHandler(self):
+        from ZServer import HTTPServer
+        return HTTPServer.zhttp_handler(self.module, '', self.cgienv)
+
+
+class WebDAVSourceServerFactory(HTTPServerFactory):
+    def createHandler(self):
+        from ZServer.WebDAVSrcHandler import WebDAVSrcHandler
+        return WebDAVSrcHandler(self.module, '', self.cgienv)
+
+
+class FTPServerFactory(ServerFactory):
+    def __init__(self, section):
+        ServerFactory.__init__(self, section.address)
+
+    def create(self):
+        from ZServer.AccessLogger import access_logger
+        from ZServer.FTPServer import FTPServer
+        return FTPServer(ip=self.host, port=self.port,
+                         module=self.module, resolver=self.dnsresolver,
+                         logger_object=access_logger)
+
+
+class PCGIServerFactory(ServerFactory):
+    def __init__(self, section):
+        ServerFactory.__init__(self)
+        self.path = section.path
+
+    def create(self):
+        from ZServer.AccessLogger import access_logger
+        from ZServer.PCGIServer import PCGIServer
+        return PCGIServer(ip=self.host, port=self.port,
+                          module=self.module, resolver=self.dnsresolver,
+                          pcgi_file=self.path,
+                          logger_object=access_logger)
+
+
+class FCGIServerFactory(ServerFactory):
+    def __init__(self, section):
+        import socket
+        if section.address.family == socket.AF_INET:
+            address = section.address.address
+            path = None
+        else:
+            address = None
+            path = section.address.address
+        ServerFactory.__init__(self, address)
+        self.path = path
+
+    def _set_default_host(self, host):
+        if self.path is None:
+            ServerFactory._set_default_host(self, host)
+
+    def create(self):
+        from ZServer.AccessLogger import access_logger
+        from ZServer.FCGIServer import FCGIServer
+        return FCGIServer(ip=self.host, port=self.port,
+                          socket_file=self.path,
+                          module=self.module, resolver=self.dnsresolver,
+                          logger_object=access_logger)
+
+
+class MonitorServerFactory(ServerFactory):
+    def __init__(self, section):
+        ServerFactory.__init__(self, section.address)
+
+    def create(self):
+        from ZServer.medusa.monitor import secure_monitor_server
+        return secure_monitor_server(hostname=self.host, port=self.port,
+                                     password=self.getPassword())
+
+    def getPassword(self):
+        # XXX This is really out of place; there should be a better
+        # way.  For now, at least we can make it a separate method.
+
+        import ZODB # :-( required to import user
+        from AccessControl.User import emergency_user
+        if hasattr(emergency_user, '__null_user__'):
+            pw = None
+        else:
+            pw = emergency_user._getPassword()
+        if pw is None:
+            import zLOG
+            zLOG.LOG("Zope", zLOG.WARNING, 'Monitor server not started'
+                     ' because no emergency user exists.')
+        return pw
+
+
+class ICPServerFactory(ServerFactory):
+    def __init__(self, section):
+        ServerFactory.__init__(self, section.address)
+
+    def create(self):
+        from ZServer.ICPServer import ICPServer
+        return ICPServer(self.host, self.port)