forked from seba/servefile
Compare commits
40 Commits
tox_pytest
...
master
Author | SHA1 | Date |
---|---|---|
Sebastian Lohff | f668fc3fe6 | |
Sebastian Lohff | 9784c82679 | |
Sebastian Lohff | f23dfd2a51 | |
Sebastian Lohff | b1145af6bb | |
MasterofJOKers | 0b010d5c10 | |
Sebastian Lohff | 4f3b916b9f | |
Sebastian Lohff | 5dcf364e0f | |
Sebastian Lohff | aa54e8536a | |
Sebastian Lohff | 96e9e76ff4 | |
Sebastian Lohff | c7af20388d | |
Sebastian Pipping | 413ea76746 | |
Sebastian Pipping | 8b16b7626c | |
Sebastian Pipping | 8f9ba0e387 | |
Sebastian Lohff | cd28811fcf | |
Sebastian Lohff | 46d4433a1d | |
Sebastian Lohff | d87a42cf8e | |
Paweł Chojnacki | 6537c054e5 | |
Sebastian Lohff | 65fcac5c49 | |
Sebastian Lohff | 0334e74996 | |
Sebastian Lohff | 8217034753 | |
Sebastian Lohff | 9fa4ed0026 | |
Sebastian Lohff | 1f451e0f29 | |
Sebastian Lohff | e31c8fb016 | |
Sebastian Lohff | 058de2f39c | |
Sebastian Lohff | 11a7d8bd13 | |
Sebastian Lohff | f2594c2adf | |
Sebastian Lohff | 95852ba11d | |
Sebastian Lohff | 14771695c4 | |
Sebastian Lohff | 5c78991bc8 | |
Sebastian Lohff | ef41f65996 | |
Sebastian Lohff | 19c1b000a4 | |
Sebastian Lohff | 3d46950d6c | |
Sebastian Pipping | 864b2161b1 | |
Sebastian Pipping | 8fe46c42a7 | |
Sebastian Pipping | 0819d23f47 | |
Sebastian Pipping | a7d273f13f | |
Sebastian Lohff | dce8c995f6 | |
Sebastian Lohff | 2b138446d4 | |
Sebastian Lohff | ccd01e8b6e | |
MasterofJOKers | 907013522c |
|
@ -0,0 +1,37 @@
|
|||
name: Run Tox
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
python: [2.7, 3.7, 3.8, 3.9, "3.10", 3.11]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: ${{ matrix.python }}
|
||||
- name: Install Tox
|
||||
run: pip install tox
|
||||
- name: Run Tox
|
||||
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"
|
57
ChangeLog
57
ChangeLog
|
@ -1,6 +1,63 @@
|
|||
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
|
||||
-----------------
|
||||
|
||||
0.5.2 released
|
||||
|
||||
* fixed bug where exception was shown on transmission abort with python3
|
||||
* fixed wrong/outdated pyopenssl package names
|
||||
* tests are now using a free non-default port to avoid clashes; if
|
||||
wished the ports can be set from outside by specifying the
|
||||
environment variables SERVEFILE_DEFAULT_PORT and
|
||||
SERVEFILE_SECONDARY_PORT
|
||||
* fixed broken redirect when filename contained umlauts or other characters
|
||||
that should have been quoted
|
||||
* fixed broken special char handling in directory listing for python2
|
||||
* drop python3.5 support
|
||||
* fixed PUT uploads with python3 and documented PUT-uploads with curl
|
||||
|
||||
|
||||
2020-10-30 v0.5.1
|
||||
-----------------
|
||||
|
||||
0.5.1 released
|
||||
|
||||
* version bump for broken pypi release
|
||||
|
||||
|
||||
2020-10-29 v0.5.0
|
||||
-----------------
|
||||
|
||||
0.5.0 released
|
||||
|
||||
* python3 support
|
||||
* test suite
|
||||
* fixed an endless redirect loop when serving ../
|
||||
* added sorting for list view
|
||||
* added lzma/xz as compression method
|
||||
|
||||
|
||||
2015-11-10 v0.4.4
|
||||
-----------------
|
||||
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
Servefile
|
||||
=========
|
||||
|
||||
Serve files from shell via a small HTTP server. The server redirects all HTTP
|
||||
requests to the file, so only IP and port must be given to another user to
|
||||
access the file. Its main purpose is to quickly send a file to users in your
|
||||
local network, independent of their current setup (OS/software). Besides that
|
||||
it also supports uploads, SSL, HTTP basic auth and directory listings.
|
||||
|
||||
Features:
|
||||
* serve single file
|
||||
* serve a directory with directory index
|
||||
* file upload via webinterface
|
||||
* HTTPS with on the fly generated self signed SSL certificates
|
||||
* HTTP basic authentication
|
||||
* serving files/directories as on request generated tar files
|
||||
|
||||
Install
|
||||
-------
|
||||
|
||||
Via pip
|
||||
```shell
|
||||
pip install servefile
|
||||
```
|
||||
After installation either execute `servefile --help` or `python -m servefile --help`
|
||||
|
||||
Standalone:
|
||||
If you don't have pip available just copy `servefile/servefile.py` onto the target machine, make it executable and you are ready to go.
|
||||
```shell
|
||||
$ wget https://raw.githubusercontent.com/sebageek/servefile/master/servefile/servefile.py -O servefile
|
||||
$ chmod +x servefile
|
||||
$ ./servefile --help
|
||||
```
|
|
@ -1,4 +1,4 @@
|
|||
.TH SERVEFILE 1 "November 2015" "servefile 0.4.4" "User Commands"
|
||||
.TH SERVEFILE 1 "January 2023" "servefile 0.5.4" "User Commands"
|
||||
|
||||
.SH NAME
|
||||
servefile \- small HTTP-Server for temporary file transfer
|
||||
|
@ -28,8 +28,8 @@ In upload mode with \fB\-u\fR servefile creates a directory and saves all
|
|||
uploaded files into that directory. When uploading with curl or wget the
|
||||
filename is extracted from the path part of the url used for the upload.
|
||||
|
||||
For SSL support python-openssl (pyssl) needs to be installed. If no key and
|
||||
cert is given, servefile will generate a key pair for you and display its
|
||||
For SSL support pyopenssl (python3-openssl) needs to be installed. If no key
|
||||
and cert is given, servefile will generate a key pair for you and display its
|
||||
fingerprint.
|
||||
|
||||
In \fB--tar\fR mode the given file or directory will be packed on (each)
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
from . import servefile
|
||||
|
||||
servefile.main()
|
File diff suppressed because it is too large
Load Diff
63
setup.py
63
setup.py
|
@ -1,18 +1,53 @@
|
|||
#!/usr/bin/python
|
||||
#!/usr/bin/env python
|
||||
|
||||
from distutils.core import setup
|
||||
from setuptools import setup
|
||||
|
||||
with open("README.md") as f:
|
||||
long_description = f.read()
|
||||
|
||||
setup(
|
||||
name='servefile',
|
||||
description='Serve files from shell via a small HTTP server',
|
||||
long_description='Serve files from shell via a small HTTP server. The server redirects all HTTP requests to the file, so only IP and port must be given to another user to access the file. Its main purpose is to quickly send a file to users in your local network, independent of their current setup (OS/software). Beneath that it also supports uploads, SSL, HTTP basic auth and directory listings.',
|
||||
platforms='posix',
|
||||
version='0.4.4',
|
||||
license='GPLv3 or later',
|
||||
url='http://seba-geek.de/stuff/servefile/',
|
||||
author='Sebastian Lohff',
|
||||
author_email='seba@someserver.de',
|
||||
install_requires=['pyopenssl'],
|
||||
scripts=['servefile'],
|
||||
name='servefile',
|
||||
description='Serve files from shell via a small HTTP server',
|
||||
long_description=long_description,
|
||||
long_description_content_type='text/markdown',
|
||||
platforms='posix',
|
||||
version='0.5.4',
|
||||
license='GPLv3 or later',
|
||||
url='https://github.com/sebageek/servefile/',
|
||||
author='Sebastian Lohff',
|
||||
author_email='seba@someserver.de',
|
||||
install_requires=['pyopenssl'],
|
||||
tests_require=[
|
||||
'pathlib2; python_version<"3"',
|
||||
'pytest',
|
||||
'requests',
|
||||
],
|
||||
packages=["servefile"],
|
||||
entry_points={
|
||||
"console_scripts": [
|
||||
"servefile = servefile.servefile:main",
|
||||
],
|
||||
},
|
||||
classifiers=[
|
||||
'Development Status :: 5 - Production/Stable',
|
||||
'Environment :: Console',
|
||||
'Intended Audience :: End Users/Desktop',
|
||||
'License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)',
|
||||
'Natural Language :: English',
|
||||
'Programming Language :: Python',
|
||||
'Programming Language :: Python :: 2',
|
||||
'Programming Language :: Python :: 2.7',
|
||||
'Programming Language :: Python :: 3',
|
||||
'Programming Language :: Python :: 3.7',
|
||||
'Programming Language :: Python :: 3.8',
|
||||
'Programming Language :: Python :: 3.9',
|
||||
'Programming Language :: Python :: 3.10',
|
||||
'Programming Language :: Python :: 3.11',
|
||||
'Topic :: Communications',
|
||||
'Topic :: Communications :: File Sharing',
|
||||
'Topic :: Internet',
|
||||
'Topic :: Internet :: WWW/HTTP',
|
||||
'Topic :: Internet :: WWW/HTTP :: HTTP Servers',
|
||||
'Topic :: Utilities',
|
||||
],
|
||||
)
|
||||
|
||||
|
|
|
@ -1,15 +1,44 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import io
|
||||
import os
|
||||
import pytest
|
||||
import requests
|
||||
import socket
|
||||
import subprocess
|
||||
import sys
|
||||
import tarfile
|
||||
import time
|
||||
import urllib3
|
||||
from requests.exceptions import ConnectionError
|
||||
|
||||
# crudly written to learn more about pytest and to have a base for refactoring
|
||||
|
||||
|
||||
if sys.version_info.major >= 3:
|
||||
from pathlib import Path
|
||||
from urllib.parse import quote
|
||||
connrefused_exc = ConnectionRefusedError
|
||||
else:
|
||||
from pathlib2 import Path
|
||||
from urllib import quote
|
||||
connrefused_exc = socket.error
|
||||
|
||||
|
||||
def _get_port_from_env(var_name, default):
|
||||
port = int(os.environ.get(var_name, default))
|
||||
if port == 0:
|
||||
# do a one-time port selection for a free port, use it for all tests
|
||||
s = socket.socket()
|
||||
s.bind(('', 0))
|
||||
port = s.getsockname()[1]
|
||||
s.close()
|
||||
return port
|
||||
|
||||
|
||||
SERVEFILE_DEFAULT_PORT = _get_port_from_env('SERVEFILE_DEFAULT_PORT', 0)
|
||||
SERVEFILE_SECONDARY_PORT = _get_port_from_env('SERVEFILE_SECONDARY_PORT', 0)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def run_servefile():
|
||||
instances = []
|
||||
|
@ -17,9 +46,19 @@ def run_servefile():
|
|||
def _run_servefile(args, **kwargs):
|
||||
if not isinstance(args, list):
|
||||
args = [args]
|
||||
print("running with args", args)
|
||||
p = subprocess.Popen(['servefile'] + args, **kwargs)
|
||||
time.sleep(kwargs.get('timeout', 0.3))
|
||||
if kwargs.pop('standalone', None):
|
||||
# directly call servefile.py
|
||||
servefile_path = [str(Path(__file__).parent.parent / 'servefile' / 'servefile.py')]
|
||||
else:
|
||||
# call servefile as python module
|
||||
servefile_path = ['-m', 'servefile']
|
||||
|
||||
# use non-default default port, if one is given via env (and none via args)
|
||||
if '-p' not in args and '--port' not in args:
|
||||
args.extend(['-p', str(SERVEFILE_DEFAULT_PORT)])
|
||||
|
||||
print("running {} with args {}".format(", ".join(servefile_path), args))
|
||||
p = subprocess.Popen([sys.executable] + servefile_path + args, **kwargs)
|
||||
instances.append(p)
|
||||
|
||||
return p
|
||||
|
@ -45,22 +84,26 @@ def datadir(tmp_path):
|
|||
_datadir(v, new_path)
|
||||
else:
|
||||
if hasattr(v, 'decode'):
|
||||
v = v.decode() # python2 compability
|
||||
v = v.decode('utf-8') # python2 compability
|
||||
(path / k).write_text(v)
|
||||
|
||||
return path
|
||||
return _datadir
|
||||
|
||||
|
||||
def make_request(path='/', host='localhost', port=8080, method='get', protocol='http', **kwargs):
|
||||
def make_request(path='/', host='localhost', port=SERVEFILE_DEFAULT_PORT, method='get', protocol='http',
|
||||
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
|
||||
|
||||
|
||||
def check_download(expected_data=None, path='/', fname=None, status_code=200, **kwargs):
|
||||
def check_download(expected_data=None, path='/', fname=None, **kwargs):
|
||||
if fname is None:
|
||||
fname = os.path.basename(path)
|
||||
r = make_request(path, **kwargs)
|
||||
|
@ -74,14 +117,45 @@ def check_download(expected_data=None, path='/', fname=None, status_code=200, **
|
|||
return r # for additional tests
|
||||
|
||||
|
||||
def test_version(run_servefile):
|
||||
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):
|
||||
# we expect the version on stdout (python3.4+) or stderr(python2.6-3.3)
|
||||
s = run_servefile('--version', stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
||||
s = run_servefile('--version', standalone=standalone, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
||||
s.wait()
|
||||
version = s.stdout.readline().decode().strip()
|
||||
|
||||
# python2 is deprecated, but we still want our tests to run for it
|
||||
# CryptographyDeprecationWarnings get in the way for this
|
||||
if 'CryptographyDeprecationWarning' in version:
|
||||
s.stdout.readline() # ignore "from x import y" line
|
||||
version = s.stdout.readline().decode().strip()
|
||||
|
||||
# hardcode version as string until servefile is a module
|
||||
assert version == 'servefile 0.4.4'
|
||||
assert version == 'servefile 0.5.4'
|
||||
|
||||
|
||||
def test_version(run_servefile):
|
||||
_test_version(run_servefile, standalone=False)
|
||||
|
||||
|
||||
def test_version_standalone(run_servefile):
|
||||
# test if servefile also works by calling servefile.py directly
|
||||
_test_version(run_servefile, standalone=True)
|
||||
|
||||
|
||||
def test_correct_headers(run_servefile, datadir):
|
||||
|
@ -89,7 +163,7 @@ def test_correct_headers(run_servefile, datadir):
|
|||
p = datadir({'testfile': data}) / 'testfile'
|
||||
run_servefile(str(p))
|
||||
|
||||
r = make_request()
|
||||
r = _retry_while(ConnectionError, make_request)()
|
||||
assert r.status_code == 200
|
||||
assert r.headers.get('Content-Type') == 'application/octet-stream'
|
||||
assert r.headers.get('Content-Disposition') == 'attachment; filename="testfile"'
|
||||
|
@ -102,7 +176,7 @@ def test_redirect_and_download(run_servefile, datadir):
|
|||
run_servefile(str(p))
|
||||
|
||||
# redirect
|
||||
r = make_request(allow_redirects=False)
|
||||
r = _retry_while(ConnectionError, make_request)(allow_redirects=False)
|
||||
assert r.status_code == 302
|
||||
assert r.headers.get('Location') == '/testfile'
|
||||
|
||||
|
@ -110,12 +184,41 @@ def test_redirect_and_download(run_servefile, datadir):
|
|||
check_download(data, fname='testfile')
|
||||
|
||||
|
||||
def test_redirect_and_download_with_umlaut(run_servefile, datadir):
|
||||
data = "NÖÖT NÖÖT"
|
||||
filename = "tästføile"
|
||||
p = datadir({filename: data}) / filename
|
||||
run_servefile(str(p))
|
||||
|
||||
# redirect
|
||||
r = _retry_while(ConnectionError, make_request)(allow_redirects=False)
|
||||
assert r.status_code == 302
|
||||
assert r.headers.get('Location') == '/{}'.format(quote(filename))
|
||||
|
||||
# normal download
|
||||
if sys.version_info.major < 3:
|
||||
data = unicode(data, 'utf-8')
|
||||
check_download(data, fname=filename)
|
||||
|
||||
|
||||
def test_specify_port(run_servefile, datadir):
|
||||
data = "NOOT NOOT"
|
||||
p = datadir({'testfile': data}) / 'testfile'
|
||||
run_servefile([str(p), '-p', '8081'])
|
||||
run_servefile([str(p), '-p', str(SERVEFILE_SECONDARY_PORT)])
|
||||
|
||||
check_download(data, fname='testfile', port=8081)
|
||||
_retry_while(ConnectionError, check_download)(data, fname='testfile', port=SERVEFILE_SECONDARY_PORT)
|
||||
|
||||
|
||||
def test_ipv4_only(run_servefile, datadir):
|
||||
data = "NOOT NOOT"
|
||||
p = datadir({'testfile': data}) / 'testfile'
|
||||
run_servefile([str(p), '-4'])
|
||||
|
||||
_retry_while(ConnectionError, check_download)(data, fname='testfile', host='127.0.0.1')
|
||||
|
||||
sock = socket.socket(socket.AF_INET6)
|
||||
with pytest.raises(connrefused_exc):
|
||||
sock.connect(("::1", SERVEFILE_DEFAULT_PORT))
|
||||
|
||||
|
||||
def test_big_download(run_servefile, datadir):
|
||||
|
@ -124,7 +227,7 @@ def test_big_download(run_servefile, datadir):
|
|||
p = datadir({'testfile': data}) / 'testfile'
|
||||
run_servefile(str(p))
|
||||
|
||||
check_download(data, fname='testfile')
|
||||
_retry_while(ConnectionError, check_download)(data, fname='testfile')
|
||||
|
||||
|
||||
def test_authentication(run_servefile, datadir):
|
||||
|
@ -133,11 +236,11 @@ def test_authentication(run_servefile, datadir):
|
|||
|
||||
run_servefile([str(p), '-a', 'user:password'])
|
||||
for auth in [('foo', 'bar'), ('user', 'wrong'), ('unknown', 'password')]:
|
||||
r = make_request(auth=auth)
|
||||
r = _retry_while(ConnectionError, make_request)(auth=auth)
|
||||
assert '401 - Unauthorized' in r.text
|
||||
assert r.status_code == 401
|
||||
|
||||
check_download(data, fname='testfile', auth=('user', 'password'))
|
||||
_retry_while(ConnectionError, check_download)(data, fname='testfile', auth=('user', 'password'))
|
||||
|
||||
|
||||
def test_serve_directory(run_servefile, datadir):
|
||||
|
@ -146,6 +249,7 @@ def test_serve_directory(run_servefile, datadir):
|
|||
'bar': {'thisisaverylongfilenamefortestingthatthisstillworksproperly': 'jup!'},
|
||||
'noot': 'still data in here',
|
||||
'bigfile': 'x' * (10 * 1024 ** 2),
|
||||
'möwe': 'KRAKRAKRAKA',
|
||||
}
|
||||
p = datadir(d)
|
||||
run_servefile([str(p), '-l'])
|
||||
|
@ -153,7 +257,34 @@ def test_serve_directory(run_servefile, datadir):
|
|||
# check if all files are in directory listing
|
||||
# (could be made more sophisticated with beautifulsoup)
|
||||
for path in '/', '/../':
|
||||
r = make_request(path)
|
||||
r = _retry_while(ConnectionError, make_request)(path)
|
||||
for k in d:
|
||||
assert quote(k) in r.text
|
||||
|
||||
for fname, content in d['foo'].items():
|
||||
_retry_while(ConnectionError, check_download)(content, '/foo/' + fname)
|
||||
|
||||
r = make_request('/unknown')
|
||||
assert r.status_code == 404
|
||||
|
||||
# download
|
||||
check_download('jup!', '/bar/thisisaverylongfilenamefortestingthatthisstillworksproperly')
|
||||
|
||||
|
||||
def test_serve_relative_directory(run_servefile, datadir):
|
||||
d = {
|
||||
'foo': {'kratzbaum': 'cat', 'I like Cats!': 'kitteh', '&&&&&&&': 'wheee'},
|
||||
'bar': {'thisisaverylongfilenamefortestingthatthisstillworksproperly': 'jup!'},
|
||||
'noot': 'still data in here',
|
||||
'bigfile': 'x' * (10 * 1024 ** 2),
|
||||
}
|
||||
p = datadir(d)
|
||||
run_servefile(['../', '-l'], cwd=os.path.join(str(p), 'foo'))
|
||||
|
||||
# check if all files are in directory listing
|
||||
# (could be made more sophisticated with beautifulsoup)
|
||||
for path in '/', '/../':
|
||||
r = _retry_while(ConnectionError, make_request)(path)
|
||||
for k in d:
|
||||
assert k in r.text
|
||||
|
||||
|
@ -177,14 +308,14 @@ def test_upload(run_servefile, tmp_path):
|
|||
|
||||
run_servefile(['-u', str(uploaddir)])
|
||||
|
||||
# check that servefile created the directory
|
||||
assert uploaddir.is_dir()
|
||||
|
||||
# check upload form present
|
||||
r = make_request()
|
||||
r = _retry_while(ConnectionError, make_request)()
|
||||
assert r.status_code == 200
|
||||
assert 'multipart/form-data' in r.text
|
||||
|
||||
# check that servefile created the directory
|
||||
assert uploaddir.is_dir()
|
||||
|
||||
# upload file
|
||||
files = {'file': ('haiku.txt', data)}
|
||||
r = make_request(method='post', files=files)
|
||||
|
@ -200,6 +331,13 @@ def test_upload(run_servefile, tmp_path):
|
|||
with open(str(uploaddir / 'haiku.txt(1)')) as f:
|
||||
assert f.read() == data
|
||||
|
||||
# upload file using PUT
|
||||
r = make_request("/haiku.txt", method='put', data=data)
|
||||
assert r.status_code == 201
|
||||
assert 'OK!' in r.text
|
||||
with open(str(uploaddir / 'haiku.txt(2)')) as f:
|
||||
assert f.read() == data
|
||||
|
||||
|
||||
def test_upload_size_limit(run_servefile, tmp_path):
|
||||
uploaddir = tmp_path / 'upload'
|
||||
|
@ -207,7 +345,7 @@ def test_upload_size_limit(run_servefile, tmp_path):
|
|||
|
||||
# upload file that is too big
|
||||
files = {'file': ('toobig', "x" * 2049)}
|
||||
r = make_request(method='post', files=files)
|
||||
r = _retry_while(ConnectionError, make_request)(method='post', files=files)
|
||||
assert 'Your file was too big' in r.text
|
||||
assert r.status_code == 413
|
||||
assert not (uploaddir / 'toobig').exists()
|
||||
|
@ -219,6 +357,20 @@ def test_upload_size_limit(run_servefile, tmp_path):
|
|||
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):
|
||||
d = {
|
||||
'foo': {
|
||||
|
@ -232,7 +384,7 @@ def test_tar_mode(run_servefile, datadir):
|
|||
# test redirect?
|
||||
|
||||
# test contents of tar file
|
||||
r = make_request()
|
||||
r = _retry_while(ConnectionError, make_request)()
|
||||
assert r.status_code == 200
|
||||
tar = tarfile.open(fileobj=io.BytesIO(r.content))
|
||||
assert len(tar.getmembers()) == 3
|
||||
|
@ -248,7 +400,7 @@ def test_tar_compression(run_servefile, datadir):
|
|||
p = datadir(d)
|
||||
run_servefile(['-c', 'gzip', '-t', str(p / 'foo')])
|
||||
|
||||
r = make_request()
|
||||
r = _retry_while(ConnectionError, make_request)()
|
||||
assert r.status_code == 200
|
||||
tar = tarfile.open(fileobj=io.BytesIO(r.content), mode='r:gz')
|
||||
assert len(tar.getmembers()) == 1
|
||||
|
@ -258,7 +410,6 @@ def test_https(run_servefile, datadir):
|
|||
data = "NOOT NOOT"
|
||||
p = datadir({'testfile': data}) / 'testfile'
|
||||
run_servefile(['--ssl', str(p)])
|
||||
time.sleep(0.2) # time for generating ssl certificates
|
||||
|
||||
# fingerprint = None
|
||||
# while not fingerprint:
|
||||
|
@ -273,14 +424,35 @@ def test_https(run_servefile, datadir):
|
|||
|
||||
# assert fingerprint
|
||||
urllib3.disable_warnings()
|
||||
check_download(data, protocol='https', verify=False)
|
||||
_retry_while(ConnectionError, check_download)(data, protocol='https', verify=False)
|
||||
|
||||
|
||||
def test_https_big_download(run_servefile, datadir):
|
||||
# test with about 10 mb of data
|
||||
data = "x" * (10 * 1024 ** 2)
|
||||
p = datadir({'testfile': data}) / 'testfile'
|
||||
run_servefile(['--ssl', str(p)])
|
||||
time.sleep(0.2) # time for generating ssl certificates
|
||||
|
||||
urllib3.disable_warnings()
|
||||
check_download(data, protocol='https', verify=False)
|
||||
_retry_while(ConnectionError, check_download)(data, protocol='https', verify=False)
|
||||
|
||||
|
||||
def test_abort_download(run_servefile, datadir):
|
||||
data = "x" * (10 * 1024 ** 2)
|
||||
p = datadir({'testfile': data}) / 'testfile'
|
||||
env = os.environ.copy()
|
||||
env['PYTHONUNBUFFERED'] = '1'
|
||||
proc = run_servefile(str(p), stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=env)
|
||||
|
||||
# provoke a connection abort
|
||||
# hopefully the buffers will not fill up with all of the 10mb
|
||||
sock = socket.socket(socket.AF_INET)
|
||||
_retry_while(connrefused_exc, sock.connect)(("localhost", SERVEFILE_DEFAULT_PORT))
|
||||
sock.send(b"GET /testfile HTTP/1.0\n\n")
|
||||
resp = sock.recv(100)
|
||||
assert resp != b''
|
||||
sock.close()
|
||||
time.sleep(0.1)
|
||||
proc.kill()
|
||||
out = proc.stdout.read().decode()
|
||||
assert "127.0.0.1 ABORTED transmission" in out
|
||||
|
|
15
tox.ini
15
tox.ini
|
@ -1,8 +1,19 @@
|
|||
[tox]
|
||||
envlist = py27,py36
|
||||
envlist = py27,py37,py38,py39,py310,py311,pep8
|
||||
|
||||
[testenv]
|
||||
deps =
|
||||
pathlib2; python_version<"3"
|
||||
pytest
|
||||
requests
|
||||
commands = pytest --tb=short {posargs}
|
||||
flake8
|
||||
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