From 128f52bd7415a426a6c3afa688789bd62d9f6c88 Mon Sep 17 00:00:00 2001 From: Simon Budig Date: Sun, 27 Dec 2009 00:00:00 +0100 Subject: Upload functionality, quite some code cleanup --- woof | 225 +++++++++++++++++++++++++++++++++++++++++++++---------------------- 1 file changed, 151 insertions(+), 74 deletions(-) diff --git a/woof b/woof index b1f509b..770231c 100755 --- a/woof +++ b/woof @@ -24,9 +24,10 @@ # FreeBSD support with the help from Andy Gimblett, # Cygwin support by Stefan Reichör # tarfile usage suggested by Morgan Lefieux +# File upload support loosely based on code from Stephen English -import sys, os, socket, getopt, commands -import urllib, BaseHTTPServer +import sys, os, errno, socket, getopt, commands, tempfile +import cgi, urllib, BaseHTTPServer import ConfigParser import shutil, tarfile, zipfile import struct @@ -35,6 +36,7 @@ maxdownloads = 1 TM = object cpid = -1 compressed = 'gz' +upload = False class EvilZipStreamWrapper(TM): @@ -91,59 +93,24 @@ class EvilZipStreamWrapper(TM): # reached from the outside. Quite nasty problem actually. def find_ip (): - if sys.platform == "cygwin": - ipcfg = os.popen("ipconfig").readlines() - for l in ipcfg: - try: - candidat = l.split(":")[1].strip() - if candidat[0].isdigit(): - break - except: - pass - return candidat - - os.environ["PATH"] = "/sbin:/usr/sbin:/usr/local/sbin:" + os.environ["PATH"] - platform = os.uname()[0]; - - if platform == "Linux": - netstat = commands.getoutput ("LC_MESSAGES=C netstat -rn") - defiface = [i.split ()[-1] for i in netstat.split ('\n') - if i.split ()[0] == "0.0.0.0"] - elif platform in ("Darwin", "FreeBSD", "NetBSD"): - netstat = commands.getoutput ("LC_MESSAGES=C netstat -rn") - defiface = [i.split ()[-1] for i in netstat.split ('\n') - if len(i) > 2 and i.split ()[0] == "default"] - elif platform == "SunOS": - netstat = commands.getoutput ("LC_MESSAGES=C netstat -arn") - defiface = [i.split ()[-1] for i in netstat.split ('\n') - if len(i) > 2 and i.split ()[0] == "0.0.0.0"] - else: - print >>sys.stderr, "Unsupported platform; please add support for your platform in find_ip()."; - return None - - if not defiface: - return None - - if platform == "Linux": - ifcfg = commands.getoutput ("LC_MESSAGES=C ifconfig " - + defiface[0]).split ("inet addr:") - elif platform in ("Darwin", "FreeBSD", "SunOS", "NetBSD"): - ifcfg = commands.getoutput ("LC_MESSAGES=C ifconfig " - + defiface[0]).split ("inet ") - - if len (ifcfg) != 2: - return None - ip_addr = ifcfg[1].split ()[0] - - # sanity check - try: - ints = [ i for i in ip_addr.split (".") if 0 <= int(i) <= 255] - if len (ints) != 4: - return None - except ValueError: - return None - - return ip_addr + # we get a UDP-socket for the TEST-networks reserved by IANA. + # It is highly unlikely, that there is special routing used + # for these networks, hence the socket later should give us + # the ip address of the default route. + # We're doing multiple tests, to guard against the computer being + # part of a test installation. + + candidates = [] + for test_ip in ["192.0.2.0", "198.51.100.0", "203.0.113.0"]: + s = socket.socket (socket.AF_INET, socket.SOCK_DGRAM) + s.connect ((test_ip, 80)) + ip_addr = s.getsockname ()[0] + s.close () + if ip_addr in candidates: + return ip_addr + candidates.append (ip_addr) + + return candidates[0] # Main class implementing an HTTP-Requesthandler, that serves just a single @@ -163,8 +130,104 @@ class FileServHTTPRequestHandler (BaseHTTPServer.BaseHTTPRequestHandler): BaseHTTPServer.BaseHTTPRequestHandler.log_request (self, code, size) + def do_POST (self): + global maxdownloads, upload + + if not upload: + self.send_error (501, "Unsupported method (POST)") + return + + # taken from + # http://mail.python.org/pipermail/python-list/2006-September/402441.html + + ctype, pdict = cgi.parse_header (self.headers.getheader ('Content-Type')) + form = cgi.FieldStorage (fp = self.rfile, + headers = self.headers, + environ = {'REQUEST_METHOD' : 'POST'}, + keep_blank_values = 1, + strict_parsing = 1) + if not form.has_key ("upfile"): + self.send_error (403, "No upload provided") + return + + upfile = form["upfile"] + + if not upfile.file or not upfile.filename: + self.send_error (403, "No upload provided") + return + + upfilename = upfile.filename + + if "\\" in upfilename: + upfilename = upfilename.split ("\\")[-1] + + upfilename = os.path.basename (upfile.filename) + + destfile = None + for suffix in ["", ".1", ".2", ".3", ".4", ".5", ".6", ".7", ".8", ".9"]: + destfilename = os.path.join (".", upfilename + suffix) + try: + destfile = os.open (destfilename, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0644) + break + except OSError, e: + if e.errno == errno.EEXIST: + continue + raise + + if not destfile: + upfilename += "." + destfile, destfilename = tempfile.mkstemp (prefix = upfilename, dir = ".") + + print >>sys.stderr, "accepting uploaded file: %s -> %s" % (upfilename, destfilename) + + shutil.copyfileobj (upfile.file, os.fdopen (destfile, "w")) + + if upfile.done == -1: + self.send_error (408, "upload interrupted") + + txt = """\ + + Woof Upload + +

Woof Upload complete

+

Thanks a lot!

+ + + """ + self.send_response (200) + self.send_header ("Content-Type", "text/html") + self.send_header ("Content-Length", str (len (txt))) + self.end_headers () + self.wfile.write (txt) + + maxdownloads -= 1 + + return + + def do_GET (self): - global maxdownloads, cpid, compressed + global maxdownloads, cpid, compressed, upload + + # Form for uploading a file + if upload: + txt = """\ + + Woof Upload + +

Woof Upload

+
+

+

+
+ + + """ + self.send_response (200) + self.send_header ("Content-Type", "text/html") + self.send_header ("Content-Length", str (len (txt))) + self.end_headers () + self.wfile.write (txt) + return # Redirect any request to the filename of the file to serve. # This hands over the filename to the client. @@ -189,7 +252,7 @@ class FileServHTTPRequestHandler (BaseHTTPServer.BaseHTTPRequestHandler): \n""" % location self.send_response (302) self.send_header ("Location", location) - self.send_header ("Content-type", "text/html") + self.send_header ("Content-Type", "text/html") self.send_header ("Content-Length", str (len (txt))) self.end_headers () self.wfile.write (txt) @@ -217,7 +280,7 @@ class FileServHTTPRequestHandler (BaseHTTPServer.BaseHTTPRequestHandler): sys.exit (1) self.send_response (200) - self.send_header ("Content-type", "application/octet-stream") + self.send_header ("Content-Type", "application/octet-stream") if os.path.isfile (self.filename): self.send_header ("Content-Length", os.path.getsize (self.filename)) @@ -232,13 +295,13 @@ class FileServHTTPRequestHandler (BaseHTTPServer.BaseHTTPRequestHandler): if compressed == 'zip': ezfile = EvilZipStreamWrapper (self.wfile) zfile = zipfile.ZipFile (ezfile, 'w', zipfile.ZIP_DEFLATED) - stripoff = os.path.dirname (self.filename) + os.sep + stripoff = os.path.dirname (self.filename) + os.sep for root, dirs, files in os.walk (self.filename): for f in files: filename = os.path.join (root, f) - if filename[:len (stripoff)] != stripoff: - raise RuntimeException, "invalid filename assumptions, please report!" + if filename[:len (stripoff)] != stripoff: + raise RuntimeException, "invalid filename assumptions, please report!" zfile.write (filename, filename[len (stripoff):]) zfile.close () else: @@ -285,6 +348,7 @@ def usage (defport, defmaxdown, errmsg = None): Usage: %s [-i ] [-p ] [-c ] %s [-i ] [-p ] [-c ] [-z|-j|-Z|-u] %s [-i ] [-p ] [-c ] -s + %s [-i ] [-p ] [-c ] -U Serves a single file times via http on port on IP address . @@ -295,6 +359,8 @@ def usage (defport, defmaxdown, errmsg = None): file described below. When -s is specified instead of a filename, %s distributes itself. + + When -U is specified, woof provides an upload form and allows uploading files. defaults: count = %d, port = %d @@ -310,7 +376,8 @@ def usage (defport, defmaxdown, errmsg = None): count = 2 ip = 127.0.0.1 compressed = gz - """ % (name, name, name, name, defmaxdown, defport) + """ % (name, name, name, name, name, defmaxdown, defport) + if errmsg: print >>sys.stderr, errmsg print >>sys.stderr @@ -319,7 +386,7 @@ def usage (defport, defmaxdown, errmsg = None): def main (): - global cpid, compressed + global cpid, upload, compressed maxdown = 1 port = 8080 @@ -352,7 +419,7 @@ def main (): defaultmaxdown = maxdown try: - options, filenames = getopt.getopt (sys.argv[1:], "hszjZui:c:p:") + options, filenames = getopt.getopt (sys.argv[1:], "hUszjZui:c:p:") except getopt.GetoptError, desc: usage (defaultport, defaultmaxdown, desc) @@ -383,6 +450,9 @@ def main (): elif option == '-h': usage (defaultport, defaultmaxdown) + elif option == '-U': + upload = True + elif option == '-z': compressed = 'gz' elif option == '-j': @@ -395,19 +465,26 @@ def main (): else: usage (defaultport, defaultmaxdown, "Unknown option: %r" % option) - if len (filenames) == 1: - filename = os.path.abspath (filenames[0]) + if upload: + if len (filenames) > 0: + usage (defaultport, defaultmaxdown, + "Conflicting usage: simultaneous up- and download not supported.") + filename = None + else: - usage (defaultport, defaultmaxdown, - "Can only serve single files/directories.") + if len (filenames) == 1: + filename = os.path.abspath (filenames[0]) + else: + usage (defaultport, defaultmaxdown, + "Can only serve single files/directories.") - if not os.path.exists (filename): - usage (defaultport, defaultmaxdown, - "%s: No such file or directory" % filenames[0]) + if not os.path.exists (filename): + usage (defaultport, defaultmaxdown, + "%s: No such file or directory" % filenames[0]) - if not (os.path.isfile (filename) or os.path.isdir (filename)): - usage (defaultport, defaultmaxdown, - "%s: Neither file nor directory" % filenames[0]) + if not (os.path.isfile (filename) or os.path.isdir (filename)): + usage (defaultport, defaultmaxdown, + "%s: Neither file nor directory" % filenames[0]) serve_files (filename, maxdown, ip_addr, port) -- cgit v1.2.3