Compare commits
2 Commits
master
...
code-forma
Author | SHA1 | Date |
---|---|---|
Sebastian Lohff | 33defb85a7 | |
Sebastian Lohff | 579363201c |
|
@ -11,7 +11,7 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
python: [2.7, 3.7, 3.8, 3.9, "3.10", 3.11]
|
python: [2.7, 3.6, 3.7, 3.8, 3.9]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
|
@ -23,15 +23,3 @@ jobs:
|
||||||
run: pip install tox
|
run: pip install tox
|
||||||
- name: Run Tox
|
- name: Run Tox
|
||||||
run: tox -e py
|
run: tox -e py
|
||||||
pep8:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
- name: Setup python
|
|
||||||
uses: actions/setup-python@v2
|
|
||||||
with:
|
|
||||||
python-version: 3.9
|
|
||||||
- name: Install Tox
|
|
||||||
run: pip install tox
|
|
||||||
- name: Run Tox pep8
|
|
||||||
run: "tox -e pep8"
|
|
||||||
|
|
18
ChangeLog
18
ChangeLog
|
@ -1,24 +1,6 @@
|
||||||
servefile changelog
|
servefile changelog
|
||||||
===================
|
===================
|
||||||
|
|
||||||
2023-01-23 v0.5.4
|
|
||||||
-----------------
|
|
||||||
|
|
||||||
0.5.4 released
|
|
||||||
|
|
||||||
* code reformatting for better maintainability
|
|
||||||
* upload to uploaddir instead of /tmp for large files
|
|
||||||
* add python3.10 / python3.11 support
|
|
||||||
* drop python3.6 support
|
|
||||||
|
|
||||||
|
|
||||||
2021-11-18 v0.5.3
|
|
||||||
-----------------
|
|
||||||
|
|
||||||
0.5.3 released
|
|
||||||
|
|
||||||
* improved test performance
|
|
||||||
|
|
||||||
|
|
||||||
2021-09-08 v0.5.2
|
2021-09-08 v0.5.2
|
||||||
-----------------
|
-----------------
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
.TH SERVEFILE 1 "January 2023" "servefile 0.5.4" "User Commands"
|
.TH SERVEFILE 1 "September 2021" "servefile 0.5.2" "User Commands"
|
||||||
|
|
||||||
.SH NAME
|
.SH NAME
|
||||||
servefile \- small HTTP-Server for temporary file transfer
|
servefile \- small HTTP-Server for temporary file transfer
|
||||||
|
|
|
@ -7,10 +7,11 @@
|
||||||
|
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
|
|
||||||
__version__ = '0.5.4'
|
__version__ = '0.5.2'
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import base64
|
import base64
|
||||||
|
import cgi
|
||||||
import datetime
|
import datetime
|
||||||
import io
|
import io
|
||||||
import mimetypes
|
import mimetypes
|
||||||
|
@ -20,9 +21,7 @@ import select
|
||||||
import socket
|
import socket
|
||||||
from subprocess import Popen, PIPE
|
from subprocess import Popen, PIPE
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
|
||||||
import time
|
import time
|
||||||
import warnings
|
|
||||||
|
|
||||||
# fix imports for python2/python3
|
# fix imports for python2/python3
|
||||||
try:
|
try:
|
||||||
|
@ -43,11 +42,6 @@ try:
|
||||||
except ImportError:
|
except ImportError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
with warnings.catch_warnings():
|
|
||||||
warnings.filterwarnings("ignore", category=DeprecationWarning)
|
|
||||||
# scheduled for removal in python3.13, used for FieldStorage
|
|
||||||
import cgi
|
|
||||||
|
|
||||||
|
|
||||||
def getDateStrNow():
|
def getDateStrNow():
|
||||||
""" Get the current time formatted for HTTP header """
|
""" Get the current time formatted for HTTP header """
|
||||||
|
@ -114,7 +108,7 @@ class FileBaseHandler(BaseHTTPServer.BaseHTTPRequestHandler):
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return (False, None)
|
return (False, None)
|
||||||
|
|
||||||
if fromto[0] >= fileLength or fromto[0] < 0 or fromto[1] >= fileLength or fromto[1] - fromto[0] < 0:
|
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)
|
# oops, already done! (requested range out of range)
|
||||||
self.send_response(416)
|
self.send_response(416)
|
||||||
self.send_header('Content-Range', 'bytes */%d' % fileLength)
|
self.send_header('Content-Range', 'bytes */%d' % fileLength)
|
||||||
|
@ -170,8 +164,8 @@ class FileBaseHandler(BaseHTTPServer.BaseHTTPRequestHandler):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def getChunk(self, myfile, fromto):
|
def getChunk(self, myfile, fromto):
|
||||||
if fromto and myfile.tell() + self.blockSize >= fromto[1]:
|
if fromto and myfile.tell()+self.blockSize >= fromto[1]:
|
||||||
readsize = fromto[1] - myfile.tell() + 1
|
readsize = fromto[1]-myfile.tell()+1
|
||||||
else:
|
else:
|
||||||
readsize = self.blockSize
|
readsize = self.blockSize
|
||||||
return myfile.read(readsize)
|
return myfile.read(readsize)
|
||||||
|
@ -532,7 +526,10 @@ class DirListingHandler(FileBaseHandler):
|
||||||
target_items.append((item, itemPath, stat))
|
target_items.append((item, itemPath, stat))
|
||||||
|
|
||||||
# Directories first, then files
|
# Directories first, then files
|
||||||
for (tuple_list, is_dir) in ((dir_items, True), (file_items, False)):
|
for (tuple_list, is_dir) in (
|
||||||
|
(dir_items, True),
|
||||||
|
(file_items, False),
|
||||||
|
):
|
||||||
for (item, itemPath, stat) in tuple_list:
|
for (item, itemPath, stat) in tuple_list:
|
||||||
self._appendToListing(content, item, itemPath, stat, is_dir=is_dir)
|
self._appendToListing(content, item, itemPath, stat, is_dir=is_dir)
|
||||||
|
|
||||||
|
@ -614,24 +611,7 @@ class FilePutter(BaseHTTPServer.BaseHTTPRequestHandler):
|
||||||
# create FieldStorage object for multipart parsing
|
# create FieldStorage object for multipart parsing
|
||||||
env = os.environ
|
env = os.environ
|
||||||
env['REQUEST_METHOD'] = "POST"
|
env['REQUEST_METHOD'] = "POST"
|
||||||
targetDir = self.targetDir
|
fstorage = cgi.FieldStorage(fp=self.rfile, headers=self.headers, environ=env)
|
||||||
|
|
||||||
class CustomFieldStorage(cgi.FieldStorage):
|
|
||||||
|
|
||||||
def make_file(self, *args, **kwargs):
|
|
||||||
"""Overwritten to use a named file and the upload directory
|
|
||||||
|
|
||||||
Python 2.7 has an unused "binary" argument while Python 3 does
|
|
||||||
not have any arguments. Python 2.7 does not have a
|
|
||||||
self._binary_file attribute.
|
|
||||||
"""
|
|
||||||
if sys.version_info.major == 2 or self._binary_file:
|
|
||||||
return tempfile.NamedTemporaryFile("wb+", dir=targetDir)
|
|
||||||
else:
|
|
||||||
return tempfile.NamedTemporaryFile(
|
|
||||||
"w+", encoding=self.encoding, newline='\n', dir=targetDir)
|
|
||||||
|
|
||||||
fstorage = CustomFieldStorage(fp=self.rfile, headers=self.headers, environ=env)
|
|
||||||
if "file" not in fstorage:
|
if "file" not in fstorage:
|
||||||
self.sendResponse(400, "No file found in request.")
|
self.sendResponse(400, "No file found in request.")
|
||||||
return
|
return
|
||||||
|
@ -641,21 +621,14 @@ class FilePutter(BaseHTTPServer.BaseHTTPRequestHandler):
|
||||||
self.sendResponse(400, "Filename was empty or invalid")
|
self.sendResponse(400, "Filename was empty or invalid")
|
||||||
return
|
return
|
||||||
|
|
||||||
# put the file at the right place, send 200 afterwards
|
# write file down to disk, send a 200 afterwards
|
||||||
if getattr(fstorage["file"].file, "name", None):
|
target = open(destFileName, "wb")
|
||||||
# the sent file was large, so we can just hard link the temporary
|
bytesLeft = length
|
||||||
# file and are done
|
while bytesLeft > 0:
|
||||||
os.link(fstorage["file"].file.name, destFileName)
|
bytesToRead = min(self.blockSize, bytesLeft)
|
||||||
else:
|
target.write(fstorage["file"].file.read(bytesToRead))
|
||||||
# write file to disk. it was small enough so no temporary file was
|
bytesLeft -= bytesToRead
|
||||||
# created
|
target.close()
|
||||||
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")
|
self.sendResponse(200, "OK! Thanks for uploading")
|
||||||
print("Received file '%s' from %s." % (destFileName, self.client_address[0]))
|
print("Received file '%s' from %s." % (destFileName, self.client_address[0]))
|
||||||
|
|
||||||
|
@ -876,7 +849,7 @@ class ServeFile():
|
||||||
if oldLang:
|
if oldLang:
|
||||||
os.environ['LC_ALL'] = oldLang
|
os.environ['LC_ALL'] = oldLang
|
||||||
else:
|
else:
|
||||||
del os.environ['LC_ALL']
|
del(os.environ['LC_ALL'])
|
||||||
if proc.wait() != 0:
|
if proc.wait() != 0:
|
||||||
# we couldn't find any ip address
|
# we couldn't find any ip address
|
||||||
proc = None
|
proc = None
|
||||||
|
@ -940,7 +913,7 @@ class ServeFile():
|
||||||
# with the same serial ==> we just use the seconds as serial.
|
# with the same serial ==> we just use the seconds as serial.
|
||||||
cert.set_serial_number(int(time.time()))
|
cert.set_serial_number(int(time.time()))
|
||||||
cert.gmtime_adj_notBefore(0)
|
cert.gmtime_adj_notBefore(0)
|
||||||
cert.gmtime_adj_notAfter(365 * 24 * 60 * 60)
|
cert.gmtime_adj_notAfter(365*24*60*60)
|
||||||
cert.set_issuer(req.get_subject())
|
cert.set_issuer(req.get_subject())
|
||||||
cert.set_subject(req.get_subject())
|
cert.set_subject(req.get_subject())
|
||||||
cert.add_extensions([ext])
|
cert.add_extensions([ext])
|
||||||
|
@ -1222,7 +1195,7 @@ def main():
|
||||||
|
|
||||||
if args.auth:
|
if args.auth:
|
||||||
dpos = args.auth.find(":")
|
dpos = args.auth.find(":")
|
||||||
if dpos <= 0 or dpos == (len(args.auth) - 1):
|
if dpos <= 0 or dpos == (len(args.auth)-1):
|
||||||
print("Error: User and password for HTTP basic authentication need to be both "
|
print("Error: User and password for HTTP basic authentication need to be both "
|
||||||
"at least one character and have to be separated by a \":\".")
|
"at least one character and have to be separated by a \":\".")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
5
setup.py
5
setup.py
|
@ -11,7 +11,7 @@ setup(
|
||||||
long_description=long_description,
|
long_description=long_description,
|
||||||
long_description_content_type='text/markdown',
|
long_description_content_type='text/markdown',
|
||||||
platforms='posix',
|
platforms='posix',
|
||||||
version='0.5.4',
|
version='0.5.2',
|
||||||
license='GPLv3 or later',
|
license='GPLv3 or later',
|
||||||
url='https://github.com/sebageek/servefile/',
|
url='https://github.com/sebageek/servefile/',
|
||||||
author='Sebastian Lohff',
|
author='Sebastian Lohff',
|
||||||
|
@ -38,11 +38,10 @@ setup(
|
||||||
'Programming Language :: Python :: 2',
|
'Programming Language :: Python :: 2',
|
||||||
'Programming Language :: Python :: 2.7',
|
'Programming Language :: Python :: 2.7',
|
||||||
'Programming Language :: Python :: 3',
|
'Programming Language :: Python :: 3',
|
||||||
|
'Programming Language :: Python :: 3.6',
|
||||||
'Programming Language :: Python :: 3.7',
|
'Programming Language :: Python :: 3.7',
|
||||||
'Programming Language :: Python :: 3.8',
|
'Programming Language :: Python :: 3.8',
|
||||||
'Programming Language :: Python :: 3.9',
|
'Programming Language :: Python :: 3.9',
|
||||||
'Programming Language :: Python :: 3.10',
|
|
||||||
'Programming Language :: Python :: 3.11',
|
|
||||||
'Topic :: Communications',
|
'Topic :: Communications',
|
||||||
'Topic :: Communications :: File Sharing',
|
'Topic :: Communications :: File Sharing',
|
||||||
'Topic :: Internet',
|
'Topic :: Internet',
|
||||||
|
|
|
@ -9,7 +9,6 @@ import sys
|
||||||
import tarfile
|
import tarfile
|
||||||
import time
|
import time
|
||||||
import urllib3
|
import urllib3
|
||||||
from requests.exceptions import ConnectionError
|
|
||||||
|
|
||||||
# crudly written to learn more about pytest and to have a base for refactoring
|
# crudly written to learn more about pytest and to have a base for refactoring
|
||||||
|
|
||||||
|
@ -59,6 +58,7 @@ def run_servefile():
|
||||||
|
|
||||||
print("running {} with args {}".format(", ".join(servefile_path), args))
|
print("running {} with args {}".format(", ".join(servefile_path), args))
|
||||||
p = subprocess.Popen([sys.executable] + servefile_path + args, **kwargs)
|
p = subprocess.Popen([sys.executable] + servefile_path + args, **kwargs)
|
||||||
|
time.sleep(kwargs.get('timeout', 0.3))
|
||||||
instances.append(p)
|
instances.append(p)
|
||||||
|
|
||||||
return p
|
return p
|
||||||
|
@ -91,7 +91,7 @@ def datadir(tmp_path):
|
||||||
return _datadir
|
return _datadir
|
||||||
|
|
||||||
|
|
||||||
def make_request(path='/', host='localhost', port=SERVEFILE_DEFAULT_PORT, method='get', protocol='http',
|
def make_request(path='/', host='localhost', port=SERVEFILE_DEFAULT_PORT, method='get', protocol='http', timeout=5,
|
||||||
encoding='utf-8', **kwargs):
|
encoding='utf-8', **kwargs):
|
||||||
url = '{}://{}:{}{}'.format(protocol, host, port, path)
|
url = '{}://{}:{}{}'.format(protocol, host, port, path)
|
||||||
print('Calling {} on {} with {}'.format(method, url, kwargs))
|
print('Calling {} on {} with {}'.format(method, url, kwargs))
|
||||||
|
@ -103,7 +103,7 @@ def make_request(path='/', host='localhost', port=SERVEFILE_DEFAULT_PORT, method
|
||||||
return r
|
return r
|
||||||
|
|
||||||
|
|
||||||
def check_download(expected_data=None, path='/', fname=None, **kwargs):
|
def check_download(expected_data=None, path='/', fname=None, status_code=200, **kwargs):
|
||||||
if fname is None:
|
if fname is None:
|
||||||
fname = os.path.basename(path)
|
fname = os.path.basename(path)
|
||||||
r = make_request(path, **kwargs)
|
r = make_request(path, **kwargs)
|
||||||
|
@ -117,22 +117,6 @@ def check_download(expected_data=None, path='/', fname=None, **kwargs):
|
||||||
return r # for additional tests
|
return r # for additional tests
|
||||||
|
|
||||||
|
|
||||||
def _retry_while(exception, function, timeout=2):
|
|
||||||
now = time.time # float seconds since epoch
|
|
||||||
|
|
||||||
def wrapped(*args, **kwargs):
|
|
||||||
timeout_after = now() + timeout
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
return function(*args, **kwargs)
|
|
||||||
except exception:
|
|
||||||
if now() >= timeout_after:
|
|
||||||
raise
|
|
||||||
time.sleep(0.1)
|
|
||||||
|
|
||||||
return wrapped
|
|
||||||
|
|
||||||
|
|
||||||
def _test_version(run_servefile, standalone):
|
def _test_version(run_servefile, standalone):
|
||||||
# we expect the version on stdout (python3.4+) or stderr(python2.6-3.3)
|
# we expect the version on stdout (python3.4+) or stderr(python2.6-3.3)
|
||||||
s = run_servefile('--version', standalone=standalone, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
s = run_servefile('--version', standalone=standalone, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
||||||
|
@ -146,7 +130,7 @@ def _test_version(run_servefile, standalone):
|
||||||
version = s.stdout.readline().decode().strip()
|
version = s.stdout.readline().decode().strip()
|
||||||
|
|
||||||
# hardcode version as string until servefile is a module
|
# hardcode version as string until servefile is a module
|
||||||
assert version == 'servefile 0.5.4'
|
assert version == 'servefile 0.5.2'
|
||||||
|
|
||||||
|
|
||||||
def test_version(run_servefile):
|
def test_version(run_servefile):
|
||||||
|
@ -163,7 +147,7 @@ def test_correct_headers(run_servefile, datadir):
|
||||||
p = datadir({'testfile': data}) / 'testfile'
|
p = datadir({'testfile': data}) / 'testfile'
|
||||||
run_servefile(str(p))
|
run_servefile(str(p))
|
||||||
|
|
||||||
r = _retry_while(ConnectionError, make_request)()
|
r = make_request()
|
||||||
assert r.status_code == 200
|
assert r.status_code == 200
|
||||||
assert r.headers.get('Content-Type') == 'application/octet-stream'
|
assert r.headers.get('Content-Type') == 'application/octet-stream'
|
||||||
assert r.headers.get('Content-Disposition') == 'attachment; filename="testfile"'
|
assert r.headers.get('Content-Disposition') == 'attachment; filename="testfile"'
|
||||||
|
@ -176,7 +160,7 @@ def test_redirect_and_download(run_servefile, datadir):
|
||||||
run_servefile(str(p))
|
run_servefile(str(p))
|
||||||
|
|
||||||
# redirect
|
# redirect
|
||||||
r = _retry_while(ConnectionError, make_request)(allow_redirects=False)
|
r = make_request(allow_redirects=False)
|
||||||
assert r.status_code == 302
|
assert r.status_code == 302
|
||||||
assert r.headers.get('Location') == '/testfile'
|
assert r.headers.get('Location') == '/testfile'
|
||||||
|
|
||||||
|
@ -191,7 +175,7 @@ def test_redirect_and_download_with_umlaut(run_servefile, datadir):
|
||||||
run_servefile(str(p))
|
run_servefile(str(p))
|
||||||
|
|
||||||
# redirect
|
# redirect
|
||||||
r = _retry_while(ConnectionError, make_request)(allow_redirects=False)
|
r = make_request(allow_redirects=False)
|
||||||
assert r.status_code == 302
|
assert r.status_code == 302
|
||||||
assert r.headers.get('Location') == '/{}'.format(quote(filename))
|
assert r.headers.get('Location') == '/{}'.format(quote(filename))
|
||||||
|
|
||||||
|
@ -206,7 +190,7 @@ def test_specify_port(run_servefile, datadir):
|
||||||
p = datadir({'testfile': data}) / 'testfile'
|
p = datadir({'testfile': data}) / 'testfile'
|
||||||
run_servefile([str(p), '-p', str(SERVEFILE_SECONDARY_PORT)])
|
run_servefile([str(p), '-p', str(SERVEFILE_SECONDARY_PORT)])
|
||||||
|
|
||||||
_retry_while(ConnectionError, check_download)(data, fname='testfile', port=SERVEFILE_SECONDARY_PORT)
|
check_download(data, fname='testfile', port=SERVEFILE_SECONDARY_PORT)
|
||||||
|
|
||||||
|
|
||||||
def test_ipv4_only(run_servefile, datadir):
|
def test_ipv4_only(run_servefile, datadir):
|
||||||
|
@ -214,7 +198,7 @@ def test_ipv4_only(run_servefile, datadir):
|
||||||
p = datadir({'testfile': data}) / 'testfile'
|
p = datadir({'testfile': data}) / 'testfile'
|
||||||
run_servefile([str(p), '-4'])
|
run_servefile([str(p), '-4'])
|
||||||
|
|
||||||
_retry_while(ConnectionError, check_download)(data, fname='testfile', host='127.0.0.1')
|
check_download(data, fname='testfile', host='127.0.0.1')
|
||||||
|
|
||||||
sock = socket.socket(socket.AF_INET6)
|
sock = socket.socket(socket.AF_INET6)
|
||||||
with pytest.raises(connrefused_exc):
|
with pytest.raises(connrefused_exc):
|
||||||
|
@ -227,7 +211,7 @@ def test_big_download(run_servefile, datadir):
|
||||||
p = datadir({'testfile': data}) / 'testfile'
|
p = datadir({'testfile': data}) / 'testfile'
|
||||||
run_servefile(str(p))
|
run_servefile(str(p))
|
||||||
|
|
||||||
_retry_while(ConnectionError, check_download)(data, fname='testfile')
|
check_download(data, fname='testfile')
|
||||||
|
|
||||||
|
|
||||||
def test_authentication(run_servefile, datadir):
|
def test_authentication(run_servefile, datadir):
|
||||||
|
@ -236,11 +220,11 @@ def test_authentication(run_servefile, datadir):
|
||||||
|
|
||||||
run_servefile([str(p), '-a', 'user:password'])
|
run_servefile([str(p), '-a', 'user:password'])
|
||||||
for auth in [('foo', 'bar'), ('user', 'wrong'), ('unknown', 'password')]:
|
for auth in [('foo', 'bar'), ('user', 'wrong'), ('unknown', 'password')]:
|
||||||
r = _retry_while(ConnectionError, make_request)(auth=auth)
|
r = make_request(auth=auth)
|
||||||
assert '401 - Unauthorized' in r.text
|
assert '401 - Unauthorized' in r.text
|
||||||
assert r.status_code == 401
|
assert r.status_code == 401
|
||||||
|
|
||||||
_retry_while(ConnectionError, check_download)(data, fname='testfile', auth=('user', 'password'))
|
check_download(data, fname='testfile', auth=('user', 'password'))
|
||||||
|
|
||||||
|
|
||||||
def test_serve_directory(run_servefile, datadir):
|
def test_serve_directory(run_servefile, datadir):
|
||||||
|
@ -257,12 +241,12 @@ def test_serve_directory(run_servefile, datadir):
|
||||||
# check if all files are in directory listing
|
# check if all files are in directory listing
|
||||||
# (could be made more sophisticated with beautifulsoup)
|
# (could be made more sophisticated with beautifulsoup)
|
||||||
for path in '/', '/../':
|
for path in '/', '/../':
|
||||||
r = _retry_while(ConnectionError, make_request)(path)
|
r = make_request(path)
|
||||||
for k in d:
|
for k in d:
|
||||||
assert quote(k) in r.text
|
assert quote(k) in r.text
|
||||||
|
|
||||||
for fname, content in d['foo'].items():
|
for fname, content in d['foo'].items():
|
||||||
_retry_while(ConnectionError, check_download)(content, '/foo/' + fname)
|
check_download(content, '/foo/' + fname)
|
||||||
|
|
||||||
r = make_request('/unknown')
|
r = make_request('/unknown')
|
||||||
assert r.status_code == 404
|
assert r.status_code == 404
|
||||||
|
@ -284,7 +268,7 @@ def test_serve_relative_directory(run_servefile, datadir):
|
||||||
# check if all files are in directory listing
|
# check if all files are in directory listing
|
||||||
# (could be made more sophisticated with beautifulsoup)
|
# (could be made more sophisticated with beautifulsoup)
|
||||||
for path in '/', '/../':
|
for path in '/', '/../':
|
||||||
r = _retry_while(ConnectionError, make_request)(path)
|
r = make_request(path)
|
||||||
for k in d:
|
for k in d:
|
||||||
assert k in r.text
|
assert k in r.text
|
||||||
|
|
||||||
|
@ -308,14 +292,14 @@ def test_upload(run_servefile, tmp_path):
|
||||||
|
|
||||||
run_servefile(['-u', str(uploaddir)])
|
run_servefile(['-u', str(uploaddir)])
|
||||||
|
|
||||||
# check upload form present
|
|
||||||
r = _retry_while(ConnectionError, make_request)()
|
|
||||||
assert r.status_code == 200
|
|
||||||
assert 'multipart/form-data' in r.text
|
|
||||||
|
|
||||||
# check that servefile created the directory
|
# check that servefile created the directory
|
||||||
assert uploaddir.is_dir()
|
assert uploaddir.is_dir()
|
||||||
|
|
||||||
|
# check upload form present
|
||||||
|
r = make_request()
|
||||||
|
assert r.status_code == 200
|
||||||
|
assert 'multipart/form-data' in r.text
|
||||||
|
|
||||||
# upload file
|
# upload file
|
||||||
files = {'file': ('haiku.txt', data)}
|
files = {'file': ('haiku.txt', data)}
|
||||||
r = make_request(method='post', files=files)
|
r = make_request(method='post', files=files)
|
||||||
|
@ -345,7 +329,7 @@ def test_upload_size_limit(run_servefile, tmp_path):
|
||||||
|
|
||||||
# upload file that is too big
|
# upload file that is too big
|
||||||
files = {'file': ('toobig', "x" * 2049)}
|
files = {'file': ('toobig', "x" * 2049)}
|
||||||
r = _retry_while(ConnectionError, make_request)(method='post', files=files)
|
r = make_request(method='post', files=files)
|
||||||
assert 'Your file was too big' in r.text
|
assert 'Your file was too big' in r.text
|
||||||
assert r.status_code == 413
|
assert r.status_code == 413
|
||||||
assert not (uploaddir / 'toobig').exists()
|
assert not (uploaddir / 'toobig').exists()
|
||||||
|
@ -357,20 +341,6 @@ def test_upload_size_limit(run_servefile, tmp_path):
|
||||||
assert r.status_code == 200
|
assert r.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
def test_upload_large_file(run_servefile, tmp_path):
|
|
||||||
# small files end up in BytesIO while large files get temporary files. this
|
|
||||||
# test makes sure we hit the large file codepath at least once
|
|
||||||
uploaddir = tmp_path / 'upload'
|
|
||||||
run_servefile(['-u', str(uploaddir)])
|
|
||||||
|
|
||||||
data = "asdf" * 1024
|
|
||||||
files = {'file': ('more_data.txt', data)}
|
|
||||||
r = _retry_while(ConnectionError, make_request)(method='post', files=files)
|
|
||||||
assert r.status_code == 200
|
|
||||||
with open(str(uploaddir / 'more_data.txt')) as f:
|
|
||||||
assert f.read() == data
|
|
||||||
|
|
||||||
|
|
||||||
def test_tar_mode(run_servefile, datadir):
|
def test_tar_mode(run_servefile, datadir):
|
||||||
d = {
|
d = {
|
||||||
'foo': {
|
'foo': {
|
||||||
|
@ -384,7 +354,7 @@ def test_tar_mode(run_servefile, datadir):
|
||||||
# test redirect?
|
# test redirect?
|
||||||
|
|
||||||
# test contents of tar file
|
# test contents of tar file
|
||||||
r = _retry_while(ConnectionError, make_request)()
|
r = make_request()
|
||||||
assert r.status_code == 200
|
assert r.status_code == 200
|
||||||
tar = tarfile.open(fileobj=io.BytesIO(r.content))
|
tar = tarfile.open(fileobj=io.BytesIO(r.content))
|
||||||
assert len(tar.getmembers()) == 3
|
assert len(tar.getmembers()) == 3
|
||||||
|
@ -400,7 +370,7 @@ def test_tar_compression(run_servefile, datadir):
|
||||||
p = datadir(d)
|
p = datadir(d)
|
||||||
run_servefile(['-c', 'gzip', '-t', str(p / 'foo')])
|
run_servefile(['-c', 'gzip', '-t', str(p / 'foo')])
|
||||||
|
|
||||||
r = _retry_while(ConnectionError, make_request)()
|
r = make_request()
|
||||||
assert r.status_code == 200
|
assert r.status_code == 200
|
||||||
tar = tarfile.open(fileobj=io.BytesIO(r.content), mode='r:gz')
|
tar = tarfile.open(fileobj=io.BytesIO(r.content), mode='r:gz')
|
||||||
assert len(tar.getmembers()) == 1
|
assert len(tar.getmembers()) == 1
|
||||||
|
@ -410,6 +380,7 @@ def test_https(run_servefile, datadir):
|
||||||
data = "NOOT NOOT"
|
data = "NOOT NOOT"
|
||||||
p = datadir({'testfile': data}) / 'testfile'
|
p = datadir({'testfile': data}) / 'testfile'
|
||||||
run_servefile(['--ssl', str(p)])
|
run_servefile(['--ssl', str(p)])
|
||||||
|
time.sleep(0.2) # time for generating ssl certificates
|
||||||
|
|
||||||
# fingerprint = None
|
# fingerprint = None
|
||||||
# while not fingerprint:
|
# while not fingerprint:
|
||||||
|
@ -424,7 +395,7 @@ def test_https(run_servefile, datadir):
|
||||||
|
|
||||||
# assert fingerprint
|
# assert fingerprint
|
||||||
urllib3.disable_warnings()
|
urllib3.disable_warnings()
|
||||||
_retry_while(ConnectionError, check_download)(data, protocol='https', verify=False)
|
check_download(data, protocol='https', verify=False)
|
||||||
|
|
||||||
|
|
||||||
def test_https_big_download(run_servefile, datadir):
|
def test_https_big_download(run_servefile, datadir):
|
||||||
|
@ -432,9 +403,10 @@ def test_https_big_download(run_servefile, datadir):
|
||||||
data = "x" * (10 * 1024 ** 2)
|
data = "x" * (10 * 1024 ** 2)
|
||||||
p = datadir({'testfile': data}) / 'testfile'
|
p = datadir({'testfile': data}) / 'testfile'
|
||||||
run_servefile(['--ssl', str(p)])
|
run_servefile(['--ssl', str(p)])
|
||||||
|
time.sleep(0.2) # time for generating ssl certificates
|
||||||
|
|
||||||
urllib3.disable_warnings()
|
urllib3.disable_warnings()
|
||||||
_retry_while(ConnectionError, check_download)(data, protocol='https', verify=False)
|
check_download(data, protocol='https', verify=False)
|
||||||
|
|
||||||
|
|
||||||
def test_abort_download(run_servefile, datadir):
|
def test_abort_download(run_servefile, datadir):
|
||||||
|
@ -447,7 +419,7 @@ def test_abort_download(run_servefile, datadir):
|
||||||
# provoke a connection abort
|
# provoke a connection abort
|
||||||
# hopefully the buffers will not fill up with all of the 10mb
|
# hopefully the buffers will not fill up with all of the 10mb
|
||||||
sock = socket.socket(socket.AF_INET)
|
sock = socket.socket(socket.AF_INET)
|
||||||
_retry_while(connrefused_exc, sock.connect)(("localhost", SERVEFILE_DEFAULT_PORT))
|
sock.connect(("localhost", SERVEFILE_DEFAULT_PORT))
|
||||||
sock.send(b"GET /testfile HTTP/1.0\n\n")
|
sock.send(b"GET /testfile HTTP/1.0\n\n")
|
||||||
resp = sock.recv(100)
|
resp = sock.recv(100)
|
||||||
assert resp != b''
|
assert resp != b''
|
||||||
|
|
14
tox.ini
14
tox.ini
|
@ -1,19 +1,9 @@
|
||||||
[tox]
|
[tox]
|
||||||
envlist = py27,py37,py38,py39,py310,py311,pep8
|
envlist = py27,py36,py37,py38,py39
|
||||||
|
|
||||||
[testenv]
|
[testenv]
|
||||||
deps =
|
deps =
|
||||||
pathlib2; python_version<"3"
|
pathlib2; python_version<"3"
|
||||||
pytest
|
pytest
|
||||||
requests
|
requests
|
||||||
flake8
|
commands = pytest --tb=short {posargs}
|
||||||
commands = pytest -v --tb=short {posargs}
|
|
||||||
|
|
||||||
[testenv:pep8]
|
|
||||||
commands = flake8 servefile/ {posargs}
|
|
||||||
|
|
||||||
[flake8]
|
|
||||||
show-source = True
|
|
||||||
max-line-length = 120
|
|
||||||
ignore = E123,E125,E241,E402,E741,W503,W504,H301
|
|
||||||
exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,build
|
|
||||||
|
|
Loading…
Reference in New Issue