Commits

Brian Mearns committed 4b89dca

Adding gonzo_push to upload files.

Comments (0)

Files changed (3)

+#!/usr/bin/python
+
+import BaseHTTPServer, SimpleHTTPServer
+import ssl
+import urlparse
+import hashlib
+import os
+import os.path
+import base64
+import sys
+import time
+import gonzo_web_common
+import tempfile
+import shutil
+import time
+
+from gonzo_out import Output
+
+class GonzoPushHandler(gonzo_web_common.GonzoCommonHandler):
+    def serverAppName(self):
+        return "GonzoPushServer"
+
+    def __sendStdHeaders(self):
+        pass
+
+    def __serveForm(self, method="GET"):
+        self.send_response(200)
+        content = """
+<!DOCTYPE html >
+<html xmlns="http://www.w3.org/1999/xhtml">
+    <head >
+        <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
+        <title>Gonzo Push Server - "%(filename)s"</title>
+    </head>
+    <body>""" % {
+    "filename" : self.server.filename(),
+}
+
+        if self.server.completed():
+            content += """
+A file has already been uploaded. If you want to upload another, contact the receiver: they'll need
+to start a new server instance.
+"""
+        else:
+            content += """
+        <form action="upload" method="post" target="output_frame" enctype="multipart/form-data">
+            <input type="file" name="ufile" title="Choose a file to upload." /><br />
+            <input type="submit" name="submit" value="Upload" />
+        </form>
+        <iframe name="output_frame" src="" id="output_frame" width="400" height="400">
+        </iframe>  
+"""
+
+        content += """
+    </body>
+</html>"""
+
+        self.__sendStdHeaders()
+        self.send_header("Content-Type", "text/html")
+        self.send_header("Content-Length", str(len(content)))
+        self.end_headers()
+
+        self.wfile.write(content)
+
+    def __serve(self, method="GET"):
+        if method == "POST":
+            self.__serveUpload()
+            return
+
+        if self.path == "/status":
+            self.__serveStatus()
+        else:
+            self.__serveForm()
+
+    def __serveStatus(self):
+        self.send_response(200)
+        self.send_header("Content-Type", "text/plain")
+        self.end_headers()
+        if not self.server.started():
+            self.wfile.write("none:")
+            while not self.server.started():
+                time.sleep(1.0)
+        self.wfile.write("started:")
+        if self.server.uploadSize is not None:
+            self.wfile.write("%d" % self.server.uploadSize)
+        self.wfile.write(":")
+        while self.server.started():
+            self.wfile.write("%d:" % self.server.progress)
+            time.sleep(1.0)
+
+    def __serveUploadError(self, message, code=410):
+        self.log_error("Error uploading (%d): %s" % (code, message))
+
+        self.send_response(code)
+
+        content = "Error uploading file:\n" + message
+        self.send_header("Content-Type", "text/plain")
+        self.send_header("Content-Length", str(len(content)))
+        self.end_headers()
+
+        self.wfile.write(content)
+
+    def __writeUploadStatus(self, message):
+        self.wfile.write("    <li class='status'>%s</li>\n" % message)
+        self.log_message("Upload status: %s" % message)
+
+    def __writeUploadError(self, message):
+        self.wfile.write("    <li class='err'>Error occurred during upload: %s</li>\n" % message)
+        self.log_error("Error during upload: %s" % message)
+
+    def __serveUpload(self):
+        if self.server.completed():
+            self.send_response(403)
+            self.send_header("Content-Type", "text/html")
+            self.end_headers()
+            self.wfile.write("File has already been uploaded, if you want to upload\n")
+            self.wfile.write("another file, contact the receiver again, they'll need\n")
+            self.wfile.write("to launch another server.")
+            return
+
+        if "content-type" not in self.headers:
+            return self.__serveUploadError("Missing Content-Type header.")
+
+        length = None
+        if "content-length" in self.headers:
+            try:
+                length = int(self.headers["content-length"])
+            except:
+                length = None
+        self.server.uploadSize = length
+
+        ct = self.headers["content-type"].strip()
+        split = ct.split(";", 1)
+        if len(split) != 2:
+            return self.__serveUploadError("Unsupported Content-Type: expected multipart/form-data with 'boundary' parameter.")
+
+        mt = split[0].strip().lower()
+        if mt != "multipart/form-data":
+            return self.__serveUploadError("Unsupported Content-Type: expected multipart/form-data.")
+
+        params = self.parseHeaderParams(split[1])
+        if "boundary" not in params:
+            return self.__serveUploadError("Invalid Content-Type: missing 'boundary' parameter for multipart/form-data.")
+
+
+        self.send_response(200)
+        self.send_header("Content-Type", "text/html")
+        self.end_headers()
+        self.wfile.write("<ul>\n    <li>Form submitted...</li>")
+
+        boundary = "--" + params["boundary"]
+        self.rfile._sock.settimeout(10)
+        totalRead = 0
+        tries = 0
+        POST = []
+        state = 0
+        data = ""
+        totalWritten = 0
+        self.log_message("Processing multi-part data.")
+        while length is None or totalRead < length:
+            #FIXME: This is wrong with the timeout (which should be like 1s):
+            # I'll need to do this the hard way: reading bytes and looking for CR+LF
+            chunk = self.rfile.readline()
+            data += chunk
+
+            if len(chunk) == 0:
+                tries += 1
+                if tries > 3:
+                    break
+                continue
+            else:
+                tries = 0
+                totalRead += len(chunk)
+
+
+            #Waiting for first boundary
+            if state == 0:
+                if chunk.startswith(boundary):
+                    isFile = False
+                    fieldName = None
+                    fieldValue = None
+                    fieldContentType = None
+                    self.__writeUploadStatus("Found first part.")
+                    state = 1
+                else:
+                    self.__writeUploadError("Found data before first boundary.")
+                    break
+
+            #Reading headers.
+            elif state == 1:
+                line = chunk.rstrip()
+                if len(line) == 0:
+                    self.__writeUploadStatus("End of headers for part.")
+                    fieldValue = ""
+                    state = 2
+                else:
+                    #Process the header.
+                    split = line.split(":", 1)
+                    if len(split) == 2:
+                        header = split[0].strip().lower()
+                        value = split[1].strip()
+
+                        if header == "content-disposition":
+                            split = value.split(";", 1)
+                            if len(split) != 2:
+                                self.__writeUploadError("Unsupported Content-Disposition: Expected form-data with parameters, no parameters found.")
+                                if fid is not None:
+                                    self.server.cancelTempFile()
+                                break
+
+                            if split[0].strip().lower() != "form-data":
+                                self.__writeUploadError("Unsupported Content-Disposition: Expected form-data.")
+                                if fid is not None:
+                                    self.server.cancelTempFile()
+                                break
+
+                            params = self.parseHeaderParams(split[1], ";")
+                            if "name" not in params:
+                                self.__writeUploadError("Missing required parameter 'name' in Content-Disposition.")
+                                if fid is not None:
+                                    self.server.cancelTempFile()
+                                break
+                            fieldName = params["name"].strip()
+                            self.__writeUploadStatus("Found field name: %s" % fieldName)
+                                
+                            if "filename" in params:
+                                self.__writeUploadStatus("Found file: %s" % params["filename"])
+                                isFile = True
+                                fid = self.server.openTempFile()
+                                if fid is None:
+                                    self.__writeUploadError("Only single file uploads are supported.")
+                                    break
+
+                        elif header == "content-type":
+                            fieldContentType = value.strip()
+                            self.__writeUploadStatus("Found part content type: %s" % fieldContentType)
+
+                        elif header == "content-length":
+                            try:
+                                server.uploadSize = int(value.strip())
+                            except:
+                                pass
+
+                        else:
+                            self.__writeUploadStatus("Ignoring part header: %s: %s" % (header, value))
+                            pass
+
+            elif state == 2:
+                if chunk.startswith(boundary):
+                    self.__writeUploadStatus("Found end of part.")
+                    if not isFile and fieldName is not None:
+                        POST.append((fieldName, fieldValue))
+                    elif isFile:
+                        self.server.completeTempFile()
+                        self.__writeUploadStatus("Upload complete.")
+                        fid = None
+
+                    if chunk.rstrip() == (boundary + "--"):
+                        self.__writeUploadStatus("Found end of body")
+                        break
+                    else:
+                        isFile = False
+                        fieldName = None
+                        fieldValue = None
+                        fieldContentType = None
+                        state = 1
+                else:
+                    if isFile:
+                        if totalWritten + len(chunk) > self.server.maxUploadSize():
+                            self.__writeUploadStatus("File is too large: max size is %d bytes" % self.server.maxUploadSize())
+                            self.server.cancelTempFile()
+                            break
+                        fid.write(chunk)
+                        totalWritten += len(chunk)
+                        self.server.progress = totalWritten
+                    else:
+                        fieldValue += chunk
+
+        if self.server.completed():
+            self.__writeUploadStatus("File upload complete.")
+        else:
+            self.__writeUploadError("No file uploaded.")
+            
+        self.wfile.write("</ul>")
+
+
+    def handleRequest(self, method="GET"):
+        addr, port = self.client_address
+        message = "(%(addr)s:%(port)d) - %(method)s %(path)s" % {
+            "addr": addr, "port": port, "method": self.command, "path": self.path
+        }
+        self.log_message(message)
+
+        if self.authorized():
+            self.__serve(method)
+        else:
+            message = "401 Not Authorized - Did someone forget to give you the password?\n"
+            self.send_header("Content-Length", str(len(message)))
+            self.end_headers()
+            if method != "HEAD":
+                self.wfile.write(message)
+        
+
+
+class GonzoPushServer(gonzo_web_common.GonzoServer):
+    def __init__(self, address, out, filepath,
+            password=None, realm="Gonzo",
+            https=True, keyfile=None, certfile=None,
+            message=None, 
+            handler=GonzoPushHandler):
+        gonzo_web_common.GonzoServer.__init__(self, address, handler, out, password, realm, https, keyfile, certfile)
+
+        self.__contentLength = None
+        self.__message = message
+        self.filepath = filepath
+
+        head, tail = os.path.split(filepath)
+        if len(tail) == 0:
+            raise Exception("Invalid destination file name, must be a file, not a directory.")
+        self.__destDir = head
+        self.__destFileName = tail
+        self.__destFilePath = os.path.join(head, tail)
+
+        #Delete the file if it exists, so there's no mistaking that it wasn't created.
+        if os.path.exists(self.__destFilePath):
+            os.remove(self.__destFilePath)
+        #If the parent directory doesn't exist, create it.
+        elif len(head) > 0 and not os.path.exists(head):
+            os.makedirs(head)
+        #Open now so we have a handle to it so hopefully noone else can write to it.
+        self.__destFile = open(self.__destFilePath, "w+b")
+        self.__tmpFd = None
+        self.__tmpFid = None
+        self.__tmpPath = None
+        self.__complete = False
+
+        self.uploadSize = None
+        self.progress = None
+
+    def cancelTempFile(self):
+        if (self.__tmpFd is not None) and (not self.__complete):
+            self.__tmpFid.close()
+            os.remove(self.__tmpPath)
+            self.__tmpFd = None
+            self.__tmpFid = None
+            self.__tmpPath = None
+            print "Canceled"
+
+    def completeTempFile(self):
+        #Copy from tmp file to dest file
+        if self.__tmpFd is not None:
+            self.__tmpFid.seek(0, os.SEEK_SET)
+            shutil.copyfileobj(self.__tmpFid, self.__destFile)
+            self.__tmpFid.close()
+            os.remove(self.__tmpPath)
+            self.__complete = True
+        self.__destFile.close()
+
+    def started(self):
+        return self.__tmpFd is not None
+
+    def completed(self):
+        return self.__complete
+
+    def openTempFile(self):
+        if self.__tmpFd is not None:
+            return None
+        
+        fd, path = tempfile.mkstemp(suffix=".prt", prefix=self.__destFileName, dir=self.__destDir, text=False)
+        self.__tmpFd = fd
+        self.__tmpPath = path
+        self.__tmpFid = os.fdopen(self.__tmpFd, "w+b")
+        return self.__tmpFid
+
+    def customMessage(self):
+        if self.__message is None:
+            return ""
+        else:
+            return self.__message
+
+    def filename(self):
+        return os.path.basename(self.filepath)
+
+    def maxUploadSize(self):
+        #TODO: make a parameter
+        #(1024^4 = 1 TiB)
+        return 1024*1024*1024*1024
+
+
+if __name__ == "__main__":
+    import argparse
+    import getpass
+    import errno
+    import mimetypes
+
+    prog_name = os.path.basename(sys.argv[0])
+
+    argp = argparse.ArgumentParser(
+        description = "Launch a simple single-purpose web server to which the specified file can be uploaded.",
+    )
+    argp.add_argument("FILE",
+        help="The path to the which the uploaded file will be saved.",
+        default=None,
+    )
+    argp.add_argument("HOST",
+        help="The address to listen on. Defaults to listening on all interfaces.",
+        nargs='?',
+        default="",
+    )
+    argp.add_argument("PORT",
+        help="The port to serve on. Defaults to 9993.",
+        nargs='?',
+        default=9993,
+        type=int,
+    )
+
+    argp.add_argument("-s", "--ssl", "--tls", "--https",
+        help="""Use a secure connection (HTTPS).""",
+        dest="https",
+        default=False,
+        action="store_true",
+    )
+    argp.add_argument("--insecure",
+        help="""Use an INSECURE connection (HTTP - no 'S'.).""",
+        dest="https",
+        default=False,
+        action="store_false",
+    )
+    argp.add_argument("-C", "--certfile",
+        help="""Specify the path to the PEM encoded SSL/TLS certificate file to use for secure connections.""",
+        dest="certfile",
+        default="default_server_cert.pem",
+    )
+    argp.add_argument("-k", "--keyfile",
+        help="""Specify the path to the PEM encoded SSL/TLS private key file to use for secure connections.""",
+        dest="keyfile",
+        default="default_server_key.pem",
+    )
+
+    argp.add_argument("-c", "--confirm",
+        help="""When prompting for password, ask for it to be entered twice. It will only be acccepted if both entries match.""",
+        dest="confirm",
+        default=False,
+        type=bool,
+    )
+    argp.add_argument("--password",
+        help="""Specify the password as a command line parameter. This is a bad idea, it's highly insecure.
+Not only is it vulnerable to over-the-shoulder and other visual monitoring attacks, it will very likely be
+saved in a command history file somewhere on your system. The default is more secure: you will be asked to
+type your password (without local echo) after the command is issued.""",
+        metavar="PASSWORD",
+        dest="password",
+        default=None,
+    )
+    argp.add_argument("--no-password",
+        help="""Do not require a password to download the file: anyone who can reach your server will be able to download the file.""",
+        dest="no_pass",
+        default=False,
+        action="store_true",
+    )
+
+    argp.add_argument("-m", "--message",
+        help="Add a custom message to the web server's index page.",
+        dest="message",
+        default=None,
+    )
+    argp.add_argument("-M", "--motd", "--mod",
+        help="Like --message, but the message will be read from the specified file.",
+        metavar="MESSAGE_FILE",
+        dest="motd",
+        default=None,
+    )
+
+    argp.add_argument("-v", "--verbose",
+        help="""Tells the program to generate verbose output.""",
+        action="store_const",
+        const=3,
+        dest="verbosity",
+        default=2,
+    )
+    argp.add_argument("-q", "--quiet",
+        help="""Tells the program to supress most non-essential output.""",
+        action="store_const",
+        const=1,
+        dest="verbosity",
+    )
+   
+    args = argp.parse_args(sys.argv[1:])
+    out = Output(prog_name, args.verbosity)
+
+    password = args.password
+    if password is None and (not args.no_pass):
+        if args.verbosity < 0:
+            prompt = prompt2 = ""
+        elif args.verbosity == 0:
+            prompt = "Password: "
+            prompt2 = "Again: "
+        elif args.verbosity == 1:
+            prompt = "%s: Password: " % prog_name
+            prompt2 = "%s: Again: " % prog_name
+        else:
+            prompt = "%s: Enter password for file transfer: " % prog_name
+            prompt2 = "%s: Enter password again: " % prog_name
+
+        password = getpass.getpass(prompt)
+        if args.confirm:
+            password2 = getpass.getpass(prompt2)
+            if password2 != password:
+                if args.verbosity >= 0:
+                    if args.verbosity == 0:
+                        message = "Password mismatch."
+                    else:
+                        message = "Error: Passwords do not match." % (prog_name)
+                    out.out(message)
+                sys.exit(errno.EINVAL)
+            
+    message = args.message
+    if message is None and args.motd is not None:
+        try:
+            fid = open(args.motd, "rb")
+            message = fid.read()
+            fid.close()
+        except Exception, e:
+            sys.stderr.write("%s: Error reading MOTD file:\n" % (prog_name))
+            sys.stderr.write(str(e))
+            sys.exit(errno.EIO)
+
+    server = GonzoPushServer((args.HOST, args.PORT), out, args.FILE,
+        password=password,
+        https=args.https, keyfile=args.keyfile, certfile=args.certfile,
+        message=message, 
+    )
+    if len(args.HOST):
+        where = "%s:%d" % (args.HOST, args.PORT)
+    else:
+        where = "port %d" % args.PORT
+    out.out("Serving on %s..." % (where)) 
+    server.serve_forever()
+
 import base64
 import sys
 import time
-from wsgiref.handlers import format_date_time
 import re
 
+from wsgiref.handlers import format_date_time
+from gonzo_web_common import VER_MAJOR, VER_MINOR
+
 from gonzo_out import Output
 
-VER_MAJOR = 0
-VER_MINOR = 1
-VER_PATCH = 0
-
 class GonzoHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
     def __init__(self, request, client_address, server):
         SimpleHTTPServer.SimpleHTTPRequestHandler.__init__(self, request, client_address, server)
     def serveFile(self, method="GET"):
         start = None
         end = None
-
-        if self.server.doCache():
-            qs = urlparse.parse_qs(urlparse.urlparse(self.path).query)
-            print self.server.fingerprint(), qs
-            if "fpr" in qs:
-                if qs["fpr"][0] == self.server.fingerprint():
-                    #Set an expiration nearly a year inthe future (that's max allowed by HTTP standard).
-                    # 31449600 seconds = 364 days.
-                    self.send_header("Expires", format_date_time(time.time() + 31449600))
-
         if "Range" in self.headers:
             rstr = self.headers["Range"]
             mobj = re.compile(r'bytes\s*=\s*([0-9]+)?\s*-\s*([0-9]+)?\s*').match(rstr)
             end = None
 
         self.__sendStdHeaders()
+
+        if self.server.doCache():
+            qs = urlparse.parse_qs(urlparse.urlparse(self.path).query)
+            print self.server.fingerprint(), qs
+            if "fpr" in qs:
+                if qs["fpr"][0] == self.server.fingerprint():
+                    #Set an expiration nearly a year inthe future (that's max allowed by HTTP standard).
+                    # 31449600 seconds = 364 days.
+                    self.send_header("Expires", format_date_time(time.time() + 31449600))
+
         self.send_header("Content-Disposition", "attachment; filename=\"%s\";" % (self.server.filename()))
         mimeType = self.server.getMimeType()
         if mimeType is not None:

gonzo_web_common.py

+
+import BaseHTTPServer, SimpleHTTPServer
+import ssl
+import urlparse
+import hashlib
+import os
+import os.path
+import base64
+import sys
+import time
+import abc
+
+from gonzo_out import Output
+
+VER_MAJOR = 0
+VER_MINOR = 1
+VER_PATCH = 0
+
+class GonzoCommonHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
+    def __init__(self, request, client_address, server):
+        SimpleHTTPServer.SimpleHTTPRequestHandler.__init__(self, request, client_address, server)
+
+    @abc.abstractmethod
+    def serverAppName(self):
+        pass
+
+    def version_string(self):
+        return "%s/%u.%u" % (self.serverAppName(), VER_MAJOR, VER_MINOR)
+
+    def time_stamp(self):
+        return time.strftime("%Y-%m-%d %H:%M:%S")
+
+    def log_request(self, code=None, size=None):
+        addr, port = self.client_address
+        message = self.time_stamp()
+        message += ":    ...Request Completed - (%(addr)s:%(port)d) - %(method)s %(path)s" % {
+            "addr": addr, "port": port, "method": self.command, "path": self.path
+        }
+        if code is not None:
+            message += " - %d" % (code)
+            if size is not None:
+                message += " (%d bytes)" % size
+
+        self.server.out.out(message, 3)
+
+    def log_error(self, format, *args):
+        self.server.out.err(self.time_stamp() + ": " + (format % args))
+
+    def log_message(self, format, *args):
+        self.server.out.out(self.time_stamp() + ": " + (format % args), 2)
+
+    def parseAuthParams(self, params):
+        return self.parseHeaderParams(params)
+
+    def parseHeaderParams(self, params, delim=","):
+        authParams = {}
+        length = len(params)
+        idx = 0
+        state = 0
+        name = None
+        value = None
+        while idx < length:
+            c = params[idx]
+            idx += 1
+            if state == 0:
+                if not c.isspace():
+                    name = c
+                    state = 1
+
+            elif state == 1:
+                if c.isspace():
+                    state = 2
+                elif c == '=': 
+                    state = 3
+                else:
+                    name += c
+
+            elif state == 2:
+                if c == '=':
+                    state = 3
+                elif not c.isspace():
+                    return False
+
+            elif state == 3:
+                if c == '"':
+                    value = ""
+                    state = 4
+                elif not c.isspace():
+                    value = c
+                    state = 5
+
+            elif state == 4:
+                if c == '"':
+                    authParams[name] = value
+                    name = value = None
+                    state = 6
+                else:
+                    value += c
+
+            elif state == 5:
+                if c.isspace():
+                    authParams[name] = value
+                    name = value = None
+                    state = 6
+                else:
+                    value += c
+
+            elif state == 6:
+                if c == delim:
+                    state = 0
+                elif not c.isspace():
+                    return False
+
+        if name is not None and value is not None:
+            authParams[name] = value
+
+        return authParams
+                
+
+    def do_POST(self):
+        self.handleRequest("POST")
+
+    def do_GET(self):
+        self.handleRequest("GET")
+
+    def do_HEAD(self):
+        self.handleRequest("HEAD")
+
+    @abc.abstractmethod
+    def handleRequest(self, method="GET"):
+        pass
+
+    def authorized(self):
+        """
+        Checks if the request is authorized. If it is authorized, or if it doesn't require auth according
+        to the server, returns True. If it needs to be authorized and isn't, sends the HEADERS ONLY for a 401
+        response, and returns False. Does not end headers, and does not write any data.
+        """
+        if not self.server.authRequired():
+            return True
+
+        stale = False
+        if "Authorization" in self.headers:
+            auth = self.headers["Authorization"]
+            parts = auth.split(None, 1)
+            if len(parts) == 2:
+                authType = parts[0].strip().lower()
+
+                #Basic auth is not supported
+                if False and authType == "basic":
+                    auth = base64.b64decode(parts[1])
+                    parts = auth.split(":", 1)
+                    if len(parts) == 2:
+                        if self.server.basic_auth(parts[0], parts[1]):
+                            return True
+
+                #Digest auth:
+                elif authType == "digest":
+                    params = self.parseAuthParams(parts[1])
+                    if "username" in params and "realm" in params and "nonce" in params and "uri" in params and "response" in params:
+                        if not self.server.useNonce(params["nonce"]):
+                            stale = True
+                        elif self.server.digest_auth(params["response"], params["username"], params["realm"], self.command, params["uri"], params["nonce"]):
+                            return True
+
+        #Authorization required and failed.
+        self.send_response(401)
+
+        nonce = hashlib.sha224(os.urandom(50)).hexdigest()
+        self.server.addNonce(nonce)
+        staleParam = ""
+        if stale:
+            staleParam = ", stale=TRUE"
+        self.send_header("WWW-Authenticate", 'Digest realm="%(realm)s", nonce="%(nonce)s"%(stale)s' % {
+            "realm": self.server.realm, "nonce": nonce, "stale": staleParam
+        })
+        self.send_header("Content-Type", "text/plain")
+
+        return False
+
+class GonzoServer(BaseHTTPServer.HTTPServer):
+    def __init__(self, address, handler, out, password=None, realm=None, https=True, keyfile=None, certfile=None):
+        BaseHTTPServer.HTTPServer.__init__(self, address, handler)
+        self.out = out
+
+        self.__password = password
+        self.realm = str(realm)
+        self.__activeNonces = []
+
+        self.origSocket = self.socket
+        self.https = https
+        if https:
+            self.socket = ssl.wrap_socket(self.socket, keyfile=keyfile, certfile=certfile, server_side=True)
+
+    def authRequired(self):
+        return (self.__password is not None)
+
+    def basic_auth(self, username, password):
+        return (password == self.__password)
+
+    def digest_auth(self, response, username, realm, method, uri, nonce):
+        if realm != self.realm:
+            return False
+
+        ha1 = hashlib.md5("%s:%s:%s" % (username, realm, self.__password)).hexdigest()
+        ha2 = hashlib.md5("%s:%s" % (method, uri)).hexdigest()
+        exp = hashlib.md5("%s:%s:%s" % (ha1, nonce, ha2)).hexdigest()
+
+        return (exp == response)
+
+    def addNonce(self, nonce):
+        self.__activeNonces.append(nonce)
+
+    def useNonce(self, nonce):
+        if nonce in self.__activeNonces:
+            del self.__activeNonces[self.__activeNonces.index(nonce)]
+            return True
+        else:
+            return False
+