Page MenuHomeDevCentral

No OneTemporary

diff --git a/woof b/woof
index 7854792..8fa6333 100755
--- a/woof
+++ b/woof
@@ -1,429 +1,541 @@
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
#
# woof -- an ad-hoc single file webserver
# Copyright (C) 2004-2009 Simon Budig <simon@budig.de>
#
# 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.
#
# A copy of the GNU General Public License is available at
# http://www.fsf.org/licenses/gpl.txt, you can also write to the
# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
# Boston, MA 02111-1307, USA.
# Darwin support with the help from Mat Caughron, <mat@phpconsulting.com>
# Solaris support by Colin Marquardt, <colin.marquardt@zmd.de>
# 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
import ConfigParser
import shutil, tarfile, zipfile
import struct
maxdownloads = 1
TM = object
cpid = -1
compressed = 'gz'
+upload = False
class EvilZipStreamWrapper(TM):
def __init__ (self, victim):
self.victim_fd = victim
self.position = 0
self.tells = []
self.in_file_data = 0
def tell (self):
self.tells.append (self.position)
return self.position
def seek (self, offset, whence = 0):
if offset != 0:
if offset == self.tells[0] + 14:
# the zipfile module tries to fix up the file header.
# write Data descriptor header instead,
# the next write from zipfile
# is CRC, compressed_size and file_size (as required)
self.write ("PK\007\010")
elif offset == self.tells[1]:
# the zipfile module goes to the end of the file. The next
# data written definitely is infrastructure (in_file_data = 0)
self.tells = []
self.in_file_data = 0
else:
raise "unexpected seek for EvilZipStreamWrapper"
def write (self, data):
# only test for headers if we know that we're not writing
# (potentially compressed) data.
if self.in_file_data == 0:
if data[:4] == zipfile.stringFileHeader:
# fix the file header for extra Data descriptor
hdr = list (struct.unpack (zipfile.structFileHeader, data[:30]))
hdr[3] |= (1 << 3)
data = struct.pack (zipfile.structFileHeader, *hdr) + data[30:]
self.in_file_data = 1
elif data[:4] == zipfile.stringCentralDir:
# fix the directory entry to match file header.
hdr = list (struct.unpack (zipfile.structCentralDir, data[:46]))
hdr[5] |= (1 << 3)
data = struct.pack (zipfile.structCentralDir, *hdr) + data[46:]
self.position += len (data)
self.victim_fd.write (data)
def __getattr__ (self, name):
return getattr (self.victim_fd, name)
# Utility function to guess the IP (as a string) where the server can be
# 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
# Main class implementing an HTTP-Requesthandler, that serves just a single
# file and redirects all other requests to this file (this passes the actual
# filename to the client).
# Currently it is impossible to serve different files with different
# instances of this class.
class FileServHTTPRequestHandler (BaseHTTPServer.BaseHTTPRequestHandler):
server_version = "Simons FileServer"
protocol_version = "HTTP/1.0"
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
+
+ # 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)
+
+ maxdownloads -= 1
+
+ return
+
+
def do_GET (self):
- global maxdownloads, cpid, compressed
+ global maxdownloads, cpid, 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.
self.path = urllib.quote (urllib.unquote (self.path))
location = "/" + urllib.quote (os.path.basename (self.filename))
if os.path.isdir (self.filename):
if compressed == 'gz':
location += ".tar.gz"
elif compressed == 'bz2':
location += ".tar.bz2"
elif compressed == 'zip':
location += ".zip"
else:
location += ".tar"
if self.path != location:
txt = """\
<html>
<head><title>302 Found</title></head>
<body>302 Found <a href="%s">here</a>.</body>
</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)
return
maxdownloads -= 1
# let a separate process handle the actual download, so that
# multiple downloads can happen simultaneously.
cpid = os.fork ()
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 or directories. Aborting."
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))
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
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"
def serve_files (filename, maxdown = 1, ip_addr = '', port = 8080):
global maxdownloads
maxdownloads = maxdown
# We have to somehow push the filename of the file to serve to the
# class handling the requests. This is an evil way to do this...
FileServHTTPRequestHandler.filename = filename
try:
httpd = BaseHTTPServer.HTTPServer ((ip_addr, port),
FileServHTTPRequestHandler)
except socket.error:
print >>sys.stderr, "cannot bind to IP address '%s' port %d" % (ip_addr, port)
sys.exit (1)
if not ip_addr:
ip_addr = find_ip ()
if ip_addr:
print "Now serving on http://%s:%s/" % (ip_addr, httpd.server_port)
while cpid != 0 and maxdownloads > 0:
httpd.handle_request ()
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>
%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 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.
You can configure your default compression method in the configuration
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
You can specify different defaults in two locations: /etc/woofrc
and ~/.woofrc can be INI-style config files containing the default
port and the default count. The file in the home directory takes
precedence. The compression methods are "off", "gz", "bz2" or "zip".
Sample file:
[main]
port = 8008
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
sys.exit (1)
def main ():
- global cpid, compressed
+ global cpid, upload, compressed
maxdown = 1
port = 8080
ip_addr = ''
config = ConfigParser.ConfigParser()
config.read (['/etc/woofrc', os.path.expanduser('~/.woofrc')])
if config.has_option ('main', 'port'):
port = config.getint ('main', 'port')
if config.has_option ('main', 'count'):
maxdown = config.getint ('main', 'count')
if config.has_option ('main', 'ip'):
ip_addr = config.get ('main', 'ip')
if config.has_option ('main', 'compressed'):
formats = { 'gz' : 'gz',
'true' : 'gz',
'bz' : 'bz2',
'bz2' : 'bz2',
'zip' : 'zip',
'off' : '',
'false' : '' }
compressed = config.get ('main', 'compressed')
compressed = formats.get (compressed, 'gz')
defaultport = port
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)
for option, val in options:
if option == '-c':
try:
maxdown = int (val)
if maxdown <= 0:
raise ValueError
except ValueError:
usage (defaultport, defaultmaxdown,
"invalid download count: %r. "
"Please specify an integer >= 0." % val)
elif option == '-i':
ip_addr = val
elif option == '-p':
try:
port = int (val)
except ValueError:
usage (defaultport, defaultmaxdown,
"invalid port number: %r. Please specify an integer" % val)
elif option == '-s':
filenames.append (__file__)
elif option == '-h':
usage (defaultport, defaultmaxdown)
+ elif option == '-U':
+ upload = True
+
elif option == '-z':
compressed = 'gz'
elif option == '-j':
compressed = 'bz2'
elif option == '-Z':
compressed = 'zip'
elif option == '-u':
compressed = ''
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)
# wait for child processes to terminate
if cpid != 0:
try:
while 1:
os.wait ()
except OSError:
pass
if __name__=='__main__':
try:
main ()
except KeyboardInterrupt:
pass

File Metadata

Mime Type
text/x-diff
Expires
Thu, Sep 18, 07:10 (13 h, 47 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2990151
Default Alt Text
(19 KB)

Event Timeline