diff --git a/servefile/servefile.py b/servefile/servefile.py
index 81f8271..c970921 100755
--- a/servefile/servefile.py
+++ b/servefile/servefile.py
@@ -37,387 +37,389 @@ except ImportError:
# only activate SSL if available
HAVE_SSL = False
try:
- from OpenSSL import SSL, crypto
- HAVE_SSL = True
+ from OpenSSL import SSL, crypto
+ HAVE_SSL = True
except ImportError:
- pass
+ 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")
+ """ 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 = None
- blockSize = 1024 * 1024
- server_version = "servefile/" + __version__
+ fileName = None
+ blockSize = 1024 * 1024
+ server_version = "servefile/" + __version__
- def checkAndDoRedirect(self, fileName=None):
- """ If request didn't request self.fileName redirect to self.fileName.
+ def checkAndDoRedirect(self, fileName=None):
+ """ If request didn't request self.fileName redirect to self.fileName.
- Returns True if a redirect was issued. """
- if not fileName:
- fileName = self.fileName
- if unquote(self.path) != "/" + fileName:
- self.send_response(302)
- self.send_header('Location', '/' + quote(fileName))
- self.end_headers()
- return True
- return False
+ Returns True if a redirect was issued. """
+ if not fileName:
+ fileName = self.fileName
+ if unquote(self.path) != "/" + fileName:
+ self.send_response(302)
+ self.send_header('Location', '/' + quote(fileName))
+ self.end_headers()
+ return True
+ return False
- def sendContentHeaders(self, fileName, fileLength, lastModified=None):
- """ Send default Content headers for given fileName and fileLength.
+ def sendContentHeaders(self, fileName, fileLength, lastModified=None):
+ """ Send default Content headers for given fileName and fileLength.
- If no lastModified is given the current date is taken. If
- fileLength is lesser than 0 no Content-Length will be sent."""
- if not lastModified:
- lastModified = getDateStrNow()
+ If no lastModified is given the current date is taken. If
+ fileLength is lesser than 0 no Content-Length will be sent."""
+ if not lastModified:
+ lastModified = getDateStrNow()
- if fileLength >= 0:
- self.send_header('Content-Length', str(fileLength))
- self.send_header('Connection', 'close')
- self.send_header('Last-Modified', lastModified)
- self.send_header('Content-Type', 'application/octet-stream')
- self.send_header('Content-Disposition', 'attachment; filename="%s"' % fileName)
- self.send_header('Content-Transfer-Encoding', 'binary')
+ if fileLength >= 0:
+ self.send_header('Content-Length', str(fileLength))
+ self.send_header('Connection', 'close')
+ self.send_header('Last-Modified', lastModified)
+ self.send_header('Content-Type', 'application/octet-stream')
+ self.send_header('Content-Disposition', 'attachment; filename="%s"' % fileName)
+ self.send_header('Content-Transfer-Encoding', 'binary')
- def isRangeRequest(self):
- """ Return True if partial content is requestet """
- return "Range" in self.headers
+ def isRangeRequest(self):
+ """ Return True if partial content is requestet """
+ return "Range" in self.headers
- def handleRangeRequest(self, fileLength):
- """ Find out and handle continuing downloads.
+ def handleRangeRequest(self, fileLength):
+ """ Find out and handle continuing downloads.
- Returns a tuple of a boolean, if this is a valid range request,
- and a range. When the requested range is out of range, range is
- set to None.
- """
- fromto = None
- if self.isRangeRequest():
- 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] = fileLength - 1
- try:
- fromto[0] = int(fromto[0])
- fromto[1] = int(fromto[1])
- except ValueError:
- return (False, None)
+ Returns a tuple of a boolean, if this is a valid range request,
+ and a range. When the requested range is out of range, range is
+ set to None.
+ """
+ fromto = None
+ if self.isRangeRequest():
+ 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] = fileLength - 1
+ try:
+ fromto[0] = int(fromto[0])
+ fromto[1] = int(fromto[1])
+ except ValueError:
+ return (False, None)
- if fromto[0] >= fileLength or fromto[0] < 0 or fromto[1] >= fileLength or fromto[1]-fromto[0] < 0:
- # oops, already done! (requested range out of range)
- self.send_response(416)
- self.send_header('Content-Range', 'bytes */%d' % fileLength)
- self.end_headers()
- return (True, None)
- return (True, fromto)
- # broken request or no range header
- return (False, None)
+ if fromto[0] >= fileLength or fromto[0] < 0 or fromto[1] >= fileLength or fromto[1]-fromto[0] < 0:
+ # oops, already done! (requested range out of range)
+ self.send_response(416)
+ self.send_header('Content-Range', 'bytes */%d' % fileLength)
+ self.end_headers()
+ return (True, None)
+ return (True, fromto)
+ # broken request or no range header
+ return (False, None)
- def sendFile(self, filePath, fileLength=None, lastModified=None):
- """ Send file with continuation support.
+ def sendFile(self, filePath, fileLength=None, lastModified=None):
+ """ Send file with continuation support.
- filePath: path to file to be sent
- fileLength: length of file (if None is given this will be found out)
- lastModified: time the file was last modified, None for "now"
- """
- if not fileLength:
- fileLength = os.stat(filePath).st_size
+ filePath: path to file to be sent
+ fileLength: length of file (if None is given this will be found out)
+ lastModified: time the file was last modified, None for "now"
+ """
+ if not fileLength:
+ fileLength = os.stat(filePath).st_size
- (responseCode, myfile) = self.getFileHandle(filePath)
- if not myfile:
- self.send_response(responseCode)
- self.end_headers()
- return
+ (responseCode, myfile) = self.getFileHandle(filePath)
+ if not myfile:
+ self.send_response(responseCode)
+ self.end_headers()
+ return
- (continueDownload, fromto) = self.handleRangeRequest(fileLength)
- if continueDownload:
- if not fromto:
- # we are done
- return True
+ (continueDownload, fromto) = self.handleRangeRequest(fileLength)
+ if continueDownload:
+ if not fromto:
+ # we are done
+ return True
- # now we can wind the file *brrrrrr*
- myfile.seek(fromto[0])
+ # now we can wind the file *brrrrrr*
+ myfile.seek(fromto[0])
- if fromto != None:
- self.send_response(216)
- self.send_header('Content-Range', 'bytes %d-%d/%d' % (fromto[0], fromto[1], fileLength))
- fileLength = fromto[1] - fromto[0] + 1
- else:
- self.send_response(200)
+ if fromto is not None:
+ self.send_response(216)
+ self.send_header('Content-Range', 'bytes %d-%d/%d' % (fromto[0], fromto[1], fileLength))
+ fileLength = fromto[1] - fromto[0] + 1
+ else:
+ self.send_response(200)
- fileName = self.fileName
- if not fileName:
- fileName = os.path.basename(filePath)
- self.sendContentHeaders(fileName, fileLength, lastModified)
- self.end_headers()
- block = self.getChunk(myfile, fromto)
- while block:
- self.wfile.write(block)
- block = self.getChunk(myfile, fromto)
- myfile.close()
- print("%s finished downloading %s" % (self.client_address[0], filePath))
- return True
+ fileName = self.fileName
+ if not fileName:
+ fileName = os.path.basename(filePath)
+ self.sendContentHeaders(fileName, fileLength, lastModified)
+ self.end_headers()
+ block = self.getChunk(myfile, fromto)
+ while block:
+ self.wfile.write(block)
+ block = self.getChunk(myfile, fromto)
+ myfile.close()
+ print("%s finished downloading %s" % (self.client_address[0], filePath))
+ return True
- 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)
+ 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)
- def getFileHandle(self, path):
- """ Get handle to a file.
+ def getFileHandle(self, path):
+ """ Get handle to a file.
- Return a tuple of HTTP response code and file handle.
- If the handle couldn't be acquired it is set to None
- and an appropriate HTTP error code is returned.
- """
- myfile = None
- responseCode = 200
- try:
- myfile = open(path, 'rb')
- except IOError as e:
- responseCode = self.getResponseForErrno(e.errno)
- return (responseCode, myfile)
+ Return a tuple of HTTP response code and file handle.
+ If the handle couldn't be acquired it is set to None
+ and an appropriate HTTP error code is returned.
+ """
+ myfile = None
+ responseCode = 200
+ try:
+ myfile = open(path, 'rb')
+ except IOError as e:
+ responseCode = self.getResponseForErrno(e.errno)
+ return (responseCode, myfile)
- def getFileLength(self, path):
- """ Get length of a file.
+ def getFileLength(self, path):
+ """ Get length of a file.
- Return a tuple of HTTP response code and file length.
- If filelength couldn't be determined, it is set to -1
- and an appropriate HTTP error code is returned.
- """
- fileSize = -1
- responseCode = 200
- try:
- fileSize = os.stat(path).st_size
- except IOError as e:
- responseCode = self.getResponseForErrno(e.errno)
- return (responseCode, fileSize)
+ Return a tuple of HTTP response code and file length.
+ If filelength couldn't be determined, it is set to -1
+ and an appropriate HTTP error code is returned.
+ """
+ fileSize = -1
+ responseCode = 200
+ try:
+ fileSize = os.stat(path).st_size
+ except IOError as e:
+ responseCode = self.getResponseForErrno(e.errno)
+ return (responseCode, fileSize)
- def getResponseForErrno(self, errno):
- """ Return HTTP response code for an IOError errno """
- if errno == errno.ENOENT:
- return 404
- elif errno == errno.EACCESS:
- return 403
- else:
- return 500
+ def getResponseForErrno(self, errno):
+ """ Return HTTP response code for an IOError errno """
+ if errno == errno.ENOENT:
+ return 404
+ elif errno == errno.EACCESS:
+ return 403
+ else:
+ return 500
class FileHandler(FileBaseHandler):
- filePath = "/dev/null"
- fileLength = 0
- startTime = getDateStrNow()
+ filePath = "/dev/null"
+ fileLength = 0
+ startTime = getDateStrNow()
- def do_HEAD(self):
- if self.checkAndDoRedirect():
- return
- self.send_response(200)
- self.sendContentHeaders(self.fileName, self.fileLength, self.startTime)
- self.end_headers()
+ def do_HEAD(self):
+ if self.checkAndDoRedirect():
+ return
+ self.send_response(200)
+ self.sendContentHeaders(self.fileName, self.fileLength, self.startTime)
+ self.end_headers()
- def do_GET(self):
- if self.checkAndDoRedirect():
- return
- self.sendFile(self.filePath, self.fileLength, self.startTime)
+ def do_GET(self):
+ if self.checkAndDoRedirect():
+ return
+ self.sendFile(self.filePath, self.fileLength, self.startTime)
class TarFileHandler(FileBaseHandler):
- target = None
- compression = "none"
- compressionMethods = ("none", "gzip", "bzip2", "xz")
+ target = None
+ compression = "none"
+ compressionMethods = ("none", "gzip", "bzip2", "xz")
- def do_HEAD(self):
- if self.checkAndDoRedirect():
- return
- self.send_response(200)
- self.sendContentHeaders(self.fileName, -1)
- self.end_headers()
+ def do_HEAD(self):
+ if self.checkAndDoRedirect():
+ return
+ self.send_response(200)
+ self.sendContentHeaders(self.fileName, -1)
+ self.end_headers()
- def do_GET(self):
- if self.checkAndDoRedirect():
- return
+ 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
+ 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() is not 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.sendContentHeaders(self.fileName, -1)
- self.end_headers()
+ self.send_response(200)
+ self.sendContentHeaders(self.fileName, -1)
+ 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]))
+ 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":
- cmd = ["tar", "-c"]
- elif self.compression == "gzip":
- cmd = ["tar", "-cz"]
- elif self.compression == "bzip2":
- cmd = ["tar", "-cj"]
- elif self.compression == "xz":
- cmd = ["tar", "-cJ"]
- else:
- raise ValueError("Unknown compression mode '%s'." % self.compression)
+ def getCompressionCmd(self):
+ if self.compression == "none":
+ cmd = ["tar", "-c"]
+ elif self.compression == "gzip":
+ cmd = ["tar", "-cz"]
+ elif self.compression == "bzip2":
+ cmd = ["tar", "-cj"]
+ elif self.compression == "xz":
+ 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
+ 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"
- elif TarFileHandler.compression == "xz":
- return ".tar.xz"
- raise ValueError("Unknown compression mode '%s'." % TarFileHandler.compression)
+ @staticmethod
+ def getCompressionExt():
+ if TarFileHandler.compression == "none":
+ return ".tar"
+ elif TarFileHandler.compression == "gzip":
+ return ".tar.gz"
+ elif TarFileHandler.compression == "bzip2":
+ return ".tar.bz2"
+ elif TarFileHandler.compression == "xz":
+ return ".tar.xz"
+ raise ValueError("Unknown compression mode '%s'." % TarFileHandler.compression)
class DirListingHandler(FileBaseHandler):
- """ DOCUMENTATION MISSING """
+ """ DOCUMENTATION MISSING """
- targetDir = None
+ targetDir = None
- def do_HEAD(self):
- self.getFileOrDirectory(head=True)
+ def do_HEAD(self):
+ self.getFileOrDirectory(head=True)
- def do_GET(self):
- self.getFileOrDirectory(head=False)
+ def do_GET(self):
+ self.getFileOrDirectory(head=False)
- def getFileOrDirectory(self, head=False):
- """ Send file or directory index, depending on requested path """
- path = self.getCleanPath()
+ def getFileOrDirectory(self, head=False):
+ """ Send file or directory index, depending on requested path """
+ path = self.getCleanPath()
- # check if path is in current serving directory
- currBaseDir = self.targetDir + os.path.sep
- requestPath = os.path.normpath(os.path.join(currBaseDir, path)) + os.path.sep
- if not requestPath.startswith(currBaseDir):
- self.send_response(301)
- self.send_header("Location", '/')
- self.end_headers()
- return
+ # check if path is in current serving directory
+ currBaseDir = self.targetDir + os.path.sep
+ requestPath = os.path.normpath(os.path.join(currBaseDir, path)) + os.path.sep
+ if not requestPath.startswith(currBaseDir):
+ self.send_response(301)
+ self.send_header("Location", '/')
+ self.end_headers()
+ return
- if os.path.isdir(path):
- if not self.path.endswith('/'):
- self.send_response(301)
- self.send_header("Location", self.path + '/')
- self.end_headers()
- else:
- self.sendDirectoryListing(path, head)
- elif os.path.isfile(path):
- if head:
- (response, length) = self.getFileLength(path)
- if length < 0:
- self.send_response(response)
- self.end_headers()
- else:
- self.send_response(200)
- self.sendContentHeaders(path, length)
- self.end_headers()
- else:
- self.sendFile(path, head)
- else:
- self.send_response(404)
- errorMsg = """
-
404 Not Found
-
- Not Found
- The requestet URL %s was not found on this server
- Back to /
-
- """ % self.escapeHTML(unquote(self.path))
- self.send_header("Content-Length", str(len(errorMsg)))
- self.send_header('Connection', 'close')
- self.end_headers()
- if not head:
- self.wfile.write(errorMsg.encode())
+ if os.path.isdir(path):
+ if not self.path.endswith('/'):
+ self.send_response(301)
+ self.send_header("Location", self.path + '/')
+ self.end_headers()
+ else:
+ self.sendDirectoryListing(path, head)
+ elif os.path.isfile(path):
+ if head:
+ (response, length) = self.getFileLength(path)
+ if length < 0:
+ self.send_response(response)
+ self.end_headers()
+ else:
+ self.send_response(200)
+ self.sendContentHeaders(path, length)
+ self.end_headers()
+ else:
+ self.sendFile(path, head)
+ else:
+ self.send_response(404)
+ errorMsg = """
+
404 Not Found
+
+ Not Found
+ The requestet URL %s was not found on this server
+ Back to /
+
+ """ % self.escapeHTML(unquote(self.path))
+ self.send_header("Content-Length", str(len(errorMsg)))
+ self.send_header('Connection', 'close')
+ self.end_headers()
+ if not head:
+ self.wfile.write(errorMsg.encode())
- def escapeHTML(self, htmlstr):
- entities = [("<", "<"), (">", ">")]
- for src, dst in entities:
- htmlstr = htmlstr.replace(src, dst)
- return htmlstr
+ def escapeHTML(self, htmlstr):
+ entities = [("<", "<"), (">", ">")]
+ for src, dst in entities:
+ htmlstr = htmlstr.replace(src, dst)
+ return htmlstr
- def _appendToListing(self, content, item, itemPath, stat, is_dir):
- # Strings to display on directory listing
- lastModifiedDate = datetime.datetime.fromtimestamp(stat.st_mtime)
- lastModified = lastModifiedDate.strftime("%Y-%m-%d %H:%M")
- fileSize = "%.1f%s" % self.convertSize(stat.st_size)
- (fileType, _) = mimetypes.guess_type(itemPath)
- if not fileType:
- fileType = "-"
+ def _appendToListing(self, content, item, itemPath, stat, is_dir):
+ # Strings to display on directory listing
+ lastModifiedDate = datetime.datetime.fromtimestamp(stat.st_mtime)
+ lastModified = lastModifiedDate.strftime("%Y-%m-%d %H:%M")
+ fileSize = "%.1f%s" % self.convertSize(stat.st_size)
+ (fileType, _) = mimetypes.guess_type(itemPath)
+ if not fileType:
+ fileType = "-"
- if is_dir:
- item += "/"
- fileType = "Directory"
- content.append("""
-
- %s |
- %s |
- %s |
- %s |
-
- """ % (quote(item), item, lastModified, fileSize, fileType))
+ if is_dir:
+ item += "/"
+ fileType = "Directory"
+ content.append("""
+
+ %s |
+ %s |
+ %s |
+ %s |
+
+ """ % (quote(item), item, lastModified, fileSize, fileType))
- def sendDirectoryListing(self, path, head):
- """ Generate a directorylisting for path and send it """
- header = """
+ def sendDirectoryListing(self, path, head):
+ """ Generate a directorylisting for path and send it """
+ header = """
-
-
- Index of %(path)s
-
-
+
+
+ Index of %(path)s
+
+
- Index of %(path)s
-
-
-
-
- Name |
- Last Modified |
- Size |
- Type |
-
-
-
- """ % {'path': os.path.normpath(unquote(self.path))}
- footer = """
+ Index of %(path)s
+
+
+
+
+ Name |
+ Last Modified |
+ Size |
+ Type |
+
+
+
+ """ % {'path': os.path.normpath(unquote(self.path))}
+ footer = """
""" % {'version': __version__}
- content = []
+ content = []
- dir_items = list()
- file_items = list()
+ dir_items = list()
+ file_items = list()
- for item in [".."] + sorted(os.listdir(path), key=lambda x:x.lower()):
- # create path to item
- itemPath = os.path.join(path, item)
+ for item in [".."] + sorted(os.listdir(path), key=lambda x: x.lower()):
+ # create path to item
+ itemPath = os.path.join(path, item)
- # Hide "../" in listing of the (virtual) root directory
- if item == '..' and path == DirListingHandler.targetDir.rstrip('/') + '/':
- continue
+ # Hide "../" in listing of the (virtual) root directory
+ if item == '..' and path == DirListingHandler.targetDir.rstrip('/') + '/':
+ continue
- # try to stat file for size, last modified... continue on error
- stat = None
- try:
- stat = os.stat(itemPath)
- except IOError:
- continue
+ # try to stat file for size, last modified... continue on error
+ stat = None
+ try:
+ stat = os.stat(itemPath)
+ except IOError:
+ continue
- if os.path.isdir(itemPath):
- target_items = dir_items
- else:
- target_items = file_items
- target_items.append((item, itemPath, stat))
+ if os.path.isdir(itemPath):
+ target_items = dir_items
+ else:
+ target_items = file_items
+ target_items.append((item, itemPath, stat))
- # Directories first, then files
- for (tuple_list, is_dir) in (
- (dir_items, True),
- (file_items, False),
- ):
- for (item, itemPath, stat) in tuple_list:
- self._appendToListing(content, item, itemPath, stat, is_dir=is_dir)
+ # Directories first, then files
+ for (tuple_list, is_dir) in (
+ (dir_items, True),
+ (file_items, False),
+ ):
+ for (item, itemPath, stat) in tuple_list:
+ self._appendToListing(content, item, itemPath, stat, is_dir=is_dir)
- listing = header + "\n".join(content) + footer
+ listing = header + "\n".join(content) + footer
- # write listing
- self.send_response(200)
- self.send_header("Content-Type", "text/html")
- if head:
- self.end_headers()
- return
- self.send_header("Content-Length", str(len(listing)))
- self.send_header('Connection', 'close')
- self.end_headers()
- if sys.version_info.major >= 3:
- listing = listing.encode()
- self.wfile.write(listing)
+ # write listing
+ self.send_response(200)
+ self.send_header("Content-Type", "text/html")
+ if head:
+ self.end_headers()
+ return
+ self.send_header("Content-Length", str(len(listing)))
+ self.send_header('Connection', 'close')
+ self.end_headers()
+ if sys.version_info.major >= 3:
+ listing = listing.encode()
+ self.wfile.write(listing)
- def convertSize(self, size):
- for ext in "KMGT":
- size /= 1024.0
- if size < 1024.0:
- break
- if ext == "K" and size < 0.1:
- size = 0.1
- return (size, ext.strip())
+ def convertSize(self, size):
+ for ext in "KMGT":
+ size /= 1024.0
+ if size < 1024.0:
+ break
+ if ext == "K" and size < 0.1:
+ size = 0.1
+ return (size, ext.strip())
- def getCleanPath(self):
- urlPath = os.path.normpath(unquote(self.path)).strip("/")
- path = os.path.join(self.targetDir, urlPath)
- return path
+ def getCleanPath(self):
+ urlPath = os.path.normpath(unquote(self.path)).strip("/")
+ path = os.path.join(self.targetDir, urlPath)
+ return path
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.
- """
+ """ 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
- blockSize = 1024 * 1024
- uploadPage = """
+ targetDir = None
+ maxUploadSize = 0
+ blockSize = 1024 * 1024
+ uploadPage = """
-
+
"""
- def do_GET(self):
- """ Answer every GET request with the upload form """
- self.sendResponse(200, self.uploadPage)
+ 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
+ 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.
+ 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 or
- curl -X POST -d @file .
- """
- length = self.getContentLength()
- if length < 0:
- return
- print(self.headers)
- ctype = self.headers.get('Content-Type')
+ Files can be uploaded with wget --post-file=path/to/file or
+ curl -X POST -d @file .
+ """
+ length = self.getContentLength()
+ if length < 0:
+ return
+ print(self.headers)
+ ctype = self.headers.get('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)
+ # 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
+ # 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 "file" not 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
+ destFileName = self.getTargetName(fstorage["file"].filename)
+ if destFileName == "":
+ self.sendResponse(400, "Filename was empty or invalid")
+ return
- # write file down to disk, send a 200 afterwards
- target = open(destFileName, "wb")
- bytesLeft = length
- while bytesLeft > 0:
- bytesToRead = min(self.blockSize, bytesLeft)
- target.write(fstorage["file"].file.read(bytesToRead))
- bytesLeft -= bytesToRead
- target.close()
- self.sendResponse(200, "OK! Thanks for uploading")
- print("Received file '%s' from %s." % (destFileName, self.client_address[0]))
+ # write file down to disk, send a 200 afterwards
+ target = open(destFileName, "wb")
+ bytesLeft = length
+ while bytesLeft > 0:
+ bytesToRead = min(self.blockSize, bytesLeft)
+ target.write(fstorage["file"].file.read(bytesToRead))
+ bytesLeft -= bytesToRead
+ 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
+ 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.
+ 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 -T file .
- """
- length = self.getContentLength()
- if length < 0:
- return
+ Files can be uploaded with e.g. curl -T file .
+ """
+ length = self.getContentLength()
+ if length < 0:
+ return
- fileName = unquote(self.path)
- if fileName == "/":
- # if no filename was given we have to generate one
- fileName = str(time.time())
+ fileName = 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
+ 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.get("Expect") == "100-continue":
- self.send_response(100)
- self.end_headers()
+ # Sometimes clients want to be told to continue with their transfer
+ if self.headers.get("Expect") == "100-continue":
+ self.send_response(100)
+ self.end_headers()
- target = open(cleanFileName, "wb")
- bytesLeft = int(self.headers['Content-Length'])
- while bytesLeft > 0:
- bytesToRead = min(self.blockSize, bytesLeft)
- target.write(self.rfile.read(bytesToRead))
- bytesLeft -= bytesToRead
- target.close()
- self.sendResponse(200 if fromPost else 201, "OK!")
+ target = open(cleanFileName, "wb")
+ bytesLeft = int(self.headers['Content-Length'])
+ while bytesLeft > 0:
+ bytesToRead = min(self.blockSize, bytesLeft)
+ target.write(self.rfile.read(bytesToRead))
+ bytesLeft -= bytesToRead
+ target.close()
+ self.sendResponse(200 if fromPost else 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. back" % self.maxUploadSize)
- return -1
- return length
+ 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. back" %
+ 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.send_header('Connection', 'close')
- self.end_headers()
- self.wfile.write(msg.encode())
+ 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.send_header('Connection', 'close')
+ self.end_headers()
+ self.wfile.write(msg.encode())
- def getTargetName(self, fname):
- """ Generate a clean and secure filename.
+ 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 = os.path.join(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
- 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 = os.path.join(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):
- def handle_error(self, request, client_address):
- _, exc_value, _ = sys.exc_info()
- print("%s ABORTED transmission (Reason: %s)" % (client_address[0], exc_value))
+ def handle_error(self, request, client_address):
+ _, exc_value, _ = sys.exc_info()
+ print("%s ABORTED transmission (Reason: %s)" % (client_address[0], exc_value))
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 as 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 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 as 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, server_address, RequestHandlerClass, bind_and_activate=True):
- ThreadedHTTPServer.__init__(self, server_address, RequestHandlerClass, bind_and_activate)
+ def __init__(self, pubKey, privKey, server_address, RequestHandlerClass, bind_and_activate=True):
+ ThreadedHTTPServer.__init__(self, server_address, RequestHandlerClass, bind_and_activate)
- # choose TLS1.2 or TLS1, if available
- sslMethod = None
- if hasattr(SSL, "TLSv1_2_METHOD"):
- sslMethod = SSL.TLSv1_2_METHOD
- elif hasattr(SSL, "TLSv1_METHOD"):
- sslMethod = SSL.TLSv1_METHOD
- else:
- # only SSLv23 available
- print("Warning: Only SSLv2/SSLv3 is available, connection might be insecure.")
- sslMethod = SSL.SSLv23_METHOD
+ # choose TLS1.2 or TLS1, if available
+ sslMethod = None
+ if hasattr(SSL, "TLSv1_2_METHOD"):
+ sslMethod = SSL.TLSv1_2_METHOD
+ elif hasattr(SSL, "TLSv1_METHOD"):
+ sslMethod = SSL.TLSv1_METHOD
+ else:
+ # only SSLv23 available
+ print("Warning: Only SSLv2/SSLv3 is available, connection might be insecure.")
+ sslMethod = SSL.SSLv23_METHOD
- ctx = SSL.Context(sslMethod)
- if type(pubKey) is crypto.X509 and type(privKey) is crypto.PKey:
- ctx.use_certificate(pubKey)
- ctx.use_privatekey(privKey)
- else:
- ctx.use_certificate_file(pubKey)
- ctx.use_privatekey_file(privKey)
+ ctx = SSL.Context(sslMethod)
+ if type(pubKey) is crypto.X509 and type(privKey) is 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.bsocket = socket.socket(self.address_family, self.socket_type)
+ self.socket = SSL.Connection(ctx, self.bsocket)
- if bind_and_activate:
- self.server_bind()
- self.server_activate()
+ if bind_and_activate:
+ self.server_bind()
+ self.server_activate()
- def shutdown_request(self, request):
- try:
- request.shutdown()
- except SSL.Error:
- # ignore SSL errors on connection shutdown
- pass
+ def shutdown_request(self, request):
+ try:
+ request.shutdown()
+ except SSL.Error:
+ # ignore SSL errors on connection shutdown
+ pass
class SecureHandler():
- def setup(self):
- self.connection = self.request
+ def setup(self):
+ self.connection = self.request
+
+ if sys.version_info[0] > 2:
+ # python3 SocketIO (replacement for socket._fileobject)
+ raw_read_sock = socket.SocketIO(self.request, 'rb')
+ raw_write_sock = socket.SocketIO(self.request, 'wb')
+ rbufsize = self.rbufsize > 0 and self.rbufsize or io.DEFAULT_BUFFER_SIZE
+ wbufsize = self.wbufsize > 0 and self.wbufsize or io.DEFAULT_BUFFER_SIZE
+ self.rfile = io.BufferedReader(raw_read_sock, rbufsize)
+ self.wfile = io.BufferedWriter(raw_write_sock, wbufsize)
+ else:
+ # python2 does not have SocketIO
+ self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
+ self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)
- if sys.version_info[0] > 2:
- # python3 SocketIO (replacement for socket._fileobject)
- raw_read_sock = socket.SocketIO(self.request, 'rb')
- raw_write_sock = socket.SocketIO(self.request, 'wb')
- rbufsize = self.rbufsize > 0 and self.rbufsize or io.DEFAULT_BUFFER_SIZE
- wbufsize = self.wbufsize > 0 and self.wbufsize or io.DEFAULT_BUFFER_SIZE
- self.rfile = io.BufferedReader(raw_read_sock, rbufsize)
- self.wfile = io.BufferedWriter(raw_write_sock, wbufsize)
- else:
- # python2 does not have SocketIO
- self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
- self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)
class ServeFileException(Exception):
- pass
+ pass
class ServeFile():
- """ Main class to manage everything. """
+ """ Main class to manage everything. """
- _NUM_MODES = 4
- (MODE_SINGLE, MODE_SINGLETAR, MODE_UPLOAD, MODE_LISTDIR) = range(_NUM_MODES)
+ _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
- self.listenIPv4 = True
- self.listenIPv6 = True
+ 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
+ self.listenIPv4 = True
+ self.listenIPv6 = True
- 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.")
+ 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 setIPv4(self, ipv4):
- """ En- or disable ipv4 """
- self.listenIPv4 = ipv4
+ def setIPv4(self, ipv4):
+ """ En- or disable ipv4 """
+ self.listenIPv4 = ipv4
- def setIPv6(self, ipv6):
- """ En- or disable ipv6 """
- self.listenIPv6 = ipv6
+ def setIPv6(self, ipv6):
+ """ En- or disable ipv6 """
+ self.listenIPv6 = ipv6
- 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,1\} \([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\{0,1\}\( addr:\)\{0,1\} \{0,1\}\([0-9a-fA-F.:]*\).*/" + \
- "\\2/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().decode().strip().split("\n")
+ 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,1\} \([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\{0,1\}\( addr:\)\{0,1\} \{0,1\}\([0-9a-fA-F.:]*\).*/" + \
+ "\\2/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().decode().strip().split("\n")
- # filter out ips we are not listening on
- if not self.listenIPv6:
- ips = [ip for ip in ips if '.' in ip]
- if not self.listenIPv4:
- ips = [ip for ip in ips if ':' in ip]
+ # filter out ips we are not listening on
+ if not self.listenIPv6:
+ ips = [ip for ip in ips if '.' in ip]
+ if not self.listenIPv4:
+ ips = [ip for ip in ips if ':' in ip]
- return ips
- return None
+ return ips
+ return None
- def setSSLKeys(self, cert, key):
- """ Set SSL cert/key. Can be either path to file or pyopenssl X509/PKey object. """
- self.cert = cert
- self.key = key
+ def setSSLKeys(self, cert, key):
+ """ Set SSL cert/key. Can be either path to file or pyopenssl X509/PKey object. """
+ self.cert = cert
+ self.key = key
- def setMaxUploadSize(self, limit):
- """ Set the maximum upload size in byte """
- self.maxUploadSize = limit
+ 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 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...", end="")
- sys.stdout.flush()
+ def genKeyPair(self):
+ print("Generating SSL certificate...", end="")
+ sys.stdout.flush()
- pkey = crypto.PKey()
- pkey.generate_key(crypto.TYPE_RSA, 2048)
+ 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"
+ 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", "::1"]:
- altNames.append("IP:%s" % ip)
- altNames.append("DNS:localhost")
- ext = crypto.X509Extension(b"subjectAltName", False, (",".join(altNames)).encode())
- req.add_extensions([ext])
+ # generate altnames
+ altNames = []
+ for ip in self.getIPs() + ["127.0.0.1", "::1"]:
+ altNames.append("IP:%s" % ip)
+ altNames.append("DNS:localhost")
+ ext = crypto.X509Extension(b"subjectAltName", False, (",".join(altNames)).encode())
+ req.add_extensions([ext])
- req.set_pubkey(pkey)
- req.sign(pkey, "sha1")
+ req.set_pubkey(pkey)
+ req.sign(pkey, "sha1")
- cert = crypto.X509()
- # Mozilla only accepts v3 certificates with v3 extensions, not v1
- cert.set_version(0x2)
- # 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")
+ cert = crypto.X509()
+ # Mozilla only accepts v3 certificates with v3 extensions, not v1
+ cert.set_version(0x2)
+ # 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
+ self.cert = cert
+ self.key = pkey
- print("done.")
- print("SHA1 fingerprint:", cert.digest("sha1").decode())
- print("MD5 fingerprint:", cert.digest("md5").decode())
+ print("done.")
+ print("SHA1 fingerprint:", cert.digest("sha1").decode())
+ print("MD5 fingerprint:", cert.digest("md5").decode())
- def _getCert(self):
- return self.cert
+ def _getCert(self):
+ return self.cert
- def _getKey(self):
- return self.key
+ def _getKey(self):
+ return self.key
- def setAuth(self, user, password, realm=None):
- if not user or not password:
- raise ServeFileException("User and password both need to be at least one character.")
- self.auth = base64.b64encode(("%s:%s" % (user, password)).encode()).decode()
- self.authrealm = realm
+ def setAuth(self, user, password, realm=None):
+ if not user or not password:
+ raise ServeFileException("User and password both need to be at least one character.")
+ self.auth = base64.b64encode(("%s:%s" % (user, password)).encode()).decode()
+ self.authrealm = realm
- def _createServer(self, handler, withv6=False):
- ThreadedHTTPServer.address_family = socket.AF_INET
- SecureThreadedHTTPServer.address_family = socket.AF_INET
- listenIp = ''
- server = None
+ def _createServer(self, handler, withv6=False):
+ ThreadedHTTPServer.address_family = socket.AF_INET
+ SecureThreadedHTTPServer.address_family = socket.AF_INET
+ listenIp = ''
+ server = None
- if withv6:
- listenIp = '::'
- ThreadedHTTPServer.address_family = socket.AF_INET6
- SecureThreadedHTTPServer.address_family = socket.AF_INET6
+ if withv6:
+ listenIp = '::'
+ ThreadedHTTPServer.address_family = socket.AF_INET6
+ SecureThreadedHTTPServer.address_family = socket.AF_INET6
- if self.useSSL:
- if not self._getKey():
- self.genKeyPair()
- try:
- server = SecureThreadedHTTPServer(self._getCert(), self._getKey(),
- (listenIp, self.port), handler, bind_and_activate=False)
- except SSL.Error as e:
- raise ServeFileException("SSL error: Could not read SSL public/private key from file(s) (error was: \"%s\")" % (e[0][0][2],))
- else:
- server = ThreadedHTTPServer((listenIp, self.port), handler,
- bind_and_activate=False)
+ if self.useSSL:
+ if not self._getKey():
+ self.genKeyPair()
+ try:
+ server = SecureThreadedHTTPServer(self._getCert(), self._getKey(),
+ (listenIp, self.port), handler, bind_and_activate=False)
+ except SSL.Error as e:
+ raise ServeFileException("SSL error: Could not read SSL public/private key "
+ "from file(s) (error was: \"%s\")" % (e[0][0][2],))
+ else:
+ server = ThreadedHTTPServer((listenIp, self.port), handler,
+ bind_and_activate=False)
- if withv6:
- server.socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 1)
+ if withv6:
+ server.socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 1)
- server.server_bind()
- server.server_activate()
+ server.server_bind()
+ server.server_activate()
- return server
+ return server
- def serve(self):
- self.handler = self._confAndFindHandler()
- self.server = []
+ def serve(self):
+ self.handler = self._confAndFindHandler()
+ self.server = []
- try:
- currsocktype = "IPv4"
- if self.listenIPv4:
- self.server.append(self._createServer(self.handler))
- currsocktype = "IPv6"
- if self.listenIPv6:
- self.server.append(self._createServer(self.handler, withv6=True))
- except socket.error as e:
- raise ServeFileException("Could not open %s socket: %s" % (currsocktype, e))
+ try:
+ currsocktype = "IPv4"
+ if self.listenIPv4:
+ self.server.append(self._createServer(self.handler))
+ currsocktype = "IPv6"
+ if self.listenIPv6:
+ self.server.append(self._createServer(self.handler, withv6=True))
+ except socket.error as e:
+ raise ServeFileException("Could not open %s socket: %s" % (currsocktype, e))
- 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))
+ 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:" % \
- ("this file" if (self.serveMode != self.MODE_UPLOAD) else "the uploadform", ))
- ips = self.getIPs()
- if not ips or len(ips) == 0 or ips[0] == '':
- print("Could not find any addresses.")
- else:
- pwPart = ""
- if self.auth:
- pwPart = base64.b64decode(self.auth).decode() + "@"
- for ip in ips:
- if ":" in ip:
- ip = "[%s]" % ip
- print("\thttp%s://%s%s:%d/" % (self.useSSL and "s" or "", pwPart, ip, self.port))
- print()
+ # print urls with local network adresses
+ print("\nSome addresses %s will be available at:" %
+ ("this file" if (self.serveMode != self.MODE_UPLOAD) else "the uploadform", ))
+ ips = self.getIPs()
+ if not ips or len(ips) == 0 or ips[0] == '':
+ print("Could not find any addresses.")
+ else:
+ pwPart = ""
+ if self.auth:
+ pwPart = base64.b64decode(self.auth).decode() + "@"
+ for ip in ips:
+ if ":" in ip:
+ ip = "[%s]" % ip
+ print("\thttp%s://%s%s:%d/" % (self.useSSL and "s" or "", pwPart, ip, self.port))
+ print()
- try:
- while True:
- (servers, _, _) = select.select(self.server, [], [])
- for server in servers:
- server.handle_request()
- except KeyboardInterrupt:
- for server in self.server:
- server.socket.close()
+ try:
+ while True:
+ (servers, _, _) = select.select(self.server, [], [])
+ for server in servers:
+ server.handle_request()
+ except KeyboardInterrupt:
+ for server in self.server:
+ 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)
+ # 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()
- except IOError as e:
- raise ServeFileException("Error: Could not open file, %r" % (str(e),))
- FileHandler.filePath = self.target
- FileHandler.fileName = os.path.basename(self.target)
- FileHandler.fileLength = os.stat(self.target).st_size
- 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()
+ def _confAndFindHandler(self):
+ handler = None
+ if self.serveMode == self.MODE_SINGLE:
+ try:
+ testit = open(self.target, 'r')
+ testit.close()
+ except IOError as e:
+ raise ServeFileException("Error: Could not open file, %r" % (str(e),))
+ FileHandler.filePath = self.target
+ FileHandler.fileName = os.path.basename(self.target)
+ FileHandler.fileLength = os.stat(self.target).st_size
+ 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) as e:
- raise ServeFileException("Error: Could not create directory '%s' for uploads, %r" % (self.target, str(e)))
- else:
- raise ServeFileException("Error: Upload directory already exists and is a file.")
- FilePutter.targetDir = os.path.abspath(self.target)
- FilePutter.maxUploadSize = self.maxUploadSize
- handler = FilePutter
- elif self.serveMode == self.MODE_LISTDIR:
- if not os.path.exists(self.target):
- raise ServeFileException("Error: Could not open file or directory.")
- if not os.path.isdir(self.target):
- raise ServeFileException("Error: '%s' is not a directory." % (self.target,))
- handler = DirListingHandler
- handler.targetDir = os.path.abspath(self.target)
+ 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) as e:
+ raise ServeFileException("Error: Could not create directory '%s' for uploads, %r" % (self.target, str(e)))
+ else:
+ raise ServeFileException("Error: Upload directory already exists and is a file.")
+ FilePutter.targetDir = os.path.abspath(self.target)
+ FilePutter.maxUploadSize = self.maxUploadSize
+ handler = FilePutter
+ elif self.serveMode == self.MODE_LISTDIR:
+ if not os.path.exists(self.target):
+ raise ServeFileException("Error: Could not open file or directory.")
+ if not os.path.isdir(self.target):
+ raise ServeFileException("Error: '%s' is not a directory." % (self.target,))
+ handler = DirListingHandler
+ handler.targetDir = os.path.abspath(self.target)
- if self.auth:
- # do authentication
- AuthenticationHandler.authString = self.auth
- if self.authrealm:
- AuthenticationHandler.realm = self.authrealm
- class AuthenticatedHandler(AuthenticationHandler, handler):
- pass
- handler = AuthenticatedHandler
+ if self.auth:
+ # do authentication
+ AuthenticationHandler.authString = self.auth
+ if self.authrealm:
+ AuthenticationHandler.realm = self.authrealm
- if self.useSSL:
- # secure handler
- @catchSSLErrors
- class AlreadySecuredHandler(SecureHandler, handler):
- pass
- handler = AlreadySecuredHandler
- return handler
+ 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"
+ # base64 encoded user:password string for authentication
+ authString = None
+ realm = "Restricted area"
- def handle_one_request(self):
- """ Overloaded function to handle one request.
+ 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
+ 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)
- self.send_header("Connection", "close")
- errorMsg = "401 - Unauthorized401 - Unauthorized
"
- self.send_header("Content-Length", str(len(errorMsg)))
- self.end_headers()
- self.wfile.write(errorMsg.encode())
+ 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)
+ self.send_header("Connection", "close")
+ errorMsg = "401 - Unauthorized401 - Unauthorized
"
+ self.send_header("Content-Length", str(len(errorMsg)))
+ self.end_headers()
+ self.wfile.write(errorMsg.encode())
def main():
- parser = argparse.ArgumentParser(prog='servefile', 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('--realm', type=str, default=None,\
- help="Set a realm 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))
- parser.add_argument('-4', '--ipv4-only', action="store_true", default=False, \
- help="Listen on IPv4 only")
- parser.add_argument('-6', '--ipv6-only', action="store_true", default=False, \
- help="Listen on IPv6 only")
+ parser = argparse.ArgumentParser(prog='servefile', 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('--realm', type=str, default=None,
+ help="Set a realm 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))
+ parser.add_argument('-4', '--ipv4-only', action="store_true", default=False,
+ help="Listen on IPv4 only")
+ parser.add_argument('-6', '--ipv6-only', action="store_true", default=False,
+ help="Listen on IPv6 only")
- args = parser.parse_args()
- maxUploadSize = 0
+ 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)
+ # 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.upload and args.list_dir:
- print("Error: Upload and dirlisting can't be enabled together.")
- sys.exit(1)
+ if args.upload and args.list_dir:
+ print("Error: Upload and dirlisting can't be enabled together.")
+ sys.exit(1)
- if args.max_upload_size:
- sizeRe = re.match("^(\d+(?:[,.]\d+)?)(?:([bkmgtpe])(?:(? 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, args.realm)
- if compression and compression != "none":
- server.setCompression(compression)
- if args.ipv4_only or not socket.has_ipv6:
- server.setIPv6(False)
- if args.ipv6_only:
- server.setIPv4(False)
- server.serve()
- except ServeFileException as e:
- print(e)
- sys.exit(1)
- print("Good bye.")
+ 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, args.realm)
+ if compression and compression != "none":
+ server.setCompression(compression)
+ if args.ipv4_only or not socket.has_ipv6:
+ server.setIPv6(False)
+ if args.ipv6_only:
+ server.setIPv4(False)
+ server.serve()
+ except ServeFileException as e:
+ print(e)
+ sys.exit(1)
+ print("Good bye.")
if __name__ == '__main__':
- main()
-
+ main()