[Zope-Checkins] CVS: Zope/lib/python/ZServer - DebugLogger.py:1.1.2.1 FCGIServer.py:1.1.2.1 FTPRequest.py:1.1.2.1 FTPResponse.py:1.1.2.1 FTPServer.py:1.1.2.1 HTTPResponse.py:1.1.2.1 HTTPServer.py:1.1.2.1 ICPServer.py:1.1.2.1 INSTALL.txt:1.1.2.1 PCGIServer.py:1.1.2.1 Producers.py:1.1.2.1 README.txt:1.1.2.1 WebDAVSrcHandler.py:1.1.2.1 ZService.py:1.1.2.1 __init__.py:1.1.2.1

Chris McDonough chrism@zope.com
Tue, 17 Sep 2002 01:16:07 -0400


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

Added Files:
      Tag: chrism-install-branch
	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 
Log Message:
Moved ZServer into lib/python.


=== Added File Zope/lib/python/ZServer/DebugLogger.py ===
##############################################################################
#
# 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 time, thread

class DebugLogger:
    """
    Logs debugging information about how ZServer is handling requests
    and responses. This log can be used to help locate troublesome requests.

    The format 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 time in localtime ISO 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.

    Note: This facility will be probably be adapted to the zLOG framework.
    """

    def __init__(self, filename):
        self.filename = filename
        self.file=open(filename, 'a+b')
        l=thread.allocate_lock()
        self._acquire=l.acquire
        self._release=l.release
        self.log('U', '000000000', 'System startup')

    def reopen(self):
        self.file.close()
        self.file=open(self.filename, 'a+b')
        self.log('U', '000000000', 'Logfile reopened')

    def log(self, code, request_id, data=''):
        self._acquire()
        try:
            t=time.strftime('%Y-%m-%dT%H:%M:%S', time.localtime(time.time()))
            self.file.write(
                '%s %s %s %s\n' % (code, request_id, t, data)
                )
            self.file.flush()
        finally:
            self._release()


def log(*args): pass


=== Added File Zope/lib/python/ZServer/FCGIServer.py === (666/766 lines abridged)
##############################################################################
#
# 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
from types import StringType
import thread


[-=- -=- -=- 666 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


#----------------------------------------------------------------------


=== Added File Zope/lib/python/ZServer/FTPRequest.py ===
##############################################################################
#
# 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


=== Added File Zope/lib/python/ZServer/FTPResponse.py ===
##############################################################################
#
# 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


=== Added File Zope/lib/python/ZServer/FTPServer.py === (537/637 lines abridged)
##############################################################################
#
# 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
  proceed to 'cd' to a directory in which they are authorized. In this
  case for the purpose of FTP limits, the user is considered anonymous
  until they cd to an authorized directory.

[-=- -=- -=- 537 lines omitted -=- -=- -=-]

                return None
        if total <= self.total_limit:
            return 1


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 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)


=== Added File Zope/lib/python/ZServer/HTTPResponse.py ===
##############################################################################
#
# 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


=== Added File Zope/lib/python/ZServer/HTTPServer.py ===
##############################################################################
#
# 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 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':
            wdav_client_reg = getattr(sys,'WEBDAV_SOURCE_PORT_CLIENTS',None)

            if wdav_client_reg:
                agent = get_header(USER_AGENT,request.header)
                if wdav_client_reg(agent):

                    env['WEBDAV_SOURCE_PORT'] = 1
                    path_info  = env['PATH_INFO']
                    path_info  = os.path.join(path_info,'manage_FTPget')
                    path_info  = os.path.normpath(path_info)
                    if os.sep != '/': path_info = path_info.replace(os.sep,'/')
                    env['PATH_INFO'] = path_info


        # 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

    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
    zombie_timeout=100*60 # 100 minutes

    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 work(self):
        "try to handle a request"
        if not self.working:
            if self.queue:
                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()


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 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)


=== Added File Zope/lib/python/ZServer/ICPServer.py ===
##############################################################################
#
# 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

    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 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 1

    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


=== Added File Zope/lib/python/ZServer/INSTALL.txt ===
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!
  


=== Added File Zope/lib/python/ZServer/PCGIServer.py ===
##############################################################################
#
# 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

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
                sys.ZServerExitCode=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


=== Added File Zope/lib/python/ZServer/Producers.py ===
##############################################################################
#
# 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 ''


=== Added File Zope/lib/python/ZServer/README.txt ===
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.
  


=== Added File Zope/lib/python/ZServer/WebDAVSrcHandler.py ===
##############################################################################
#
# 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
#
##############################################################################

"""
    Special HTTP handler which forces GET requests to return the "source"
    of documents (uses 'manage_FTPget').  Works around current WebDAV
    clients' failure to implement the "source-link" feature of the spec.
"""

__version__ = "1.0"

from HTTPServer import zhttp_handler
import  os

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']
            path_info = os.path.join( path_info, 'manage_FTPget' )
            path_info = os.path.normpath( path_info )
            if os.sep != '/':
                path_info =  path_info.replace( os.sep, '/' )
            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


=== Added File Zope/lib/python/ZServer/ZService.py ===
##############################################################################
#
# 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


=== Added File Zope/lib/python/ZServer/__init__.py ===
##############################################################################
#
# 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, os

# HACKERY to get around asyncore issues. This ought to go away! We're
# currently using the Python 2.2 asyncore bundled with Zope to override
# brokenness in the Python 2.1 version. We need to do some funny business
# to make this work, as a 2.2-ism crept into the asyncore code.
if os.name == 'posix':
    import fcntl
    if not hasattr(fcntl, 'F_GETFL'):
        import FCNTL
        fcntl.F_GETFL = FCNTL.F_GETFL
        fcntl.F_SETFL = FCNTL.F_SETFL

from medusa import asyncore
sys.modules['asyncore'] = asyncore



from medusa.test import max_sockets
CONNECTION_LIMIT=max_sockets.max_select_sockets()

ZSERVER_VERSION='1.1b1'
import App.FindHomes
try:
    import App.version_txt
    ZOPE_VERSION=App.version_txt.version_txt()
except:
    ZOPE_VERSION='experimental'


# 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.
try:
    from zLOG import LOG, register_subsystem, BLATHER, INFO, WARNING, ERROR
    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
except:
    pass

# 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
    try:
        from fcntl import F_SETFD, FD_CLOEXEC
    except ImportError:
        from FCNTL import F_SETFD, FD_CLOEXEC

    def requestCloseOnExec(sock):
        try:    fcntl.fcntl(sock.fileno(), F_SETFD, FD_CLOEXEC)
        except: 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'