diff options
Diffstat (limited to 'woof')
-rwxr-xr-x | woof | 429 |
1 files changed, 0 insertions, 429 deletions
@@ -1,429 +0,0 @@ -#!/usr/bin/python2 -# -*- 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> - -import sys, os, socket, getopt, commands -import urllib, BaseHTTPServer -import ConfigParser -import shutil, tarfile, zipfile -import struct - -maxdownloads = 1 -TM = object -cpid = -1 -compressed = 'gz' - - -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_GET (self): - global maxdownloads, cpid, compressed - - # 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-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") - 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 - - 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. - - 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) - if errmsg: - print >>sys.stderr, errmsg - print >>sys.stderr - sys.exit (1) - - - -def main (): - global cpid, 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:") - 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 == '-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]) - 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.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 - |