From 11ebc79bad89075267574a032b46eb992a0a6f32 Mon Sep 17 00:00:00 2001 From: Florian Pritz Date: Mon, 27 Jul 2015 15:33:12 +0200 Subject: add new scripts Signed-off-by: Florian Pritz --- woof | 620 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 620 insertions(+) create mode 100644 woof (limited to 'woof') diff --git a/woof b/woof new file mode 100644 index 0000000..cf128dc --- /dev/null +++ b/woof @@ -0,0 +1,620 @@ +#!/usr/bin/env python +# -*- encoding: utf-8 -*- +# +# woof -- an ad-hoc single file webserver +# Copyright (C) 2004-2009 Simon Budig +# +# 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, +# Solaris support by Colin Marquardt, +# 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, errno, socket, getopt, commands, tempfile +import cgi, urllib, urlparse, BaseHTTPServer +import readline +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 (): + # 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] + + +# our own HTTP server class, fixing up a change in python 2.7 +# since we do our fork() in the request handler +# the server must not shutdown() the socket. + +class ForkingHTTPServer (BaseHTTPServer.HTTPServer): + def process_request(self, request, client_address): + self.finish_request (request, client_address) + self.close_request (request) + + +# 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 = """\ + + 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, 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. + + 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 = """\ + + 302 Found + 302 Found here. + \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") + self.send_header ("Content-Disposition", "attachment;filename=%s" % urllib.quote (os.path.basename (self.filename))) + 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 = ForkingHTTPServer ((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: + if filename: + location = "http://%s:%s/%s" % (ip_addr, httpd.server_port, + urllib.quote (os.path.basename (filename))) + if os.path.isdir (filename): + if compressed == 'gz': + location += ".tar.gz" + elif compressed == 'bz2': + location += ".tar.bz2" + elif compressed == 'zip': + location += ".zip" + else: + location += ".tar" + else: + location = "http://%s:%s/" % (ip_addr, httpd.server_port) + + print "Now serving on %s" % location + + 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 ] [-p ] [-c ] + %s [-i ] [-p ] [-c ] [-z|-j|-Z|-u] + %s [-i ] [-p ] [-c ] -s + %s [-i ] [-p ] [-c ] -U + + %s + + Serves a single file times via http on port on IP + address . + 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, allowing file uploads. + + defaults: count = %d, port = %d + + If started with an url as an argument, woof acts as a client, + downloading the file and saving it in the current directory. + + 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, name, name, defmaxdown, defport) + + if errmsg: + print >>sys.stderr, errmsg + print >>sys.stderr + sys.exit (1) + + + +def woof_client (url): + urlparts = urlparse.urlparse (url, "http") + if urlparts[0] not in [ "http", "https" ] or urlparts[1] == '': + return None + + fname = None + + f = urllib.urlopen (url) + + f_meta = f.info () + disp = f_meta.getheader ("Content-Disposition") + + if disp: + disp = disp.split (";") + + if disp and disp[0].lower () == 'attachment': + fname = [x[9:] for x in disp[1:] if x[:9].lower () == "filename="] + if len (fname): + fname = fname[0] + else: + fname = None + + if fname == None: + url = f.geturl () + urlparts = urlparse.urlparse (url) + fname = urlparts[2] + + if not fname: + fname = "woof-out.bin" + + if fname: + fname = urllib.unquote (fname) + fname = os.path.basename (fname) + + readline.set_startup_hook (lambda: readline.insert_text (fname)) + fname = raw_input ("Enter target filename: ") + readline.set_startup_hook (None) + + override = False + + destfile = None + destfilename = os.path.join (".", fname) + try: + destfile = os.open (destfilename, + os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0644) + except OSError, e: + if e.errno == errno.EEXIST: + override = raw_input ("File exists. Overwrite (y/n)? ") + override = override.lower () in [ "y", "yes" ] + else: + raise + + if destfile == None: + if override == True: + destfile = os.open (destfilename, os.O_WRONLY | os.O_CREAT, 0644) + else: + for suffix in [".1", ".2", ".3", ".4", ".5", ".6", ".7", ".8", ".9"]: + destfilename = os.path.join (".", fname + 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: + destfile, destfilename = tempfile.mkstemp (prefix = fname + ".", + dir = ".") + print "alternate filename is:", destfilename + + print "downloading file: %s -> %s" % (fname, destfilename) + + shutil.copyfileobj (f, os.fdopen (destfile, "w")) + + return 1; + + + +def main (): + 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:], "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 upload: + if len (filenames) > 0: + usage (defaultport, defaultmaxdown, + "Conflicting usage: simultaneous up- and download not supported.") + filename = None + + else: + if len (filenames) == 1: + if woof_client (filenames[0]) != None: + sys.exit (0) + + 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: + print + -- cgit v1.2.3-24-g4f1b