From e62d7c5ea3bc014123cf6f78e5345c588cd4f068 Mon Sep 17 00:00:00 2001 From: Simeon Simeonov Date: Thu, 12 Jan 2023 22:36:04 +0100 Subject: Make the project PEP517 compatible --- Makefile | 6 +-- README.md | 2 +- ngus/__init__.py | 128 --------------------------------------------------- ngus/__main__.py | 114 --------------------------------------------- pyproject.toml | 7 +++ setup.cfg | 41 +++++++++++++++++ setup.py | 47 ++----------------- src/ngus/__init__.py | 128 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/ngus/__main__.py | 114 +++++++++++++++++++++++++++++++++++++++++++++ 9 files changed, 297 insertions(+), 290 deletions(-) delete mode 100644 ngus/__init__.py delete mode 100644 ngus/__main__.py create mode 100644 pyproject.toml create mode 100644 setup.cfg create mode 100644 src/ngus/__init__.py create mode 100644 src/ngus/__main__.py diff --git a/Makefile b/Makefile index 852f221..e9fb990 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ -package: COPYING ngus/__init__.py ngus/__main__.py LICENSE README.md setup.py - python setup.py sdist bdist_wheel +package: COPYING src/ngus/__init__.py src/ngus/__main__.py LICENSE README.md setup.py pyproject.toml setup.cfg + python -m build upload: dist/* python -m twine upload dist/* @@ -8,4 +8,4 @@ upload-test: dist/* python -m twine upload --repository testpypi dist/* clean: - rm -rf build dist ngus/__pycache__ ngus.egg-info + rm -rf dist src/ngus/__pycache__ src/ngus.egg-info diff --git a/README.md b/README.md index 67689ad..670720a 100644 --- a/README.md +++ b/README.md @@ -163,7 +163,7 @@ Simeon Simeonov - sgs @ LiberaChat ## [License](https://github.com/blackm0re/ngus/blob/master/LICENSE) -Copyright (c) 2021, Simeon Simeonov +Copyright (c) 2021-2023 Simeon Simeonov All rights reserved. [Licensed](https://github.com/blackm0re/ngus/blob/master/LICENSE) under the diff --git a/ngus/__init__.py b/ngus/__init__.py deleted file mode 100644 index 2b2be7d..0000000 --- a/ngus/__init__.py +++ /dev/null @@ -1,128 +0,0 @@ -# ngus v1.0 -# Copyright (C) 2021 Simeon Simeonov - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -import cgi -import http.server -import pathlib - -__author__ = 'Simeon Simeonov' -__version__ = '1.0' -__license__ = 'GPL3' - -DEFAULT_UPLOAD_PAGE = bytes(''' - - -File Upload - - - - -

File Upload

-
- -
-
- -
- -''', 'utf-8') - - -class NgusBaseHTTPRequestHandler(http.server.BaseHTTPRequestHandler): - """ - A custom http.server.SimpleHTTPRequestHandler that handles POST uploads - """ - def do_GET(self): - """GET requests will load the upload page""" - self._send_upload_page() - - def do_POST(self): - """POST requests will be handled according to the settings""" - if self.server.basic_auth is not None: - if ( - 'Authorization' not in self.headers - or len(self.headers['Authorization'].split()) != 2 - or self.headers['Authorization'].split()[0].lower() != 'basic' - or self.headers[ - 'Authorization' - ].split()[1] != self.server.basic_auth - ): - self.send_response(http.HTTPStatus.UNAUTHORIZED) - self.send_header('WWW-Authenticate', - 'Basic realm="ngus", charset="UTF-8"') - self.end_headers() - return None - form = cgi.FieldStorage(fp=self.rfile, - headers=self.headers, - environ={'REQUEST_METHOD': 'POST'}) - if not (upload_dir := self.server.upload_dir).is_dir(): - raise ValueError('upload_dir is not a directory') - i_name = self.server.input_name - if i_name in form and form[i_name].file and form[i_name].filename: - with open( - upload_dir / pathlib.Path(form[i_name].filename).name, - 'wb' - ) as f: - f.write(form[i_name].file.read()) - self._send_upload_page() - return None - - def _send_upload_page(self): - """Renders an upload page (form)""" - self.send_response(http.HTTPStatus.OK) - self.send_header('Content-Type', 'text/html; charset=utf-8') - self.send_header('Content-Length', len(self.server.upload_page)) - self.end_headers() - self.wfile.write(self.server.upload_page) - - -class NgusHTTPServer(http.server.HTTPServer): - """A custom http.server.HTTPServer""" - def __init__(self, *arg, **kwargs): - """ - """ - self._basic_auth = kwargs.pop('basic_auth', None) - self._input_name = kwargs.pop('input_name', 'ufile') - self._upload_dir = kwargs.pop('upload_dir', pathlib.Path.cwd()) - self._upload_page = kwargs.pop('upload_page', DEFAULT_UPLOAD_PAGE) - super().__init__(*arg, **kwargs) - - @property - def basic_auth(self): - """basic_auth-property""" - return self._basic_auth - - @property - def input_name(self): - """input_name-property""" - return self._input_name - - @property - def upload_dir(self): - """upload_dir-property""" - return self._upload_dir - - @property - def upload_page(self): - """upload_page-property""" - return self._upload_page diff --git a/ngus/__main__.py b/ngus/__main__.py deleted file mode 100644 index 3d09dbf..0000000 --- a/ngus/__main__.py +++ /dev/null @@ -1,114 +0,0 @@ -# ngus v1.0 -# Copyright (C) 2021 Simeon Simeonov - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -import argparse -import base64 -import logging -import pathlib -import sys - -import ngus - - -def eprint(*arg, **kwargs): - """stdderr print wrapper""" - print(*arg, file=sys.stderr, flush=True, **kwargs) - - -def main(inargs=None): - """main entry point""" - parser = argparse.ArgumentParser( - description='The following options are available') - parser.add_argument( - '-b', '--basic-auth', - metavar='', - type=str, - dest='basic_auth', - default='', - help=('Require Basic auth from the client in order to POST a file ' - '(default: No basic auth required)')) - parser.add_argument( - '-H', '--hostname', - metavar='', - type=str, - dest='hostname', - default='127.0.0.1', - help='ngus server IP / hostname (default: 127.0.0.1)') - parser.add_argument( - '-i', '--input-name', - metavar='', - type=str, - dest='input_name', - default='ufile', - help='The name of the form input field (default: "ufile")') - parser.add_argument( - '-p', '--port', - metavar='', - type=int, - dest='port', - default=8080, - help='ngus server port (default: 8080)') - parser.add_argument( - '-u', '--upload-dir', - metavar='', - type=pathlib.Path, - dest='upload_dir', - default=pathlib.Path.cwd(), - help='ngus server upload dir (default: CWD)') - parser.add_argument( - '-U', '--upload-page', - metavar='', - type=argparse.FileType('rb'), - dest='upload_page', - default=None, - help='Alternative upload page (form) to display (default: None)') - parser.add_argument( - '-v', '--version', - action='version', - version=f'%(prog)s {ngus.__version__}', - help='Display program-version and exit') - args = parser.parse_args(inargs) - try: - logging.basicConfig(format='%(asctime)s - %(levelname)s - %(message)s', - level=logging.DEBUG) - logger = logging.getLogger('ngus') - logger.info('ngus starting') - server_params = { - 'input_name': args.input_name, - 'upload_dir': args.upload_dir - } - if args.upload_page is not None: - server_params['upload_page'] = args.upload_page.read() - if args.basic_auth and len(args.basic_auth.split(':')) == 2: - server_params['basic_auth'] = base64.b64encode( - args.basic_auth.strip().encode('utf-8')).decode() - logger.info('Requiring basic-auth') - ngus_server = ngus.NgusHTTPServer((args.hostname, args.port), - ngus.NgusBaseHTTPRequestHandler, - **server_params) - logger.info(f'Listening on {args.hostname}:{args.port}') - logger.info('Done!') - ngus_server.serve_forever() - except KeyboardInterrupt: - print('\n\nTerminating...') - except Exception as e: - eprint(f'ngus critical error: {e}') - sys.exit(1) - sys.exit(0) - - -if __name__ == '__main__': - main() diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..73c35b2 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,7 @@ +[build-system] +requires = [ + "setuptools>=51", + "wheel" +] + +build-backend = "setuptools.build_meta" diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..795c715 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,41 @@ +[metadata] +name = ngus +version = attr: ngus.__version__ +author = attr: ngus.__author__ +author_email = sgs@pichove.org +description = A simple HTTP server for handleing file uploads +long_description = file: README.md +long_description_content_type = text/markdown +url = https://github.com/blackm0re/ngus + +classifiers = + Development Status :: 5 - Production/Stable + Intended Audience :: End Users/Desktop + License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+) + 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 + Programming Language :: Python :: 3.12 + Programming Language :: Python :: Implementation + Operating System :: OS Independent + Topic :: Internet :: WWW/HTTP :: HTTP Servers + +project_urls = + Bug Tracker = https://github.com/blackm0re/ngus/issues + Source = https://github.com/blackm0re/ngus + +[options] +package_dir = + = src +packages = find: +python_requires = >=3.7 + +[options.packages.find] +where = src + +[options.entry_points] +console_scripts = + ngus = ngus.__main__:main diff --git a/setup.py b/setup.py index fbdefcb..a5d21d8 100644 --- a/setup.py +++ b/setup.py @@ -1,46 +1,5 @@ -import setuptools +# Legacy setup for some build / install systems -import ngus +from setuptools import setup -with open('README.md', 'r') as fh: - long_description = fh.read() - - -setuptools.setup( - name='ngus', - version=ngus.__version__, - author=ngus.__author__, - author_email='sgs@pichove.org', - description='A simple HTTP server for handleing file uploads', - long_description=long_description, - long_description_content_type='text/markdown', - url='https://github.com/blackm0re/ngus', - packages=setuptools.find_packages(), - exclude_package_data={'': ['.gitignore']}, - entry_points={ - 'console_scripts': [ - 'ngus=ngus.__main__:main', - ], - }, - classifiers=[ - 'Development Status :: 5 - Production/Stable', - 'Intended Audience :: End Users/Desktop', - ('License :: OSI Approved :: GNU General Public License v3 or later ' - '(GPLv3+)'), - 'Programming Language :: Python :: 3 :: Only', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: 3.7', - 'Programming Language :: Python :: 3.8', - 'Programming Language :: Python :: 3.9', - 'Programming Language :: Python :: 3.10', - 'Programming Language :: Python :: Implementation', - 'Operating System :: OS Independent', - 'Topic :: Internet :: WWW/HTTP :: HTTP Servers' - ], - keywords='upload http httpd', - project_urls={ - 'Bug Reports': 'https://github.com/blackm0re/ngus/issues', - 'Source': 'https://github.com/blackm0re/ngus' - }, - python_requires='>=3.6', -) +setup() diff --git a/src/ngus/__init__.py b/src/ngus/__init__.py new file mode 100644 index 0000000..d085fc1 --- /dev/null +++ b/src/ngus/__init__.py @@ -0,0 +1,128 @@ +# ngus +# Copyright (C) 2021-2023 Simeon Simeonov + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import cgi +import http.server +import pathlib + +__author__ = 'Simeon Simeonov' +__version__ = '2.0' +__license__ = 'GPL3' + +DEFAULT_UPLOAD_PAGE = bytes(''' + + +File Upload + + + + +

File Upload

+
+ +
+
+ +
+ +''', 'utf-8') + + +class NgusBaseHTTPRequestHandler(http.server.BaseHTTPRequestHandler): + """ + A custom http.server.SimpleHTTPRequestHandler that handles POST uploads + """ + def do_GET(self): + """GET requests will load the upload page""" + self._send_upload_page() + + def do_POST(self): + """POST requests will be handled according to the settings""" + if self.server.basic_auth is not None: + if ( + 'Authorization' not in self.headers + or len(self.headers['Authorization'].split()) != 2 + or self.headers['Authorization'].split()[0].lower() != 'basic' + or self.headers[ + 'Authorization' + ].split()[1] != self.server.basic_auth + ): + self.send_response(http.HTTPStatus.UNAUTHORIZED) + self.send_header('WWW-Authenticate', + 'Basic realm="ngus", charset="UTF-8"') + self.end_headers() + return None + form = cgi.FieldStorage(fp=self.rfile, + headers=self.headers, + environ={'REQUEST_METHOD': 'POST'}) + if not (upload_dir := self.server.upload_dir).is_dir(): + raise ValueError('upload_dir is not a directory') + i_name = self.server.input_name + if i_name in form and form[i_name].file and form[i_name].filename: + with open( + upload_dir / pathlib.Path(form[i_name].filename).name, + 'wb' + ) as f: + f.write(form[i_name].file.read()) + self._send_upload_page() + return None + + def _send_upload_page(self): + """Renders an upload page (form)""" + self.send_response(http.HTTPStatus.OK) + self.send_header('Content-Type', 'text/html; charset=utf-8') + self.send_header('Content-Length', len(self.server.upload_page)) + self.end_headers() + self.wfile.write(self.server.upload_page) + + +class NgusHTTPServer(http.server.HTTPServer): + """A custom http.server.HTTPServer""" + def __init__(self, *arg, **kwargs): + """ + """ + self._basic_auth = kwargs.pop('basic_auth', None) + self._input_name = kwargs.pop('input_name', 'ufile') + self._upload_dir = kwargs.pop('upload_dir', pathlib.Path.cwd()) + self._upload_page = kwargs.pop('upload_page', DEFAULT_UPLOAD_PAGE) + super().__init__(*arg, **kwargs) + + @property + def basic_auth(self): + """basic_auth-property""" + return self._basic_auth + + @property + def input_name(self): + """input_name-property""" + return self._input_name + + @property + def upload_dir(self): + """upload_dir-property""" + return self._upload_dir + + @property + def upload_page(self): + """upload_page-property""" + return self._upload_page diff --git a/src/ngus/__main__.py b/src/ngus/__main__.py new file mode 100644 index 0000000..efa2c4a --- /dev/null +++ b/src/ngus/__main__.py @@ -0,0 +1,114 @@ +# ngus +# Copyright (C) 2021-2023 Simeon Simeonov + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import argparse +import base64 +import logging +import pathlib +import sys + +import ngus + + +def eprint(*arg, **kwargs): + """stdderr print wrapper""" + print(*arg, file=sys.stderr, flush=True, **kwargs) + + +def main(inargs=None): + """main entry point""" + parser = argparse.ArgumentParser( + description='The following options are available') + parser.add_argument( + '-b', '--basic-auth', + metavar='', + type=str, + dest='basic_auth', + default='', + help=('Require Basic auth from the client in order to POST a file ' + '(default: No basic auth required)')) + parser.add_argument( + '-H', '--hostname', + metavar='', + type=str, + dest='hostname', + default='127.0.0.1', + help='ngus server IP / hostname (default: 127.0.0.1)') + parser.add_argument( + '-i', '--input-name', + metavar='', + type=str, + dest='input_name', + default='ufile', + help='The name of the form input field (default: "ufile")') + parser.add_argument( + '-p', '--port', + metavar='', + type=int, + dest='port', + default=8080, + help='ngus server port (default: 8080)') + parser.add_argument( + '-u', '--upload-dir', + metavar='', + type=pathlib.Path, + dest='upload_dir', + default=pathlib.Path.cwd(), + help='ngus server upload dir (default: CWD)') + parser.add_argument( + '-U', '--upload-page', + metavar='', + type=argparse.FileType('rb'), + dest='upload_page', + default=None, + help='Alternative upload page (form) to display (default: None)') + parser.add_argument( + '-v', '--version', + action='version', + version=f'%(prog)s {ngus.__version__}', + help='Display program-version and exit') + args = parser.parse_args(inargs) + try: + logging.basicConfig(format='%(asctime)s - %(levelname)s - %(message)s', + level=logging.DEBUG) + logger = logging.getLogger('ngus') + logger.info('ngus starting') + server_params = { + 'input_name': args.input_name, + 'upload_dir': args.upload_dir + } + if args.upload_page is not None: + server_params['upload_page'] = args.upload_page.read() + if args.basic_auth and len(args.basic_auth.split(':')) == 2: + server_params['basic_auth'] = base64.b64encode( + args.basic_auth.strip().encode('utf-8')).decode() + logger.info('Requiring basic-auth') + ngus_server = ngus.NgusHTTPServer((args.hostname, args.port), + ngus.NgusBaseHTTPRequestHandler, + **server_params) + logger.info(f'Listening on {args.hostname}:{args.port}') + logger.info('Done!') + ngus_server.serve_forever() + except KeyboardInterrupt: + print('\n\nTerminating...') + except Exception as e: + eprint(f'ngus critical error: {e}') + sys.exit(1) + sys.exit(0) + + +if __name__ == '__main__': + main() -- cgit v1.3