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
---
src/ngus/__init__.py | 128 +++++++++++++++++++++++++++++++++++++++++++++++++++
src/ngus/__main__.py | 114 +++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 242 insertions(+)
create mode 100644 src/ngus/__init__.py
create mode 100644 src/ngus/__main__.py
(limited to 'src')
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