~ubuntu-branches/ubuntu/saucy/python-django/saucy-updates

« back to all changes in this revision

Viewing changes to .pc/CVE-2014-0472-regression.patch/django/core/urlresolvers.py

  • Committer: Package Import Robot
  • Author(s): Marc Deslauriers
  • Date: 2014-04-22 23:12:52 UTC
  • Revision ID: package-import@ubuntu.com-20140422231252-8cu8s89mk8mik8ac
Tags: 1.5.4-1ubuntu1.2
* SECURITY REGRESSION: security fix regression when a view is a partial
  (LP: #1311433)
  - debian/patches/CVE-2014-0472-regression.patch: create the lookup_str
    from the original function whenever a partial is provided as an
    argument to a url pattern in django/core/urlresolvers.py,
    added tests to tests/regressiontests/urlpatterns_reverse/urls.py,
    tests/regressiontests/urlpatterns_reverse/views.py.
  - CVE-2014-0472

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
"""
 
2
This module converts requested URLs to callback view functions.
 
3
 
 
4
RegexURLResolver is the main class here. Its resolve() method takes a URL (as
 
5
a string) and returns a tuple in this format:
 
6
 
 
7
    (view_function, function_args, function_kwargs)
 
8
"""
 
9
from __future__ import unicode_literals
 
10
 
 
11
import re
 
12
from threading import local
 
13
 
 
14
from django.http import Http404
 
15
from django.core.exceptions import ImproperlyConfigured, ViewDoesNotExist
 
16
from django.utils.datastructures import MultiValueDict
 
17
from django.utils.encoding import force_str, force_text, iri_to_uri
 
18
from django.utils.functional import memoize, lazy
 
19
from django.utils.http import urlquote
 
20
from django.utils.importlib import import_module
 
21
from django.utils.module_loading import module_has_submodule
 
22
from django.utils.regex_helper import normalize
 
23
from django.utils import six
 
24
from django.utils.translation import get_language
 
25
 
 
26
 
 
27
_resolver_cache = {} # Maps URLconf modules to RegexURLResolver instances.
 
28
_ns_resolver_cache = {} # Maps namespaces to RegexURLResolver instances.
 
29
_callable_cache = {} # Maps view and url pattern names to their view functions.
 
30
 
 
31
# SCRIPT_NAME prefixes for each thread are stored here. If there's no entry for
 
32
# the current thread (which is the only one we ever access), it is assumed to
 
33
# be empty.
 
34
_prefixes = local()
 
35
 
 
36
# Overridden URLconfs for each thread are stored here.
 
37
_urlconfs = local()
 
38
 
 
39
 
 
40
class ResolverMatch(object):
 
41
    def __init__(self, func, args, kwargs, url_name=None, app_name=None, namespaces=None):
 
42
        self.func = func
 
43
        self.args = args
 
44
        self.kwargs = kwargs
 
45
        self.app_name = app_name
 
46
        if namespaces:
 
47
            self.namespaces = [x for x in namespaces if x]
 
48
        else:
 
49
            self.namespaces = []
 
50
        if not url_name:
 
51
            if not hasattr(func, '__name__'):
 
52
                # An instance of a callable class
 
53
                url_name = '.'.join([func.__class__.__module__, func.__class__.__name__])
 
54
            else:
 
55
                # A function
 
56
                url_name = '.'.join([func.__module__, func.__name__])
 
57
        self.url_name = url_name
 
58
 
 
59
    @property
 
60
    def namespace(self):
 
61
        return ':'.join(self.namespaces)
 
62
 
 
63
    @property
 
64
    def view_name(self):
 
65
        return ':'.join([ x for x in [ self.namespace, self.url_name ]  if x ])
 
66
 
 
67
    def __getitem__(self, index):
 
68
        return (self.func, self.args, self.kwargs)[index]
 
69
 
 
70
    def __repr__(self):
 
71
        return "ResolverMatch(func=%s, args=%s, kwargs=%s, url_name='%s', app_name='%s', namespace='%s')" % (
 
72
            self.func, self.args, self.kwargs, self.url_name, self.app_name, self.namespace)
 
73
 
 
74
class Resolver404(Http404):
 
75
    pass
 
76
 
 
77
class NoReverseMatch(Exception):
 
78
    # Don't make this raise an error when used in a template.
 
79
    silent_variable_failure = True
 
80
 
 
81
def get_callable(lookup_view, can_fail=False):
 
82
    """
 
83
    Convert a string version of a function name to the callable object.
 
84
 
 
85
    If the lookup_view is not an import path, it is assumed to be a URL pattern
 
86
    label and the original string is returned.
 
87
 
 
88
    If can_fail is True, lookup_view might be a URL pattern label, so errors
 
89
    during the import fail and the string is returned.
 
90
    """
 
91
    if not callable(lookup_view):
 
92
        mod_name, func_name = get_mod_func(lookup_view)
 
93
        if func_name == '':
 
94
            return lookup_view
 
95
 
 
96
        try:
 
97
            mod = import_module(mod_name)
 
98
        except ImportError:
 
99
            parentmod, submod = get_mod_func(mod_name)
 
100
            if (not can_fail and submod != '' and
 
101
                    not module_has_submodule(import_module(parentmod), submod)):
 
102
                raise ViewDoesNotExist(
 
103
                    "Could not import %s. Parent module %s does not exist." %
 
104
                    (lookup_view, mod_name))
 
105
            if not can_fail:
 
106
                raise
 
107
        else:
 
108
            try:
 
109
                lookup_view = getattr(mod, func_name)
 
110
                if not callable(lookup_view):
 
111
                    raise ViewDoesNotExist(
 
112
                        "Could not import %s.%s. View is not callable." %
 
113
                        (mod_name, func_name))
 
114
            except AttributeError:
 
115
                if not can_fail:
 
116
                    raise ViewDoesNotExist(
 
117
                        "Could not import %s. View does not exist in module %s." %
 
118
                        (lookup_view, mod_name))
 
119
    return lookup_view
 
120
get_callable = memoize(get_callable, _callable_cache, 1)
 
121
 
 
122
def get_resolver(urlconf):
 
123
    if urlconf is None:
 
124
        from django.conf import settings
 
125
        urlconf = settings.ROOT_URLCONF
 
126
    return RegexURLResolver(r'^/', urlconf)
 
127
get_resolver = memoize(get_resolver, _resolver_cache, 1)
 
128
 
 
129
def get_ns_resolver(ns_pattern, resolver):
 
130
    # Build a namespaced resolver for the given parent urlconf pattern.
 
131
    # This makes it possible to have captured parameters in the parent
 
132
    # urlconf pattern.
 
133
    ns_resolver = RegexURLResolver(ns_pattern,
 
134
                                          resolver.url_patterns)
 
135
    return RegexURLResolver(r'^/', [ns_resolver])
 
136
get_ns_resolver = memoize(get_ns_resolver, _ns_resolver_cache, 2)
 
137
 
 
138
def get_mod_func(callback):
 
139
    # Converts 'django.views.news.stories.story_detail' to
 
140
    # ['django.views.news.stories', 'story_detail']
 
141
    try:
 
142
        dot = callback.rindex('.')
 
143
    except ValueError:
 
144
        return callback, ''
 
145
    return callback[:dot], callback[dot+1:]
 
146
 
 
147
class LocaleRegexProvider(object):
 
148
    """
 
149
    A mixin to provide a default regex property which can vary by active
 
150
    language.
 
151
 
 
152
    """
 
153
    def __init__(self, regex):
 
154
        # regex is either a string representing a regular expression, or a
 
155
        # translatable string (using ugettext_lazy) representing a regular
 
156
        # expression.
 
157
        self._regex = regex
 
158
        self._regex_dict = {}
 
159
 
 
160
 
 
161
    @property
 
162
    def regex(self):
 
163
        """
 
164
        Returns a compiled regular expression, depending upon the activated
 
165
        language-code.
 
166
        """
 
167
        language_code = get_language()
 
168
        if language_code not in self._regex_dict:
 
169
            if isinstance(self._regex, six.string_types):
 
170
                regex = self._regex
 
171
            else:
 
172
                regex = force_text(self._regex)
 
173
            try:
 
174
                compiled_regex = re.compile(regex, re.UNICODE)
 
175
            except re.error as e:
 
176
                raise ImproperlyConfigured(
 
177
                    '"%s" is not a valid regular expression: %s' %
 
178
                    (regex, six.text_type(e)))
 
179
 
 
180
            self._regex_dict[language_code] = compiled_regex
 
181
        return self._regex_dict[language_code]
 
182
 
 
183
 
 
184
class RegexURLPattern(LocaleRegexProvider):
 
185
    def __init__(self, regex, callback, default_args=None, name=None):
 
186
        LocaleRegexProvider.__init__(self, regex)
 
187
        # callback is either a string like 'foo.views.news.stories.story_detail'
 
188
        # which represents the path to a module and a view function name, or a
 
189
        # callable object (view).
 
190
        if callable(callback):
 
191
            self._callback = callback
 
192
        else:
 
193
            self._callback = None
 
194
            self._callback_str = callback
 
195
        self.default_args = default_args or {}
 
196
        self.name = name
 
197
 
 
198
    def __repr__(self):
 
199
        return force_str('<%s %s %s>' % (self.__class__.__name__, self.name, self.regex.pattern))
 
200
 
 
201
    def add_prefix(self, prefix):
 
202
        """
 
203
        Adds the prefix string to a string-based callback.
 
204
        """
 
205
        if not prefix or not hasattr(self, '_callback_str'):
 
206
            return
 
207
        self._callback_str = prefix + '.' + self._callback_str
 
208
 
 
209
    def resolve(self, path):
 
210
        match = self.regex.search(path)
 
211
        if match:
 
212
            # If there are any named groups, use those as kwargs, ignoring
 
213
            # non-named groups. Otherwise, pass all non-named arguments as
 
214
            # positional arguments.
 
215
            kwargs = match.groupdict()
 
216
            if kwargs:
 
217
                args = ()
 
218
            else:
 
219
                args = match.groups()
 
220
            # In both cases, pass any extra_kwargs as **kwargs.
 
221
            kwargs.update(self.default_args)
 
222
 
 
223
            return ResolverMatch(self.callback, args, kwargs, self.name)
 
224
 
 
225
    @property
 
226
    def callback(self):
 
227
        if self._callback is not None:
 
228
            return self._callback
 
229
 
 
230
        self._callback = get_callable(self._callback_str)
 
231
        return self._callback
 
232
 
 
233
class RegexURLResolver(LocaleRegexProvider):
 
234
    def __init__(self, regex, urlconf_name, default_kwargs=None, app_name=None, namespace=None):
 
235
        LocaleRegexProvider.__init__(self, regex)
 
236
        # urlconf_name is a string representing the module containing URLconfs.
 
237
        self.urlconf_name = urlconf_name
 
238
        if not isinstance(urlconf_name, six.string_types):
 
239
            self._urlconf_module = self.urlconf_name
 
240
        self.callback = None
 
241
        self.default_kwargs = default_kwargs or {}
 
242
        self.namespace = namespace
 
243
        self.app_name = app_name
 
244
        self._reverse_dict = {}
 
245
        self._namespace_dict = {}
 
246
        self._app_dict = {}
 
247
        # set of dotted paths to all functions and classes that are used in
 
248
        # urlpatterns
 
249
        self._callback_strs = set()
 
250
        self._populated = False
 
251
 
 
252
    def __repr__(self):
 
253
        if isinstance(self.urlconf_name, list) and len(self.urlconf_name):
 
254
            # Don't bother to output the whole list, it can be huge
 
255
            urlconf_repr = '<%s list>' % self.urlconf_name[0].__class__.__name__
 
256
        else:
 
257
            urlconf_repr = repr(self.urlconf_name)
 
258
        return str('<%s %s (%s:%s) %s>') % (
 
259
            self.__class__.__name__, urlconf_repr, self.app_name,
 
260
            self.namespace, self.regex.pattern)
 
261
 
 
262
    def _populate(self):
 
263
        lookups = MultiValueDict()
 
264
        namespaces = {}
 
265
        apps = {}
 
266
        language_code = get_language()
 
267
        for pattern in reversed(self.url_patterns):
 
268
            if hasattr(pattern, '_callback_str'):
 
269
                self._callback_strs.add(pattern._callback_str)
 
270
            elif hasattr(pattern, '_callback'):
 
271
                callback = pattern._callback
 
272
                if not hasattr(callback, '__name__'):
 
273
                    lookup_str = callback.__module__ + "." + callback.__class__.__name__
 
274
                else:
 
275
                    lookup_str = callback.__module__ + "." + callback.__name__
 
276
                self._callback_strs.add(lookup_str)
 
277
            p_pattern = pattern.regex.pattern
 
278
            if p_pattern.startswith('^'):
 
279
                p_pattern = p_pattern[1:]
 
280
            if isinstance(pattern, RegexURLResolver):
 
281
                if pattern.namespace:
 
282
                    namespaces[pattern.namespace] = (p_pattern, pattern)
 
283
                    if pattern.app_name:
 
284
                        apps.setdefault(pattern.app_name, []).append(pattern.namespace)
 
285
                else:
 
286
                    parent = normalize(pattern.regex.pattern)
 
287
                    for name in pattern.reverse_dict:
 
288
                        for matches, pat, defaults in pattern.reverse_dict.getlist(name):
 
289
                            new_matches = []
 
290
                            for piece, p_args in parent:
 
291
                                new_matches.extend([(piece + suffix, p_args + args) for (suffix, args) in matches])
 
292
                            lookups.appendlist(name, (new_matches, p_pattern + pat, dict(defaults, **pattern.default_kwargs)))
 
293
                    for namespace, (prefix, sub_pattern) in pattern.namespace_dict.items():
 
294
                        namespaces[namespace] = (p_pattern + prefix, sub_pattern)
 
295
                    for app_name, namespace_list in pattern.app_dict.items():
 
296
                        apps.setdefault(app_name, []).extend(namespace_list)
 
297
                    self._callback_strs.update(pattern._callback_strs)
 
298
            else:
 
299
                bits = normalize(p_pattern)
 
300
                lookups.appendlist(pattern.callback, (bits, p_pattern, pattern.default_args))
 
301
                if pattern.name is not None:
 
302
                    lookups.appendlist(pattern.name, (bits, p_pattern, pattern.default_args))
 
303
        self._reverse_dict[language_code] = lookups
 
304
        self._namespace_dict[language_code] = namespaces
 
305
        self._app_dict[language_code] = apps
 
306
        self._populated = True
 
307
 
 
308
    @property
 
309
    def reverse_dict(self):
 
310
        language_code = get_language()
 
311
        if language_code not in self._reverse_dict:
 
312
            self._populate()
 
313
        return self._reverse_dict[language_code]
 
314
 
 
315
    @property
 
316
    def namespace_dict(self):
 
317
        language_code = get_language()
 
318
        if language_code not in self._namespace_dict:
 
319
            self._populate()
 
320
        return self._namespace_dict[language_code]
 
321
 
 
322
    @property
 
323
    def app_dict(self):
 
324
        language_code = get_language()
 
325
        if language_code not in self._app_dict:
 
326
            self._populate()
 
327
        return self._app_dict[language_code]
 
328
 
 
329
    def resolve(self, path):
 
330
        tried = []
 
331
        match = self.regex.search(path)
 
332
        if match:
 
333
            new_path = path[match.end():]
 
334
            for pattern in self.url_patterns:
 
335
                try:
 
336
                    sub_match = pattern.resolve(new_path)
 
337
                except Resolver404 as e:
 
338
                    sub_tried = e.args[0].get('tried')
 
339
                    if sub_tried is not None:
 
340
                        tried.extend([[pattern] + t for t in sub_tried])
 
341
                    else:
 
342
                        tried.append([pattern])
 
343
                else:
 
344
                    if sub_match:
 
345
                        sub_match_dict = dict(match.groupdict(), **self.default_kwargs)
 
346
                        sub_match_dict.update(sub_match.kwargs)
 
347
                        return ResolverMatch(sub_match.func, sub_match.args, sub_match_dict, sub_match.url_name, self.app_name or sub_match.app_name, [self.namespace] + sub_match.namespaces)
 
348
                    tried.append([pattern])
 
349
            raise Resolver404({'tried': tried, 'path': new_path})
 
350
        raise Resolver404({'path' : path})
 
351
 
 
352
    @property
 
353
    def urlconf_module(self):
 
354
        try:
 
355
            return self._urlconf_module
 
356
        except AttributeError:
 
357
            self._urlconf_module = import_module(self.urlconf_name)
 
358
            return self._urlconf_module
 
359
 
 
360
    @property
 
361
    def url_patterns(self):
 
362
        patterns = getattr(self.urlconf_module, "urlpatterns", self.urlconf_module)
 
363
        try:
 
364
            iter(patterns)
 
365
        except TypeError:
 
366
            raise ImproperlyConfigured("The included urlconf %s doesn't have any patterns in it" % self.urlconf_name)
 
367
        return patterns
 
368
 
 
369
    def _resolve_special(self, view_type):
 
370
        callback = getattr(self.urlconf_module, 'handler%s' % view_type, None)
 
371
        if not callback:
 
372
            # No handler specified in file; use default
 
373
            # Lazy import, since django.urls imports this file
 
374
            from django.conf import urls
 
375
            callback = getattr(urls, 'handler%s' % view_type)
 
376
        return get_callable(callback), {}
 
377
 
 
378
    def resolve403(self):
 
379
        return self._resolve_special('403')
 
380
 
 
381
    def resolve404(self):
 
382
        return self._resolve_special('404')
 
383
 
 
384
    def resolve500(self):
 
385
        return self._resolve_special('500')
 
386
 
 
387
    def reverse(self, lookup_view, *args, **kwargs):
 
388
        return self._reverse_with_prefix(lookup_view, '', *args, **kwargs)
 
389
 
 
390
    def _reverse_with_prefix(self, lookup_view, _prefix, *args, **kwargs):
 
391
        if args and kwargs:
 
392
            raise ValueError("Don't mix *args and **kwargs in call to reverse()!")
 
393
 
 
394
        if not self._populated:
 
395
            self._populate()
 
396
 
 
397
        try:
 
398
            if lookup_view in self._callback_strs:
 
399
                lookup_view = get_callable(lookup_view, True)
 
400
        except (ImportError, AttributeError) as e:
 
401
            raise NoReverseMatch("Error importing '%s': %s." % (lookup_view, e))
 
402
        possibilities = self.reverse_dict.getlist(lookup_view)
 
403
 
 
404
        prefix_norm, prefix_args = normalize(urlquote(_prefix))[0]
 
405
        for possibility, pattern, defaults in possibilities:
 
406
            for result, params in possibility:
 
407
                if args:
 
408
                    if len(args) != len(params) + len(prefix_args):
 
409
                        continue
 
410
                    unicode_args = [force_text(val) for val in args]
 
411
                    candidate = (prefix_norm + result) % dict(zip(prefix_args + params, unicode_args))
 
412
                else:
 
413
                    if set(kwargs.keys()) | set(defaults.keys()) != set(params) | set(defaults.keys()) | set(prefix_args):
 
414
                        continue
 
415
                    matches = True
 
416
                    for k, v in defaults.items():
 
417
                        if kwargs.get(k, v) != v:
 
418
                            matches = False
 
419
                            break
 
420
                    if not matches:
 
421
                        continue
 
422
                    unicode_kwargs = dict([(k, force_text(v)) for (k, v) in kwargs.items()])
 
423
                    candidate = (prefix_norm.replace('%', '%%') + result) % unicode_kwargs
 
424
                if re.search('^%s%s' % (prefix_norm, pattern), candidate, re.UNICODE):
 
425
                    return candidate
 
426
        # lookup_view can be URL label, or dotted path, or callable, Any of
 
427
        # these can be passed in at the top, but callables are not friendly in
 
428
        # error messages.
 
429
        m = getattr(lookup_view, '__module__', None)
 
430
        n = getattr(lookup_view, '__name__', None)
 
431
        if m is not None and n is not None:
 
432
            lookup_view_s = "%s.%s" % (m, n)
 
433
        else:
 
434
            lookup_view_s = lookup_view
 
435
        raise NoReverseMatch("Reverse for '%s' with arguments '%s' and keyword "
 
436
                "arguments '%s' not found." % (lookup_view_s, args, kwargs))
 
437
 
 
438
class LocaleRegexURLResolver(RegexURLResolver):
 
439
    """
 
440
    A URL resolver that always matches the active language code as URL prefix.
 
441
 
 
442
    Rather than taking a regex argument, we just override the ``regex``
 
443
    function to always return the active language-code as regex.
 
444
    """
 
445
    def __init__(self, urlconf_name, default_kwargs=None, app_name=None, namespace=None):
 
446
        super(LocaleRegexURLResolver, self).__init__(
 
447
            None, urlconf_name, default_kwargs, app_name, namespace)
 
448
 
 
449
    @property
 
450
    def regex(self):
 
451
        language_code = get_language()
 
452
        if language_code not in self._regex_dict:
 
453
            regex_compiled = re.compile('^%s/' % language_code, re.UNICODE)
 
454
            self._regex_dict[language_code] = regex_compiled
 
455
        return self._regex_dict[language_code]
 
456
 
 
457
def resolve(path, urlconf=None):
 
458
    if urlconf is None:
 
459
        urlconf = get_urlconf()
 
460
    return get_resolver(urlconf).resolve(path)
 
461
 
 
462
def reverse(viewname, urlconf=None, args=None, kwargs=None, prefix=None, current_app=None):
 
463
    if urlconf is None:
 
464
        urlconf = get_urlconf()
 
465
    resolver = get_resolver(urlconf)
 
466
    args = args or []
 
467
    kwargs = kwargs or {}
 
468
 
 
469
    if prefix is None:
 
470
        prefix = get_script_prefix()
 
471
 
 
472
    if not isinstance(viewname, six.string_types):
 
473
        view = viewname
 
474
    else:
 
475
        parts = viewname.split(':')
 
476
        parts.reverse()
 
477
        view = parts[0]
 
478
        path = parts[1:]
 
479
 
 
480
        resolved_path = []
 
481
        ns_pattern = ''
 
482
        while path:
 
483
            ns = path.pop()
 
484
 
 
485
            # Lookup the name to see if it could be an app identifier
 
486
            try:
 
487
                app_list = resolver.app_dict[ns]
 
488
                # Yes! Path part matches an app in the current Resolver
 
489
                if current_app and current_app in app_list:
 
490
                    # If we are reversing for a particular app,
 
491
                    # use that namespace
 
492
                    ns = current_app
 
493
                elif ns not in app_list:
 
494
                    # The name isn't shared by one of the instances
 
495
                    # (i.e., the default) so just pick the first instance
 
496
                    # as the default.
 
497
                    ns = app_list[0]
 
498
            except KeyError:
 
499
                pass
 
500
 
 
501
            try:
 
502
                extra, resolver = resolver.namespace_dict[ns]
 
503
                resolved_path.append(ns)
 
504
                ns_pattern = ns_pattern + extra
 
505
            except KeyError as key:
 
506
                if resolved_path:
 
507
                    raise NoReverseMatch(
 
508
                        "%s is not a registered namespace inside '%s'" %
 
509
                        (key, ':'.join(resolved_path)))
 
510
                else:
 
511
                    raise NoReverseMatch("%s is not a registered namespace" %
 
512
                                         key)
 
513
        if ns_pattern:
 
514
            resolver = get_ns_resolver(ns_pattern, resolver)
 
515
 
 
516
    return iri_to_uri(resolver._reverse_with_prefix(view, prefix, *args, **kwargs))
 
517
 
 
518
reverse_lazy = lazy(reverse, str)
 
519
 
 
520
def clear_url_caches():
 
521
    global _resolver_cache
 
522
    global _ns_resolver_cache
 
523
    global _callable_cache
 
524
    _resolver_cache.clear()
 
525
    _ns_resolver_cache.clear()
 
526
    _callable_cache.clear()
 
527
 
 
528
def set_script_prefix(prefix):
 
529
    """
 
530
    Sets the script prefix for the current thread.
 
531
    """
 
532
    if not prefix.endswith('/'):
 
533
        prefix += '/'
 
534
    _prefixes.value = prefix
 
535
 
 
536
def get_script_prefix():
 
537
    """
 
538
    Returns the currently active script prefix. Useful for client code that
 
539
    wishes to construct their own URLs manually (although accessing the request
 
540
    instance is normally going to be a lot cleaner).
 
541
    """
 
542
    return getattr(_prefixes, "value", '/')
 
543
 
 
544
def set_urlconf(urlconf_name):
 
545
    """
 
546
    Sets the URLconf for the current thread (overriding the default one in
 
547
    settings). Set to None to revert back to the default.
 
548
    """
 
549
    if urlconf_name:
 
550
        _urlconfs.value = urlconf_name
 
551
    else:
 
552
        if hasattr(_urlconfs, "value"):
 
553
            del _urlconfs.value
 
554
 
 
555
def get_urlconf(default=None):
 
556
    """
 
557
    Returns the root URLconf to use for the current thread if it has been
 
558
    changed from the default one.
 
559
    """
 
560
    return getattr(_urlconfs, "value", default)
 
561
 
 
562
def is_valid_path(path, urlconf=None):
 
563
    """
 
564
    Returns True if the given path resolves against the default URL resolver,
 
565
    False otherwise.
 
566
 
 
567
    This is a convenience method to make working with "is this a match?" cases
 
568
    easier, avoiding unnecessarily indented try...except blocks.
 
569
    """
 
570
    try:
 
571
        resolve(path, urlconf)
 
572
        return True
 
573
    except Resolver404:
 
574
        return False