@ -7,7 +7,7 @@
from __future__ import print_function
__version__ = ' 0.5. 1 '
__version__ = ' 0.5. 2 '
import argparse
import base64
@ -42,11 +42,13 @@ try:
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 = None
blockSize = 1024 * 1024
@ -141,7 +143,7 @@ class FileBaseHandler(BaseHTTPServer.BaseHTTPRequestHandler):
# now we can wind the file *brrrrrr*
myfile . seek ( fromto [ 0 ] )
if fromto != None :
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
@ -246,7 +248,7 @@ class TarFileHandler(FileBaseHandler):
# 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 :
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 )
@ -416,7 +418,7 @@ class DirListingHandler(FileBaseHandler):
< / tr >
< / thead >
< tbody >
""" % { ' path ' : os.path.normpath(unquote(self.path))}
""" % { ' path ' : os.path.normpath(unquote(self.path))} # noqa: E501
footer = """ </tbody></table></div>
< div class = " footer " > < a href = " http://seba-geek.de/stuff/servefile/ " > servefile % ( version ) s < / a > < / div >
< script >
@ -610,7 +612,7 @@ class FilePutter(BaseHTTPServer.BaseHTTPRequestHandler):
env = os . environ
env [ ' REQUEST_METHOD ' ] = " POST "
fstorage = cgi . FieldStorage ( fp = self . rfile , headers = self . headers , environ = env )
if not " file " in fstorage :
if " file " not in fstorage :
self . sendResponse ( 400 , " No file found in request. " )
return
@ -677,7 +679,8 @@ class FilePutter(BaseHTTPServer.BaseHTTPRequestHandler):
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 )
self . sendResponse ( 413 , " Your file was too big! Maximum allowed size is %d byte. <a href= \" / \" >back</a> " %
self . maxUploadSize )
return - 1
return length
@ -714,6 +717,7 @@ class FilePutter(BaseHTTPServer.BaseHTTPRequestHandler):
return extraDestFileName
# never reached
class ThreadedHTTPServer ( SocketServer . ThreadingMixIn , BaseHTTPServer . HTTPServer ) :
def handle_error ( self , request , client_address ) :
_ , exc_value , _ = sys . exc_info ( )
@ -789,6 +793,7 @@ class SecureHandler():
self . rfile = socket . _fileobject ( self . request , " rb " , self . rbufsize )
self . wfile = socket . _fileobject ( self . request , " wb " , self . wbufsize )
class ServeFileException ( Exception ) :
pass
@ -813,7 +818,8 @@ class ServeFile():
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. " )
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 """
@ -827,18 +833,18 @@ class ServeFile():
""" 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 ' " , \
proc = Popen ( r " ip addr| "
r " sed -n -e ' s/.*inet6 \ { 0,1 \ } \ ([0-9.a-fA-F:] \ + \ ).*/ \1/ p ' | "
r " 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 ' " , \
proc = Popen ( r " ifconfig| "
r " sed -n ' s/.*inet6 \ { 0,1 \ } \ ( addr: \ ) \ { 0,1 \ } \ { 0,1 \ } \ ([0-9a-fA-F.:]* \ ).*/ "
r " \ 2/p ' | "
r " grep -v ' ^fe80 \ |^127.0.0.1 \ |^::1 ' " ,
shell = True , stdout = PIPE , stderr = PIPE )
if oldLang :
os . environ [ ' LC_ALL ' ] = oldLang
@ -886,7 +892,7 @@ class ServeFile():
req = crypto . X509Req ( )
subj = req . get_subject ( )
subj . CN = " 127.0.0.1 "
subj . O = " servefile laboratories "
subj . O = " servefile laboratories " # noqa: E741
subj . OU = " servefile "
# generate altnames
@ -951,7 +957,8 @@ class ServeFile():
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 ] , ) )
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 )
@ -984,7 +991,7 @@ class ServeFile():
print ( " Serving \" %s \" for uploads at port %d . " % ( self . target , self . port ) )
# print urls with local network adresses
print ( " \n Some addresses %s will be available at: " % \
print ( " \n Some 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 ] == ' ' :
@ -1041,7 +1048,8 @@ class ServeFile():
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 ) ) )
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 )
@ -1060,6 +1068,7 @@ class ServeFile():
AuthenticationHandler . authString = self . auth
if self . authrealm :
AuthenticationHandler . realm = self . authrealm
class AuthenticatedHandler ( AuthenticationHandler , handler ) :
pass
handler = AuthenticatedHandler
@ -1105,7 +1114,8 @@ class AuthenticationHandler():
self . send_response ( 401 )
self . send_header ( " WWW-Authenticate " , " Basic realm= \" %s \" " % self . realm )
self . send_header ( " Connection " , " close " )
errorMsg = " <html><head><title>401 - Unauthorized</title></head><body><h1>401 - Unauthorized</h1></body></html> "
errorMsg = ( " <html><head><title>401 - Unauthorized</title></head> "
" <body><h1>401 - Unauthorized</h1></body></html> " )
self . send_header ( " Content-Length " , str ( len ( errorMsg ) ) )
self . end_headers ( )
self . wfile . write ( errorMsg . encode ( ) )
@ -1115,32 +1125,35 @@ 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 , \
parser . add_argument ( ' -p ' , ' --port ' , type = int , default = 8080 ,
help = ' Port to listen on ' )
parser . add_argument ( ' -u ' , ' --upload ' , action = " store_true " , default = False , \
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 , \
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 , \
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 , \
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 , \
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 ' , \
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 , \
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 , \
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 , \
parser . add_argument ( ' -6 ' , ' --ipv6-only ' , action = " store_true " , default = False ,
help = " Listen on IPv6 only " )
args = parser . parse_args ( )
@ -1156,7 +1169,7 @@ def main():
sys . exit ( 1 )
if args . max_upload_size :
sizeRe = re . match ( " ^( \ d+(?:[,.] \ d+)?)(?:([bkmgtpe])(?:(?<!b)b?)?)?$ " , args . max_upload_size . lower ( ) )
sizeRe = re . match ( r " ^( \ d+(?:[,.] \ d+)?)(?:([bkmgtpe])(?:(?<!b)b?)?)?$ " , args . max_upload_size . lower ( ) )
if not sizeRe :
print ( " Error: Your max upload size param is broken. Try something like 3M or 2.5Gb. " )
sys . exit ( 1 )
@ -1183,7 +1196,8 @@ def main():
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 and have to be separated by a \" : \" . " )
print ( " Error: User and password for HTTP basic authentication need to be both "
" at least one character and have to be separated by a \" : \" . " )
sys . exit ( 1 )
if args . realm and not args . auth :
@ -1254,4 +1268,3 @@ def main():
if __name__ == ' __main__ ' :
main ( )