| 1 |
# Created by Noah Kantrowitz on 2007-04-09. |
|---|
| 2 |
# Copyright (c) 2007 Noah Kantrowitz. All rights reserved. |
|---|
| 3 |
import sys |
|---|
| 4 |
import inspect |
|---|
| 5 |
import traceback |
|---|
| 6 |
|
|---|
| 7 |
from trac.core import * |
|---|
| 8 |
from trac.core import ComponentMeta |
|---|
| 9 |
from trac.web.api import IRequestHandler, Request, Cookie |
|---|
| 10 |
from trac.web.main import dispatch_request, RequestDispatcher, RequestDone |
|---|
| 11 |
|
|---|
| 12 |
# Compatibility hack to make this code work with older 0.11 revisions |
|---|
| 13 |
try: |
|---|
| 14 |
from trac.env import open_environment as _open_environment |
|---|
| 15 |
def open_environment(x): return _open_environment(x,True) |
|---|
| 16 |
except: |
|---|
| 17 |
from trac.web.main import _open_environment as open_environment |
|---|
| 18 |
|
|---|
| 19 |
from trac.web.chrome import INavigationContributor |
|---|
| 20 |
from trac.perm import PermissionCache |
|---|
| 21 |
from trac.util.text import to_unicode |
|---|
| 22 |
from trac.web.href import Href |
|---|
| 23 |
from trac.util.html import html as tag |
|---|
| 24 |
|
|---|
| 25 |
import os |
|---|
| 26 |
import os.path |
|---|
| 27 |
import copy |
|---|
| 28 |
|
|---|
| 29 |
class TracForgeIndexModule(Component): |
|---|
| 30 |
"""A request handler to show a nicer project index.""" |
|---|
| 31 |
|
|---|
| 32 |
implements(IRequestHandler, INavigationContributor) |
|---|
| 33 |
|
|---|
| 34 |
def match_request(self, req): |
|---|
| 35 |
return req.path_info == '/projects' |
|---|
| 36 |
|
|---|
| 37 |
def process_request(self, req): |
|---|
| 38 |
parent_dir = os.path.dirname(self.env.path) |
|---|
| 39 |
#env_paths = dict([(filename, os.path.join(parent_dir, filename)) |
|---|
| 40 |
# for filename in os.listdir(parent_dir)]) |
|---|
| 41 |
projects = [] |
|---|
| 42 |
|
|---|
| 43 |
for env_name in os.listdir(parent_dir): |
|---|
| 44 |
env_path = os.path.join(parent_dir, env_name) |
|---|
| 45 |
|
|---|
| 46 |
# Don't list this environment |
|---|
| 47 |
if env_path == self.env.path: |
|---|
| 48 |
continue |
|---|
| 49 |
|
|---|
| 50 |
try: |
|---|
| 51 |
env = open_environment(env_path) |
|---|
| 52 |
|
|---|
| 53 |
try: |
|---|
| 54 |
#self.log.debug(env.path) |
|---|
| 55 |
env_perm = PermissionCache(env, req.authname) |
|---|
| 56 |
#self.log.debug(env_perm.perms) |
|---|
| 57 |
if env_perm.has_permission('PROJECT_VIEW'): |
|---|
| 58 |
projects.append({ |
|---|
| 59 |
'name': env.project_name, |
|---|
| 60 |
'description': env.project_description, |
|---|
| 61 |
'href': req.href.projects(env_name), |
|---|
| 62 |
}) |
|---|
| 63 |
except Exception, e: |
|---|
| 64 |
# Only show errors to admins to prevent excessive disclosure |
|---|
| 65 |
if req.perm.has_permission('TRACFORGE_ADMIN'): |
|---|
| 66 |
projects.append({ |
|---|
| 67 |
'name': env.project_name, |
|---|
| 68 |
'description': to_unicode(e) |
|---|
| 69 |
}) |
|---|
| 70 |
except Exception, e: |
|---|
| 71 |
if req.perm.has_permission('TRACFORGE_ADMIN'): |
|---|
| 72 |
projects.append({ |
|---|
| 73 |
'name': env_path, |
|---|
| 74 |
'description': to_unicode(e), |
|---|
| 75 |
}) |
|---|
| 76 |
|
|---|
| 77 |
projects.sort(lambda x, y: cmp(x['name'].lower(), y['name'].lower())) |
|---|
| 78 |
req.hdf['tracforge.projects'] = projects |
|---|
| 79 |
return 'tracforge_project_index.cs', None |
|---|
| 80 |
|
|---|
| 81 |
# INavigationContributor methods |
|---|
| 82 |
def get_active_navigation_item(self, req): |
|---|
| 83 |
return 'projects' |
|---|
| 84 |
|
|---|
| 85 |
def get_navigation_items(self, req): |
|---|
| 86 |
yield 'mainnav', 'projects', tag.a('Projects', href=req.href.projects()) |
|---|
| 87 |
|
|---|
| 88 |
|
|---|
| 89 |
class TracForgeDispatcherModule(Component): |
|---|
| 90 |
"""A request handler to dispatch /projects as if it were a TRAC_ENV_PARENT_DIR folder.""" |
|---|
| 91 |
|
|---|
| 92 |
implements(IRequestHandler) |
|---|
| 93 |
|
|---|
| 94 |
# IRequestHandler methods |
|---|
| 95 |
def match_request(self, req): |
|---|
| 96 |
if req.path_info.startswith('/projects'): |
|---|
| 97 |
path_info = req.path_info[10:] |
|---|
| 98 |
if path_info: |
|---|
| 99 |
self.log.debug('TracForgeDispatch: Starting WSGI relaunch for %s (%s)', path_info, req.method) |
|---|
| 100 |
project = path_info.split('/', 1)[0] |
|---|
| 101 |
# Check that we aren't trying to recurse (possible link loop) |
|---|
| 102 |
if project == os.path.basename(self.env.path): |
|---|
| 103 |
req.redirect(req.href()) |
|---|
| 104 |
|
|---|
| 105 |
# Assert permissions on the desination environment |
|---|
| 106 |
try: |
|---|
| 107 |
project_env = open_environment(os.path.join(os.path.dirname(self.env.path), project)) |
|---|
| 108 |
except IOError: |
|---|
| 109 |
raise TracError('No such project "%s"'%project) |
|---|
| 110 |
|
|---|
| 111 |
authname = RequestDispatcher(self.env).authenticate(req) |
|---|
| 112 |
project_perm = PermissionCache(project_env, authname) |
|---|
| 113 |
project_perm.assert_permission('PROJECT_VIEW') |
|---|
| 114 |
self.log.debug('TracForgeDispath: Access granted, running relaunch') |
|---|
| 115 |
self.log.debug('TracForgeDispatch: Status of req.args is %r', req.__dict__.get('args', 'NOT FOUND')) |
|---|
| 116 |
#self.log.debug('TracForgeDispatch: wsgi.input contains %s', req.read()) |
|---|
| 117 |
self._send_project(req, path_info) |
|---|
| 118 |
self.log.debug('TracForgeDispatch: Relaunch completed, terminating request') |
|---|
| 119 |
self.log.debug('TracForgeDispatch: Response was %r', req._response) |
|---|
| 120 |
|
|---|
| 121 |
req._tf_print = True |
|---|
| 122 |
|
|---|
| 123 |
raise RequestDone, 'request done' |
|---|
| 124 |
|
|---|
| 125 |
def process_request(self, req): |
|---|
| 126 |
raise TracError('How did I get here?') |
|---|
| 127 |
path_info = req.path_info[10:] |
|---|
| 128 |
|
|---|
| 129 |
if path_info: |
|---|
| 130 |
project = path_info.split('/', 1)[0] |
|---|
| 131 |
|
|---|
| 132 |
# Check that we aren't trying to recurse (possible link loop) |
|---|
| 133 |
if project == os.path.basename(self.env.path): |
|---|
| 134 |
req.redirect(req.href()) |
|---|
| 135 |
|
|---|
| 136 |
# Assert permissions on the desination environment |
|---|
| 137 |
project_path = os.path.join(os.path.dirname(self.env.path), project) |
|---|
| 138 |
try: |
|---|
| 139 |
project_env = open_environment(project_path) |
|---|
| 140 |
except IOError: |
|---|
| 141 |
raise TracError('No such project "%s" at %s'% (project,project_path)) |
|---|
| 142 |
project_perm = PermissionCache(project_env, req.authname) |
|---|
| 143 |
project_perm.assert_permission('PROJECT_VIEW') |
|---|
| 144 |
|
|---|
| 145 |
return self._send_project(req, path_info) |
|---|
| 146 |
else: |
|---|
| 147 |
return self._send_index(req) |
|---|
| 148 |
|
|---|
| 149 |
# Internal methods |
|---|
| 150 |
def _send_project(self, req, path_info): |
|---|
| 151 |
start_response = req._start_response |
|---|
| 152 |
environ = copy.copy(req.environ) |
|---|
| 153 |
|
|---|
| 154 |
class hacked_start_response(object): |
|---|
| 155 |
|
|---|
| 156 |
def __init__(self, start_response, log): |
|---|
| 157 |
if hasattr(start_response, 'log'): |
|---|
| 158 |
raise Exception("BOOM!") |
|---|
| 159 |
self.start_response = start_response |
|---|
| 160 |
self.log = log |
|---|
| 161 |
|
|---|
| 162 |
def __call__(self, *args): |
|---|
| 163 |
self.log.debug('TracForgeDispatch: start_response called with (%s)', ', '.join(repr(x) for x in args)) |
|---|
| 164 |
return self.start_response(*args) |
|---|
| 165 |
|
|---|
| 166 |
environ['SCRIPT_NAME'] = req.href.projects('/') |
|---|
| 167 |
environ['PATH_INFO'] = path_info |
|---|
| 168 |
environ['trac.env_parent_dir'] = os.path.dirname(self.env.path) |
|---|
| 169 |
if 'TRAC_ENV' in environ: |
|---|
| 170 |
del environ['TRAC_ENV'] |
|---|
| 171 |
if 'trac.env_path' in environ: |
|---|
| 172 |
# WARNING: this is a really FRAGILE HACK!! |
|---|
| 173 |
# |
|---|
| 174 |
# The code here used to delete the 'trac.env_path' key from environ, |
|---|
| 175 |
# but then other code in web/main.py would use setdefault to bring |
|---|
| 176 |
# it back to life... with the wrong value. Code later in that file |
|---|
| 177 |
# checks environ['trac.env_path'] as a truth value and treats empty |
|---|
| 178 |
# results as absent. |
|---|
| 179 |
environ['trac.env_path'] = '' |
|---|
| 180 |
|
|---|
| 181 |
environ['tracforge_master_link'] = req.href.projects() |
|---|
| 182 |
|
|---|
| 183 |
# Remove mod_python option to avoid conflicts |
|---|
| 184 |
if 'mod_python.subprocess_env' in environ: |
|---|
| 185 |
del environ['mod_python.subprocess_env'] |
|---|
| 186 |
if 'mod_python.options' in environ: |
|---|
| 187 |
del environ['mod_python.options'] |
|---|
| 188 |
|
|---|
| 189 |
|
|---|
| 190 |
self.log.debug('TracForgeDispatch: environ %r', environ) |
|---|
| 191 |
self.log.debug('TracForgeDispatch: Calling next dispatch_request') |
|---|
| 192 |
try: |
|---|
| 193 |
if not hasattr(start_response, 'log'): |
|---|
| 194 |
self.log.debug('TracForgeDispatch: Setting start_request logging hack') |
|---|
| 195 |
#start_response = hacked_start_response(start_response, self.log) |
|---|
| 196 |
req._response = dispatch_request(environ, start_response) |
|---|
| 197 |
except RequestDone: |
|---|
| 198 |
self.log.debug('TracForgeDispatch: Masking inner RequestDone') |
|---|
| 199 |
self.log.debug('TracForgeDispatch: Done') |
|---|
| 200 |
|
|---|
| 201 |
# Make sure we are first |
|---|
| 202 |
ComponentMeta._registry[IRequestHandler].remove(TracForgeDispatcherModule) |
|---|
| 203 |
ComponentMeta._registry[IRequestHandler].insert(0, TracForgeDispatcherModule) |
|---|