partage public
This commit is contained in:
429
woof
Executable file
429
woof
Executable file
@@ -0,0 +1,429 @@
|
||||
#!/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>
|
||||
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user