#!/usr/bin/python
# coding: iso-8859-15

global asyncore
import asyncore
global asynchat
import asynchat
global SimpleHTTPServer
import SimpleHTTPServer
global socket
import socket
global cStringIO
import cStringIO
global hashlib
import hashlib
global urllib
import urllib
global base64
import base64
#//LIB_START
global string
import string
global os
import os
global time
import time
#//LIB_END



## @brief Klassen zum Bereitstellen eines HTTP-Servers
## @deprecated Dieses Modul sollte nicht mehr eingesetzt werden. In den Beispielen wird gezeigt, wie es durch Standardmodule von Python ersetzt werden kann.
## Klasse ist deprecated
class hsl20_4_http_server:
    ## GET
    GET = 1

    ## POST
    POST = 2

    ## PUT
    PUT = 4

    ## DELETE
    DELETE = 8

    ## ALL
    ALL = 255

    ## HTTP-Requests
    ##
    ## In dieser Klasse werden alle Parameter gekapselt, die der HTTP-Request beim Server hinterlassen hat.
    ## Weitere Informationen zum Ablauf und der Verwendung dieser Klasse unter @ref hsl20_4_http_server.hsl20_4_http_server.Server "HTTP-Server".
    class Request:

        ## Konstruktor
        ##
        ## @warning Diese Klasse sollte nicht direkt instanziert werden.
        def __init__(self):
            ## @cond NO_DOC
            self.__method = "GET"
            self.__fullpath = "/"
            self.__path = "/"
            self.__http_version = "1.0"
            self.__query_string = ""
            self.__query = {}
            self.__header = {}
            self.__body = None
            ## @endcond


        ## Setzt die HTTP-Informationen
        def _set_info(self, method, path, version):
            ## @cond NO_DOC
            self.__method  = method
            self.__fullpath = path
            self.__query = {}
            try:
                idx = path.find("?")
                if idx>-1:
                    self.__path = path[:idx]
                    self.__query_string = path[idx+1:]
                    t = string.split(self.__query_string, "&")
                    for token in t:
                        tt = string.split(token, "=", 1)
                        self.__query[tt[0]] = urllib.unquote_plus(tt[1])
                else:
                    self.__path = path
                    self.__query_string = ""
            except Exception as e:
                print "E", e
                self.__path = path
            self.__http_version = version
            ## @endcond


        ## Setzt die Header
        def _set_header(self, header):
            ## @cond NO_DOC
            self.__header = header
            ## @endcond


        ## Setzt den Request-Body
        def _set_body(self, body):
            ## @cond NO_DOC
            self.__body = body
            ## @endcond


        ## Entfernt alle Referenzen
        def _clear(self):
            ## @cond NO_DOC
            self.__query = None
            self.__header = None
            self.__body = None
            ## @endcond


        ## Liefert die Abrufmethode.
        ## @result @e string @n Abrufmethode.
        def get_method(self):
            return self.__method


        ## Liefert den kompletten Pfad der Abfrage ohne den Query-Teil.
        ## @result @e string @n Pfad
        def get_path(self):
            return self.__path

        ## Liefert den kompletten Pfad der Abfrage inklusive des Query-Teils.
        ## @result @e string @n Pfad
        ## @if OLD_CHANGELOG
        ## @chlg04 neue Methode
        ## @endif
        def get_fullpath(self):
            return self.__fullpath


        ## Liefert die HTTP-Version des Abrufs.
        ## @result @e string @n Version (z.B. HTTP\1.0)
        def get_http_version(self):
            return self.__http_version


        ## Liefert alle Parameter aus dem Query-Teil der Abfrage in Form eines Dictionaries.
        ## Die Werte sind bereits dekodiert.
        ## @result @e dictionary @n Parameter
        ## @if OLD_CHANGELOG
        ## @chlg04 Beschreibung umformuliert
        ## @endif
        def get_query(self):
            return self.__query

        ## Liefert den gesamten Query-String der Abfrage.
        ## @result @e string @n Query-String
        def get_query_string(self):
            return self.__query_string

        ## Liefert den Wert eines Header-Eintrags.
        ## @param key @e string @n Schlüssel
        ## @result @e string @n Wert. Liefert @e None, falls ein ungültiger Schlüssel übergeben wurde.
        def get_header(self, key):
            ## @cond NO_DOC
            if self.__header.has_key(key):
                return self.__header[key]
            else:
                return None
            ## @endcond

        ## Liefert die von der Gegenstelle übermittelten Header als Dictionary zurück.
        ## @note Zu Beachten: Die Schlüssel werden komplett in Kleinbuchstaben zurückgegeben!
        ## @result @e dictionary @n Alle Header in Form eines Dictionaries
        ## @if OLD_CHANGELOG
        ## @chlg04 neu
        ## @endif
        def get_headers(self):
            return self.__header

        ## Liefert den Body der HTTP-Anfrage.
        ## @result @e string @n Daten. Liefert @e None, wenn die Anfrage kein Body-Element enthält.
        def get_body(self):
            return self.__body

    ## HTTP-Response
	## @deprecated Diese Klasse sollte nicht mehr eingesetzt werden.
    ##
    ## Hier werden alle Parameter gekapselt, die der HTTP-Server an die Gegenstelle antwortet.
    ## Weitere Informationen zum Ablauf und der Verwendung dieser Klasse unter @ref hsl20_4_http_server.Server "HTTP-Server".
    class Response:

        ## Konstruktor
        ##
        ## @warning Diese Klasse sollte nicht direkt instanziert werden.
        def __init__(self):
            ## @cond NO_DOC
            self._code = None
            self._internal_errorcode = None
            self._text = None
            self._header = { }
            self._header["Content-type"] = "text/html"
            self._body = ''
            ## @endcond

        ## Entfernt alle Referenzen
        def _clear(self):
            ## @cond NO_DOC
            self._header = None
            self._body = None
            ## @endcond


        ## Setzt den Status-Code und optional den Status-Text. Wird kein Code gesetzt, antwortet der Server mit Code 500 (Internal Server Error).
        ## @param code @e int @n Code (z.B. 200)
        ## @param text @e string @n Text (z.B. OK)
        def set_status_code(self, code, text=None):
            ## @cond NO_DOC
            self._code = code
            self._text = text
            ## @endcond


        ## Setzt einen Header
        ## @param key @e string @n Schlüssel
        ## @param value @e string @n Wert
        def set_header(self, key, value):
            ## @cond NO_DOC
            self._header[key]= value
            ## @endcond


        ## Setzt den Body der Antwort. Die hier zugewiesenen Daten sind die eigentliche Antwort auf die Anfrage.
        ## @param data @e string @n Daten
        def set_body(self, data):
            ## @cond NO_DOC
            self._body = data
            ## @endcond



    ## @brief Alle Methoden des HTTP-Servers.
	## @deprecated Diese Klasse sollte nicht mehr eingesetzt werden.
    ## @details
    ## Bietet einen Server-Dienst zur Kommunikation per HTTP.
    ##
    ## Wurde der HTTP-Server vom Framework erzeugt und an eine IP und einen Port gebunden, kann dieser gestartet werden. @n
    ## Erfolgen nun Aufrufe durch einen Client, gibt es zwei Möglichkeiten:
    ## - @e Der Client ruft eine registrierte URL auf:
    ## Der Server ruft im @b Kontext des Bausteins mit der Methode @e register_uri die entsprechende Callback-Methode mit den Parametern
    ## @ref hsl20_4_http_server.Request "request" und @ref hsl20_4_http_server.Response "response" auf. @n
    ## Die Methode kann nun auf die Anfrage reagieren und im response-Objekt darauf antworten. @n
    ## - @e Der Client ruft eine unbekannte URL auf:
    ## Der Server ruft, sofern mit @e set_error_callback zugewiesen, das Error-Callback auf.
    ## Wurde kein Callback zugewiesen, antwortet der Server mit dem Statuscode 404 auf die Anfrage.
    ##
    ## Nach Abarbeitung der jeweiligen Callback-Methode sendet der Server die erzeugte Antwort an die Gegenstelle. Wird im response-Objekt kein Statuscode gesetzt,
    ## antwortet der Server mit Statuscode 501.
    ## @note Die Klasse arbeitet asynchron. Keine der angebotenen Methoden blockiert.
    ##
    ## HTTP-Request-Methoden
    ## --
    ## Bei der Registrierung einer URI können direkt eine oder mehrere HTTP-Methoden
    ## (per logischem ODER, z.B. hsl20_4_http_server.GET|hsl20_4_http_server.PUT)
    ## definiert werden, alternativ kann man auch hsl20_4_http_server.ALL zuweisen,
    ## dann wird die Callback-Methode bei jeder Methode aufgerufen. Der HTTP-Server
    ## bietet direkte Unterstützung für die folgenden Methoden:
    ## - GET
    ## - POST
    ## - PUT
    ## - DELETE
    ##
    ## Bei den Methoden POST und PUT wird ein Body-Element im Request unterstützt,
    ## hierfür muss im Header das Feld @e "Content-length" gesetzt sein. Sollte
    ## der Client die maximal zulässige Größe überschreiten, wird die Verbindung
    ## durch den Server getrennt. @n
    ## Siehe auch  hsl20_4_http_server.Server#set_max_post_size
    ##
    ## Timeouts
    ## --
    ## Folgende Timeouts, bei deren Eintreten die Verbindung getrennt wird, sind zu beachten:
    ## - Der Zeitraum zwischen zwei gesendeten Paketen ist größer als 15 Sekunden
    ## - Seit Aufbau der Verbindung sind mehr als 30 Sekunden vergangen und der Header wurde noch nicht korrekt gesendet
    ##
    ## Authentifizierung
    ## --
    ## Es besteht die Möglichkeit, die Zugriffe auf den Server mit Benutzer/Passwort zu sichern.
    ## Über die Methode @ref hsl20_4_http_server.Server#set_authentication kann die Authentifizierung aktiviert und deaktiviert werden.
    ## Zur Übermittlung der Benutzerdaten stehen folgende zwei Varianten zur Verfügung:
    ##
    ## - @b "Basic Authentication" @n
    ## Der Client schickt Benutzername und Passwort base64 kodiert an den Server. @n
    ## @n
    ## @e "Algorithmus zur Erzeugung des zu übertragenden Werts:"
    ##
    ##     RESULT = BASE64(Benutzername + ":" + Passwort)
    ## @n
    ## - @b "Digest Access Authentication" @n
    ## Der Client sendet anstelle des Passworts einen Hashwert an den Server.
    ## Um diesen Hashwert zu ermitteln, erhält er vom Server einen Zufallsstring (NONCE_SERVER).
    ## Das Framework erfordert hierbei als Hash-Algorithmus "MD5", desweiteren die QOP-Variante "auth" (QOP = (Q)uality (o)f (p)rotection).
    ## Dies wiederum erfordert Client-seitig die Erzeugung eines Zufallswerts (NONCE_CLIENT) und einen Zähler (NONCE_COUNTER).@n
    ## @n
    ## @e "Algorithmus zur Erzeugung des Hashwerts:"
    ##
    ## HA1 = MD5(BENUTZERNAME + ":" + REALM + ":" + PASSWORT)
    ## HA2 = MD5(UPPERCASE(HTTP_METHODE) + ":" + URI)
    ## RESULT = MD5(HA1 + ":" + NONCE_SERVER + ":" + NONCE_COUNTER + ":" + NONCE_CLIENT + ":auth:" + HA2)
    ##
    ## @note Es wird nur ein Benutzer/Passwort unterstützt.
    ## @note Wird die Authentifizierung aktiviert, betrifft dies alle Zugriffe (Pfade) auf den Server.
    ##
    ##
    ## Code-Beispiel
    ## --
    ## Die Verwendung dieser Klasse anhand eines kurzen Quellcode-Ausschnitts:
    ## @code
    ## class mein_demo_modul(hsl20.BaseModule):
    ##
    ##   ...
    ##
    ##   def start_my_server(self):
    ##       if self.server == None:
    ##           self.server = self.FRAMEWORK.create_http_server()
    ##           self.server.set_address("", 8080)
    ##           self.server.set_authentication("user", "pw", 1)
    ##           self.server.register_uri(hsl20_4_http_server.GET, "/", self.on_index_page)
    ##           self.server.start_server()
    ##
    ##   def on_index_page(self, request, response):
    ##       if request.get_method() == "GET":
    ##           response.set_status_code(200)
    ##           response.set_header("Content-type", "text/html")
    ##           response.set_body("<html><body>Hello World!</body></html>")
    ## @endcode
    ##
    ## @if OLD_CHANGELOG
    ## @chlg03 Präzisierung von Fehlercodes: @n
    ## - Fehlender Statuscode im response-Objekt nach Abarbeitung des Callbacks: Alter Code: 500, Jetzt: 501.
    ## - Antwort auf Anfrage mit fehlendem Callback: Alter Code: 500, Jetzt: 404
    ## @chlg03 Neue Sektion @b HTTP-Request-Methoden @n
    ## Neue Sektion @b Timeouts @n
    ## Neue Sektion @b Authentifizierung
    ## @chlg15 Beispiel-Code korrigiert
    ## @endif
    class Server:


        ## Konstruktor
        ##
        ## @warning Diese Klasse sollte nicht direkt instanziert werden.
        def __init__ (self, framework, context_map):
            ## @cond NO_DOC
            self.__server = hsl20_4_http_server._AsyncHttpServer(framework, context_map)
            self.__ip = None
            self.__port = None
            self.__routing = {}
            self.__started = False
            ## @endcond



        ## Setzt die HTTP-Authentifizierung:
        ## - Werden @b beide Parameter übergeben, wird die HTTP-Authentifizierung aktiviert, ansonsten wird sie deaktiviert.
        ##
        ## Die Authentifizierung zählt für alle registrierten URIs.
        ## @param user @e string @n Benutzer
        ## @param pw @e string @n Passwort
        ## @param digest @e bool @n Optional. Wenn TRUE wird als Authenifizierungsmechanismus @e "Digest Access Authentication" verwendet, ansonsten @e "Basic Authentication".
        ## @if OLD_CHANGELOG
        ## @chlg03 Parameter @e digest hinzugefügt
        ## @endif
        ## @chg20_03 Parameter @e pw wird in der Doku nun korrekt angegeben
        def set_authentication(self, user=None, pw=None, digest=False):
            ## @cond NO_DOC
            if (user!=None) and (pw!=None):
                self.__server.enable_auth(user, pw, digest)
            else:
                self.__server.disable_auth()
            ## @endcond



        ## Setzt die IP-Adresse und den IP-Port.
        ## @param ip @e string @n IP-Adresse, an die der Server gebunden wird. Ein Leerstring bindet den Server an alle Netzwerk-Adressen des HS/FS.
        ## @param port @e int @n IP-Port, unter dem der Server erreichbar ist.
        def set_address(self, ip, port):
            ## @cond NO_DOC
            self.__ip = ip
            self.__port = port
            ## @endcond


        ## Setzt eine Callback-Methode für den Fehlerfall.
        ##
        ## Wird aufgerufen, wenn der Client eine nicht mit @e register_uri registrierte URL aufruft. Wird vom HTTP-Server nach Beenden dieser Methode ausgewertet.
        ##
        ## @param callback @e function @n Die Callback-Methode benötigt folgende Parameter:
        ## - @b errorcode @e int @n Fehlercode, der den aufgetretenen Fehler beschreibt.
        ## - @b request @e string @n HTTP-Request-Objekt, das alle Informationen bezüglich des HTTP-Request zur Verfügung stellt. Wird vom HTTP-Server erzeugt.
        ## - @b response @e string @n HTTP-Response-Objekt, das alle Informationen bezüglich des HTTP-Response enthalten muss.
        ## @if OLD_CHANGELOG
        ## @chlg10 Beschreibung erweitert.
        ## @endif
        def set_error_callback(self, callback):
            ## @cond NO_DOC
            self.__server.set_error_callback(callback)
            ## @endcond


        ## Setzt die zulässige Größe des Body für POST/PUT-Aufrufe. Wird der Wert überschritten, wird der Aufruf abgebrochen.
        ##
        ## Standardwert: 1.000.000 Byte @n
        ## Zulässiger Höchstwert (MAXWERT): 10.000.000 Byte @n
        ## @param size @e int @n Größe in Byte. Ist @e size > MAXWERT wird @e size = MAXWERT gesetzt!
        ## @if OLD_CHANGELOG
        ## @chlg03 Neu: Höchstwert (MAXWERT) @n
        ## Wert für @e size wurde begrenzt
        ## @chlg12 Doku: Formulierung geändert @n
        ## @endif
        def set_max_post_size(self, size):
            ## @cond NO_DOC
            if size>10000000:
                self.__server.set_max_post_size(10000000)
            else:
                self.__server.set_max_post_size(size)
            ## @endcond


        ## Weist dem Server eine @ref hsl20_4.hsl20_4.Logger Instanz zu.
        ## @param logger @e hsl20_4.Logger @n Logger-Objekt
        ## @if OLD_CHANGELOG
        ## @chlg03 neu
        ## @endif
        def set_logger(self, logger):
            ## @cond NO_DOC
            self.__server.set_logger(logger)
            ## @endcond

        ## Startet den Server
        ##
        ## @exception AttributeError Wird ausgelöst, wenn keine gültige Adresse angegeben ist.
        def start_server(self):
            ## @cond NO_DOC
            if self.__ip==None:
                raise AttributeError('invalid ip: ' + str(self.__ip))
            if self.__port==None or (not (type(self.__port) is int)) or self.__port<0 or self.__port>65535:
                raise AttributeError('invalid port:' + str(self.__port))

            if not self.__started:
                self.__started = True
                self.__server.start_server(self.__ip, self.__port)
            ## @endcond


        ## Registriert eine URI am Server. Wird diese URI von einem Client aufgerufen, wird das übergebene Callback ausgeführt.
        ## @param method @e int @n HTTP-Abrufmethoden. Es werden per ODER mehrere Optionen pro URI unterstützt. Es stehen folgende Konstanten zur Verfügung:
        ## - hsl20.framework.http_server.GET
        ## - hsl20.framework.http_server.POST
        ## - hsl20.framework.http_server.PUT
        ## - hsl20.framework.http_server.DELETE
        ## - hsl20.framework.http_server.ALL
        ## @param path @e string @n Pfad (z.B.: /index)
        ## @param callback @e function @n Callback. Die hier übergebene Methode benötigt folgende Parameter: @n
        ## - @ref hsl20_4_http_server.Request "request"
        ## - @ref hsl20_4_http_server.Response "response"
        ## @if OLD_CHANGELOG
        ## @chlg03 Parameter @e uri in @e path umbenannt, um Unklarheiten zu vermeiden
        ## @endif
        def register_uri(self, method, path, callback):
            ## @cond NO_DOC
            self.__server.register_uri(method, path, callback)
            ## @endcond

        ## Setzt den Server-Name. Wenn gesetzt, wird dieser Wert bei einem HTTP-Request als Standard-Wert für das Feld 'Server' verwendet.
        ## Der hier gesetzte Wert kann mit response.set_header überschrieben werden.
		## Wenn kein Wert per set_server_name angegeben wird und kein Wert per response.set_header angegeben wird, wird der Context als Server-Name übermittelt.
        ## @param name @e string @n Neuer Server-Name
        ## @if OLD_CHANGELOG
        ## @chlg15 Neue Methode
        ## @endif
        def set_server_name(self, name):
            ## @cond NO_DOC
            self.__server.set_server_name(name)
            ## @endcond

    ## @cond NO_DOC
    class _RequestHandler(asynchat.async_chat, SimpleHTTPServer.SimpleHTTPRequestHandler):

        def __init__ (self, conn, addr, server, fw, context_map, debug, logger):
            self.__created = time.time()
            self.__last_msg = self.__created
            self.__header_done = False
            self.__data_done = False
            self.__first_packet_sent = False

            self.__framework = fw
            self.__debug = debug
            self.__logger = logger

            asynchat.async_chat.__init__(self, sock=conn, map=context_map)
            self.client_address = addr
            self.connection = conn
            self.server = server
            self.set_terminator ('\r\n\r\n')
            self.found_terminator = self.parse_http_header
            self.rfile = cStringIO.StringIO()
            self.wfile = cStringIO.StringIO()
            self.__closed = False
            self.__server_name = None


        def log_exception(self, format, *args):
            self.log_message(format, *args)
            self.__debug.add_exception()


        def log_message(self, format, *args):
            if self.__logger!=None:
                s = format % args
                self.__logger.info("http log: " + s)


        def version_string(self):
            if not self.__server_name is None:
                return self.__server_name
            else:
                sn = self.server.get_server_name()
                if sn is None:
                    return self.__framework._context_id
                else:
                    return sn


        def handle_error(self):
            try:
                self.log_exception("handle_error")
            except:
                pass
            self.handle_close()


        def _set_data_signal(self):
            self.__data_done = True
            self.__framework._signal_asyncore_select_interrupt()


        def collect_incoming_data(self,data):
            self.__last_msg = time.time()
            self.rfile.write(data)


        def parse_http_header(self):
            # header komplett eingelesen
            # header auswerten
            self.rfile.seek(0)
            self.raw_requestline = self.rfile.readline()
            if self.parse_request():
                try:
                    if self.command=="POST" or self.command=="PUT":
                        content_length = int(self.headers.getheader('content-length'))
                        if content_length>self.server.get_max_post_size():
                            self.log_message("invalid post size: %d > %d", content_length, self.server.get_max_post_size())
                            self.close()
                        elif content_length>0:
                            self.set_terminator(content_length)
                            self.rfile = cStringIO.StringIO()
                            self.found_terminator = self.parse_http_post_data
                        else:
                            self.__header_done = True
                            self.__framework._run_in_context_thread(self.finish_request)
                    else:
                        self.__header_done = True
                        self.__framework._run_in_context_thread(self.finish_request)
                except:
                    self.log_exception("invalid http request.")
                    self.send_error(500)
                    self._set_data_signal()


        def parse_http_post_data(self):
            try:
                self.rfile.seek(0)
                body = self.rfile.getvalue()
                self.__header_done = True
                self.__framework._run_in_context_thread(self.finish_request, (body,))
            except:
                self.log_exception("invald http post request.")
                self.send_error(500)
                self._set_data_signal()


        ## wird im baustein-kontext ausgeführt.
        def finish_request(self, body=None):
            try:
                ## Request
                req=hsl20_4_http_server.Request()
                req._set_info(self.command, self.path, self.request_version)
                req._set_header(self.headers)
                req._set_body(body)

                ## Response
                resp=hsl20_4_http_server.Response()

                ## Callback
                self.server.raise_callback(req, resp)

                ## Antwort (Kein Keep-Alive erlauben???)
                if resp._code==None:
                    if resp._internal_errorcode==None:
                        self.send_error(501)
                    else:
                        self.send_error(resp._internal_errorcode)
                else:
                    # header bereinigen und connection==close erzwingen
                    invalid_header_keys = []
                    for hdr in resp._header:
                        if hdr.lower()=="server":
                            self.__server_name = resp._header[hdr]
                            invalid_header_keys.append(hdr)
                        if hdr.lower()=="date":
                            invalid_header_keys.append(hdr)
                    for key in invalid_header_keys:
                        del resp._header[key]
                    resp._header["Connection"] = "close"

                    self.send_response(resp._code, resp._text)
                    for hdr in resp._header:
                        self.send_header(hdr, resp._header[hdr])
                    self.end_headers()
                    if resp._body!=None:
                        if (type(resp._body) is str):
                            self.wfile.write(resp._body)
                        else:
                            self.wfile.write(str(resp._body))

                req._clear()
                req = None
                resp._clear()
                resp = None
                self._set_data_signal()
            except Exception as e:
                self.log_exception("error in http request callback")
                self.send_error(500)
                self._set_data_signal()


        def readable(self):
            if not self.__header_done:
                now = time.time()
                # timeout
                try:
                    if (now-self.__last_msg>15.0) or (now-self.__created>30.0):
                        self.close()
                        return 0
                except:
                    self.log_exception("http log: client timeout")
                    return 0
            return True

        def writable(self):
            # header empfangen und daten in buffer:
            # auf den socket kann geschrieben werden
            return self.__data_done and (not self.__closed)


        def handle_write(self):
            # bereit fuer senden auf socket

            ## NUR PAKETE SENDEN
            if not self.__first_packet_sent:
                self.wfile.seek(0)
                self.__first_packet_sent = True

            data = self.wfile.read(8192*5)
            if len(data)>0:
                self.send(data)
            else:
                self.close()

            #### ALLES SENDEN
            ##data = self.wfile.getvalue()
            ##self.send(data)
            ##self.close()


        def handle_close(self):
            self.__closed = True
            self.close()
    ## @endcond

    ## @cond NO_DOC
    class _HttpDigest:

        @staticmethod
        def parse_digest(auth_str):
            result = hsl20_4_http_server._HttpDigest()
            tmp = string.strip(auth_str[6:])
            tokens = string.split(tmp, ",")
            for p in tokens:
                kv = string.split(string.strip(p), "=", 1)
                if len(kv)==2:
                    key = string.lower(kv[0])
                    value = kv[1]
                    if key=="username":
                        result.user = value[1:-1]
                    elif key=="realm":
                        result.realm = value[1:-1]
                    elif key=="nonce":
                        result.s_nonce = value[1:-1]
                    elif key=="opaque":
                        result.s_opaque = value[1:-1]
                    elif key=="response":
                        result.response = value[1:-1]
                    elif key=="qop":
                        if (len(value)>2) and value[0]=="\"" and value[-1]=="\"":
                            result.qop = value[1:-1]
                        else:
                            result.qop = value
                    elif key=="nc":
                        result.c_counter = value
                    elif key=="cnonce":
                        if value[0:1]=='"':
                            result.c_nonce = value[1:-1]
                        else:
                            result.c_nonce = value
                    elif key=="stale":
                        v = string.upper(value)
                        result.stale = v=='TRUE' or v=='"TRUE"'
            return result

        def __init__(self):
            self.user = ''
            self.pw = ''
            self.realm = ''
            self.s_nonce = ''
            self.s_opaque = ''
            self.qop = ''
            self.c_nonce = ''
            self.c_counter = ''
            self.response = ''
            self.stale=False

        def __str__(self):
            return self.user+', '+self.pw+', '+self.realm+', '+self.s_nonce+', '+self.s_opaque+', '+self.qop+', '+self.c_nonce+', '+self.c_counter+', '+self.response+', '+str(self.stale)
    ## @endcond

    ## @cond NO_DOC
    class _HttpAuth:

        def __init__(self, realm, digest, init_nonce=None):
            self.user = None
            self.pw = None
            self.realm = realm
            self.digest = digest
            self.nonces = {}
            if init_nonce!=None and len(init_nonce)>=2:
                self.nonces[init_nonce[0]] = (init_nonce[0], init_nonce[1], '00000000', time.time())


        def add_user(self, user, pw):
            self.user = user
            self.pw = pw


        def __check_timeouts(self):
            if len(self.nonces)>=1000:
                oldest_unused = None
                oldest_used = None
                ts = time.time()
                cnt = 0
                for hash in self.nonces:
                    cnt+=1
                    k = self.nonces[hash]
                    if k[2]=="00000000":
                        if oldest_unused==None:
                            oldest_unused = k
                        else:
                            if k[3]<oldest_unused[3]:
                                oldest_unused = None
                                oldest_unused = k
                    else:
                        if oldest_used==None:
                            oldest_used = k
                        else:
                            if k[3]<oldest_used[3]:
                                oldest_used = None
                                oldest_used = k

                if oldest_unused!=None:
                    del self.nonces[oldest_unused[0]]
                elif oldest_used!=None:
                    del self.nonces[oldest_used[0]]


        def __register(self, nonce, opaque):
            self.__check_timeouts()
            self.nonces[nonce] = [nonce, opaque, '00000000', time.time(), False]


        def __check_username(self, username):
            return (self.user==username)


        def __md5(self, data):
            m = hashlib.md5()
            m.update(data)
            return m.hexdigest()


        def __generate_nonce(self):
            m1 = hashlib.md5()
            m1.update(os.urandom(16))
            m2 = hashlib.md5()
            m2.update(os.urandom(16))
            return (m1.hexdigest(),m2.hexdigest())


        def get_http_header_string(self, old_nonce=None, stolen=False):
            if self.digest:
                if old_nonce!=None and self.nonces.has_key(old_nonce):
                    t = self.nonces[old_nonce]
                    nonce = t[0]
                    opaque = t[1]
                else:
                    nonce, opaque = self.__generate_nonce()
                self.__register(nonce, opaque)
                if stolen:
                    return 'Digest realm="'+self.realm+'", qop=auth, nonce="'+nonce+'", opaque="'+opaque+'", stale=TRUE'
                else:
                    return 'Digest realm="'+self.realm+'", qop=auth, nonce="'+nonce+'", opaque="'+opaque+'"'
            else:
                return 'Basic realm="'+self.realm+'"'


        def checkAuthorization(self, auth, method=None, uri=None):
            if auth!=None and (type(auth) is str):
                auth = string.strip(auth)
                if (len(auth)>0):
                    if self.digest==False and string.upper(auth[:5])=="BASIC" and (len(auth)>=2):
                        b64 = base64.decodestring(string.strip(auth[5:]))
                        if b64==self.user+":"+self.pw:
                            return 0
                    elif self.digest and string.upper(auth[:6])=="DIGEST":
                        digest = hsl20_4_http_server._HttpDigest.parse_digest(auth)
                        if (len(digest.response)>0) and digest.qop=="auth" and self.__check_username(digest.user):
                            if self.nonces.has_key(digest.s_nonce):
                                n = self.nonces[digest.s_nonce]
                                if n[1]==digest.s_opaque:
                                    if n[2]<digest.c_counter:
                                        a1 = self.__md5(self.user+":"+self.realm+":"+self.pw)
                                        a2 = self.__md5(method+":"+uri)
                                        s = a1+":"+n[0]+":"+digest.c_counter+":"+digest.c_nonce+":auth:"+a2
                                        resp = self.__md5(s)
                                        if string.upper(resp)==string.upper(digest.response):
                                            if n[4]:
                                                # stolen
                                                del self.nonces[digest.s_nonce]
                                                return 1
                                            else:
                                                self.nonces[digest.s_nonce][2] = digest.c_counter
                                                self.nonces[digest.s_nonce][3] = time.time()
                                                return 0
                                    else:
                                        self.nonces[digest.s_nonce][4] = True
            return -1
    ## @endcond

    ## @cond NO_DOC
    class _AsyncHttpServer(asyncore.dispatcher):


        def __init__ (self, framework, context_map):
            asyncore.dispatcher.__init__ (self, map=context_map)

            self.__framework = framework
            self.__context_map = context_map
            self.__debug = framework._context.get_debug_section()
            self.__started = False
            self.__routing = {}
            self.__logger = None
            self.__error_callback = None
            self.__max_post_size = 1 * 1000 * 1000
            self.__auth = None
            self.__server_name = None


        def set_logger(self, logger):
            self.__logger = logger


        def set_max_post_size(self, size):
            self.__max_post_size = size


        def get_max_post_size(self):
            return self.__max_post_size


        def set_server_name(self, name):
            self.__server_name = name


        def get_server_name(self):
            return self.__server_name


        def disable_auth(self):
            self.__auth = None


        def enable_auth(self, user, pw, digest):
            self.__auth = None
            self.__auth = hsl20_4_http_server._HttpAuth(self.__framework._context_id, digest)
            self.__auth.add_user(user, pw)


        def start_server(self, ip, port):
            if not self.__started:
                self.__started = True
                self.create_socket (socket.AF_INET, socket.SOCK_STREAM)
                self.set_reuse_addr()
                self.bind((ip, port))
                self.listen(30)
                self.__framework._signal_asyncore_select_interrupt()
                if self.__logger!=None:
                    self.__logger.info('http log: server started at "%s:%d"' % (ip, port))


        def set_error_callback(self, callback):
            self.__error_callback = callback


        def register_uri(self, method, url, callback):
            self.__routing[url] = [method, callback]


        def raise_callback(self, req, res):
            path = req.get_path()
            hasRoute = False
            hasAuth = 0
            if self.__auth!=None:
                a = req.get_header('Authorization')
                hasAuth = self.__auth.checkAuthorization(a, req.get_method(), req.get_fullpath())

            if hasAuth==0:
                if self.__routing.has_key(path):
                    method = string.upper(req.get_method())
                    if (self.__routing[path][0]==255):
                        hasRoute = True
                    elif (method == "GET") and ((self.__routing[path][0] & 1)>0):
                        hasRoute = True
                    elif (method == "DELETE") and ((self.__routing[path][0] & 8)>0):
                        hasRoute = True
                    elif (method == "POST") and ((self.__routing[path][0] & 2)>0):
                        hasRoute = True
                    elif (method == "PUT") and ((self.__routing[path][0] & 4)>0):
                        hasRoute = True

                if hasRoute:
                    self.__routing[path][1](req, res)
                elif self.__error_callback!=None:
                    self.__error_callback(404, req, res)
                else:
                    res._internal_errorcode = 404
            else:
                ## Keine Authorisierung
                res.set_status_code(401)
                if self.__auth!=None:
                    res.set_header('WWW-Authenticate', self.__auth.get_http_header_string(stolen=(hasAuth==1)))
                res.set_header("Content-type", "text/html")
                res.set_header("Connection", "close")
                res.set_body("")


        ## override
        def handle_error(self):
            try:
                if self.__logger!=None:
                    self.__logger.exception('http log: uncaught exception')
                self.__debug.add_exception()
            except:
                pass


        ## override
        def handle_accept(self):
            try:
                conn, addr = self.accept()
            except socket.error:
                if self.__logger!=None:
                    self.__logger.exception('http log: accept(1)')
                self.__debug.add_exception()
                return
            except TypeError:
                if self.__logger!=None:
                    self.__logger.exception('http log: accept(2)')
                self.__debug.add_exception()
                return
            except:
                if self.__logger!=None:
                    self.__logger.exception('http log: accept(3)')
                self.__debug.add_exception()
                return
            hsl20_4_http_server._RequestHandler(conn, addr, self, self.__framework, self.__context_map, self.__debug, self.__logger)

    ## @endcond
