##// END OF EJS Templates
config: Sanitize 'default encoding' setting.
Martin Bornhold -
r586:5b4131ee default
parent child Browse files
Show More
@@ -1,457 +1,460 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 Pylons middleware initialization
23 23 """
24 24 import logging
25 25 from collections import OrderedDict
26 26
27 27 from paste.registry import RegistryManager
28 28 from paste.gzipper import make_gzip_middleware
29 29 from pylons.wsgiapp import PylonsApp
30 30 from pyramid.authorization import ACLAuthorizationPolicy
31 31 from pyramid.config import Configurator
32 32 from pyramid.static import static_view
33 33 from pyramid.settings import asbool, aslist
34 34 from pyramid.wsgi import wsgiapp
35 35 from pyramid.httpexceptions import HTTPError, HTTPInternalServerError
36 36 from pylons.controllers.util import abort, redirect
37 37 from pyramid.events import ApplicationCreated
38 38 import pyramid.httpexceptions as httpexceptions
39 39 from pyramid.renderers import render_to_response, render
40 40 from routes.middleware import RoutesMiddleware
41 41 import routes.util
42 42
43 43 import rhodecode
44 44 import rhodecode.integrations # do not remove this as it registers celery tasks
45 45 from rhodecode.config import patches
46 46 from rhodecode.config.routing import STATIC_FILE_PREFIX
47 47 from rhodecode.config.environment import (
48 48 load_environment, load_pyramid_environment)
49 49 from rhodecode.lib.middleware import csrf
50 50 from rhodecode.lib.middleware.appenlight import wrap_in_appenlight_if_enabled
51 51 from rhodecode.lib.middleware.disable_vcs import DisableVCSPagesWrapper
52 52 from rhodecode.lib.middleware.https_fixup import HttpsFixup
53 53 from rhodecode.lib.middleware.vcs import VCSMiddleware
54 54 from rhodecode.lib.plugins.utils import register_rhodecode_plugin
55 55 from rhodecode.subscribers import scan_repositories_if_enabled
56 56
57 57
58 58 log = logging.getLogger(__name__)
59 59
60 60
61 61 # this is used to avoid avoid the route lookup overhead in routesmiddleware
62 62 # for certain routes which won't go to pylons to - eg. static files, debugger
63 63 # it is only needed for the pylons migration and can be removed once complete
64 64 class SkippableRoutesMiddleware(RoutesMiddleware):
65 65 """ Routes middleware that allows you to skip prefixes """
66 66
67 67 def __init__(self, *args, **kw):
68 68 self.skip_prefixes = kw.pop('skip_prefixes', [])
69 69 super(SkippableRoutesMiddleware, self).__init__(*args, **kw)
70 70
71 71 def __call__(self, environ, start_response):
72 72 for prefix in self.skip_prefixes:
73 73 if environ['PATH_INFO'].startswith(prefix):
74 74 # added to avoid the case when a missing /_static route falls
75 75 # through to pylons and causes an exception as pylons is
76 76 # expecting wsgiorg.routingargs to be set in the environ
77 77 # by RoutesMiddleware.
78 78 if 'wsgiorg.routing_args' not in environ:
79 79 environ['wsgiorg.routing_args'] = (None, {})
80 80 return self.app(environ, start_response)
81 81
82 82 return super(SkippableRoutesMiddleware, self).__call__(
83 83 environ, start_response)
84 84
85 85
86 86 def make_app(global_conf, full_stack=True, static_files=True, **app_conf):
87 87 """Create a Pylons WSGI application and return it
88 88
89 89 ``global_conf``
90 90 The inherited configuration for this application. Normally from
91 91 the [DEFAULT] section of the Paste ini file.
92 92
93 93 ``full_stack``
94 94 Whether or not this application provides a full WSGI stack (by
95 95 default, meaning it handles its own exceptions and errors).
96 96 Disable full_stack when this application is "managed" by
97 97 another WSGI middleware.
98 98
99 99 ``app_conf``
100 100 The application's local configuration. Normally specified in
101 101 the [app:<name>] section of the Paste ini file (where <name>
102 102 defaults to main).
103 103
104 104 """
105 105 # Apply compatibility patches
106 106 patches.kombu_1_5_1_python_2_7_11()
107 107 patches.inspect_getargspec()
108 108
109 109 # Configure the Pylons environment
110 110 config = load_environment(global_conf, app_conf)
111 111
112 112 # The Pylons WSGI app
113 113 app = PylonsApp(config=config)
114 114 if rhodecode.is_test:
115 115 app = csrf.CSRFDetector(app)
116 116
117 117 expected_origin = config.get('expected_origin')
118 118 if expected_origin:
119 119 # The API can be accessed from other Origins.
120 120 app = csrf.OriginChecker(app, expected_origin,
121 121 skip_urls=[routes.util.url_for('api')])
122 122
123 123
124 124 if asbool(full_stack):
125 125
126 126 # Appenlight monitoring and error handler
127 127 app, appenlight_client = wrap_in_appenlight_if_enabled(app, config)
128 128
129 129 # Establish the Registry for this application
130 130 app = RegistryManager(app)
131 131
132 132 app.config = config
133 133
134 134 return app
135 135
136 136
137 137 def make_pyramid_app(global_config, **settings):
138 138 """
139 139 Constructs the WSGI application based on Pyramid and wraps the Pylons based
140 140 application.
141 141
142 142 Specials:
143 143
144 144 * We migrate from Pylons to Pyramid. While doing this, we keep both
145 145 frameworks functional. This involves moving some WSGI middlewares around
146 146 and providing access to some data internals, so that the old code is
147 147 still functional.
148 148
149 149 * The application can also be integrated like a plugin via the call to
150 150 `includeme`. This is accompanied with the other utility functions which
151 151 are called. Changing this should be done with great care to not break
152 152 cases when these fragments are assembled from another place.
153 153
154 154 """
155 155 # The edition string should be available in pylons too, so we add it here
156 156 # before copying the settings.
157 157 settings.setdefault('rhodecode.edition', 'Community Edition')
158 158
159 159 # As long as our Pylons application does expect "unprepared" settings, make
160 160 # sure that we keep an unmodified copy. This avoids unintentional change of
161 161 # behavior in the old application.
162 162 settings_pylons = settings.copy()
163 163
164 164 sanitize_settings_and_apply_defaults(settings)
165 165 config = Configurator(settings=settings)
166 166 add_pylons_compat_data(config.registry, global_config, settings_pylons)
167 167
168 168 load_pyramid_environment(global_config, settings)
169 169
170 170 includeme_first(config)
171 171 includeme(config)
172 172 pyramid_app = config.make_wsgi_app()
173 173 pyramid_app = wrap_app_in_wsgi_middlewares(pyramid_app, config)
174 174 return pyramid_app
175 175
176 176
177 177 def make_not_found_view(config):
178 178 """
179 179 This creates the view shich should be registered as not-found-view to
180 180 pyramid. Basically it contains of the old pylons app, converted to a view.
181 181 Additionally it is wrapped by some other middlewares.
182 182 """
183 183 settings = config.registry.settings
184 184
185 185 # Make pylons app from unprepared settings.
186 186 pylons_app = make_app(
187 187 config.registry._pylons_compat_global_config,
188 188 **config.registry._pylons_compat_settings)
189 189 config.registry._pylons_compat_config = pylons_app.config
190 190
191 191 # The VCSMiddleware shall operate like a fallback if pyramid doesn't find
192 192 # a view to handle the request. Therefore we wrap it around the pylons app
193 193 # and it will be added as not found view.
194 194 if settings['vcs.server.enable']:
195 195 pylons_app = VCSMiddleware(
196 196 pylons_app, settings, None, registry=config.registry)
197 197
198 198 pylons_app_as_view = wsgiapp(pylons_app)
199 199
200 200 # Protect from VCS Server error related pages when server is not available
201 201 vcs_server_enabled = asbool(settings.get('vcs.server.enable', 'true'))
202 202 if not vcs_server_enabled:
203 203 pylons_app_as_view = DisableVCSPagesWrapper(pylons_app_as_view)
204 204
205 205 def pylons_app_with_error_handler(context, request):
206 206 """
207 207 Handle exceptions from rc pylons app:
208 208
209 209 - old webob type exceptions get converted to pyramid exceptions
210 210 - pyramid exceptions are passed to the error handler view
211 211 """
212 212 try:
213 213 response = pylons_app_as_view(context, request)
214 214 if 400 <= response.status_int <= 599: # webob type error responses
215 215 return error_handler(
216 216 webob_to_pyramid_http_response(response), request)
217 217 except HTTPError as e: # pyramid type exceptions
218 218 return error_handler(e, request)
219 219 except Exception:
220 220 if settings.get('debugtoolbar.enabled', False):
221 221 raise
222 222 return error_handler(HTTPInternalServerError(), request)
223 223 return response
224 224
225 225 return pylons_app_with_error_handler
226 226
227 227
228 228 def add_pylons_compat_data(registry, global_config, settings):
229 229 """
230 230 Attach data to the registry to support the Pylons integration.
231 231 """
232 232 registry._pylons_compat_global_config = global_config
233 233 registry._pylons_compat_settings = settings
234 234
235 235
236 236 def webob_to_pyramid_http_response(webob_response):
237 237 ResponseClass = httpexceptions.status_map[webob_response.status_int]
238 238 pyramid_response = ResponseClass(webob_response.status)
239 239 pyramid_response.status = webob_response.status
240 240 pyramid_response.headers.update(webob_response.headers)
241 241 if pyramid_response.headers['content-type'] == 'text/html':
242 242 pyramid_response.headers['content-type'] = 'text/html; charset=UTF-8'
243 243 return pyramid_response
244 244
245 245
246 246 def error_handler(exception, request):
247 247 # TODO: dan: replace the old pylons error controller with this
248 248 from rhodecode.model.settings import SettingsModel
249 249 from rhodecode.lib.utils2 import AttributeDict
250 250
251 251 try:
252 252 rc_config = SettingsModel().get_all_settings()
253 253 except Exception:
254 254 log.exception('failed to fetch settings')
255 255 rc_config = {}
256 256
257 257 base_response = HTTPInternalServerError()
258 258 # prefer original exception for the response since it may have headers set
259 259 if isinstance(exception, HTTPError):
260 260 base_response = exception
261 261
262 262 c = AttributeDict()
263 263 c.error_message = base_response.status
264 264 c.error_explanation = base_response.explanation or str(base_response)
265 265 c.visual = AttributeDict()
266 266
267 267 c.visual.rhodecode_support_url = (
268 268 request.registry.settings.get('rhodecode_support_url') or
269 269 request.route_url('rhodecode_support')
270 270 )
271 271 c.redirect_time = 0
272 272 c.rhodecode_name = rc_config.get('rhodecode_title', '')
273 273 if not c.rhodecode_name:
274 274 c.rhodecode_name = 'Rhodecode'
275 275
276 276 response = render_to_response(
277 277 '/errors/error_document.html', {'c': c}, request=request,
278 278 response=base_response)
279 279
280 280 return response
281 281
282 282
283 283 def includeme(config):
284 284 settings = config.registry.settings
285 285
286 286 # plugin information
287 287 config.registry.rhodecode_plugins = OrderedDict()
288 288
289 289 config.add_directive(
290 290 'register_rhodecode_plugin', register_rhodecode_plugin)
291 291
292 292 if asbool(settings.get('appenlight', 'false')):
293 293 config.include('appenlight_client.ext.pyramid_tween')
294 294
295 295 # Includes which are required. The application would fail without them.
296 296 config.include('pyramid_mako')
297 297 config.include('pyramid_beaker')
298 298 config.include('rhodecode.channelstream')
299 299 config.include('rhodecode.admin')
300 300 config.include('rhodecode.authentication')
301 301 config.include('rhodecode.integrations')
302 302 config.include('rhodecode.login')
303 303 config.include('rhodecode.tweens')
304 304 config.include('rhodecode.api')
305 305 config.include('rhodecode.svn_support')
306 306 config.add_route(
307 307 'rhodecode_support', 'https://rhodecode.com/help/', static=True)
308 308
309 309 # Add subscribers.
310 310 config.add_subscriber(scan_repositories_if_enabled, ApplicationCreated)
311 311
312 312 # Set the authorization policy.
313 313 authz_policy = ACLAuthorizationPolicy()
314 314 config.set_authorization_policy(authz_policy)
315 315
316 316 # Set the default renderer for HTML templates to mako.
317 317 config.add_mako_renderer('.html')
318 318
319 319 # include RhodeCode plugins
320 320 includes = aslist(settings.get('rhodecode.includes', []))
321 321 for inc in includes:
322 322 config.include(inc)
323 323
324 324 # This is the glue which allows us to migrate in chunks. By registering the
325 325 # pylons based application as the "Not Found" view in Pyramid, we will
326 326 # fallback to the old application each time the new one does not yet know
327 327 # how to handle a request.
328 328 config.add_notfound_view(make_not_found_view(config))
329 329
330 330 if not settings.get('debugtoolbar.enabled', False):
331 331 # if no toolbar, then any exception gets caught and rendered
332 332 config.add_view(error_handler, context=Exception)
333 333
334 334 config.add_view(error_handler, context=HTTPError)
335 335
336 336
337 337 def includeme_first(config):
338 338 # redirect automatic browser favicon.ico requests to correct place
339 339 def favicon_redirect(context, request):
340 340 return redirect(
341 341 request.static_path('rhodecode:public/images/favicon.ico'))
342 342
343 343 config.add_view(favicon_redirect, route_name='favicon')
344 344 config.add_route('favicon', '/favicon.ico')
345 345
346 346 config.add_static_view(
347 347 '_static/deform', 'deform:static')
348 348 config.add_static_view(
349 349 '_static/rhodecode', path='rhodecode:public', cache_max_age=3600 * 24)
350 350
351 351 def wrap_app_in_wsgi_middlewares(pyramid_app, config):
352 352 """
353 353 Apply outer WSGI middlewares around the application.
354 354
355 355 Part of this has been moved up from the Pylons layer, so that the
356 356 data is also available if old Pylons code is hit through an already ported
357 357 view.
358 358 """
359 359 settings = config.registry.settings
360 360
361 361 # enable https redirects based on HTTP_X_URL_SCHEME set by proxy
362 362 pyramid_app = HttpsFixup(pyramid_app, settings)
363 363
364 364 # Add RoutesMiddleware to support the pylons compatibility tween during
365 365 # migration to pyramid.
366 366 pyramid_app = SkippableRoutesMiddleware(
367 367 pyramid_app, config.registry._pylons_compat_config['routes.map'],
368 368 skip_prefixes=(STATIC_FILE_PREFIX, '/_debug_toolbar'))
369 369
370 370 if asbool(settings.get('appenlight', 'false')):
371 371 pyramid_app, _ = wrap_in_appenlight_if_enabled(
372 372 pyramid_app, config.registry._pylons_compat_config)
373 373
374 374 if asbool(settings.get('gzip_responses', 'true')):
375 375 pyramid_app = make_gzip_middleware(
376 376 pyramid_app, settings, compress_level=1)
377 377
378 378 return pyramid_app
379 379
380 380
381 381 def sanitize_settings_and_apply_defaults(settings):
382 382 """
383 383 Applies settings defaults and does all type conversion.
384 384
385 385 We would move all settings parsing and preparation into this place, so that
386 386 we have only one place left which deals with this part. The remaining parts
387 387 of the application would start to rely fully on well prepared settings.
388 388
389 389 This piece would later be split up per topic to avoid a big fat monster
390 390 function.
391 391 """
392 392
393 393 # Pyramid's mako renderer has to search in the templates folder so that the
394 394 # old templates still work. Ported and new templates are expected to use
395 395 # real asset specifications for the includes.
396 396 mako_directories = settings.setdefault('mako.directories', [
397 397 # Base templates of the original Pylons application
398 398 'rhodecode:templates',
399 399 ])
400 400 log.debug(
401 401 "Using the following Mako template directories: %s",
402 402 mako_directories)
403 403
404 404 # Default includes, possible to change as a user
405 405 pyramid_includes = settings.setdefault('pyramid.includes', [
406 406 'rhodecode.lib.middleware.request_wrapper',
407 407 ])
408 408 log.debug(
409 409 "Using the following pyramid.includes: %s",
410 410 pyramid_includes)
411 411
412 412 # TODO: johbo: Re-think this, usually the call to config.include
413 413 # should allow to pass in a prefix.
414 414 settings.setdefault('rhodecode.api.url', '/_admin/api')
415 415
416 # Set the default encoding.
417 _list_setting(settings, 'default_encoding', 'UTF-8')
418
416 419 _bool_setting(settings, 'is_test', 'false')
417 420
418 421 # Call split out functions that sanitize settings for each topic.
419 422 _sanitize_vcs_settings(settings)
420 423
421 424 return settings
422 425
423 426
424 427 def _sanitize_vcs_settings(settings):
425 428 """
426 429 Applies settings defaults and does type conversion for all VCS related
427 430 settings.
428 431 """
429 432 _string_setting(settings, 'vcs.svn.compatible_version', '')
430 433 _string_setting(settings, 'git_rev_filter', '--all')
431 434 _string_setting(settings, 'vcs.hooks.protocol', 'pyro4')
432 435 _string_setting(settings, 'vcs.server', '')
433 436 _string_setting(settings, 'vcs.server.log_level', 'debug')
434 437 _string_setting(settings, 'vcs.server.protocol', 'pyro4')
435 438 _bool_setting(settings, 'startup.import_repos', 'false')
436 439 _bool_setting(settings, 'vcs.hooks.direct_calls', 'false')
437 440 _bool_setting(settings, 'vcs.server.enable', 'true')
438 441 _bool_setting(settings, 'vcs.start_server', 'false')
439 442 _list_setting(settings, 'vcs.backends', 'hg, git, svn')
440 443
441 444
442 445 def _bool_setting(settings, name, default):
443 446 settings[name] = asbool(settings.get(name, default))
444 447
445 448
446 449 def _list_setting(settings, name, default):
447 450 raw_value = settings.get(name, default)
448 451
449 452 # Check if we get a setting with the old syntax (comma separated).
450 453 if ',' in raw_value:
451 454 raw_value = raw_value.replace(',', ' ')
452 455
453 456 settings[name] = aslist(raw_value)
454 457
455 458
456 459 def _string_setting(settings, name, default):
457 460 settings[name] = settings.get(name, default).lower()
General Comments 0
You need to be logged in to leave comments. Login now