[Zope-Checkins] CVS: Zope3/lib/python/Zope/Server/HTTP - Chunking.py:1.1.4.1 CommonHitLogger.py:1.1.4.1 HTTPRequestParser.py:1.1.4.1 HTTPServer.py:1.1.4.1 HTTPServerChannel.py:1.1.4.1 HTTPTask.py:1.1.4.1 PublisherHTTPServer.py:1.1.4.1 __init__.py:1.1.4.1 http_date.py:1.1.4.1

Shane Hathaway shane@cvs.zope.org
Fri, 12 Apr 2002 17:30:58 -0400


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

Added Files:
      Tag: Zope-3x-branch
	Chunking.py CommonHitLogger.py HTTPRequestParser.py 
	HTTPServer.py HTTPServerChannel.py HTTPTask.py 
	PublisherHTTPServer.py __init__.py http_date.py 
Log Message:
Merged Zope3-Server-Branch.


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

$Id: Chunking.py,v 1.1.4.1 2002/04/12 21:30:55 shane Exp $
"""

from Zope.Server.Utilities import find_double_newline
from Zope.Server.IStreamConsumer import IStreamConsumer


class ChunkedReceiver:

    __implements__ = IStreamConsumer

    chunk_remainder = 0
    control_line = ''
    all_chunks_received = 0
    trailer = ''
    completed = 0

    # max_control_line = 1024
    # max_trailer = 65536


    def __init__(self, buf):
        self.buf = buf

    def received(self, s):
        # Returns the number of bytes consumed.
        if self.completed:
            return 0
        orig_size = len(s)
        while s:
            rm = self.chunk_remainder
            if rm > 0:
                # Receive the remainder of a chunk.
                to_write = s[:rm]
                self.buf.append(to_write)
                written = len(to_write)
                s = s[written:]
                self.chunk_remainder -= written
            elif not self.all_chunks_received:
                # Receive a control line.
                s = self.control_line + s
                pos = s.find('\n')
                if pos < 0:
                    # Control line not finished.
                    self.control_line = s
                    s = ''
                else:
                    # Control line finished.
                    line = s[:pos]
                    s = s[pos + 1:]
                    self.control_line = ''
                    line = line.strip()
                    if line:
                        # Begin a new chunk.
                        semi = line.find(';')
                        if semi >= 0:
                            # discard extension info.
                            line = line[:semi]
                        sz = int(line.strip(), 16)  # hexadecimal
                        if sz > 0:
                            # Start a new chunk.
                            self.chunk_remainder = sz
                        else:
                            # Finished chunks.
                            self.all_chunks_received = 1
                    # else expect a control line.
            else:
                # Receive the trailer.
                trailer = self.trailer + s
                if trailer.startswith('\r\n'):
                    # No trailer.
                    self.completed = 1
                    return orig_size - (len(trailer) - 2)
                elif trailer.startswith('\n'):
                    # No trailer.
                    self.completed = 1
                    return orig_size - (len(trailer) - 1)
                pos = find_double_newline(trailer)
                if pos < 0:
                    # Trailer not finished.
                    self.trailer = trailer
                    s = ''
                else:
                    # Finished the trailer.
                    self.completed = 1
                    self.trailer = trailer[:pos]
                    return orig_size - (len(trailer) - pos)
        return orig_size


    def getfile(self):
        return self.buf.getfile()



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

$Id: CommonHitLogger.py,v 1.1.4.1 2002/04/12 21:30:55 shane Exp $
"""

import time
import sys

from http_date import monthname
from Zope.Server.Logger.FileLogger import FileLogger
from Zope.Server.Logger.ResolvingLogger import ResolvingLogger
from Zope.Server.Logger.UnresolvingLogger import UnresolvingLogger


class CommonHitLogger:
    """Outputs hits in common HTTP log format.
    """

    def __init__(self, logger_object=None, resolver=None):
        if logger_object is None:
            logger_object = FileLogger(sys.stdout)

        if resolver is not None:
            self.output = ResolvingLogger(resolver, logger_object)
        else:
            self.output = UnresolvingLogger(logger_object)

    def compute_timezone_for_log(self, tz):
        if tz > 0:
            neg = 1
        else:
            neg = 0
            tz = -tz
        h, rem = divmod (tz, 3600)
        m, rem = divmod (rem, 60)
        if neg:
            return '-%02d%02d' % (h, m)
        else:
            return '+%02d%02d' % (h, m)

    tz_for_log = None
    tz_for_log_alt = None

    def log_date_string(self, when):
        logtime = time.localtime(when)
        Y, M, D, h, m, s = logtime[:6]

        if not time.daylight:
            tz = self.tz_for_log
            if tz is None:
                tz = self.compute_timezone_for_log(time.timezone)
                self.tz_for_log = tz
        else:
            tz = self.tz_for_log_alt
            if tz is None:
                tz = self.compute_timezone_for_log(time.altzone)
                self.tz_for_log_alt = tz

        return '%d/%s/%02d:%02d:%02d:%02d %s' % (
            Y, monthname[M], D, h, m, s, tz)


    def log(self, task):
        """
        Receives a completed task and logs it in the
        common log format.
        """
        now = time.time()
        request_data = task.request_data
        req_headers = request_data.headers

        user_name = task.auth_user_name or 'anonymous'
        user_agent = req_headers.get('USER_AGENT', '')
        referer = req_headers.get('REFERER', '')

        self.output.log(
            task.channel.addr[0],
            ' - %s [%s] "%s" %s %d "%s" "%s"\n' % (
                user_name,
                self.log_date_string(now),
                request_data.first_line,
                task.status,
                task.bytes_written,
                referer,
                user_agent
                )
            )



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

$Id: HTTPRequestParser.py,v 1.1.4.1 2002/04/12 21:30:56 shane Exp $
"""

import re
from urllib import unquote

from Zope.Server.FixedStreamReceiver import FixedStreamReceiver
from Zope.Server.Buffers import OverflowableBuffer
from Zope.Server.Utilities import find_double_newline
from Zope.Server.IStreamConsumer import IStreamConsumer

try:
    from cStringIO import StringIO
except ImportError:
    from StringIO import StringIO


class HTTPRequestParser:
    """A structure that collects the HTTP request.

    Once the stream is completed, the instance is passed to
    a server task constructor.
    """

    __implements__ = IStreamConsumer

    completed = 0  # Set once request is completed.
    empty = 0        # Set if no request was made.
    header_plus = ''
    chunked = 0
    content_length = 0
    body_rcv = None
    # Other attributes: first_line, header, headers, command, uri, version,
    # path, query, fragment

    # headers is a mapping containing keys translated to uppercase
    # with dashes turned into underscores.

    def __init__(self, adj):
        """
        adj is an Adjustments object.
        """
        self.headers = {}
        self.adj = adj

    def received(self, data):
        """
        Receives the HTTP stream for one request.
        Returns the number of bytes consumed.
        Sets the completed flag once both the header and the
        body have been received.
        """
        if self.completed:
            return 0  # Can't consume any more.
        datalen = len(data)
        br = self.body_rcv
        if br is None:
            # In header.
            s = self.header_plus + data
            index = find_double_newline(s)
            if index >= 0:
                # Header finished.
                header_plus = s[:index]
                consumed = len(data) - (len(s) - index)
                self.in_header = 0
                # Remove preceeding blank lines.
                header_plus = header_plus.lstrip()
                if not header_plus:
                    self.empty = 1
                    self.completed = 1
                else:
                    self.parse_header(header_plus)
                    if self.body_rcv is None:
                        self.completed = 1
                return consumed
            else:
                # Header not finished yet.
                self.header_plus = s
                return datalen
        else:
            # In body.
            consumed = br.received(data)
            if br.completed:
                self.completed = 1
            return consumed


    def parse_header(self, header_plus):
        """
        Parses the header_plus block of text (the headers plus the
        first line of the request).
        """
        index = header_plus.find('\n')
        if index >= 0:
            first_line = header_plus[:index].rstrip()
            header = header_plus[index + 1:]
        else:
            first_line = header_plus.rstrip()
            header = ''
        self.first_line = first_line
        self.header = header

        lines = self.get_header_lines()
        headers = self.headers
        for line in lines:
            index = line.find(':')
            if index > 0:
                key = line[:index]
                value = line[index + 1:].strip()
                key1 = key.upper().replace('-', '_')
                headers[key1] = value
            # else there's garbage in the headers?

        command, uri, version = self.crack_first_line()
        self.command = str(command)
        if uri and '%' in uri:
            uri = unquote(uri)
        self.uri = str(uri)
        self.version = version
        self.split_uri()

        if version == '1.1':
            te = headers.get('TRANSFER_ENCODING', '')
            if te == 'chunked':
                from Chunking import ChunkedReceiver
                self.chunked = 1
                buf = OverflowableBuffer(self.adj.inbuf_overflow)
                self.body_rcv = ChunkedReceiver(buf)
        if not self.chunked:
            cl = int(headers.get('CONTENT_LENGTH', 0))
            self.content_length = cl
            if cl > 0:
                buf = OverflowableBuffer(self.adj.inbuf_overflow)
                self.body_rcv = FixedStreamReceiver(cl, buf)


    def get_header_lines(self):
        """
        Splits the header into lines, putting multi-line headers together.
        """
        r = []
        lines = self.header.split('\n')
        for line in lines:
            if line and line[0] in ' \t':
                r[-1] = r[-1] + line[1:]
            else:
                r.append(line)
        return r

    first_line_re = re.compile (
        '([^ ]+) (?:[^ :?#]+://[^ ?#/]*)?([^ ]+)(( HTTP/([0-9.]+))$|$)')

    def crack_first_line(self):
        r = self.first_line
        m = self.first_line_re.match (r)
        if m is not None and m.end() == len(r):
            if m.group(3):
                version = m.group(5)
            else:
                version = None
            return m.group(1).upper(), m.group(2), version
        else:
            return None, None, None

    path_regex = re.compile (
    #     path    query     fragment
        r'([^?#]*)(\?[^#]*)?(#.*)?'
        )

    def split_uri(self):
        m = self.path_regex.match (self.uri)
        if m.end() != len(self.uri):
            raise ValueError, "Broken URI"
        else:
            self.path, query, self.fragment = m.groups()
            if query:
                query = query[1:]
            self.query = query

    def getBodyStream(self):
        body_rcv = self.body_rcv
        if body_rcv is not None:
            return body_rcv.getfile()
        else:
            return StringIO('')


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

$Id: HTTPServer.py,v 1.1.4.1 2002/04/12 21:30:56 shane Exp $
"""

from Zope.Server.ServerBase import ServerBase
from HTTPServerChannel import HTTPServerChannel


class HTTPServer(ServerBase):
    """This is a generic HTTP Server."""

    __implements__ = ServerBase.__implements__

    channel_class = HTTPServerChannel
    SERVER_IDENT = 'Zope.Server.HTTPServer'

    def executeRequest(self, task):
        """Execute an HTTP request."""
        # This is a default implementation, meant to be overridden.
        body = "The HTTP server is running!\r\n" * 10
        task.response_headers['Content-Type'] = 'text/plain'
        task.response_headers['Content-Length'] = str(len(body))
        task.write(body)


if __name__ == '__main__':

    from Zope.Server.TaskThreads import ThreadedTaskDispatcher
    td = ThreadedTaskDispatcher()
    td.setThreadCount(4)
    HTTPServer('', 8080, task_dispatcher=td)

    try:
        import asyncore
        while 1:
            asyncore.poll(5)

    except KeyboardInterrupt:
        print 'shutting down...'
        td.shutdown()


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

$Id: HTTPServerChannel.py,v 1.1.4.1 2002/04/12 21:30:56 shane Exp $
"""

from Zope.Server.ServerChannelBase import ServerChannelBase
from HTTPTask import HTTPTask
from HTTPRequestParser import HTTPRequestParser


class HTTPServerChannel(ServerChannelBase):
    """HTTP-specific Server Channel"""

    __implements__ = ServerChannelBase.__implements__

    task_class = HTTPTask
    parser_class = HTTPRequestParser


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

$Id: HTTPTask.py,v 1.1.4.1 2002/04/12 21:30:56 shane Exp $
"""

import socket
import time

from http_date import build_http_date

from Zope.Server.IHeaderOutput import IHeaderOutput
from Zope.Server.ITask import ITask


rename_headers = {
    'CONTENT_LENGTH' : 'CONTENT_LENGTH',
    'CONTENT_TYPE'   : 'CONTENT_TYPE',
    'CONNECTION'     : 'CONNECTION_TYPE',
    }


class HTTPTask:
    """An HTTP task accepts a request and writes to a channel.

       Subclass this and override the execute() method.
    """

    __implements__ = ITask, IHeaderOutput  #, IOutputStream

    instream = None
    close_on_finish = 1
    status = '200'
    reason = 'Ok'
    wrote_header = 0
    accumulated_headers = None
    bytes_written = 0
    auth_user_name = ''
    cgi_env = None

    def __init__(self, channel, request_data):
        self.channel = channel
        self.request_data = request_data
        self.response_headers = {
            'Server': channel.server.SERVER_IDENT,
            }
        version = request_data.version
        if version not in ('1.0', '1.1'):
            # fall back to a version we support.
            version = '1.0'
        self.version = version


    ############################################################
    # Implementation methods for interface
    # Zope.Server.ITask

    def service(self):
        'See Zope.Server.ITask.ITask'
        try:
            try:
                self.start()
                self.channel.server.executeRequest(self)
                self.finish()
            except socket.error:
                self.close_on_finish = 1
                if self.channel.adj.log_socket_errors:
                    raise
        finally:
            self.channel.end_task(self.close_on_finish)

    def cancel(self):
        'See Zope.Server.ITask.ITask'
        self.channel.close_when_done()

    def defer(self):
        'See Zope.Server.ITask.ITask'
        pass

    #
    ############################################################

    def setResponseStatus(self, status, reason):
        """See the IHeaderOutput interface."""
        self.status = status
        self.reason = reason

    def setResponseHeaders(self, mapping):
        """See the IHeaderOutput interface."""
        self.response_headers.update(mapping)

    def appendResponseHeaders(self, lst):
        """See the IHeaderOutput interface."""
        accum = self.accumulated_headers
        if accum is None:
            self.accumulated_headers = accum = []
        accum.extend(lst)

    def wroteResponseHeader(self):
        """See the IHeaderOutput interface."""
        return self.wrote_header

    def setAuthUserName(self, name):
        """See the IHeaderOutput interface."""
        self.auth_user_name = name

    def prepareResponseHeaders(self):
        version = self.version
        # Figure out whether the connection should be closed.
        connection = self.request_data.headers.get('CONNECTION', '').lower()
        close_it = 0
        response_headers = self.response_headers

        if version == '1.0':
            if connection == 'keep-alive':
                if not response_headers.has_key('Content-Length'):
                    close_it = 1
                else:
                    response_headers['Connection'] = 'Keep-Alive'
            else:
                close_it = 1
        elif version == '1.1':
            if connection == 'close':
                close_it = 1
            elif response_headers.has_key ('Transfer-Encoding'):
                if not response_headers['Transfer-Encoding'] == 'chunked':
                    close_it = 1
            elif self.status == '304':
                # Replying with headers only.
                pass
            elif not response_headers.has_key ('Content-Length'):
                close_it = 1
        else:
            # Close if unrecognized HTTP version.
            close_it = 1

        self.close_on_finish = close_it
        if close_it:
            self.response_headers['Connection'] = 'close'

    def buildResponseHeader(self):
        self.prepareResponseHeaders()
        first_line = 'HTTP/%s %s %s' % (self.version, self.status, self.reason)
        lines = [first_line] + map(
            lambda hv: '%s: %s' % hv, self.response_headers.items())
        accum = self.accumulated_headers
        if accum is not None:
            lines.extend(accum)
        res = '%s\r\n\r\n' % '\r\n'.join(lines)
        return res

    def getCGIEnvironment(self):
        """Returns a CGI-like environment."""
        env = self.cgi_env
        if env is not None:
            # Return the cached copy.
            return env

        request_data = self.request_data
        path = request_data.path
        channel = self.channel
        server = channel.server

        while path and path.startswith('/'):
            path = path[1:]

        env = {}
        env['REQUEST_METHOD'] = request_data.command.upper()
        env['SERVER_PORT'] = str(server.port)
        env['SERVER_NAME'] = server.server_name
        env['SERVER_SOFTWARE'] = server.SERVER_IDENT
        env['SERVER_PROTOCOL'] = "HTTP/%s" % self.version
        env['CHANNEL_CREATION_TIME'] = channel.creation_time
        env['SCRIPT_NAME']=''
        env['PATH_INFO']='/' + path
        query = request_data.query
        if query:
            env['QUERY_STRING'] = query
        env['GATEWAY_INTERFACE'] = 'CGI/1.1'
        addr = channel.addr[0]
        env['REMOTE_ADDR'] = addr

        # If the server has a resolver, try to get the
        # remote host from the resolver's cache.
        resolver = getattr(server, 'resolver', None)
        if resolver is not None:
            dns_cache = resolver.cache
            if dns_cache.has_key(addr):
                remote_host = dns_cache[addr][2]
                if remote_host is not None:
                    env['REMOTE_HOST'] = remote_host

        env_has = env.has_key

        for key, value in request_data.headers.items():
            value = value.strip()
            mykey = rename_headers.get(key, None)
            if mykey is None:
                mykey = 'HTTP_%s' % key
            if not env_has(mykey):
                env[mykey] = value

        self.cgi_env = env
        return env

    def start(self):
        now = time.time()
        self.start_time = now
        self.response_headers['Date'] = build_http_date (now)

    def finish(self):
        if not self.wrote_header:
            self.write('')
        hit_log = self.channel.server.hit_log
        if hit_log is not None:
            hit_log.log(self)

    def write(self, data):
        channel = self.channel
        if not self.wrote_header:
            rh = self.buildResponseHeader()
            channel.write(rh)
            self.bytes_written += len(rh)
            self.wrote_header = 1
        if data:
            channel.write(data)
            self.bytes_written += len(data)

    def flush(self):
        self.channel.flush()


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

$Id: PublisherHTTPServer.py,v 1.1.4.1 2002/04/12 21:30:56 shane Exp $
"""

from HTTPServer import HTTPServer
from Zope.Publisher.Publish import publish


class PublisherHTTPServer(HTTPServer):
    """Zope Publisher-specific HTTP Server"""

    __implements__ = HTTPServer.__implements__

    def __init__(self, request_factory, sub_protocol=None, *args, **kw):
        self.request_factory = request_factory

        # An HTTP server is not limited to server up HTML; it can be
        # used for other protocols, like XML-RPC, SOAP and so as well
        # Here we just allow the logger to output the sub-protocol type.
        if sub_protocol:
            self.SERVER_IDENT += ' (%s)' %sub_protocol

        HTTPServer.__init__(self, *args, **kw)

    def executeRequest(self, task):
        """Overrides HTTPServer.executeRequest()."""
        env = task.getCGIEnvironment()
        instream = task.request_data.getBodyStream()

        request = self.request_factory(instream, task, env)
        response = request.getResponse()
        response.setHeaderOutput(task)
        publish(request)


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

$Id: __init__.py,v 1.1.4.1 2002/04/12 21:30:56 shane Exp $
"""


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

$Id: http_date.py,v 1.1.4.1 2002/04/12 21:30:56 shane Exp $
"""

import re
import string
import time

def concat (*args):
    return ''.join (args)

def join (seq, field=' '):
    return field.join (seq)

def group (s):
    return '(' + s + ')'

short_days = ['sun','mon','tue','wed','thu','fri','sat']
long_days = ['sunday','monday','tuesday','wednesday',
             'thursday','friday','saturday']

short_day_reg = group(join (short_days, '|'))
long_day_reg = group(join (long_days, '|'))

daymap = {}
for i in range(7):
    daymap[short_days[i]] = i
    daymap[long_days[i]] = i

hms_reg = join (3 * [group('[0-9][0-9]')], ':')

months = ['jan','feb','mar','apr','may','jun','jul',
          'aug','sep','oct','nov','dec']

monmap = {}
for i in range(12):
    monmap[months[i]] = i+1

months_reg = group (join (months, '|'))

# From draft-ietf-http-v11-spec-07.txt/3.3.1
#       Sun, 06 Nov 1994 08:49:37 GMT  ; RFC 822, updated by RFC 1123
#       Sunday, 06-Nov-94 08:49:37 GMT ; RFC 850, obsoleted by RFC 1036
#       Sun Nov  6 08:49:37 1994       ; ANSI C's asctime() format

# rfc822 format
rfc822_date = join (
        [concat (short_day_reg,','),        # day
         group('[0-9][0-9]?'),                        # date
         months_reg,                                        # month
         group('[0-9]+'),                                # year
         hms_reg,                                                # hour minute second
         'gmt'
         ],
        ' '
        )

rfc822_reg = re.compile (rfc822_date)

def unpack_rfc822 (m):
    g = m.group
    a = string.atoi
    return (
            a(g(4)),                   # year
            monmap[g(3)],        # month
            a(g(2)),                # day
            a(g(5)),                # hour
            a(g(6)),                # minute
            a(g(7)),                # second
            0,
            0,
            0
            )

    # rfc850 format
rfc850_date = join (
        [concat (long_day_reg,','),
         join (
                 [group ('[0-9][0-9]?'),
                  months_reg,
                  group ('[0-9]+')
                  ],
                 '-'
                 ),
         hms_reg,
         'gmt'
         ],
        ' '
        )

rfc850_reg = re.compile (rfc850_date)
# they actually unpack the same way
def unpack_rfc850 (m):
    g = m.group
    a = string.atoi
    return (
            a(g(4)),                   # year
            monmap[g(3)],        # month
            a(g(2)),                # day
            a(g(5)),                # hour
            a(g(6)),                # minute
            a(g(7)),                # second
            0,
            0,
            0
            )

    # parsdate.parsedate        - ~700/sec.
    # parse_http_date            - ~1333/sec.

weekdayname = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
monthname = [None, 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
             'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']

def build_http_date (when):
    year, month, day, hh, mm, ss, wd, y, z = time.gmtime(when)
    return "%s, %02d %3s %4d %02d:%02d:%02d GMT" % (
            weekdayname[wd],
            day, monthname[month], year,
            hh, mm, ss)

def parse_http_date (d):
    d = string.lower (d)
    tz = time.timezone
    m = rfc850_reg.match (d)
    if m and m.end() == len(d):
        retval = int (time.mktime (unpack_rfc850(m)) - tz)
    else:
        m = rfc822_reg.match (d)
        if m and m.end() == len(d):
            retval = int (time.mktime (unpack_rfc822(m)) - tz)
        else:
            return 0
            # Thanks to Craig Silverstein <csilvers@google.com> for pointing
            # out the DST discrepancy
    if time.daylight and time.localtime(retval)[-1] == 1: # DST correction
        retval = retval + (tz - time.altzone)
    return retval