Mercurial > p > roundup > code
comparison roundup/scripts/roundup_server.py @ 6972:8e4028669d2a
flake8 fixes.
| author | John Rouillard <rouilj@ieee.org> |
|---|---|
| date | Tue, 13 Sep 2022 21:12:00 -0400 |
| parents | 5129fc03dc1f |
| children | c0d030bd472e |
comparison
equal
deleted
inserted
replaced
| 6971:861511d23a56 | 6972:8e4028669d2a |
|---|---|
| 18 """Command-line script that runs a server over roundup.cgi.client. | 18 """Command-line script that runs a server over roundup.cgi.client. |
| 19 """ | 19 """ |
| 20 from __future__ import print_function | 20 from __future__ import print_function |
| 21 __docformat__ = 'restructuredtext' | 21 __docformat__ = 'restructuredtext' |
| 22 | 22 |
| 23 import base64 # decode icon | |
| 24 import errno | |
| 25 import getopt | |
| 26 import io | |
| 27 import logging | |
| 28 import os | |
| 29 import socket | |
| 30 import sys # modify sys.path when running in source tree | |
| 31 import time | |
| 32 import traceback | |
| 33 import zlib # decompress icon | |
| 34 | |
| 35 try: | |
| 36 # Python 3. | |
| 37 import socketserver | |
| 38 except ImportError: | |
| 39 # Python 2. | |
| 40 import SocketServer as socketserver | |
| 41 | |
| 42 try: | |
| 43 # Python 2. | |
| 44 reload | |
| 45 except NameError: | |
| 46 # Python 3. | |
| 47 from importlib import reload | |
| 48 | |
| 49 try: | |
| 50 from OpenSSL import SSL | |
| 51 except ImportError: | |
| 52 SSL = None | |
| 23 | 53 |
| 24 # --- patch sys.path to make sure 'import roundup' finds correct version | 54 # --- patch sys.path to make sure 'import roundup' finds correct version |
| 25 import sys | |
| 26 import os.path as osp | 55 import os.path as osp |
| 27 | |
| 28 import logging | |
| 29 | 56 |
| 30 thisdir = osp.dirname(osp.abspath(__file__)) | 57 thisdir = osp.dirname(osp.abspath(__file__)) |
| 31 rootdir = osp.dirname(osp.dirname(thisdir)) | 58 rootdir = osp.dirname(osp.dirname(thisdir)) |
| 32 if (osp.exists(thisdir + '/__init__.py') and | 59 if (osp.exists(thisdir + '/__init__.py') and |
| 33 osp.exists(rootdir + '/roundup/__init__.py')): | 60 osp.exists(rootdir + '/roundup/__init__.py')): |
| 34 # the script is located inside roundup source code | 61 # the script is located inside roundup source code |
| 35 sys.path.insert(0, rootdir) | 62 sys.path.insert(0, rootdir) |
| 36 # --/ | 63 # --/ |
| 37 | 64 |
| 38 | 65 import roundup.instance # noqa: E402 |
| 39 import errno, getopt, io, os, socket, sys, traceback, time | 66 |
| 40 | 67 # python version_check raises exception if imported for wrong python version |
| 41 try: | 68 from roundup import configuration, version_check # noqa: F401,E402 |
| 42 # Python 3. | 69 from roundup import __version__ as roundup_version # noqa: E402 |
| 43 import socketserver | |
| 44 except ImportError: | |
| 45 # Python 2. | |
| 46 import SocketServer as socketserver | |
| 47 | |
| 48 try: | |
| 49 # Python 2. | |
| 50 reload | |
| 51 except NameError: | |
| 52 # Python 3. | |
| 53 from importlib import reload | |
| 54 | |
| 55 try: | |
| 56 from OpenSSL import SSL | |
| 57 except ImportError: | |
| 58 SSL = None | |
| 59 | |
| 60 from roundup.anypy.html import html_escape | |
| 61 | |
| 62 # python version check | |
| 63 from roundup import configuration, version_check | |
| 64 from roundup import __version__ as roundup_version | |
| 65 | |
| 66 # Roundup modules of use here | 70 # Roundup modules of use here |
| 67 from roundup.anypy import http_, urllib_ | 71 from roundup.anypy import http_, urllib_ # noqa: E402 |
| 68 from roundup.anypy.strings import s2b, StringIO | 72 from roundup.anypy.html import html_escape # noqa: E402 |
| 69 from roundup.cgi import cgitb, client | 73 from roundup.anypy.strings import s2b, StringIO # noqa: E402 |
| 70 from roundup.cgi.PageTemplates.PageTemplate import PageTemplate | 74 from roundup.cgi import cgitb, client # noqa: E402 |
| 71 import roundup.instance | 75 from roundup.cgi.PageTemplates.PageTemplate import PageTemplate # noqa: E402 |
| 72 from roundup.i18n import _ | 76 from roundup.i18n import _ # noqa: E402 |
| 73 | 77 |
| 74 # "default" favicon.ico | 78 # "default" favicon.ico |
| 75 # generate by using "icotool" and tools/base64 | 79 # generate by using "icotool" and tools/base64 |
| 76 import zlib, base64 | |
| 77 favico = zlib.decompress(base64.b64decode(b''' | 80 favico = zlib.decompress(base64.b64decode(b''' |
| 78 eJztjr1PmlEUh59XgVoshdYPWorFIhaRFq0t9pNq37b60lYSTRzcTFw6GAfj5gDYaF0dTB0MxMSE | 81 eJztjr1PmlEUh59XgVoshdYPWorFIhaRFq0t9pNq37b60lYSTRzcTFw6GAfj5gDYaF0dTB0MxMSE |
| 79 gQQd3FzKJiEC0UCIUUN1M41pV2JCXySg/0ITn5tfzvmdc+85FwT56HSc81UJjXJsk1UsNcsSqCk1 | 82 gQQd3FzKJiEC0UCIUUN1M41pV2JCXySg/0ITn5tfzvmdc+85FwT56HSc81UJjXJsk1UsNcsSqCk1 |
| 80 BS64lK+vr7OyssLJyQl2ux2j0cjU1BQajYZIJEIwGMRms+H3+zEYDExOTjI2Nsbm5iZWqxWv18vW | 83 BS64lK+vr7OyssLJyQl2ux2j0cjU1BQajYZIJEIwGMRms+H3+zEYDExOTjI2Nsbm5iZWqxWv18vW |
| 81 1hZDQ0Ok02kmJiY4Ojpienqa3d1dxsfHUSqVeDwe5ufnyeVyrK6u4nK5ODs7Y3FxEYfDwdzcHCaT | 84 1hZDQ0Ok02kmJiY4Ojpienqa3d1dxsfHUSqVeDwe5ufnyeVyrK6u4nK5ODs7Y3FxEYfDwdzcHCaT |
| 93 # Note: the order is important. Preferred multiprocess type | 96 # Note: the order is important. Preferred multiprocess type |
| 94 # is the last element of this list. | 97 # is the last element of this list. |
| 95 # "debug" means "none" + no tracker/template cache | 98 # "debug" means "none" + no tracker/template cache |
| 96 MULTIPROCESS_TYPES = ["debug", "none"] | 99 MULTIPROCESS_TYPES = ["debug", "none"] |
| 97 try: | 100 try: |
| 98 import threading # nosrc: F401 | 101 import threading # noqa: F401 |
| 99 except ImportError: | 102 except ImportError: |
| 100 pass | 103 pass |
| 101 else: | 104 else: |
| 102 MULTIPROCESS_TYPES.append("thread") | 105 MULTIPROCESS_TYPES.append("thread") |
| 103 if hasattr(os, 'fork'): | 106 if hasattr(os, 'fork'): |
| 105 DEFAULT_MULTIPROCESS = MULTIPROCESS_TYPES[-1] | 108 DEFAULT_MULTIPROCESS = MULTIPROCESS_TYPES[-1] |
| 106 | 109 |
| 107 | 110 |
| 108 def auto_ssl(): | 111 def auto_ssl(): |
| 109 print(_('WARNING: generating temporary SSL certificate')) | 112 print(_('WARNING: generating temporary SSL certificate')) |
| 110 import OpenSSL, random | 113 import OpenSSL, random # noqa: E401 |
| 111 pkey = OpenSSL.crypto.PKey() | 114 pkey = OpenSSL.crypto.PKey() |
| 112 pkey.generate_key(OpenSSL.crypto.TYPE_RSA, 2048) | 115 pkey.generate_key(OpenSSL.crypto.TYPE_RSA, 2048) |
| 113 cert = OpenSSL.crypto.X509() | 116 cert = OpenSSL.crypto.X509() |
| 114 cert.set_serial_number(random.randint(0, sys.maxsize)) | 117 cert.set_serial_number(random.randint(0, sys.maxsize)) |
| 115 cert.gmtime_adj_notBefore(0) | 118 cert.gmtime_adj_notBefore(0) |
| 116 cert.gmtime_adj_notAfter(60 * 60 * 24 * 365) # one year | 119 cert.gmtime_adj_notAfter(60 * 60 * 24 * 365) # one year |
| 117 cert.get_subject().CN = '*' | 120 cert.get_subject().CN = '*' |
| 118 cert.get_subject().O = 'Roundup Dummy Certificate' | 121 cert.get_subject().O = 'Roundup Dummy Certificate' # noqa: E741 |
| 119 cert.get_issuer().CN = 'Roundup Dummy Certificate Authority' | 122 cert.get_issuer().CN = 'Roundup Dummy Certificate Authority' |
| 120 cert.get_issuer().O = 'Self-Signed' | 123 cert.get_issuer().O = 'Self-Signed' # noqa: E741 |
| 121 cert.set_pubkey(pkey) | 124 cert.set_pubkey(pkey) |
| 122 cert.sign(pkey, 'sha512') | 125 cert.sign(pkey, 'sha512') |
| 123 ctx = SSL.Context(OpenSSL.SSL.TLSv1_2_METHOD) | 126 ctx = SSL.Context(OpenSSL.SSL.TLSv1_2_METHOD) |
| 124 ctx.use_privatekey(pkey) | 127 ctx.use_privatekey(pkey) |
| 125 ctx.use_certificate(cert) | 128 ctx.use_certificate(cert) |
| 262 self.inner_run_cgi() | 265 self.inner_run_cgi() |
| 263 except client.NotFound: | 266 except client.NotFound: |
| 264 self.send_error(404, self.path) | 267 self.send_error(404, self.path) |
| 265 except client.Unauthorised as message: | 268 except client.Unauthorised as message: |
| 266 self.send_error(403, '%s (%s)' % (self.path, message)) | 269 self.send_error(403, '%s (%s)' % (self.path, message)) |
| 267 except: | 270 except Exception: |
| 268 exc, val, tb = sys.exc_info() | 271 exc, val, tb = sys.exc_info() |
| 269 if hasattr(socket, 'timeout') and isinstance(val, socket.timeout): | 272 if hasattr(socket, 'timeout') and isinstance(val, socket.timeout): |
| 270 self.log_error('timeout') | 273 self.log_error('timeout') |
| 271 else: | 274 else: |
| 272 # it'd be nice to be able to detect if these are going to have | 275 # it'd be nice to be able to detect if these are going to have |
| 277 if self.DEBUG_MODE: | 280 if self.DEBUG_MODE: |
| 278 try: | 281 try: |
| 279 reload(cgitb) | 282 reload(cgitb) |
| 280 self.wfile.write(s2b(cgitb.breaker())) | 283 self.wfile.write(s2b(cgitb.breaker())) |
| 281 self.wfile.write(s2b(cgitb.html())) | 284 self.wfile.write(s2b(cgitb.html())) |
| 282 except: | 285 except Exception: |
| 283 s = StringIO() | 286 s = StringIO() |
| 284 traceback.print_exc(None, s) | 287 traceback.print_exc(None, s) |
| 285 self.wfile.write(b"<pre>") | 288 self.wfile.write(b"<pre>") |
| 286 self.wfile.write(s2b(html_escape(s.getvalue()))) | 289 self.wfile.write(s2b(html_escape(s.getvalue()))) |
| 287 self.wfile.write(b"</pre>\n") | 290 self.wfile.write(b"</pre>\n") |
| 294 # out to the logfile | 297 # out to the logfile |
| 295 print('EXCEPTION AT', ts) | 298 print('EXCEPTION AT', ts) |
| 296 traceback.print_exc() | 299 traceback.print_exc() |
| 297 | 300 |
| 298 do_GET = do_POST = do_HEAD = do_PUT = do_DELETE = \ | 301 do_GET = do_POST = do_HEAD = do_PUT = do_DELETE = \ |
| 299 do_PATCH = do_OPTIONS = run_cgi | 302 do_PATCH = do_OPTIONS = run_cgi |
| 300 | 303 |
| 301 def index(self): | 304 def index(self): |
| 302 ''' Print up an index of the available trackers | 305 ''' Print up an index of the available trackers |
| 303 ''' | 306 ''' |
| 304 keys = list(self.TRACKER_HOMES.keys()) | 307 keys = list(self.TRACKER_HOMES.keys()) |
| 318 pt = PageTemplate() | 321 pt = PageTemplate() |
| 319 pt.write(template) | 322 pt.write(template) |
| 320 extra = {'trackers': self.TRACKERS, | 323 extra = {'trackers': self.TRACKERS, |
| 321 'nothing': None, | 324 'nothing': None, |
| 322 'true': 1, | 325 'true': 1, |
| 323 'false': 0, | 326 'false': 0} |
| 324 } | |
| 325 w(s2b(pt.pt_render(extra_context=extra))) | 327 w(s2b(pt.pt_render(extra_context=extra))) |
| 326 else: | 328 else: |
| 327 w(s2b(_('<html><head><title>Roundup trackers index</title></head>\n' | 329 w(s2b(_('<html><head><title>Roundup trackers index</title></head>\n' |
| 328 '<body><h1>Roundup trackers index</h1><ol>\n'))) | 330 '<body><h1>Roundup trackers index</h1><ol>\n'))) |
| 329 keys.sort() | 331 keys.sort() |
| 528 self.log_date_time_string(), | 530 self.log_date_time_string(), |
| 529 format % args)) | 531 format % args)) |
| 530 else: | 532 else: |
| 531 try: | 533 try: |
| 532 http_.server.BaseHTTPRequestHandler.log_message(self, | 534 http_.server.BaseHTTPRequestHandler.log_message(self, |
| 533 format, *args) | 535 format, *args) |
| 534 except IOError: | 536 except IOError: |
| 535 # stderr is no longer viable | 537 # stderr is no longer viable |
| 536 pass | 538 pass |
| 537 | 539 |
| 538 def start_response(self, headers, response): | 540 def start_response(self, headers, response): |
| 616 | 618 |
| 617 | 619 |
| 618 class ServerConfig(configuration.Config): | 620 class ServerConfig(configuration.Config): |
| 619 | 621 |
| 620 SETTINGS = ( | 622 SETTINGS = ( |
| 621 ("main", ( | 623 ("main", ( |
| 622 (configuration.Option, "host", "localhost", | 624 (configuration.Option, "host", "localhost", |
| 623 "Host name of the Roundup web server instance.\n" | 625 "Host name of the Roundup web server instance.\n" |
| 624 "If left unconfigured (no 'host' setting) the default\n" | 626 "If left unconfigured (no 'host' setting) the default\n" |
| 625 "will be used.\n" | 627 "will be used.\n" |
| 626 "If empty, listen on all network interfaces.\n" | 628 "If empty, listen on all network interfaces.\n" |
| 726 self.add_option(TrackerHomeOption(self, "trackers", name)) | 728 self.add_option(TrackerHomeOption(self, "trackers", name)) |
| 727 | 729 |
| 728 def getopt(self, args, short_options="", long_options=(), | 730 def getopt(self, args, short_options="", long_options=(), |
| 729 config_load_options=("C", "config"), **options): | 731 config_load_options=("C", "config"), **options): |
| 730 options.update(self.OPTIONS) | 732 options.update(self.OPTIONS) |
| 731 return configuration.Config.getopt(self, args, | 733 return configuration.Config.getopt( |
| 732 short_options, long_options, config_load_options, **options) | 734 self, args, short_options, long_options, |
| 735 config_load_options, **options) | |
| 733 | 736 |
| 734 def _get_name(self): | 737 def _get_name(self): |
| 735 return "Roundup server" | 738 return "Roundup server" |
| 736 | 739 |
| 737 def trackers(self): | 740 def trackers(self): |
| 774 # perform initial ssl handshake. This will set | 777 # perform initial ssl handshake. This will set |
| 775 # internal state correctly so that later closing SSL | 778 # internal state correctly so that later closing SSL |
| 776 # socket works (with SSL end-handshake started) | 779 # socket works (with SSL end-handshake started) |
| 777 self.request.do_handshake() | 780 self.request.do_handshake() |
| 778 RoundupRequestHandler.protocol_version = \ | 781 RoundupRequestHandler.protocol_version = \ |
| 779 self.CONFIG["HTTP_VERSION"] | 782 self.CONFIG["HTTP_VERSION"] |
| 780 RoundupRequestHandler.setup(self) | 783 RoundupRequestHandler.setup(self) |
| 781 | 784 |
| 782 def finish(self): | 785 def finish(self): |
| 783 RoundupRequestHandler.finish(self) | 786 RoundupRequestHandler.finish(self) |
| 784 if self.CONFIG["SSL"]: | 787 if self.CONFIG["SSL"]: |
| 825 except socket.error as e: | 828 except socket.error as e: |
| 826 if e.args[0] == errno.EADDRINUSE: | 829 if e.args[0] == errno.EADDRINUSE: |
| 827 raise socket.error(_("Unable to bind to port %s, " | 830 raise socket.error(_("Unable to bind to port %s, " |
| 828 "port already in use.") % self["PORT"]) | 831 "port already in use.") % self["PORT"]) |
| 829 if e.args[0] == errno.EACCES: | 832 if e.args[0] == errno.EACCES: |
| 830 raise socket.error(_("Unable to bind to port %(port)s, " | 833 raise socket.error(_( |
| 831 "access not allowed, " | 834 "Unable to bind to port %(port)s, " |
| 832 "errno: %(errno)s %(msg)s" % { | 835 "access not allowed, " |
| 833 "port": self["PORT"], | 836 "errno: %(errno)s %(msg)s" % { |
| 834 "errno": e.args[0], | 837 "port": self["PORT"], |
| 835 "msg": e.args[1] } | 838 "errno": e.args[0], |
| 839 "msg": e.args[1]} | |
| 836 )) | 840 )) |
| 837 | 841 |
| 838 raise | 842 raise |
| 839 # change user and/or group | 843 # change user and/or group |
| 840 setgid(self["GROUP"]) | 844 setgid(self["GROUP"]) |
| 841 setuid(self["USER"]) | 845 setuid(self["USER"]) |
| 842 # return the server | 846 # return the server |
| 867 import servicemanager | 871 import servicemanager |
| 868 self.ReportServiceStatus(win32service.SERVICE_START_PENDING) | 872 self.ReportServiceStatus(win32service.SERVICE_START_PENDING) |
| 869 config = ServerConfig() | 873 config = ServerConfig() |
| 870 (optlist, args) = config.getopt(sys.argv[1:]) | 874 (optlist, args) = config.getopt(sys.argv[1:]) |
| 871 if not config["LOGFILE"]: | 875 if not config["LOGFILE"]: |
| 872 servicemanager.LogMsg(servicemanager.EVENTLOG_ERROR_TYPE, | 876 servicemanager.LogMsg( |
| 873 servicemanager.PYS_SERVICE_STOPPED, | 877 servicemanager.EVENTLOG_ERROR_TYPE, |
| 878 servicemanager.PYS_SERVICE_STOPPED, | |
| 874 (self._svc_display_name_, "\r\nMissing logfile option")) | 879 (self._svc_display_name_, "\r\nMissing logfile option")) |
| 875 self.ReportServiceStatus(win32service.SERVICE_STOPPED) | 880 self.ReportServiceStatus(win32service.SERVICE_STOPPED) |
| 876 return | 881 return |
| 877 config.set_logging() | 882 config.set_logging() |
| 878 self.server = config.get_server() | 883 self.server = config.get_server() |
