"Fossies" - the Fresh Open Source Software Archive

Member "PURELIB/trac/web/standalone.py" (27 Aug 2019, 16712 Bytes) of package /windows/misc/Trac-1.4.win32.exe:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file. See also the last Fossies "Diffs" side-by-side code changes report for "standalone.py": 1.3.5_vs_1.3.6.

    1 #!/usr/bin/env python
    2 # -*- coding: utf-8 -*-
    3 #
    4 # Copyright (C) 2003-2019 Edgewall Software
    5 # Copyright (C) 2003-2005 Jonas Borgström <jonas@edgewall.com>
    6 # Copyright (C) 2005-2006 Matthew Good <trac@matt-good.net>
    7 # Copyright (C) 2005-2006 Christopher Lenz <cmlenz@gmx.de>
    8 # All rights reserved.
    9 #
   10 # This software is licensed as described in the file COPYING, which
   11 # you should have received as part of this distribution. The terms
   12 # are also available at https://trac.edgewall.org/wiki/TracLicense.
   13 #
   14 # This software consists of voluntary contributions made by many
   15 # individuals. For the exact contribution history, see the revision
   16 # history and logs, available at https://trac.edgewall.org/log/.
   17 #
   18 # Author: Jonas Borgström <jonas@edgewall.com>
   19 #         Matthew Good <trac@matt-good.net>
   20 #         Christopher Lenz <cmlenz@gmx.de>
   21 
   22 from __future__ import print_function
   23 
   24 import argparse
   25 import functools
   26 import importlib
   27 import os
   28 import pkg_resources
   29 import socket
   30 import ssl
   31 import sys
   32 from SocketServer import ThreadingMixIn
   33 
   34 from trac import __version__ as VERSION
   35 from trac.util import autoreload, daemon
   36 from trac.util.text import printerr
   37 from trac.web.auth import BasicAuthentication, DigestAuthentication
   38 from trac.web.main import dispatch_request
   39 from trac.web.wsgi import WSGIServer, WSGIRequestHandler
   40 
   41 
   42 class AuthenticationMiddleware(object):
   43 
   44     def __init__(self, application, auths, single_env_name=None):
   45         self.application = application
   46         self.auths = auths
   47         self.single_env_name = single_env_name
   48         if single_env_name:
   49             self.part = 0
   50         else:
   51             self.part = 1
   52 
   53     def __call__(self, environ, start_response):
   54         path_info = environ.get('PATH_INFO', '')
   55         path_parts = filter(None, path_info.split('/'))
   56         if len(path_parts) > self.part and path_parts[self.part] == 'login':
   57             env_name = self.single_env_name or path_parts[0]
   58             if env_name:
   59                 auth = self.auths.get(env_name, self.auths.get('*'))
   60                 if auth:
   61                     remote_user = auth.do_auth(environ, start_response)
   62                     if not remote_user:
   63                         return []
   64                     environ['REMOTE_USER'] = remote_user
   65         return self.application(environ, start_response)
   66 
   67 
   68 class BasePathMiddleware(object):
   69 
   70     def __init__(self, application, base_path):
   71         self.base_path = '/' + base_path.strip('/')
   72         self.application = application
   73 
   74     def __call__(self, environ, start_response):
   75         path = environ['SCRIPT_NAME'] + environ.get('PATH_INFO', '')
   76         environ['PATH_INFO'] = path[len(self.base_path):]
   77         environ['SCRIPT_NAME'] = self.base_path
   78         return self.application(environ, start_response)
   79 
   80 
   81 class TracEnvironMiddleware(object):
   82 
   83     def __init__(self, application, env_parent_dir, env_paths, single_env):
   84         self.application = application
   85         self.environ = {'trac.env_path': None}
   86         if env_parent_dir:
   87             self.environ['trac.env_parent_dir'] = env_parent_dir
   88         elif single_env:
   89             self.environ['trac.env_path'] = env_paths[0]
   90         else:
   91             self.environ['trac.env_paths'] = env_paths
   92 
   93     def __call__(self, environ, start_response):
   94         for k, v in self.environ.iteritems():
   95             environ.setdefault(k, v)
   96         return self.application(environ, start_response)
   97 
   98 
   99 class TracHTTPServer(ThreadingMixIn, WSGIServer):
  100     daemon_threads = True
  101 
  102     def __init__(self, server_address, application, env_parent_dir, env_paths,
  103                  use_http_11=False):
  104         request_handlers = (TracHTTPRequestHandler, TracHTTP11RequestHandler)
  105         WSGIServer.__init__(self, server_address, application,
  106                             request_handler=request_handlers[bool(use_http_11)])
  107 
  108 
  109 class TracHTTPRequestHandler(WSGIRequestHandler):
  110 
  111     server_version = 'tracd/' + VERSION
  112 
  113     def address_string(self):
  114         # Disable reverse name lookups
  115         return self.client_address[:2][0]
  116 
  117 
  118 class TracHTTP11RequestHandler(TracHTTPRequestHandler):
  119     protocol_version = 'HTTP/1.1'
  120 
  121 
  122 def parse_args(args=None):
  123     parser = argparse.ArgumentParser()
  124 
  125     class _AuthAction(argparse.Action):
  126 
  127         def __init__(self, option_strings, dest, nargs=None, **kwargs):
  128             self.cls = kwargs.pop('cls')
  129             super(_AuthAction, self).__init__(option_strings, dest, nargs,
  130                                               **kwargs)
  131 
  132         def __call__(self, parser, namespace, values, option_string=None):
  133             info = values.split(',')
  134             if len(info) != 3:
  135                 raise argparse.ArgumentError(self,
  136                                              "Incorrect number of parameters")
  137             env_name, filename, realm = info
  138             filepath = os.path.abspath(filename)
  139             if not os.path.exists(filepath):
  140                 raise argparse.ArgumentError(self,
  141                                              "Path does not exist: '%s'"
  142                                              % filename)
  143             auths = getattr(namespace, self.dest)
  144             if env_name in auths:
  145                 printerr("Ignoring duplicate authentication option for "
  146                          "project: %s" % env_name)
  147             else:
  148                 auths.update({env_name: self.cls(filepath, realm)})
  149                 setattr(namespace, self.dest, auths)
  150 
  151     class _PathAction(argparse.Action):
  152 
  153         def __init__(self, option_strings, dest, nargs=None, **kwargs):
  154             self.must_exist = kwargs.pop('must_exist', False)
  155             super(_PathAction, self).__init__(option_strings, dest, nargs,
  156                                               **kwargs)
  157 
  158         def __call__(self, parser, namespace, values, option_string=None):
  159             def to_abspath(path):
  160                 abspath = os.path.abspath(path)
  161                 if self.must_exist and not os.path.exists(abspath):
  162                     raise argparse.ArgumentError(self,
  163                                                  "Path does not exist: '%s'"
  164                                                  % path)
  165                 return abspath
  166             if isinstance(values, list):
  167                 paths = [to_abspath(paths) for paths in values]
  168             else:
  169                 paths = to_abspath(values)
  170             setattr(namespace, self.dest, paths)
  171 
  172     parser.add_argument('--version', action='version',
  173                         version='%%(prog)s %s' % VERSION)
  174     parser.add_argument('envs', action=_PathAction, must_exist=True,
  175                         nargs='*', help="path of the project environment(s)")
  176 
  177     parser_group = parser.add_mutually_exclusive_group()
  178     parser_group.add_argument('-e', '--env-parent-dir', action=_PathAction,
  179                               must_exist=True, metavar='PARENTDIR',
  180                               help="parent directory of the project "
  181                                    "environments")
  182     parser_group.add_argument('-s', '--single-env', action='store_true',
  183                               help="only serve a single project without the "
  184                                    "project list")
  185 
  186     parser_group = parser.add_mutually_exclusive_group()
  187     parser_group.add_argument('-a', '--auth', default={},
  188                               metavar='DIGESTAUTH', dest='auths',
  189                               action=_AuthAction, cls=DigestAuthentication,
  190                               help="[projectdir],[htdigest_file],[realm]")
  191     parser_group.add_argument('--basic-auth', default={},
  192                               metavar='BASICAUTH', dest='auths',
  193                               action=_AuthAction, cls=BasicAuthentication,
  194                               help="[projectdir],[htpasswd_file],[realm]")
  195 
  196     parser.add_argument('-p', '--port', type=int,
  197                         help="the port number to bind to")
  198     parser.add_argument('-b', '--hostname', default='',
  199                         help="the host name or IP address to bind to")
  200     parser.add_argument('--protocol', default='http',
  201                         choices=('http', 'https', 'scgi', 'ajp', 'fcgi'),
  202                         help="the server protocol (default: http)")
  203     parser.add_argument('--certfile', help="PEM certificate file for HTTPS")
  204     parser.add_argument('--keyfile', help="PEM key file for HTTPS")
  205     parser.add_argument('-q', '--unquote', action='store_true',
  206                         help="unquote PATH_INFO (may be needed when using "
  207                              "the ajp protocol)")
  208     parser.add_argument('--base-path', default='',  # XXX call this url_base_path?
  209                         help="the initial portion of the request URL's "
  210                              "\"path\"")
  211 
  212     parser_group = parser.add_mutually_exclusive_group()
  213     parser_group.add_argument('--http10', action='store_false', dest='http11',
  214                               help="use HTTP/1.0 protocol instead of "
  215                                    "HTTP/1.1")
  216     parser_group.add_argument('--http11', action='store_true', default=True,
  217                               help="use HTTP/1.1 protocol (default)")
  218 
  219     if os.name == 'posix':
  220         class _GroupAction(argparse.Action):
  221 
  222             def __call__(self, parser, namespace, values, option_string=None):
  223                 import grp
  224                 try:
  225                     value = int(values)
  226                 except ValueError:
  227                     try:
  228                         value = grp.getgrnam(values)[2]
  229                     except KeyError:
  230                         raise argparse.ArgumentError(self, "group not found: "
  231                                                            "%r" % values)
  232                 setattr(namespace, self.dest, value)
  233 
  234         class _UserAction(argparse.Action):
  235 
  236             def __call__(self, parser, namespace, values, option_string=None):
  237                 import pwd
  238                 try:
  239                     value = int(values)
  240                 except ValueError:
  241                     try:
  242                         value = pwd.getpwnam(values)[2]
  243                     except KeyError:
  244                         raise argparse.ArgumentError(self, "user not found: "
  245                                                            "%r" % values)
  246                 setattr(namespace, self.dest, value)
  247 
  248         class _OctalValueAction(argparse.Action):
  249 
  250             octal = functools.partial(int, base=8)
  251 
  252             def __call__(self, parser, namespace, values, option_string=None):
  253                 try:
  254                     value = self.octal(values)
  255                 except ValueError:
  256                     raise argparse.ArgumentError(self, "Invalid octal umask "
  257                                                        "value: %r" % values)
  258                 setattr(namespace, self.dest, value)
  259 
  260         parser_group = parser.add_mutually_exclusive_group()
  261         parser_group.add_argument('-r', '--auto-reload', action='store_true',
  262                                   help="restart automatically when sources "
  263                                        "are modified")
  264         parser_group.add_argument('-d', '--daemonize', action='store_true',
  265                                   help="run in the background as a daemon")
  266         parser.add_argument('--pidfile', action=_PathAction,
  267                             help="file to write pid when daemonizing")
  268         parser.add_argument('--umask', action=_OctalValueAction,
  269                             default=0o022, metavar='MASK',
  270                             help="when daemonizing, file mode creation mask "
  271                                  "to use, in octal notation (default: 022)")
  272         parser.add_argument('--group', action=_GroupAction,
  273                             help="the group to run as")
  274         parser.add_argument('--user', action=_UserAction,
  275                             help="the user to run as")
  276     else:
  277         parser.add_argument('-r', '--auto-reload', action='store_true',
  278                             help="restart automatically when sources are "
  279                                  "modified")
  280 
  281     parser.set_defaults(daemonize=False, user=None, group=None)
  282     args = parser.parse_args(args)
  283 
  284     if not args.env_parent_dir and not args.envs:
  285         parser.error("either the --env-parent-dir (-e) option or at least "
  286                      "one environment must be specified")
  287     if args.single_env and len(args.envs) > 1:
  288         parser.error("the --single-env (-s) option cannot be used with more "
  289                      "than one environment")
  290     if args.protocol == 'https' and not args.certfile:
  291         parser.error("the --certfile option is required when using the https "
  292                      "protocol")
  293 
  294     if args.port is None:
  295         args.port = {
  296             'http': 80,
  297             'https': 443,
  298             'scgi': 4000,
  299             'ajp': 8009,
  300             'fcgi': 8000,
  301         }[args.protocol]
  302 
  303     return args
  304 
  305 
  306 def main():
  307     args = parse_args()
  308 
  309     wsgi_app = TracEnvironMiddleware(dispatch_request, args.env_parent_dir,
  310                                      args.envs, args.single_env)
  311     if args.auths:
  312         if args.single_env:
  313             project_name = os.path.basename(args.envs[0])
  314             wsgi_app = AuthenticationMiddleware(wsgi_app, args.auths,
  315                                                 project_name)
  316         else:
  317             wsgi_app = AuthenticationMiddleware(wsgi_app, args.auths)
  318     base_path = args.base_path.strip('/')
  319     if base_path:
  320         wsgi_app = BasePathMiddleware(wsgi_app, base_path)
  321 
  322     server_address = (args.hostname, args.port)
  323     if args.protocol in ('http', 'https'):
  324         def serve():
  325             addr, port = server_address
  326             if not addr or addr == '0.0.0.0':
  327                 loc = '0.0.0.0:%s view at %s://127.0.0.1:%s/%s' \
  328                        % (port, args.protocol, port, base_path)
  329             else:
  330                 loc = '%s://%s:%s/%s' % (args.protocol, addr, port, base_path)
  331 
  332             try:
  333                 httpd = TracHTTPServer(server_address, wsgi_app,
  334                                        args.env_parent_dir, args.envs,
  335                                        use_http_11=args.http11)
  336             except socket.error as e:
  337                 print("Error starting Trac server on %s" % loc)
  338                 print("[Errno %s] %s" % e.args)
  339                 sys.exit(1)
  340 
  341             print("Server starting in PID %s." % os.getpid())
  342             print("Serving on %s" % loc)
  343             if args.http11:
  344                 print("Using HTTP/1.1 protocol version")
  345             if args.protocol == 'https':
  346                 httpd.socket = ssl.wrap_socket(httpd.socket, server_side=True,
  347                                                certfile=args.certfile,
  348                                                keyfile=args.keyfile)
  349             httpd.serve_forever()
  350     elif args.protocol in ('scgi', 'ajp', 'fcgi'):
  351         def serve():
  352             module = 'flup.server.%s' % args.protocol
  353             try:
  354                 server_cls = importlib.import_module(module).WSGIServer
  355             except ImportError:
  356                 printerr("Install the flup package to use the '%s' "
  357                          "protocol" % args.protocol)
  358                 sys.exit(1)
  359             flup_app = wsgi_app
  360             if args.unquote:
  361                 from trac.web.fcgi_frontend import FlupMiddleware
  362                 flup_app = FlupMiddleware(flup_app)
  363             ret = server_cls(flup_app, bindAddress=server_address).run()
  364             sys.exit(42 if ret else 0)  # if SIGHUP exit with status 42
  365 
  366     try:
  367         if args.daemonize:
  368             daemon.daemonize(pidfile=args.pidfile, progname='tracd',
  369                              umask=args.umask)
  370         if args.group is not None:
  371             os.setgid(args.group)
  372         if args.user is not None:
  373             os.setuid(args.user)
  374 
  375         if args.auto_reload:
  376             def modification_callback(file):
  377                 printerr("Detected modification of %s, restarting." % file)
  378             autoreload.main(serve, modification_callback)
  379         else:
  380             serve()
  381 
  382     except OSError as e:
  383         printerr("%s: %s" % (e.__class__.__name__, e))
  384         sys.exit(1)
  385     except KeyboardInterrupt:
  386         pass
  387 
  388 
  389 if __name__ == '__main__':
  390     pkg_resources.require('Trac==%s' % VERSION)
  391     main()