#!/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.4.1'

import argparse
import base64
import cgi
import BaseHTTPServer
import commands
import datetime
import urllib
import os
import re
import SimpleHTTPServer
import SocketServer
import socket
from stat import ST_SIZE
from subprocess import Popen, PIPE
import sys
import time

# only activate SSL if available
HAVE_SSL = False
try:
	from OpenSSL import SSL, crypto
	HAVE_SSL = True
except ImportError:
	pass

def getDateStrNow():
	""" Get the current time formatted for HTTP header """
	now = datetime.datetime.fromtimestamp(time.mktime(time.gmtime()))
	return now.strftime("%a, %d %b %Y %H:%M:%S GMT")

class FileBaseHandler(BaseHTTPServer.BaseHTTPRequestHandler):
	fileName = "Undefined"
	blockSize = 1024 * 1024

	def checkAndDoRedirect(self):
		""" If request didn't request self.fileName redirect to self.fileName.

		Returns True if a redirect was issued. """
		if urllib.unquote(self.path) != "/" + self.fileName:
			self.send_response(302)
			self.send_header('Location', '/' + self.fileName)
			self.end_headers()
			return True
		return False

class FileHandler(FileBaseHandler):
	filePath = "/dev/null"
	fileLength = 0
	startTime = getDateStrNow()

	def do_HEAD(self):
		if self.checkAndDoRedirect():
			return
		self.send_response(200)
		self.send_header('Content-Length', self.fileLength)
		self.send_header('Last-Modified', self.startTime)
		self.send_header('Content-Type', 'application/octet-stream')
		self.send_header('Content-Disposition', 'attachment; filename="%s"' % self.fileName)
		self.end_headers()

	def do_GET(self):
		if self.checkAndDoRedirect():
			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 TarFileHandler(FileBaseHandler):
	target = None
	compression = "none"
	compressionMethods = ("none", "gzip", "bzip2")

	def do_HEAD(self):
		if self.checkAndDoRedirect():
			return
		self.send_response(200)
		self.send_header('Last-Modified', getDateStrNow())
		self.send_header('Content-Type', 'application/octet-stream')
		self.send_header('Content-Disposition', 'attachment; filename="%s"' % self.fileName)
		self.end_headers()

	def do_GET(self):
		if self.checkAndDoRedirect():
			return

		tarCmd = Popen(self.getCompressionCmd(), stdout=PIPE)
		# give the process a short time to find out if it can
		# pack/compress the file
		time.sleep(0.05)
		if tarCmd.poll() != None and tarCmd.poll() != 0:
			# something went wrong
			print "Error while compressing '%s'. Aborting request." % self.target
			self.send_response(500)
			self.end_headers()
			return

		self.send_response(200)
		self.send_header('Last-Modified', getDateStrNow())
		self.send_header('Content-Type', 'application/octet-stream')
		self.end_headers()

		block = True
		while block and block != '':
			block = tarCmd.stdout.read(self.blockSize)
			if block and block != '':
				self.wfile.write(block)
		print "%s finished downloading" % (self.client_address[0])

	def getCompressionCmd(self):
		if self.compression == "none":
			self.fileName += ".tar"
			cmd = ["tar", "-c"]
		elif self.compression == "gzip":
			self.fileName += ".tar.gz"
			cmd = ["tar", "-cz"]
		elif self.compression == "bzip2":
			self.fileName += ".tar.bz2"
			cmd = ["tar", "-cj"]
		else:
			raise ValueError("Unknown compression mode '%s'." % self.compression)

		dirname = os.path.basename(self.target.rstrip("/"))
		chdirTo = os.path.dirname(self.target.rstrip("/"))
		if chdirTo != '':
			cmd.extend(["-C", chdirTo])
		cmd.append(dirname)
		return cmd

	@staticmethod
	def getCompressionExt():
		if TarFileHandler.compression == "none":
			return ".tar"
		elif TarFileHandler.compression == "gzip":
			return ".tar.gz"
		elif TarFileHandler.compression == "bzip2":
			return ".tar.bz2"
		raise ValueError("Unknown compression mode '%s'." % self.compression)

class FilePutter(BaseHTTPServer.BaseHTTPRequestHandler):
	""" Simple HTTP Server which allows uploading to a specified directory
	either via multipart/form-data or POST/PUT requests containing the file.
	"""

	targetDir = None
	maxUploadSize = 0
	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):
		""" Answer every GET request with the upload form """
		self.sendResponse(200, self.uploadPage)

	def do_POST(self):
		""" Upload a file via POST

		If the content-type is multipart/form-data it checks for the file
		field and saves the data to disk. For other content-types it just
		calls do_PUT and is handled as such except for the http response code.

		Files can be uploaded with wget --post-file=path/to/file <url> or
		curl -X POST -d @file <url> .
		"""
		length = self.getContentLength()
		if length < 0:
			return
		ctype = self.headers.getheader('Content-Type')

		# check for multipart/form-data.
		if not (ctype and ctype.lower().startswith("multipart/form-data")):
			# not a normal multipart request ==> handle as PUT request
			return self.do_PUT(fromPost=True)

		# create FieldStorage object for multipart parsing
		env = os.environ
		env['REQUEST_METHOD'] = "POST"
		fstorage = cgi.FieldStorage(fp=self.rfile, headers=self.headers, environ=env)
		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 or invalid")
			return

		# write file down to disk, send an
		target = open(destFileName, "w")
		target.write(fstorage["file"].file.read(length))
		target.close()
		self.sendResponse(200, "OK! Thanks for uploading")
		print "Received file '%s' from %s." % (destFileName, self.client_address[0])

	def do_PUT(self, fromPost=False):
		""" Upload a file via PUT

		The request path is used as filename, so uploading a file to the url
		http://host:8080/testfile will cause the file to be named testfile. If
		no filename is given, a random name will be generated.

		Files can be uploaded with e.g. curl -X POST -d @file <url> .
		"""
		length = self.getContentLength()
		if length < 0:
			return

		fileName = urllib.unquote(self.path)
		if fileName == "/":
			# if no filename was given we have to generate one
			fileName = str(time.time())

		cleanFileName = self.getTargetName(fileName)
		if cleanFileName == "":
			self.sendResponse(400, "Filename was invalid")
			return

		# Sometimes clients want to be told to continue with their transfer
		if self.headers.getheader("Expect") == "100-continue":
			self.send_response(100)
			self.end_headers()

		target = open(cleanFileName, "w")
		target.write(self.rfile.read(int(self.headers['Content-Length'])))
		target.close()
		self.sendResponse(fromPost and 200 or 201, "OK!")

	def getContentLength(self):
		length = 0
		try:
			length = int(self.headers['Content-Length'])
		except (ValueError, KeyError):
			pass
		if length <= 0:
			self.sendResponse(411, "Content-Length was invalid or not set.")
			return -1
		if self.maxUploadSize > 0 and length > self.maxUploadSize:
			self.sendResponse(413, "Your file was too big! Maximum allowed size is %d byte. <a href=\"/\">back</a>" % self.maxUploadSize)
			return -1
		return length

	def sendResponse(self, code, msg):
		""" Send a HTTP response with HTTP statuscode code and message msg,
		providing the correct content-length.
		"""
		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):
		""" Generate a clean and secure filename.

		This function takes a filename and strips all the slashes out of it.
		If the file already exists in the target directory, a (NUM) will be
		appended, so no file will be overwritten.
		"""
		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 catchSSLErrors(BaseSSLClass):
	""" Class decorator which catches SSL errors and prints them. """
	class X(BaseSSLClass):
		def handle_one_request(self, *args, **kwargs):
			try:
				BaseSSLClass.handle_one_request(self, *args, **kwargs)
			except SSL.Error, e:
				if str(e) == "":
					print "%s SSL Error (Empty error message)" % (self.client_address[0],)
				else:
					print "%s SSL Error: %s" % (self.client_address[0], e)
	return X

class SecureThreadedHTTPServer(ThreadedHTTPServer):
	def __init__(self, pubKey, privKey, *args, **kwargs):
		ThreadedHTTPServer.__init__(self, *args, **kwargs)
		ctx = SSL.Context(SSL.SSLv23_METHOD)
		if type(pubKey) == crypto.X509 and type(privKey) == crypto.PKey:
			ctx.use_certificate(pubKey)
			ctx.use_privatekey(privKey)
		else:
			ctx.use_certificate_file(pubKey)
			ctx.use_privatekey_file(privKey)

		self.bsocket = socket.socket(self.address_family, self.socket_type)
		self.socket = SSL.Connection(ctx, self.bsocket)

		self.server_bind()
		self.server_activate()

	def shutdown_request(self, request):
		request.shutdown()

class SecureHandler():
	def setup(self):
		self.connection = self.request
		self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
		self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)

class ServeFileException(Exception):
	pass

class ServeFile():
	""" Main class to manage everything. """

	_NUM_MODES = 4
	(MODE_SINGLE, MODE_SINGLETAR, MODE_UPLOAD, MODE_LISTDIR) = range(_NUM_MODES)

	def __init__(self, target, port=8080, serveMode=0, useSSL=False):
		self.target = target
		self.port = port
		self.serveMode = serveMode
		self.dirCreated = False
		self.useSSL = useSSL
		self.cert = self.key = None
		self.auth = None
		self.maxUploadSize = 0

		if self.serveMode not in range(self._NUM_MODES):
			self.serveMode = None
			raise ValueError("Unknown serve mode, needs to be MODE_SINGLE, MODE_SINGLETAR, MODE_UPLOAD or MODE_DIRLIST.")

	def getIPs(self):
		""" Get IPs from all interfaces via ip or ifconfig. """
		# 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, stderr=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:
				# we couldn't find any ip address
				proc = None
		if proc:
			ips = proc.stdout.read().strip().split("\n")
			# FIXME: When BaseHTTP supports ipv6 properly, delete this line
			ips = filter(lambda ip: ip.find(":") == -1, ips)
			return ips
		return None

	def setSSLKeys(self, cert, key):
		""" Set SSL cert/key. Can be either path to file or pyssl X509/PKey object. """
		self.cert = cert
		self.key = key

	def setMaxUploadSize(self, limit):
		""" Set the maximum upload size in byte """
		self.maxUploadSize = limit

	def setCompression(self, compression):
		""" Set the compression of TarFileHandler """
		if self.serveMode != self.MODE_SINGLETAR:
			raise ServeFileException("Compression mode can only be set in tar-mode.")
		if compression not in TarFileHandler.compressionMethods:
			raise ServeFileException("Compression mode not available.")
		TarFileHandler.compression = compression

	def genKeyPair(self):
		print "Generating SSL certificate...",
		sys.stdout.flush()

		pkey = crypto.PKey()
		pkey.generate_key(crypto.TYPE_RSA, 2048)

		req = crypto.X509Req()
		subj = req.get_subject()
		subj.CN = "127.0.0.1"
		subj.O = "servefile laboratories"
		subj.OU = "servefile"

		# generate altnames
		altNames = []
		for ip in self.getIPs() + ["127.0.0.1"]:
			altNames.append("IP:%s" % ip)
		altNames.append("DNS:localhost")
		ext = crypto.X509Extension("subjectAltName", False, ",".join(altNames))
		req.add_extensions([ext])

		req.set_pubkey(pkey)
		req.sign(pkey, "sha1")

		cert = crypto.X509()
		# some browsers complain if they see a cert from the same authority
		# with the same serial ==> we just use the seconds as serial.
		cert.set_serial_number(int(time.time()))
		cert.gmtime_adj_notBefore(0)
		cert.gmtime_adj_notAfter(365*24*60*60)
		cert.set_issuer(req.get_subject())
		cert.set_subject(req.get_subject())
		cert.add_extensions([ext])
		cert.set_pubkey(req.get_pubkey())
		cert.sign(pkey, "sha1")

		self.cert = cert
		self.key = pkey

		print "done."
		print "SHA1 fingerprint:", cert.digest("sha1")
		print "MD5  fingerprint:", cert.digest("md5")

	def _getCert(self):
		return self.cert

	def _getKey(self):
		return self.key

	def setAuth(self, user, password):
		if len(user) == "" or len(password) == "":
			raise ServeFileException("User and password both need to be at least one character.")
		self.auth = base64.b64encode("%s:%s" % (user, password))

	def _createServer(self, handler):
		server = None
		if self.useSSL:
			if not self._getKey():
				self.genKeyPair()
			server = SecureThreadedHTTPServer(self._getCert(), self._getKey(), ('', self.port), handler)
		else:
			server = ThreadedHTTPServer(('', self.port), handler)
		return server

	def serve(self):
		self.handler = self._confAndFindHandler()
		self.server = self._createServer(self.handler)

		if self.serveMode != self.MODE_UPLOAD:
			print "Serving \"%s\" at port %d." % (self.target, self.port)
		else:
			print "Serving \"%s\" for uploads at port %d." % (self.target, self.port)

		# print urls with local network adresses
		print "\nSome addresses %s will be available at:" % \
				((self.serveMode != self.MODE_UPLOAD) and "this file" or "the uploadform", )
		ips = self.getIPs()
		if not ips or len(ips) == 0 or ips[0] == '':
			print "Could not find any addresses."
		else:
			for ip in ips:
				print "\thttp%s://%s:%d/" % (self.useSSL and "s" or "", ip, self.port)
		print ""

		try:
			self.server.serve_forever()
		except KeyboardInterrupt:
			self.server.socket.close()

		# cleanup potential upload directory
		if self.dirCreated and len(os.listdir(self.target)) == 0:
			# created upload dir was not used
			os.rmdir(self.target)

	def _confAndFindHandler(self):
		handler = None
		if self.serveMode == self.MODE_SINGLE:
			try:
				testit = open(self.target, 'r')
				testit.close()
				FileHandler.filePath = self.target
				FileHandler.fileName = os.path.basename(self.target)
				FileHandler.fileLength = os.stat(self.target)[ST_SIZE]
			except IOError:
				raise ServeFileException("Error: Could not open file!")
			handler = FileHandler
		elif self.serveMode == self.MODE_SINGLETAR:
			self.realTarget = os.path.realpath(self.target)
			if not os.path.exists(self.realTarget):
				raise ServeFileException("Error: Could not open file or directory.")
			TarFileHandler.target = self.realTarget
			TarFileHandler.fileName = os.path.basename(self.realTarget.rstrip("/")) + TarFileHandler.getCompressionExt()

			handler = TarFileHandler
		elif self.serveMode == self.MODE_UPLOAD:
			if os.path.isdir(self.target):
				print "Warning: Uploading to an already existing directory."
			elif not os.path.exists(self.target):
				self.dirCreated = True
				try:
					os.mkdir(self.target)
				except IOError, OSError:
					raise ServeFileException("Error: Could not create directory '%s' for uploads." % (self.target,) )
			else:
				raise ServeFileException("Error: Upload directory already exists and is a file.")
			FilePutter.targetDir = self.target
			FilePutter.maxUploadSize = self.maxUploadSize
			handler = FilePutter
		elif self.serveMode == self.MODE_LISTDIR:
			try:
				os.chdir(self.target)
			except OSError:
				raise ServeFileException("Error: Could not change directory to '%s'." % self.target)
			handler = SimpleHTTPServer.SimpleHTTPRequestHandler


		if self.auth:
			# do authentication
			AuthenticationHandler.authString = self.auth
			class AuthenticatedHandler(AuthenticationHandler, handler):
				pass
			handler = AuthenticatedHandler

		if self.useSSL:
			# secure handler
			@catchSSLErrors
			class AlreadySecuredHandler(SecureHandler, handler):
				pass
			handler = AlreadySecuredHandler
		return handler

class AuthenticationHandler():
	# base64 encoded user:password string for authentication
	authString = None
	realm = "Restricted area"

	def handle_one_request(self):
		""" Overloaded function to handle one request.

		Before calling the responsible do_METHOD function, check credentials
		"""
		self.raw_requestline = self.rfile.readline()
		if not self.raw_requestline:
			self.close_connection = 1
			return
		if not self.parse_request(): # An error code has been sent, just exit
			return

		authorized = False
		if "Authorization" in self.headers:
			if self.headers["Authorization"] == ("Basic " + self.authString):
				authorized = True
		if authorized:
			mname = 'do_' + self.command
			if not hasattr(self, mname):
				self.send_error(501, "Unsupported method (%r)" % self.command)
				return
			method = getattr(self, mname)
			method()
		else:
			self.send_response(401)
			self.send_header("WWW-Authenticate", "Basic realm=\"%s\"" % self.realm)

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('target', 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")
	parser.add_argument('-s', '--max-upload-size', type=str, \
	                    help="Limit upload size in kB. Size modifiers are allowed, e.g. 2G, 12MB, 1B")
	parser.add_argument('-l', '--list-dir', action="store_true", default=False, \
	                    help="Show directory indexes and allow access to all subdirectories")
	parser.add_argument('--ssl', action="store_true", default=False, \
	                    help="Enable SSL. If no key/cert is specified one will be generated")
	parser.add_argument('--key', type=str, \
	                    help="Keyfile to use for SSL. If no cert is given with --cert the keyfile will also be searched for a cert")
	parser.add_argument('--cert', type=str, \
	                    help="Certfile to use for SSL")
	parser.add_argument('-a', '--auth', type=str, metavar='user:password', \
	                    help="Set user and password for HTTP basic authentication")
	parser.add_argument('-t', '--tar', action="store_true", default=False, \
	                    help="Enable on the fly tar creation for given file or directory. Note: Download continuation will not be available")
	parser.add_argument('-c', '--compression', type=str, metavar='method', \
	                    default="none", \
	                    help="Set compression method, only in combination with --tar. Can be one of %s" % ", ".join(TarFileHandler.compressionMethods))

	args = parser.parse_args()
	maxUploadSize = 0

	# check for invalid option combinations/preparse stuff
	if args.max_upload_size and not args.upload:
		print "Error: Maximum upload size can only be specified when in upload mode."
		sys.exit(1)

	if args.max_upload_size:
		sizeRe = re.match("^(\d+(?:[,.]\d+)?)(?:([bkmgtpe])(?:(?<!b)b?)?)?$", args.max_upload_size.lower())
		if not sizeRe:
			print "Error: Your max upload size param is broken."
			sys.exit(1)
		uploadSize, modifier = sizeRe.groups()
		uploadSize = float(uploadSize.replace(",", "."))
		sizes = ["b", "k", "m", "g", "t", "p", "e"]
		maxUploadSize = int(uploadSize * pow(1024, sizes.index(modifier or "k")))
		if maxUploadSize < 0:
			print "Error: Your max upload size can't be negative"
			sys.exit(1)

	if args.ssl and not HAVE_SSL:
		print "Error: SSL is not available, please install pyssl (python-openssl)."
		sys.exit(1)

	if args.cert and not args.key:
		print "Error: Please specify a key along with your cert."
		sys.exit(1)

	if not args.ssl and (args.cert or args.key):
		print "Error: You need to enable ssl with --ssl when specifying certs/keys."
		sys.exit(1)

	if args.auth:
		dpos = args.auth.find(":")
		if dpos <= 0 or dpos == (len(args.auth)-1):
			print "Error: User and password for HTTP basic authentication need to be both at least one character band have to be seperated by a \":\"."
			sys.exit(1)

	if args.compression != "none" and not args.tar:
		print "Error: Please use --tar if you want to tar everything."
		sys.exit(1)

	if args.tar and args.upload:
		print "Error: --tar mode will not work with uploads."
		sys.exit(1)

	if args.tar and args.list_dir:
		print "Error: --tar mode will not work with directory listings."
		sys.exit(1)

	compression = None
	if args.compression:
		if args.compression in TarFileHandler.compressionMethods:
			compression = args.compression
		else:
			print "Error: Compression mode '%s' is unknown." % self.compression
			sys.exit(1)

	mode = None
	if args.upload:
		mode = ServeFile.MODE_UPLOAD
	elif args.list_dir:
		mode = ServeFile.MODE_LISTDIR
	elif args.tar:
		mode = ServeFile.MODE_SINGLETAR
	else:
		mode = ServeFile.MODE_SINGLE

	server = None
	try:
		server = ServeFile(args.target, args.port, mode, args.ssl)
		if maxUploadSize > 0:
			server.setMaxUploadSize(maxUploadSize)
		if args.ssl and args.key:
			cert = args.cert or args.key
			server.setSSLKeys(cert, args.key)
		if args.auth:
			user, password = args.auth.split(":", 1)
			server.setAuth(user, password)
		if compression and compression != "none":
			server.setCompression(compression)
		server.serve()
	except ServeFileException, e:
		print e
		sys.exit(1)
	print "Good bye."

if __name__ == '__main__':
	main()

