Created
May 29, 2011 08:31
-
-
Save thedod/997571 to your computer and use it in GitHub Desktop.
Woof (one-time file exchange) + helper scripts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Woof is an amazingly simple and effective file exchange tool: | |
http://www.home.unix-ag.org/simon/woof.html | |
This distribution doesn't include simon Budig's original woof | |
(that started breaking for me), but Edward Samson's fork: | |
https://bitbucket.org/edu/woof | |
Desktop helper scripts: | |
filewoof and folderwoof "open with" actions for nautilus etc. | |
and woofget that runs "woof -U" in a terminal window in /tmp | |
(e.g. for a launcher button) | |
"Install" | |
Put all scripts somewhere in your path (e.g. ~/bin), | |
and make sure they're executable :) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/sh | |
xterm -T "woof file: $1" -n woof -e pauser woof -p 9876 \"$1\" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/sh | |
xterm -T "woof folder: $1" -n woof -e pauser woof -Z -p 9876 \"$1\" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/sh | |
echo Doing "$*"... | |
/bin/sh -c "$*" | |
echo -n hit Enter... | |
head -1 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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, 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 | |
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] | |
# 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 | |
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, 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-Length", str (len (txt))) | |
self.end_headers () | |
self.wfile.write (txt) | |
return | |
maxdownloads -= 1 | |
if maxdownloads < 1: | |
httpd.shutdown() | |
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" | |
class ThreadedHTTPServer(ThreadingMixIn, BaseHTTPServer.HTTPServer): | |
"""Handle requests in a separate thread""" | |
def serve_files (filename, maxdown = 1, ip_addr = '', port = 8080): | |
global maxdownloads, httpd | |
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 = 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) | |
if not ip_addr: | |
ip_addr = find_ip () | |
if ip_addr: | |
print "Now serving on http://%s:%s/" % (ip_addr, httpd.server_port) | |
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> | |
%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, name, defmaxdown, defport) | |
if errmsg: | |
print >>sys.stderr, errmsg | |
print >>sys.stderr | |
sys.exit (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: | |
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) | |
if __name__=='__main__': | |
try: | |
main () | |
except KeyboardInterrupt: | |
pass | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/sh | |
cd /tmp | |
xterm -T "woof upload" -n woof -e pauser woof -p 9876 -U |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment