# -*- coding: UTF-8 -*-


# Expression
# By: Frederic Peters <fpeters@entrouvert.com>
#     Emmanuel Raviart <eraviart@entrouvert.com>
#
# Copyright (C) 2004 Entr'ouvert, Frederic Peters & Emmanuel Raviart
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.


"""HTTP & HTTPS Module"""


import BaseHTTPServer
import Cookie
import cStringIO
import gzip
import httplib
import logging
import os
import random
import socket
import SocketServer
import sys
import time

try:
    from OpenSSL import SSL
except ImportError:
    SSL = None

import environs
import expression
import logs


class BaseHTTPSRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
    def setup(self):
        """
        We need to use socket._fileobject Because SSL.Connection
        doesn't have a 'dup'. Not exactly sure WHY this is, but
        this is backed up by comments in socket.py and SSL/connection.c
        """

        self.connection = self.request # for doPOST
        self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
        self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)


class BaseHTTPSServer(SocketServer.TCPServer):
    randomCharacters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'

    def __init__(self, server_address, RequestHandlerClass, privateKeyFilePath,
                 certificateFilePath, peerCaCertificateFile = None,
                 certificateChainFilePath = None, verifyClient = None):
        SocketServer.BaseServer.__init__(self, server_address, RequestHandlerClass)
        self.verifyClient = verifyClient

        ctx = SSL.Context(SSL.SSLv23_METHOD)
        nVerify = SSL.VERIFY_NONE
        ctx.set_options(SSL.OP_NO_SSLv2)

        # Needed for the clients to be able to reuse the same connection.
        # Without this, we got OpenSSL.SSL.Error:
        # [('SSL routines', 'SSL_GET_PREV_SESSION', 'session id context uninitialized')]
        sessionId = ''.join([random.choice(self.randomCharacters) for i in range(16)])
        ctx.set_session_id(sessionId)

        ctx.use_privatekey_file(privateKeyFilePath)
        ctx.use_certificate_file(certificateFilePath)
        if peerCaCertificateFile:
            ctx.load_verify_locations(peerCaCertificateFile)
        if certificateChainFilePath:
            ctx.use_certificate_chain_file(certificateChainFilePath)
        if verifyClient == "require":
            nVerify |= SSL.VERIFY_PEER | SSL.VERIFY_FAIL_IF_NO_PEER_CERT
        elif verifyClient in ("optional", "optional_on_ca"):
            nVerify |= SSL.VERIFY_PEER
        ctx.set_verify(nVerify, self.verifyCallback)

        self.socket = SSL.Connection(ctx, socket.socket(self.address_family, self.socket_type))
        self.server_bind()
        self.server_activate()

    def verifyCallback(self, connection, x509Object, errorNumber, errorDepth, returnCode):
        logs.info("http.BaseHTTPSServer.verifyCallback(%s, %s, %s, %s, %s, %s)" % (
            self, connection, x509Object, errorNumber, errorDepth, returnCode))
        return returnCode

    #~ def server_bind(self):
        #~ """Override server_bind to store the server name."""

        #~ SocketServer.TCPServer.server_bind(self)
        #~ host, port = self.socket.getsockname()[:2]
        #~ self.server_name = socket.getfqdn(host)
        #~ self.server_port = port


class HttpRequestHandlerMixin:
    application = None # Class variable.
    baseEnviron = None # Class variable.
    socketCreationTime = None
    protocol_version = "HTTP/1.1"
    server_version = "Expression/%s" % expression.versionNumber
    sys_version = "Python/%s" % sys.version.split()[0]

    def doHttpCommand(self):
        # Initialize virtual server logger.
        loggerName = self.headers.get("Host", "").replace(".", "_")
        logger = logging.getLogger(loggerName)

        logger.info(self.raw_requestline.strip())
        logger.debug(str(self.headers))
        environs.initFromOther(
            self.baseEnviron,
            _level = "doHttpCommand",
            logger = logger,
            )
        environ = environs.get()
        if hasattr(self.connection, "get_peer_certificate"):
            cert = self.connection.get_peer_certificate()
            environs.setVar("peerCertificate", cert)

        try:
            self.application.handleHttpCommand(self)
        except IOError:
            logger.exception("An exception occured:")
            path = self.path.split("?")[0]
            return self.outputErrorNotFound(path)
        assert environ == environs.get()

    do_DELETE = doHttpCommand
    do_GET = doHttpCommand
    do_HEAD = doHttpCommand
    do_MKCOL = doHttpCommand
    do_OPTIONS = doHttpCommand
    do_POST = doHttpCommand
    do_PROPFIND = doHttpCommand
    do_PUT = doHttpCommand

    def handle(self):
        self.socketCreationTime = time.time()
        try:
            try:
                self.__class__.__bases__[1].handle(self)
            except socket.timeout:
                pass
            except KeyboardInterrupt:
                raise
            except SSL.ZeroReturnError:
                pass
            except SSL.Error, exception:
                raise str((exception, exception[0]))
                if exception[0] == (
                        "PEM routines", "PEM_read_bio", "no start line"):
                    pass
                else:
                    self.outputUnknownException()
            except:
                self.outputUnknownException()
        finally:
            del self.socketCreationTime

    def log_date_time_string(self):
        return time.strftime("%d/%b/%Y:%H:%M:%S %z")

    def log_message(self, format, *args):
        """Override HttpRequestHandler method to use logging module."""

        logs.info("%s - - [%s ] %s" % (
            self.address_string(),
            self.log_date_time_string(),
            format % args))
        virtualHost = environs.getVar("virtualHost", default = None)
        if virtualHost and virtualHost.accessLogFile:
            referer = self.headers.get("Referer", "-")
            useragent = self.headers.get("User-agent", "-")
            virtualHost.accessLogFile.write("%s - - [%s] %s \"%s\" \"%s\"\n" %
                (self.address_string(), self.log_date_time_string(), format%args,
                 referer, useragent))

    def log_request(self, code = "-", size = "-"):
        if size == "-":
            try:
                size = self.response_headers["Content-Length"]
            except (AttributeError, KeyError):
                pass
        self.log_message('"%s" %s %s', self.requestline, str(code), str(size))

    def outputAlert(self, data, station = "current", title = None, url = None):
        import html
        if title is None:
            title = N_("Alert")
        if url:
            buttonsBar = html.div(class_ = "buttons-bar")
            actionButtonsBar = html.span(class_ = "action-buttons-bar")
            buttonsBar.append(actionButtonsBar)
            actionButtonsBar.append(html.a(_("OK"), class_ = "button", href = url))
        else:
            buttonsBar = None
        layout = html.html(
            html.head(html.title(_(title))),
            html.body(
                html.div(
                    html.label(_(title)),
                    html.p(_(data), class_ = "alert"),
                    buttonsBar,
                    class_ = "xforms-group"
                )
            )
        )
        xmlDocument = layout.generateXmlDocument()
        if station == "current":
            station = environs.getVar("currentStation", default = None)
        if station:
            xslt = station.getDataHolder().getSiteXslt()
            mimeType, styledData = station.generateXmlDocumentStyledData(xmlDocument, xslt)
            if mimeType == "text/html":
                data = styledData
        else:
            data = xmlDocument.serialize()
        self.outputData(data, contentLocation = None, mimeType = "text/html",
                        station = station)

    def outputData(self, data, contentLocation = "current", headers = None, mimeType = None,
                   modificationTime = None, charset = None, station = "current", successCode = 200):
        if station == "current":
            station = environs.getVar("currentStation", default = None)
        if isinstance(data, basestring):
            dataFile = None
            dataSize = len(data)
        else:
            dataFile = data
            data = ""
            if hasattr(dataFile, "fileno"):
                dataSize = os.fstat(dataFile.fileno())[6]
            else:
                # For StringIO and cStringIO classes.
                dataSize = len(dataFile.getvalue())
        # We need to save the session before sending response, otherwise, the
        # server may receive a new HTTP request before the session is saved.
        session = environs.getVar("session")
        if session is not None:
            sessionDocument = session.getDocument()
            if sessionDocument.isDirty:
                sessionDocument.save()

        if headers is None:
            headers = {}
        self.response_headers = headers
        if time.time() > self.socketCreationTime + 300:
            headers["Connection"] = "close"
        elif not self.close_connection:
            headers["Connection"] = "Keep-Alive"
        if contentLocation == "current":
            contentLocation = station.getUriAbsolutePath()
        if contentLocation is not None:
            headers["Content-Location"] = contentLocation
        if mimeType:
            if not mimeType.startswith('text/'):
                headers["Content-Type"] = mimeType
            else:
                if not charset:
                    charset = "utf-8" # default to utf-8
                headers["Content-Type"] = "%s; charset=%s" % (mimeType, charset)
        if modificationTime:
            headers["Last-Modified"] = time.strftime("%a, %d %b %Y %H:%M:%S GMT", modificationTime)
        # TODO: Could also output Content-MD5.
        ifModifiedSince = self.headers.get("If-Modified-Since")
        if modificationTime and ifModifiedSince:
            # We don't want to use bandwith if the file was not modified.
            try:
                ifModifiedSinceTime = time.strptime(ifModifiedSince[:25], "%a, %d %b %Y %H:%M:%S")
                if modificationTime[:8] <= ifModifiedSinceTime[:8]:
                    self.send_response(304, "Not Modified.")
                    for key in ("Connection", "Content-Location"):
                        if key in headers:
                            self.send_header(key, headers[key])
                    self.setCookie()
                    self.end_headers()
                    return
            except (ValueError, KeyError):
                pass
        if dataFile is not None:
            assert not data
            data = dataFile.read(1048576) # Read first MB chunk
        if mimeType == "text/html" and data.startswith("<?xml"):
            # Internet Explorer 6 renders the page differently when they start with <?xml...>, so
            # skip it.
            i = data.find("\n")
            if i > 0:
                data = data[i + 1:]
            else:
                i = data.find(">")
                if i > 0:
                    data = data[i + 1:]
            dataSize -= i + 1
        # Compress data if possible and if data is not too big.
        acceptEncoding = self.headers.get("Accept-Encoding", "")
        if 0 < dataSize < 1048576 and "gzip" in acceptEncoding \
               and "gzip;q=0" not in acceptEncoding:
            # Since dataSize < 1 MB, the data is fully contained in string.
            zbuf = cStringIO.StringIO()
            zfile = gzip.GzipFile(mode = "wb",  fileobj = zbuf)
            zfile.write(data)
            zfile.close()
            data = zbuf.getvalue()
            dataSize = len(data)
            headers["Content-Encoding"] = "gzip"
        headers["Content-Length"] = "%d" % dataSize
        successMessages = {
            200: "OK",
            207: "Multi-Status",
            }
        assert successCode in successMessages, "Unknown success code %d." % successCode
        if environs.getVar("httpAuthenticationLogoutTrick", default = False) \
               and successCode == 200:
            successCode = 401
            successMessage = "Access Unauthorized"
            headers["WWW-Authenticate"] = 'Basic realm="%s"' % station.getConfigString(
                "yep:title", default = "Expression")
        else:
            successMessage = successMessages[successCode]
        self.send_response(successCode, successMessage)
        for key, value in headers.items():
            self.send_header(key, value)
        self.setCookie()
        self.end_headers()
        if self.command != "HEAD" and dataSize > 0:
            outputFile = self.wfile
            if data:
                outputFile.write(data)
            if dataFile is not None:
                while True:
                    chunk = dataFile.read(1048576) # 1 MB chunk
                    if not chunk:
                        break
                    outputFile.write(chunk)
        return

    def outputErrorAccessForbidden(self, filePath, station = "current"):
        if filePath is None:
            message = "Access Forbidden"
        else:
            message = """Access to "%s" Forbidden.""" % filePath
        if station == "current":
            station = environs.getVar("currentStation", default = None)
        logs.info(message)
        data = "<html><body>%s</body></html>" % message
        if station:
            import httpstatuses
            errorElement = httpstatuses.Http403.newInTemporaryDocument(message, filePath)
            errorDescription = errorElement.getDescription()
            if errorDescription:
                xmlDocument = errorDescription.generateXmlDocument()
                xslt = station.getDataHolder().getSiteXslt()
                mimeType, styledData = station.generateXmlDocumentStyledData(xmlDocument, xslt)
                if mimeType == "text/html":
                    data = styledData
        return self.send_error(403, message, data, setCookie = True)

    def outputErrorBadRequest(self, reason, station = "current"):
        if reason:
            message = "Bad Request: %s" % reason
        else:
            message = "Bad Request"
        if station == "current":
            station = environs.getVar("currentStation", default = None)
        logs.info(message)
        data = "<html><body>%s</body></html>" % message
        if station:
            import httpstatuses
            errorElement = httpstatuses.Http400.newInTemporaryDocument(message)
            errorDescription = errorElement.getDescription()
            if errorDescription:
                xmlDocument = errorDescription.generateXmlDocument()
                xslt = station.getDataHolder().getSiteXslt()
                mimeType, styledData = station.generateXmlDocumentStyledData(xmlDocument, xslt)
                if mimeType == "text/html":
                    data = styledData
        return self.send_error(400, message, data)

    def outputErrorInternalServer(self, station = "current"):
        if station == "current":
            station = environs.getVar("currentStation", default = None)
        message = "Internal Server Error"
        logs.info(message)
        data = "<html><body>%s</body></html>" % message
        if station:
            import httpstatuses
            errorElement = httpstatuses.Http500.newInTemporaryDocument(message)
            errorDescription = errorElement.getDescription()
            if errorDescription:
                xmlDocument = errorDescription.generateXmlDocument()
                xslt = station.getDataHolder().getSiteXslt()
                mimeType, styledData = station.generateXmlDocumentStyledData(xmlDocument, xslt)
                if mimeType == "text/html":
                    data = styledData
        return self.send_error(500, message, data)

    def outputErrorMethodNotAllowed(self, reason, station = "current"):
        if reason:
            message = "Method Not Allowed: %s" % reason
        else:
            message = "Method Not Allowed"
        if station == "current":
            station = environs.getVar("currentStation", default = None)
        logs.info(message)
        data = "<html><body>%s</body></html>" % message
        # This error doesn't need a pretty interface.
        # FIXME: Add an "Allow" header containing a list of valid methods for the requested
        # resource.
        return self.send_error(405, message, data)

    def outputErrorNotFound(self, filePath, station = "current"):
        if filePath is None:
            message = "Not Found"
        else:
            message = """Path "%s" Not Found.""" % filePath
        if station == "current":
            station = environs.getVar("currentStation", default = None)
        logs.info(message)
        data = "<html><body>%s</body></html>" % message
        if station:
            import httpstatuses
            errorElement = httpstatuses.Http404.newInTemporaryDocument(message, filePath)
            errorDescription = errorElement.getDescription()
            if errorDescription:
                xmlDocument = errorDescription.generateXmlDocument()
                xslt = station.getDataHolder().getSiteXslt()
                mimeType, styledData = station.generateXmlDocumentStyledData(xmlDocument, xslt)
                if mimeType == "text/html":
                    data = styledData
        return self.send_error(404, message, data, setCookie = True)

    def outputErrorUnauthorized(self, filePath, station = "current"):
        if filePath is None:
            message = "Access Unauthorized"
        else:
            message = """Access to "%s" Unauthorized.""" % filePath
        if station == "current":
            station = environs.getVar("currentStation", default = None)
        logs.info(message)
        data = "<html><body>%s</body></html>" % message
        headers = {}
        if station:
            import httpstatuses
            errorElement = httpstatuses.Http401.newInTemporaryDocument(message, filePath)
            errorDescription = errorElement.getDescription()
            if errorDescription:
                xmlDocument = errorDescription.generateXmlDocument()
                xslt = station.getDataHolder().getSiteXslt()
                mimeType, styledData = station.generateXmlDocumentStyledData(xmlDocument, xslt)
                if mimeType == "text/html":
                    data = styledData
            if station.getConfigBoolean("yep:useHttpAuthentication", default = False):
                headers["WWW-Authenticate"] = 'Basic realm="%s"' % station.getConfigString(
                    "yep:title", default = "Expression")
        return self.send_error(401, message, data, headers, setCookie = True)

    def outputInformationContinue(self):
        message = "Continue"
        logs.debug(message)
        self.send_response(100, message)

    def outputRedirect(self, uri, station = "current"):
        assert uri
        if station == "current":
            station = environs.getVar("currentStation", default = None)
        # We need to save the session before sending response, otherwise, the
        # server may receive a new HTTP request before the session is saved.
        session = environs.getVar("session")
        if session is not None:
            sessionDocument = session.getDocument()
            if sessionDocument.isDirty:
                sessionDocument.save()

        message = """Moved Temporarily to "%s".""" % uri
        logs.debug(message)
        data = "<html><body>%s</body></html>" % message
        if station:
            import httpstatuses
            errorElement = httpstatuses.Http302.newInTemporaryDocument(message, uri)
            errorDescription = errorElement.getDescription()
            if errorDescription:
                xmlDocument = errorDescription.generateXmlDocument()
                xslt = station.getDataHolder().getSiteXslt()
                mimeType, styledData = station.generateXmlDocumentStyledData(xmlDocument, xslt)
                if mimeType == "text/html":
                    data = styledData
        self.send_response(302, message)
        self.send_header("Location", uri)
        if time.time() > self.socketCreationTime + 300:
            self.send_header("Connection", "close")
        elif not self.close_connection:
            self.send_header("Connection", "Keep-Alive")
        self.send_header("Content-Type", "text/html; charset=utf-8")
        self.send_header("Content-Length", "%d" % len(data))
        self.setCookie()
        self.end_headers()
        if self.command != "HEAD":
            self.wfile.write(data)

    def outputRedirectPermanent(self, uri, station = "current"):
        assert uri
        if station == "current":
            station = environs.getVar("currentStation", default = None)
        # We need to save the session before sending response, otherwise, the
        # server may receive a new HTTP request before the session is saved.
        session = environs.getVar("session")
        if session is not None:
            sessionDocument = session.getDocument()
            if sessionDocument.isDirty:
                sessionDocument.save()

        message = """Moved Permanently to "%s".""" % uri
        logs.debug(message)
        data = "<html><body>%s</body></html>" % message
        if station:
            import httpstatuses
            errorElement = httpstatuses.Http301.newInTemporaryDocument(message, uri)
            errorDescription = errorElement.getDescription()
            if errorDescription:
                xmlDocument = errorDescription.generateXmlDocument()
                xslt = station.getDataHolder().getSiteXslt()
                mimeType, styledData = station.generateXmlDocumentStyledData(xmlDocument, xslt)
                if mimeType == "text/html":
                    data = styledData
        self.send_response(301, message)
        self.send_header("Location", uri)
        if time.time() > self.socketCreationTime + 300:
            self.send_header("Connection", "close")
        elif not self.close_connection:
            self.send_header("Connection", "Keep-Alive")
        self.send_header("Content-Type", "text/html; charset=utf-8")
        self.send_header("Content-Length", "%d" % len(data))
        self.setCookie()
        self.end_headers()
        if self.command != "HEAD":
            self.wfile.write(data)

    def outputSuccessCreated(self, filePath, station = "current"):
        if filePath is None:
            message = "Created"
        else:
            message = """File "%s" Created.""" % filePath
        if station == "current":
            station = environs.getVar("currentStation", default = None)
        logs.debug(message)
        data = "<html><body>%s</body></html>" % message
        if station:
            import httpstatuses
            errorElement = httpstatuses.Http201.newInTemporaryDocument(message, filePath)
            errorDescription = errorElement.getDescription()
            if errorDescription:
                xmlDocument = errorDescription.generateXmlDocument()
                xslt = station.getDataHolder().getSiteXslt()
                mimeType, styledData = station.generateXmlDocumentStyledData(xmlDocument, xslt)
                if mimeType == "text/html":
                    data = styledData
        self.send_response(201, message)
        if time.time() > self.socketCreationTime + 300:
            self.send_header("Connection", "close")
        elif not self.close_connection:
            self.send_header("Connection", "Keep-Alive")
        self.send_header("Content-Type", "text/html; charset=utf-8")
        self.send_header("Content-Length", "%d" % len(data))
        self.setCookie()
        self.end_headers()
        if self.command != "HEAD":
            self.wfile.write(data)

    def outputSuccessNoContent(self):
        message = "No Content"
        logs.debug(message)
        self.send_response(204, message)
        if time.time() > self.socketCreationTime + 300:
            self.send_header("Connection", "close")
        elif not self.close_connection:
            self.send_header("Connection", "Keep-Alive")
        self.setCookie()
        self.end_headers()

    def outputUnknownException(self):
        import traceback, cStringIO
        f = cStringIO.StringIO()
        traceback.print_exc(file = f)
        exceptionTraceback = f.getvalue()
        exceptionType, exception = sys.exc_info()[:2]
        virtualHost = environs.getVar("virtualHost", default = None)

        if environs.getVar("debug"):
            logs.debug("""\
An exception "%(exception)s" of class "%(exceptionType)s" occurred.

%(traceback)s
""" % {
                "exception": exception,
                "exceptionType": exceptionType,
                "traceback": exceptionTraceback,
                })
        if virtualHost and virtualHost.getConfigExistence("yep:talkback") \
               and environs.getVar("sendEmails"):
            try:
                userAgent = self.headers["User-Agent"]
            except KeyError:
                userAgent = None
            try:
                host = self.headers["Host"]
            except:
                host = "%s (from socket.getfqdn())" % socket.getfqdn()

            adminEmailAddress = virtualHost.getConfigString(
                "yep:adminEmail", default = "root@localhost")
            message = """\
From: %(from)s
To: %(to)s
Subject: [%(host)s] Expression Talkback

An exception "%(exception)s" of class "%(exceptionType)s" occurred.

Url: %(uriScheme)s://%(host)s%(url)s
User-Agent: %(userAgent)s
Client: %(clientIp)s

%(traceback)s
""" % {
                "clientIp": self.request.getpeername()[0],
                "exception": exception,
                "exceptionType": exceptionType,
                "from": adminEmailAddress,
                "host": host,
                "to": adminEmailAddress,
                "traceback": exceptionTraceback,
                "uriScheme": environs.getVar("httpServer").uriScheme,
                "url": self.path,
                "userAgent": userAgent,
                }

            import smtplib
            server = smtplib.SMTP("localhost")
            try:
                server.sendmail(adminEmailAddress, adminEmailAddress, message)
            except smtplib.SMTPException, error:
                logs.warning("SMTP error while sending talkback: %r" % error)

        return self.outputErrorInternalServer()

    def send_error(self, code, message = None, data = None, headers = None, setCookie = False):
        # We need to save the session before sending response, otherwise, the
        # server may receive a new HTTP request before the session is saved.
        session = environs.getVar("session", default = None)
        if session is not None:
            sessionDocument = session.getDocument()
            if sessionDocument.isDirty:
                sessionDocument.save()

        if not data:
            return self.__class__.__bases__[1].send_error(self, code, message)
        shortMessage, longMessage = self.responses[code]
        if message is None:
            message = shortMessage
        self.send_response(code, message)
        self.send_header("Content-Type", "text/html; charset=utf-8")
        self.send_header("Content-Length", "%d" % len(data))
        self.send_header("Connection", "close")
        if headers is not None:
            for name, value in headers.items():
                self.send_header(name, value)
        if setCookie:
            self.setCookie()
        self.end_headers()
        if self.command != "HEAD":
            self.wfile.write(data)

    def setCookie(self):
        if not environs.getVar("canUseCookie", default = False):
            return
        oldCookie = environs.getVar("cookie")
        cookie = Cookie.SimpleCookie()
        cookieContent = {}
        session = environs.getVar("session")
        if session is not None and session.publishToken:
            cookieContent["sessionToken"] = session.token
        for key, value in cookieContent.items():
            cookie[key] = value
            cookie[key]["path"] = "/"
        if not cookieContent:
            if oldCookie:
                for key, morsel in oldCookie.items():
                    cookie[key] = ""
                    cookie[key]["max-age"] = 0
                    cookie[key]["path"] = "/"
            else:
                cookie = None
        if cookie is not None:
            # Is new cookie different from previous one?
            sameCookie = False
            if oldCookie is not None and cookie.keys() == oldCookie.keys():
                for key, morsel in cookie.items():
                    oldMorsel = oldCookie[key]
                    if morsel.value != oldMorsel.value:
                        break
                else:
                    sameCookie = True
            if not sameCookie:
                for morsel in cookie.values():
                    self.send_header(
                        "Set-Cookie", morsel.output(header = "")[1:])
                environs.get(_level = "handleHttpCommand").setVar(
                    "cookie", cookie)

    def version_string(self):
        """Return the server software version string."""
        return "Expression %s" % expression.versionNumber


class HttpRequestHandler(HttpRequestHandlerMixin,
                         BaseHTTPServer.BaseHTTPRequestHandler):
    pass


class HttpsConnection(httplib.HTTPConnection):
    certificateFile = None
    default_port = httplib.HTTPS_PORT
    peerCaCertificateFile = None
    privateKeyFile = None

    def __init__(self, host, port = None, privateKeyFile = None, certificateFile = None,
                 peerCaCertificateFile = None, strict = None):
        httplib.HTTPConnection.__init__(self, host, port, strict)
        self.privateKeyFile = privateKeyFile
        self.certificateFile = certificateFile
        self.peerCaCertificateFile = peerCaCertificateFile

    def connect(self):
        "Connect to a host on a given (SSL) port."

        context = SSL.Context(SSL.SSLv23_METHOD)
        if not self.peerCaCertificateFile:
            context.set_verify(SSL.VERIFY_NONE, self.verifyCallback)
        else:
            # Demand a certificate.
            context.set_verify(SSL.VERIFY_PEER | SSL.VERIFY_FAIL_IF_NO_PEER_CERT, self.verifyCallback)
        if self.privateKeyFile:
            context.use_privatekey_file(self.privateKeyFile)
        if self.certificateFile:
            context.use_certificate_file(self.certificateFile)
        if self.peerCaCertificateFile:
            context.load_verify_locations(self.peerCaCertificateFile)

        # Strange hack, that is derivated from httplib.HTTPSConnection, but that I (Emmanuel) don't
        # really understand...
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sslSocket = SSL.Connection(context, sock)
        sslSocket.connect((self.host, self.port))
        self.sock = httplib.FakeSocket(sslSocket, sslSocket)

    def verifyCallback(self, connection, x509Object, errorNumber, errorDepth, returnCode):
        logs.debug("http.HttpsConnection.verifyCallback(%s, %s, %s, %s, %s, %s)" % (
            self, connection, x509Object, errorNumber, errorDepth, returnCode))
        # FIXME: What should be done?
        return returnCode


class HttpsRequestHandler(HttpRequestHandlerMixin, BaseHTTPSRequestHandler):
    pass


# We use ForkingMixIn instead of ThreadingMixIn because the Python binding for
# libxml2 limits the number of registered xpath functions to 10. Even if we use
# only one xpathContext, this would limit the number of threads to 10, wich is
# not enough for a web server.

class HttpServer(SocketServer.ForkingMixIn, BaseHTTPServer.HTTPServer):
    application = None
    uriScheme = "http"


class HttpsServer(SocketServer.ForkingMixIn, BaseHTTPSServer):
    application = None
    uriScheme = "https"
