blob: 964624d0bc63744a9ccaa9fcf3f04e5b574d585a [file] [log] [blame]
# 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__')