servefile/servefile

255 lines
7.8 KiB
Python
Executable File

#!/usr/bin/python
# -*- coding: utf-8 -*-
# Licensed under GNU General Public License v3 or later
# Written by Sebastian Lohff (seba@seba-geek.de)
# http://seba-geek.de/stuff/servefile/
__version__ = '0.3.2'
import argparse
import cgi
import BaseHTTPServer
import commands
import urllib
import os
import SocketServer
import socket
from stat import ST_SIZE
from subprocess import Popen, PIPE
import sys
class FileHandler(BaseHTTPServer.BaseHTTPRequestHandler):
fileName = "Undefined"
filePath = "/dev/null"
fileLength = 0
blockSize = 1024 * 1024
def do_GET(self):
if urllib.unquote(self.path) != "/" + self.fileName:
self.send_response(302)
self.send_header('Location', '/' + self.fileName)
self.end_headers()
return
myfile = open(self.filePath, 'rb')
# find out if this is a continuing download
fromto = None
if "Range" in self.headers:
cont = self.headers.get("Range").split("=")
if len(cont) > 1 and cont[0] == 'bytes':
fromto = cont[1].split('-')
if len(fromto) > 1:
if fromto[1] == '':
fromto[1] = self.fileLength-1
fromto[0] = int(fromto[0])
fromto[1] = int(fromto[1])
if fromto[0] >= self.fileLength or fromto[0] < 0 or fromto[1] >= self.fileLength or fromto[1]-fromto[0] < 0:
# oops, already done!
self.send_response(416)
self.send_header('Content-Range', 'bytes */%s' % self.fileLength)
self.end_headers()
return
# now we can wind the file *brrrrrr*
myfile.seek(fromto[0])
if fromto != None:
self.send_response(216)
self.send_header('Content-Range', 'bytes %s-%s/%s' % (fromto[0], fromto[1], self.fileLength))
self.send_header('Content-Length', fromto[1]-fromto[0]+1)
else:
self.send_response(200)
self.send_header('Content-Length', self.fileLength)
self.send_header('Content-Disposition', 'attachment; filename="%s"' % self.fileName)
self.send_header('Content-type', 'application/octet-stream')
self.send_header('Content-Transfer-Encoding', ' binary')
self.end_headers()
block = self.getChunk(myfile, fromto)
while block:
try:
self.wfile.write(block)
except socket.error, e:
print "%s ABORTED transmission (Reason %s: %s)" % (self.client_address[0], e[0], e[1])
return
block = self.getChunk(myfile, fromto)
myfile.close()
print "%s finished downloading" % (self.client_address[0])
return
def getChunk(self, myfile, fromto):
if fromto and myfile.tell()+self.blockSize >= fromto[1]:
readsize = fromto[1]-myfile.tell()+1
else:
readsize = self.blockSize
return myfile.read(readsize)
class FilePutter(BaseHTTPServer.BaseHTTPRequestHandler):
targetDir = "unknown"
uploadPage = """
<!docype html>
<html>
<form action="/" method="post" enctype="multipart/form-data">
<label for="file">Filename:</label>
<input type="file" name="file" id="file" />
<br />
<input type="submit" name="submit" value="Upload" />
</form>
</html>
"""
def do_GET(self):
self.sendResponse(200, self.uploadPage)
def do_POST(self):
env = os.environ
env['REQUEST_METHOD'] = "POST"
ctype = self.headers.getheader('content-type')
fstorage = cgi.FieldStorage(fp=self.rfile, headers=self.headers, environ=env)
if not (ctype and ctype.lower().startswith("multipart/form-data")):
self.sendResponse(400, "Request was not a multipart request. If you want to upload data with curl, do a PUT request, e.g. curl -X PUT -d @file http://ip:port/your-filename")
if not "file" in fstorage:
self.sendResponse(400, "No file found in request.")
return
destFileName = self.getTargetName(fstorage["file"].filename)
if destFileName == "":
self.sendResponse(400, "Filename was empty")
return
destFileName = self.targetDir + "/" + cleanFileName
if os.path.exists(destFileName):
i = 1
extraDestFileName = destFileName + "(%s)" % i
while os.path.exists(extraDestFileName):
i += 1
extraDestFileName = destFileName + "(%s)" % i
destFileName = extraDestFileName
print fstorage["file"].file
target = open(destFileName, "w")
target.write(fstorage["file"].file.read())
target.close()
msg = "OK!"
self.sendResponse(200, "OK!")
def sendResponse(self, code, msg):
self.send_response(code)
self.send_header('Content-Type', 'text/html')
self.send_header('content-Length', str(len(msg)))
self.end_headers()
self.wfile.write(msg)
def getTargetName(self, fname):
cleanFileName = fname.replace("/", "")
if cleanFileName == "":
return ""
destFileName = self.targetDir + "/" + cleanFileName
if not os.path.exists(destFileName):
return destFileName
else:
i = 1
extraDestFileName = destFileName + "(%s)" % i
while os.path.exists(extraDestFileName):
i += 1
extraDestFileName = destFileName + "(%s)" % i
return extraDestFileName
# never reached
class ThreadedHTTPServer(SocketServer.ThreadingMixIn, BaseHTTPServer.HTTPServer):
pass
def main():
parser = argparse.ArgumentParser(description='Serve a single file via HTTP')
parser.add_argument('--version', action='version', version='%(prog)s ' + __version__)
parser.add_argument('filename', metavar='file/directory', type=str)
parser.add_argument('-p', '--port', type=int, default=8080, \
help='port to listen on')
parser.add_argument('-u', '--upload', action="store_true", default=False, \
help="Enable uploads to a given directory")
args = parser.parse_args()
handler = None
dirCreated = False
print args.upload
if not args.upload:
try:
testit = open(args.filename, 'r')
testit.close()
FileHandler.filePath = args.filename
FileHandler.fileName = os.path.basename(args.filename)
FileHandler.fileLength = os.stat(args.filename)[ST_SIZE]
except IOError:
print "Error: Could not open file!"
sys.exit(3)
handler = FileHandler
else:
if os.path.isdir(args.filename):
print "Warning: Uploading to an already existing directory"
elif not os.path.exists(args.filename):
dirCreated = True
try:
os.mkdir(args.filename)
except IOError, OSError:
print "Error: Could not create directory '%s' for uploads" % (args.filename,)
sys.exit(3)
else:
print "Error: Upload directory already exists and is a file"
sys.exit(3)
FilePutter.targetDir = args.filename
handler = FilePutter
server = ThreadedHTTPServer(('', args.port), handler)
if args.upload:
print "Serving \"%s\" under port %d" % (args.filename, args.port)
else:
print "Serving \"%s\" for uploads under port %d" % (args.filename, args.port)
# print urls with local network adresses
print "\nSome addresses this will be available under:"
# ip and ifconfig sometimes are located in /sbin/
os.environ['PATH'] += ':/sbin:/usr/sbin'
proc = Popen(r"ip addr|" + \
"sed -n -e 's/.*inet6\? \([0-9.a-fA-F:]\+\)\/.*/\\1/ p'|" + \
"grep -v '^fe80\|^127.0.0.1\|^::1'", shell=True, stdout=PIPE)
if proc.wait() != 0:
# ip failed somehow, falling back to ifconfig
oldLang = os.environ.get("LC_ALL", None)
os.environ['LC_ALL'] = "C"
proc = Popen(r"ifconfig|" + \
"sed -n 's/.*inet6\? addr: \?\([0-9a-fA-F.:]*\).*/" + \
"\\1/p'|" + \
"grep -v '^fe80\|^127.0.0.1\|^::1'", \
shell=True, stdout=PIPE, stderr=PIPE)
if oldLang:
os.environ['LC_ALL'] = oldLang
else:
del(os.environ['LC_ALL'])
if proc.wait() != 0:
print "Error: Could not locate any ips for you."
proc = None
if proc:
ips = proc.stdout.read().strip()
for ip in ips.split("\n"):
if ip.find(":") >= 0:
# ipv6
ip = "[%s]" % ip
# FIXME: When BaseHTTP supports ipv6 properly, delete this line
continue
print "http://%s:%d/" % (ip, args.port)
try:
server.serve_forever()
except KeyboardInterrupt:
server.socket.close()
# cleanup potential upload directory
if dirCreated and len(os.listdir(args.filename)) == 0:
# created upload dir was not used
os.rmdir(args.filename)
print "Good bye.."
if __name__ == '__main__':
main()