| # Licensed to the Apache Software Foundation (ASF) under one |
| # or more contributor license agreements. See the NOTICE file |
| # distributed with this work for additional information |
| # regarding copyright ownership. The ASF licenses this file |
| # to you under the Apache License, Version 2.0 (the |
| # "License"); you may not use this file except in compliance |
| # with the License. You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, |
| # software distributed under the License is distributed on an |
| # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| # KIND, either express or implied. See the License for the |
| # specific language governing permissions and limitations |
| # under the License. |
| |
| import re |
| |
| import webob |
| import tg.decorators |
| from decorator import decorator |
| from tg import request |
| import mock |
| import json |
| |
| from allura.lib import helpers as h |
| import six |
| |
| _patched = False |
| def apply(): |
| global _patched # noqa: PLW0603 |
| if _patched: |
| return |
| _patched = True |
| |
| old_lookup_template_engine = tg.decorators.Decoration.lookup_template_engine |
| |
| @h.monkeypatch(tg.decorators.Decoration) |
| def lookup_template_engine(self, request): |
| '''Wrapper to handle totally borked-up HTTP-ACCEPT headers''' |
| try: |
| return old_lookup_template_engine(self, request) |
| except Exception: |
| pass |
| environ = dict(request.environ, HTTP_ACCEPT='*/*') |
| request = webob.Request(environ) |
| return old_lookup_template_engine(self, request) |
| |
| @h.monkeypatch(tg, tg.decorators) |
| def override_template(controller, template): |
| '''Copy-pasted patch to allow multiple colons in a template spec''' |
| if hasattr(controller, 'decoration'): |
| decoration = controller.decoration |
| else: |
| return |
| if hasattr(decoration, 'engines'): |
| engines = decoration.engines |
| else: |
| return |
| |
| for content_type, content_engine in engines.items(): |
| template = template.split(':', 1) |
| template.extend(content_engine[2:]) |
| try: |
| override_mapping = request._override_mapping |
| except AttributeError: |
| override_mapping = request._override_mapping = {} |
| override_mapping[controller.__func__] = {content_type: template} |
| |
| @h.monkeypatch(tg, tg.decorators) |
| @decorator |
| def without_trailing_slash(func, *args, **kwargs): |
| '''Monkey-patched to use 301 redirects for SEO, and handle query strings''' |
| __traceback_hide__ = 'before_and_this' # for paste/werkzeug shorter traces |
| response_type = getattr(request, 'response_type', None) |
| if (request.method == 'GET' and request.path.endswith('/') and not response_type): |
| location = request.path_url[:-1] |
| if request.query_string: |
| location += '?' + request.query_string |
| raise webob.exc.HTTPMovedPermanently(location=location) |
| return func(*args, **kwargs) |
| |
| @h.monkeypatch(tg, tg.decorators) |
| @decorator |
| def with_trailing_slash(func, *args, **kwargs): |
| '''Monkey-patched to use 301 redirects for SEO, and handle query strings''' |
| __traceback_hide__ = 'before_and_this' # for paste/werkzeug shorter traces |
| response_type = getattr(request, 'response_type', None) |
| if (request.method == 'GET' and not request.path.endswith('/') and not response_type): |
| location = request.path_url + '/' |
| if request.query_string: |
| location += '?' + request.query_string |
| raise webob.exc.HTTPMovedPermanently(location=location) |
| return func(*args, **kwargs) |
| |
| # http://blog.watchfire.com/wfblog/2011/10/json-based-xss-exploitation.html |
| # change < to its unicode escape when rendering JSON out of turbogears |
| # This is to avoid IE9 and earlier, which don't know the json content type |
| # and may attempt to render JSON data as HTML if the URL ends in .html |
| original_tg_jsonify_JSONEncoder_encode = tg.jsonify.JSONEncoder.encode |
| |
| @h.monkeypatch(tg.jsonify.JSONEncoder) |
| def encode(self, o): |
| return original_tg_jsonify_JSONEncoder_encode(self, o).replace('<', r'\u003C') |
| |
| |
| old_controller_call = tg.controllers.DecoratedController._call |
| |
| |
| def newrelic(): |
| @h.monkeypatch(tg.controllers.DecoratedController, |
| tg.controllers.decoratedcontroller.DecoratedController) |
| def _call(self, controller, *args, **kwargs): |
| '''Set NewRelic transaction name to actual controller name''' |
| __traceback_hide__ = 'before_and_this' # for paste/werkzeug shorter traces |
| import newrelic.agent |
| controller_name = newrelic.agent.callable_name(controller) |
| # https://docs.newrelic.com/docs/apm/agents/python-agent/python-agent-api/settransactionname-python-agent-api/ |
| # if a second internal request for /error/document happens, use a lower (1) priority so original name stays |
| name_priority = 1 if 'ErrorController' in controller_name else 2 |
| newrelic.agent.set_transaction_name(controller_name, priority=name_priority) |
| return old_controller_call(self, controller, *args, **kwargs) |
| |
| import newrelic.api.error_trace |
| import newrelic.api.function_trace |
| # These are based on newrelic/hooks/framework_pylons.py since TG is similar to Pylons |
| # capture exceptions: |
| newrelic.api.error_trace.wrap_error_trace('tg.wsgiapp', 'TGApp.__call__') |
| # record as its own component in transaction breakdown; should help distinguish middleware vs controller time |
| newrelic.api.function_trace.wrap_function_trace('tg.controllers.tgcontroller', 'TGController.__call__') |