Compare commits

...

3 Commits

Author SHA1 Message Date
Sebastian Lohff 4161a3d721 Further codeformatting
* break up some extra long lines
 * add a bit of noqa for warnings I don't want to have
 * rework formatting for ip addr / ifconfig part
2021-09-07 23:21:49 +02:00
Sebastian Lohff 2d434d83a1 Code reformatting
* replace tabs with spaces
 * replace some of the != None with is not None etc.
 * more whitespace fixes
 * remove all the newline \
2021-09-07 23:21:49 +02:00
Sebastian Lohff 41a0f64ff7 Explicitly set encoding for http requests in tests
Due to the upgrade to charset-normalizer 2.0.4 guessing the encoding
inside the tests did not work anymore and caused the umlaut tests to
fail. Explicitly specifying the encoding on the requests' response
object fixes this.
2021-09-07 23:12:01 +02:00
2 changed files with 972 additions and 956 deletions

View File

@ -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>
@ -502,7 +504,7 @@ class DirListingHandler(FileBaseHandler):
dir_items = list()
file_items = list()
for item in [".."] + sorted(os.listdir(path), key=lambda x:x.lower()):
for item in [".."] + sorted(os.listdir(path), key=lambda x: x.lower()):
# create path to item
itemPath = os.path.join(path, item)
@ -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("\nSome addresses %s will be available at:" % \
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] == '':
@ -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()

View File

@ -92,11 +92,14 @@ def datadir(tmp_path):
def make_request(path='/', host='localhost', port=SERVEFILE_DEFAULT_PORT, method='get', protocol='http', timeout=5,
**kwargs):
encoding='utf-8', **kwargs):
url = '{}://{}:{}{}'.format(protocol, host, port, path)
print('Calling {} on {} with {}'.format(method, url, kwargs))
r = getattr(requests, method)(url, **kwargs)
if r.encoding is None and encoding:
r.encoding = encoding
return r