This commit is contained in:
Meutel 2015-09-15 20:25:20 +02:00
parent c3d86ec53a
commit 1cd39658f0
1 changed files with 208 additions and 131 deletions

339
woof
View File

@ -24,17 +24,19 @@
# FreeBSD support with the help from Andy Gimblett, <A.M.Gimblett@swansea.ac.uk>
# Cygwin support by Stefan Reichör <stefan@xsteve.at>
# tarfile usage suggested by Morgan Lefieux <comete@geekandfree.org>
# File upload support loosely based on code from Stephen English <steve@secomputing.co.uk>
import sys, os, socket, getopt, commands
import urllib, BaseHTTPServer
import sys, os, errno, socket, getopt, commands, tempfile
import cgi, urllib, BaseHTTPServer
from SocketServer import ThreadingMixIn
import ConfigParser
import shutil, tarfile, zipfile
import struct
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
# 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.
os.environ["PATH"] = "/sbin:/usr/sbin:/usr/local/sbin:" + os.environ["PATH"]
platform = os.uname()[0];
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)
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
return candidates[0]
# Main class implementing an HTTP-Requesthandler, that serves just a single
@ -156,15 +123,114 @@ class FileServHTTPRequestHandler (BaseHTTPServer.BaseHTTPRequestHandler):
server_version = "Simons FileServer"
protocol_version = "HTTP/1.0"
filename = "."
filename = "-"
def log_request (self, code='-', size='-'):
if code == 200:
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
maxdownloads -= 1
if maxdownloads < 1:
httpd.shutdown()
# 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 = """\
<html>
<head><title>Woof Upload</title></head>
<body>
<h1>Woof Upload complete</title></h1>
<p>Thanks a lot!</p>
</body>
</html>
"""
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
def do_GET (self):
global maxdownloads, cpid, compressed
global maxdownloads, compressed, upload
# Form for uploading a file
if upload:
txt = """\
<html>
<head><title>Woof Upload</title></head>
<body>
<h1>Woof Upload</title></h1>
<form name="upload" method="POST" enctype="multipart/form-data">
<p><input type="file" name="upfile" /></p>
<p><input type="submit" value="Upload!" /></p>
</form>
</body>
</html>
"""
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 +255,7 @@ class FileServHTTPRequestHandler (BaseHTTPServer.BaseHTTPRequestHandler):
</html>\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)
@ -197,63 +263,67 @@ class FileServHTTPRequestHandler (BaseHTTPServer.BaseHTTPRequestHandler):
maxdownloads -= 1
# let a separate process handle the actual download, so that
# multiple downloads can happen simultaneously.
if maxdownloads < 1:
httpd.shutdown()
cpid = os.fork ()
type = None
if os.path.isfile (self.filename):
type = "file"
elif os.path.isdir (self.filename):
type = "dir"
elif self.filename == "-":
type = "stdin"
if cpid == 0:
# Child process
child = None
type = None
if os.path.isfile (self.filename):
type = "file"
elif os.path.isdir (self.filename):
type = "dir"
if not type:
print >> sys.stderr, "can only serve files, directories or stdin. Aborting."
sys.exit (1)
if not type:
print >> sys.stderr, "can only serve files or directories. Aborting."
sys.exit (1)
self.send_response (200)
self.send_header ("Content-Type", "application/octet-stream")
if os.path.isfile (self.filename):
self.send_header ("Content-Length",
os.path.getsize (self.filename))
self.end_headers ()
self.send_response (200)
self.send_header ("Content-type", "application/octet-stream")
if os.path.isfile (self.filename):
self.send_header ("Content-Length",
os.path.getsize (self.filename))
self.end_headers ()
try:
if type == "file":
datafile = file (self.filename)
shutil.copyfileobj (datafile, self.wfile)
datafile.close ()
elif type == "dir":
if compressed == 'zip':
ezfile = EvilZipStreamWrapper (self.wfile)
zfile = zipfile.ZipFile (ezfile, 'w', zipfile.ZIP_DEFLATED)
stripoff = os.path.dirname (self.filename) + os.sep
try:
if type == "file":
datafile = file (self.filename)
shutil.copyfileobj (datafile, self.wfile)
datafile.close ()
elif type == "dir":
if compressed == 'zip':
ezfile = EvilZipStreamWrapper (self.wfile)
zfile = zipfile.ZipFile (ezfile, 'w', zipfile.ZIP_DEFLATED)
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!"
zfile.write (filename, filename[len (stripoff):])
zfile.close ()
else:
tfile = tarfile.open (mode=('w|' + compressed),
fileobj=self.wfile)
tfile.add (self.filename,
arcname=os.path.basename(self.filename))
tfile.close ()
elif type == "stdin":
datafile = sys.stdin
shutil.copyfileobj (datafile, self.wfile)
except Exception, e:
print e
print >>sys.stderr, "Connection broke. Aborting"
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!"
zfile.write (filename, filename[len (stripoff):])
zfile.close ()
else:
tfile = tarfile.open (mode=('w|' + compressed),
fileobj=self.wfile)
tfile.add (self.filename,
arcname=os.path.basename(self.filename))
tfile.close ()
except Exception, e:
print e
print >>sys.stderr, "Connection broke. Aborting"
class ThreadedHTTPServer(ThreadingMixIn, BaseHTTPServer.HTTPServer):
"""Handle requests in a separate thread"""
def serve_files (filename, maxdown = 1, ip_addr = '', port = 8080):
global maxdownloads
global maxdownloads, httpd
maxdownloads = maxdown
@ -263,8 +333,7 @@ def serve_files (filename, maxdown = 1, ip_addr = '', port = 8080):
FileServHTTPRequestHandler.filename = filename
try:
httpd = BaseHTTPServer.HTTPServer ((ip_addr, port),
FileServHTTPRequestHandler)
httpd = ThreadedHTTPServer ((ip_addr, port), FileServHTTPRequestHandler)
except socket.error:
print >>sys.stderr, "cannot bind to IP address '%s' port %d" % (ip_addr, port)
sys.exit (1)
@ -274,20 +343,21 @@ def serve_files (filename, maxdown = 1, ip_addr = '', port = 8080):
if ip_addr:
print "Now serving on http://%s:%s/" % (ip_addr, httpd.server_port)
while cpid != 0 and maxdownloads > 0:
httpd.handle_request ()
httpd.serve_forever ()
def usage (defport, defmaxdown, errmsg = None):
name = os.path.basename (sys.argv[0])
print >>sys.stderr, """
Usage: %s [-i <ip_addr>] [-p <port>] [-c <count>] <file>
Usage: %s [-i <ip_addr>] [-p <port>] [-c <count>] [<file>]
%s [-i <ip_addr>] [-p <port>] [-c <count>] [-z|-j|-Z|-u] <dir>
%s [-i <ip_addr>] [-p <port>] [-c <count>] -s
%s [-i <ip_addr>] [-p <port>] [-c <count>] -U
Serves a single file <count> times via http on port <port> on IP
address <ip_addr>.
When no filename is specified, or set to '-', then stdin will be read.
When a directory is specified, an tar archive gets served. By default
it is gzip compressed. You can specify -z for gzip compression,
-j for bzip2 compression, -Z for ZIP compression or -u for no compression.
@ -295,6 +365,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 +382,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 +392,9 @@ def usage (defport, defmaxdown, errmsg = None):
def main ():
global cpid, compressed
global cpid, upload, compressed
filename = '-'
maxdown = 1
port = 8080
@ -352,7 +427,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 +458,9 @@ def main ():
elif option == '-h':
usage (defaultport, defaultmaxdown)
elif option == '-U':
upload = True
elif option == '-z':
compressed = 'gz'
elif option == '-j':
@ -395,30 +473,29 @@ 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:
if filenames[0] != "-":
filename = os.path.abspath (filenames[0])
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,
"Can only serve single files/directories.")
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])
else:
filename = "-"
serve_files (filename, maxdown, ip_addr, port)
# wait for child processes to terminate
if cpid != 0:
try:
while 1:
os.wait ()
except OSError:
pass
if __name__=='__main__':