def process_exception(self, request, exception): '''Rewrite the URL based on settings.APPEND_SLASH and the urlconf of the current page.''' if isinstance(exception, Http404): if settings.APPEND_SLASH and not request.path_info.endswith('/'): page = request.pages.current if page: script_name = page.get_absolute_url()[:-1] path_info = request.path[len(script_name):] urlconf = getattr(page.content, 'urlconf', None) if hasattr( page, 'content') else None # Check if the URL with a slash appended is resolved by the current page's urlconf if (is_valid_path(path_info, urlconf) or not is_valid_path(f'{path_info}/', urlconf)): # Check if the URL with a slash appended resolves for something other than a page match = resolve(f'{path_info}/', getattr(request, 'urlconf', None)) if getattr(match.func, 'view_class', None) is PageDispatcherView: # Couldn't find any view that would be resolved for this URL # No point redirecting to a URL that will 404 return None new_path = request.get_full_path(force_append_slash=True) # Prevent construction of scheme relative urls. new_path = escape_leading_slashes(new_path) return HttpResponsePermanentRedirect(new_path)
def page_view(request, path): m = find_route(path) if m is None: if settings.APPEND_SLASH and not path.endswith('/'): m = find_route(f'{path}/') if m is not None: new_path = request.get_full_path(force_append_slash=True) new_path = escape_leading_slashes(new_path) return HttpResponsePermanentRedirect(new_path) raise Http404 data = None if m.route.endpoint: endpoint, params = m.build(allow_preview=settings.ALLOW_PREVIEW) r = requests.get(endpoint, params=params) if r.status_code == 404: raise Http404 r.raise_for_status() data = r.json() context = { 'data': data, } return TemplateResponse( request, m.route.template_name, context=context, content_type=m.route.content_type, )
def test(self): tests = ( ('//example.com', '/%2Fexample.com'), ('//', '/%2F'), ) for url, expected in tests: self.assertEqual(http.escape_leading_slashes(url), expected)
def test(self): tests = ( ('//example.com', '/%2Fexample.com'), ('//', '/%2F'), ) for url, expected in tests: with self.subTest(url=url): self.assertEqual(escape_leading_slashes(url), expected)
def get_full_path_with_slash(self, request): """ Return the full path of the request with a trailing slash appended without Exception in Debug mode """ new_path = request.get_full_path(force_append_slash=True) # Prevent construction of scheme relative urls. new_path = escape_leading_slashes(new_path) return new_path
def test(self): tests = ( ('//example.com', '/%2Fexample.com'), ('//', '/%2F'), ) for url, expected in tests: with self.subTest(url=url): self.assertEqual(escape_leading_slashes(url), expected)
def test(self): tests = ( ("//example.com", "/%2Fexample.com"), ("//", "/%2F"), ) for url, expected in tests: with self.subTest(url=url): self.assertEqual(escape_leading_slashes(url), expected)
def __call__(self, request): response = self.get_response(request) # This is based on APPEND_SLASH handling in Django if response.status_code == 404 and self.should_redirect_with_slash( request): new_path = request.get_full_path(force_append_slash=True) # Prevent construction of scheme relative urls. new_path = escape_leading_slashes(new_path) return HttpResponsePermanentRedirect(new_path) return response
def reverse_route(route_name, args=None, kwargs=None): args = args or [] kwargs = kwargs or {} prefix = get_script_prefix() try: route = Route.objects.filter(name=route_name).get() except Route.DoesNotExist: msg = ("Reverse for '%s' not found." % (route_name)) raise NoReverseMatch(msg) converters = _route_to_regex(route.path)[1] for result, params in normalize(_route_to_regex(route.path)[0]): if args: if len(args) != len(params): continue candidate_subs = dict(zip(params, args)) else: if set(kwargs).symmetric_difference(params): continue candidate_subs = kwargs text_candidate_subs = {} for k, v in candidate_subs.items(): if k in converters: text_candidate_subs[k] = converters[k].to_url(v) else: text_candidate_subs[k] = str(v) candidate_pat = prefix.replace('%', '%%') + result url = urllib.parse.quote(candidate_pat % text_candidate_subs, safe=RFC3986_SUBDELIMS + '/~:@') return escape_leading_slashes(url) if args: arg_msg = "arguments '%s'" % (args, ) elif kwargs: arg_msg = "keyword arguments '%s'" % (kwargs, ) else: arg_msg = "no arguments" msg = ("Reverse for '%s' with %s not matched." % (route_name, arg_msg)) raise NoReverseMatch(msg)
def get_full_path_with_slash(self, request): """ Return the full path of the request with a trailing slash appended. Raise a RuntimeError if settings.DEBUG is True and request.method is POST, PUT, or PATCH. """ new_path = request.get_full_path(force_append_slash=True) # Prevent construction of scheme relative urls. new_path = escape_leading_slashes(new_path) if settings.DEBUG and request.method in ("POST", "PUT", "PATCH"): raise RuntimeError( "You called this URL via %(method)s, but the URL doesn't end " "in a slash and you have APPEND_SLASH set. Django can't " "redirect to the slash URL while maintaining %(method)s data. " "Change your form to point to %(url)s (note the trailing " "slash), or set APPEND_SLASH=False in your Django settings." % {"method": request.method, "url": request.get_host() + new_path} ) return new_path
def get_full_path_with_slash(self, request): """ Return the full path of the request with a trailing slash appended. Raise a RuntimeError if settings.DEBUG is True and request.method is POST, PUT, or PATCH. """ new_path = request.get_full_path(force_append_slash=True) # Prevent construction of scheme relative urls. new_path = escape_leading_slashes(new_path) if settings.DEBUG and request.method in ('POST', 'PUT', 'PATCH'): raise RuntimeError( "You called this URL via %(method)s, but the URL doesn't end " "in a slash and you have APPEND_SLASH set. Django can't " "redirect to the slash URL while maintaining %(method)s data. " "Change your form to point to %(url)s (note the trailing " "slash), or set APPEND_SLASH=False in your Django settings." % { 'method': request.method, 'url': request.get_host() + new_path, } ) return new_path
def _reverse_with_prefix(self, lookup_view, _prefix, *args, **kwargs): if args and kwargs: raise ValueError( "Don't mix *args and **kwargs in call to reverse()!") if not self._populated: self._populate() possibilities = self.reverse_dict.getlist(lookup_view) for possibility, pattern, defaults, converters in possibilities: for result, params in possibility: if args: if len(args) != len(params): continue candidate_subs = dict(zip(params, args)) else: if set(kwargs).symmetric_difference(params).difference( defaults): continue if any(kwargs.get(k, v) != v for k, v in defaults.items()): continue candidate_subs = kwargs # Convert the candidate subs to text using Converter.to_url(). text_candidate_subs = {} match = True for k, v in candidate_subs.items(): if k in converters: try: text_candidate_subs[k] = converters[k].to_url(v) except ValueError: match = False break else: text_candidate_subs[k] = str(v) if not match: continue # WSGI provides decoded URLs, without %xx escapes, and the URL # resolver operates on such URLs. First substitute arguments # without quoting to build a decoded URL and look for a match. # Then, if we have a match, redo the substitution with quoted # arguments in order to return a properly encoded URL. candidate_pat = _prefix.replace('%', '%%') + result if re.search('^%s%s' % (re.escape(_prefix), pattern), candidate_pat % text_candidate_subs): # safe characters from `pchar` definition of RFC 3986 url = quote(candidate_pat % text_candidate_subs, safe=RFC3986_SUBDELIMS + '/~:@') # Don't allow construction of scheme relative urls. return escape_leading_slashes(url) # lookup_view can be URL name or callable, but callables are not # friendly in error messages. m = getattr(lookup_view, '__module__', None) n = getattr(lookup_view, '__name__', None) if m is not None and n is not None: lookup_view_s = "%s.%s" % (m, n) else: lookup_view_s = lookup_view patterns = [pattern for (_, pattern, _, _) in possibilities] if patterns: if args: arg_msg = "arguments '%s'" % (args, ) elif kwargs: arg_msg = "keyword arguments '%s'" % kwargs else: arg_msg = "no arguments" msg = ( "Reverse for '%s' with %s not found. %d pattern(s) tried: %s" % (lookup_view_s, arg_msg, len(patterns), patterns)) else: msg = ("Reverse for '%(view)s' not found. '%(view)s' is not " "a valid view function or pattern name." % { 'view': lookup_view_s }) raise NoReverseMatch(msg)
def _reverse_with_prefix(self, lookup_view, _prefix, *args, **kwargs): if args and kwargs: raise ValueError("Don't mix *args and **kwargs in call to reverse()!") if not self._populated: self._populate() possibilities = self.reverse_dict.getlist(lookup_view) for possibility, pattern, defaults, converters in possibilities: for result, params in possibility: if args: if len(args) != len(params): continue candidate_subs = dict(zip(params, args)) else: if set(kwargs).symmetric_difference(params).difference(defaults): continue if any(kwargs.get(k, v) != v for k, v in defaults.items()): continue candidate_subs = kwargs # Convert the candidate subs to text using Converter.to_url(). text_candidate_subs = {} for k, v in candidate_subs.items(): if k in converters: text_candidate_subs[k] = converters[k].to_url(v) else: text_candidate_subs[k] = str(v) # WSGI provides decoded URLs, without %xx escapes, and the URL # resolver operates on such URLs. First substitute arguments # without quoting to build a decoded URL and look for a match. # Then, if we have a match, redo the substitution with quoted # arguments in order to return a properly encoded URL. candidate_pat = _prefix.replace('%', '%%') + result if re.search('^%s%s' % (re.escape(_prefix), pattern), candidate_pat % text_candidate_subs): # safe characters from `pchar` definition of RFC 3986 url = quote(candidate_pat % text_candidate_subs, safe=RFC3986_SUBDELIMS + '/~:@') # Don't allow construction of scheme relative urls. return escape_leading_slashes(url) # lookup_view can be URL name or callable, but callables are not # friendly in error messages. m = getattr(lookup_view, '__module__', None) n = getattr(lookup_view, '__name__', None) if m is not None and n is not None: lookup_view_s = "%s.%s" % (m, n) else: lookup_view_s = lookup_view patterns = [pattern for (_, pattern, _, _) in possibilities] if patterns: if args: arg_msg = "arguments '%s'" % (args,) elif kwargs: arg_msg = "keyword arguments '%s'" % (kwargs,) else: arg_msg = "no arguments" msg = ( "Reverse for '%s' with %s not found. %d pattern(s) tried: %s" % (lookup_view_s, arg_msg, len(patterns), patterns) ) else: msg = ( "Reverse for '%(view)s' not found. '%(view)s' is not " "a valid view function or pattern name." % {'view': lookup_view_s} ) raise NoReverseMatch(msg)
def get_full_path_with_slash(self, request): new_path = request.get_full_path(force_append_slash=True) return escape_leading_slashes(new_path)
def test_escape_leading_slashes(inp): escape_leading_slashes(inp)