Merge pull request #2858 from RasmusWL/python-support-django2

Approved by tausbn
This commit is contained in:
semmle-qlci
2020-03-23 09:35:46 +00:00
committed by GitHub
21 changed files with 423 additions and 102 deletions

View File

@@ -4,6 +4,8 @@ The following changes in version 1.24 affect Python analysis in all applications
## General improvements ## General improvements
Support for Django version 2.x and 3.x
## New queries ## New queries
| **Query** | **Tags** | **Purpose** | | **Query** | **Tags** | **Purpose** |

View File

@@ -36,7 +36,7 @@ class BottleRoute extends ControlFlowNode {
Function getFunction() { bottle_route(this, _, result) } Function getFunction() { bottle_route(this, _, result) }
Parameter getNamedArgument() { Parameter getANamedArgument() {
exists(string name, Function func | exists(string name, Function func |
func = this.getFunction() and func = this.getFunction() and
func.getArgByName(name) = result and func.getArgByName(name) = result and

View File

@@ -71,7 +71,7 @@ class UntrustedFile extends TaintKind {
/** Parameter to a bottle request handler function */ /** Parameter to a bottle request handler function */
class BottleRequestParameter extends HttpRequestTaintSource { class BottleRequestParameter extends HttpRequestTaintSource {
BottleRequestParameter() { BottleRequestParameter() {
exists(BottleRoute route | route.getNamedArgument() = this.(ControlFlowNode).getNode()) exists(BottleRoute route | route.getANamedArgument() = this.(ControlFlowNode).getNode())
} }
override predicate isSourceOf(TaintKind kind) { kind instanceof UntrustedStringKind } override predicate isSourceOf(TaintKind kind) { kind instanceof UntrustedStringKind }

View File

@@ -2,39 +2,142 @@ import python
import semmle.python.regex import semmle.python.regex
import semmle.python.web.Http import semmle.python.web.Http
predicate django_route(CallNode call, ControlFlowNode regex, FunctionValue view) { // TODO: Since django uses `path = partial(...)`, our analysis doesn't understand this is
exists(FunctionValue url | // a FunctionValue, so we can't use `FunctionValue.getArgumentForCall`
Value::named("django.conf.urls.url") = url and // https://github.com/django/django/blob/master/django/urls/conf.py#L76
url.getArgumentForCall(call, 0) = regex and abstract class DjangoRoute extends CallNode {
url.getArgumentForCall(call, 1).pointsTo(view) DjangoViewHandler getViewHandler() {
) result = view_handler_from_view_arg(this.getArg(1))
or
result = view_handler_from_view_arg(this.getArgByName("view"))
}
abstract string getANamedArgument();
/**
* Get the number of positional arguments that will be passed to the view.
* Will only return a result if there are no named arguments.
*/
abstract int getNumPositionalArguments();
} }
class DjangoRouteRegex extends RegexString { /**
DjangoRouteRegex() { django_route(_, this.getAFlowNode(), _) } * For function based views -- also see `DjangoClassBasedViewHandler`
* https://docs.djangoproject.com/en/1.11/topics/http/views/
* https://docs.djangoproject.com/en/3.0/topics/http/views/
*/
class DjangoViewHandler extends PythonFunctionValue {
/** Gets the index of the 'request' argument */
int getRequestArgIndex() {
result = 0
}
} }
class DjangoRoute extends CallNode { /**
DjangoRoute() { django_route(this, _, _) } * Class based views
* https://docs.djangoproject.com/en/1.11/topics/class-based-views/
* https://docs.djangoproject.com/en/3.0/topics/class-based-views/
*/
private class DjangoViewClass extends ClassValue {
DjangoViewClass() {
Value::named("django.views.generic.View") = this.getASuperType()
or
Value::named("django.views.View") = this.getASuperType()
}
}
FunctionValue getViewFunction() { django_route(this, _, result) } class DjangoClassBasedViewHandler extends DjangoViewHandler {
DjangoClassBasedViewHandler() {
string getNamedArgument() { exists(DjangoViewClass cls |
exists(DjangoRouteRegex regex | cls.lookup(httpVerbLower()) = this
django_route(this, regex.getAFlowNode(), _) and
regex.getGroupName(_, _) = result
) )
} }
/** override int getRequestArgIndex() {
* Get the number of positional arguments that will be passed to the view. // due to `self` being the first parameter
* Will only return a result if there are no named arguments. result = 1
*/ }
int getNumPositionalArguments() { }
exists(DjangoRouteRegex regex |
django_route(this, regex.getAFlowNode(), _) and /**
not exists(string s | s = regex.getGroupName(_, _)) and * Gets the function that will handle requests when `view_arg` is used as the view argument to a
* django route. That is, this methods handles Class-based Views and its `as_view()` function.
*/
private DjangoViewHandler view_handler_from_view_arg(ControlFlowNode view_arg) {
// Function-based view
result = view_arg.pointsTo()
or
// Class-based view
exists(ClassValue cls |
cls = view_arg.(CallNode).getFunction().(AttrNode).getObject("as_view").pointsTo() and
result = cls.lookup(httpVerbLower())
)
}
// We need this "dummy" class, since otherwise the regex argument would not be considered
// a regex (RegexString is abstract)
class DjangoRouteRegex extends RegexString {
DjangoRouteRegex() { exists(DjangoRegexRoute route | route.getRouteArg() = this.getAFlowNode()) }
}
class DjangoRegexRoute extends DjangoRoute {
ControlFlowNode route;
DjangoRegexRoute() {
exists(FunctionValue route_maker |
// Django 1.x: https://docs.djangoproject.com/en/1.11/ref/urls/#django.conf.urls.url
Value::named("django.conf.urls.url") = route_maker and
route_maker.getArgumentForCall(this, 0) = route
)
or
// Django 2.x and 3.x: https://docs.djangoproject.com/en/3.0/ref/urls/#re-path
this = Value::named("django.urls.re_path").getACall() and
(
route = this.getArg(0)
or
route = this.getArgByName("route")
)
}
ControlFlowNode getRouteArg() { result = route }
override string getANamedArgument() {
exists(DjangoRouteRegex regex | regex.getAFlowNode() = route |
result = regex.getGroupName(_, _)
)
}
override int getNumPositionalArguments() {
not exists(this.getANamedArgument()) and
exists(DjangoRouteRegex regex | regex.getAFlowNode() = route |
result = count(regex.getGroupNumber(_, _)) result = count(regex.getGroupNumber(_, _))
) )
} }
} }
class DjangoPathRoute extends DjangoRoute {
ControlFlowNode route;
DjangoPathRoute() {
// Django 2.x and 3.x: https://docs.djangoproject.com/en/3.0/ref/urls/#path
this = Value::named("django.urls.path").getACall() and
(
route = this.getArg(0)
or
route = this.getArgByName("route")
)
}
override string getANamedArgument() {
// regexp taken from django:
// https://github.com/django/django/blob/7d1bf29977bb368d7c28e7c6eb146db3b3009ae7/django/urls/resolvers.py#L199
exists(StrConst route_str, string match |
route_str = route.getNode() and
match = route_str.getText().regexpFind("<(?:(?<converter>[^>:]+):)?(?<parameter>\\w+)>", _, _) and
result = match.regexpCapture("<(?:(?<converter>[^>:]+):)?(?<parameter>\\w+)>", 2)
)
}
override int getNumPositionalArguments() { none() }
}

View File

@@ -17,9 +17,6 @@ class DjangoRedirect extends HttpRedirectTaintSink {
override string toString() { result = "django.redirect" } override string toString() { result = "django.redirect" }
DjangoRedirect() { DjangoRedirect() {
exists(CallNode call | this = redirect().getACall().getAnArg()
redirect().getACall() = call and
this = call.getAnArg()
)
} }
} }

View File

@@ -39,54 +39,35 @@ class DjangoQueryDict extends TaintKind {
} }
} }
abstract class DjangoRequestSource extends HttpRequestTaintSource { /** A Django request parameter */
class DjangoRequestSource extends HttpRequestTaintSource {
DjangoRequestSource() {
exists(DjangoRoute route, DjangoViewHandler view, int request_arg_index |
route.getViewHandler() = view and
request_arg_index = view.getRequestArgIndex() and
this = view.getScope().getArg(request_arg_index).asName().getAFlowNode()
)
}
override string toString() { result = "Django request source" } override string toString() { result = "Django request source" }
override predicate isSourceOf(TaintKind kind) { kind instanceof DjangoRequest } override predicate isSourceOf(TaintKind kind) { kind instanceof DjangoRequest }
} }
/**
* Function based views
* https://docs.djangoproject.com/en/1.11/topics/http/views/
*/
private class DjangoFunctionBasedViewRequestArgument extends DjangoRequestSource {
DjangoFunctionBasedViewRequestArgument() {
exists(FunctionValue view |
django_route(_, _, view) and
this = view.getScope().getArg(0).asName().getAFlowNode()
)
}
}
/**
* Class based views
* https://docs.djangoproject.com/en/1.11/topics/class-based-views/
*/
private class DjangoView extends ClassValue {
DjangoView() { Value::named("django.views.generic.View") = this.getASuperType() }
}
private FunctionValue djangoViewHttpMethod() {
exists(DjangoView view | view.lookup(httpVerbLower()) = result)
}
class DjangoClassBasedViewRequestArgument extends DjangoRequestSource {
DjangoClassBasedViewRequestArgument() {
this = djangoViewHttpMethod().getScope().getArg(1).asName().getAFlowNode()
}
}
/** An argument specified in a url routing table */ /** An argument specified in a url routing table */
class DjangoRequestParameter extends HttpRequestTaintSource { class DjangoRequestParameter extends HttpRequestTaintSource {
DjangoRequestParameter() { DjangoRequestParameter() {
exists(DjangoRoute route, Function f | exists(DjangoRoute route, Function f, DjangoViewHandler view, int request_arg_index |
f = route.getViewFunction().getScope() | route.getViewHandler() = view and
this.(ControlFlowNode).getNode() = f.getArgByName(route.getNamedArgument()) request_arg_index = view.getRequestArgIndex() and
f = view.getScope()
|
this.(ControlFlowNode).getNode() = f.getArgByName(route.getANamedArgument())
or or
exists(int i | i >= 0 | exists(int i | i >= 0 |
i < route.getNumPositionalArguments() and i < route.getNumPositionalArguments() and
// +1 because first argument is always the request // +1 because first argument is always the request
this.(ControlFlowNode).getNode() = f.getArg(i+1) this.(ControlFlowNode).getNode() = f.getArg(request_arg_index + 1 + i)
) )
) )
} }

View File

@@ -14,7 +14,15 @@ class DjangoResponse extends TaintKind {
} }
private ClassValue theDjangoHttpResponseClass() { private ClassValue theDjangoHttpResponseClass() {
result = Value::named("django.http.response.HttpResponse") and (
// version 1.x
result = Value::named("django.http.response.HttpResponse")
or
// version 2.x
// https://docs.djangoproject.com/en/2.2/ref/request-response/#httpresponse-objects
result = Value::named("django.http.HttpResponse")
) and
// TODO: does this do anything? when could they be the same???
not result = theDjangoHttpRedirectClass() not result = theDjangoHttpRedirectClass()
} }

View File

@@ -4,5 +4,9 @@ import python
FunctionValue redirect() { result = Value::named("django.shortcuts.redirect") } FunctionValue redirect() { result = Value::named("django.shortcuts.redirect") }
ClassValue theDjangoHttpRedirectClass() { ClassValue theDjangoHttpRedirectClass() {
// version 1.x
result = Value::named("django.http.response.HttpResponseRedirectBase") result = Value::named("django.http.response.HttpResponseRedirectBase")
or
// version 2.x
result = Value::named("django.http.HttpResponseRedirectBase")
} }

View File

@@ -0,0 +1,2 @@
| test_1x.py:13:21:13:24 | django.redirect | externally controlled string |
| test_2x_3x.py:13:21:13:24 | django.redirect | externally controlled string |

View File

@@ -0,0 +1,7 @@
import python
import semmle.python.web.HttpRedirect
import semmle.python.security.strings.Untrusted
from HttpRedirectTaintSink sink, TaintKind kind
where sink.sinks(kind)
select sink, kind

View File

@@ -1,7 +1,23 @@
| views.py:7:25:7:63 | django.Response(...) | externally controlled string | | views_1x.py:8:25:8:63 | django.Response(...) | externally controlled string |
| views.py:11:25:11:52 | django.Response(...) | externally controlled string | | views_1x.py:12:25:12:52 | django.Response(...) | externally controlled string |
| views.py:15:25:15:53 | django.Response(...) | externally controlled string | | views_1x.py:16:25:16:53 | django.Response(...) | externally controlled string |
| views.py:23:29:23:60 | django.Response(...) | externally controlled string | | views_1x.py:21:15:21:42 | django.Response.write(...) | externally controlled string |
| views.py:29:29:29:65 | django.Response(...) | externally controlled string | | views_1x.py:30:29:30:60 | django.Response(...) | externally controlled string |
| views.py:34:25:34:63 | django.Response(...) | externally controlled string | | views_1x.py:36:29:36:65 | django.Response(...) | externally controlled string |
| views.py:38:25:38:70 | django.Response(...) | externally controlled string | | views_1x.py:41:25:41:63 | django.Response(...) | externally controlled string |
| views_1x.py:45:25:45:70 | django.Response(...) | externally controlled string |
| views_1x.py:66:25:66:55 | django.Response(...) | externally controlled string |
| views_1x.py:75:25:75:33 | django.Response(...) | externally controlled string |
| views_2x_3x.py:8:25:8:63 | django.Response(...) | externally controlled string |
| views_2x_3x.py:12:25:12:52 | django.Response(...) | externally controlled string |
| views_2x_3x.py:16:25:16:53 | django.Response(...) | externally controlled string |
| views_2x_3x.py:21:15:21:42 | django.Response.write(...) | externally controlled string |
| views_2x_3x.py:30:29:30:60 | django.Response(...) | externally controlled string |
| views_2x_3x.py:36:29:36:65 | django.Response(...) | externally controlled string |
| views_2x_3x.py:41:25:41:63 | django.Response(...) | externally controlled string |
| views_2x_3x.py:45:25:45:70 | django.Response(...) | externally controlled string |
| views_2x_3x.py:66:25:66:40 | django.Response(...) | externally controlled string |
| views_2x_3x.py:79:25:79:61 | django.Response(...) | externally controlled string |
| views_2x_3x.py:82:25:82:69 | django.Response(...) | externally controlled string |
| views_2x_3x.py:85:25:85:64 | django.Response(...) | externally controlled string |
| views_2x_3x.py:88:25:88:32 | django.Response(...) | externally controlled string |

View File

@@ -1,19 +1,51 @@
| test.py:5:19:5:25 | request | django.request.HttpRequest | | test_1x.py:6:19:6:25 | request | django.request.HttpRequest |
| test.py:5:28:5:31 | path | externally controlled string | | test_1x.py:6:28:6:31 | path | externally controlled string |
| test.py:11:19:11:25 | request | django.request.HttpRequest | | test_1x.py:12:19:12:25 | request | django.request.HttpRequest |
| test.py:11:28:11:31 | path | externally controlled string | | test_1x.py:12:28:12:31 | path | externally controlled string |
| views.py:6:19:6:25 | request | django.request.HttpRequest | | test_2x_3x.py:6:19:6:25 | request | django.request.HttpRequest |
| views.py:6:28:6:30 | foo | externally controlled string | | test_2x_3x.py:6:28:6:31 | path | externally controlled string |
| views.py:6:33:6:35 | bar | externally controlled string | | test_2x_3x.py:12:19:12:25 | request | django.request.HttpRequest |
| views.py:10:20:10:26 | request | django.request.HttpRequest | | test_2x_3x.py:12:28:12:31 | path | externally controlled string |
| views.py:14:21:14:27 | request | django.request.HttpRequest | | views_1x.py:7:19:7:25 | request | django.request.HttpRequest |
| views.py:22:20:22:26 | request | django.request.HttpRequest | | views_1x.py:7:28:7:30 | foo | externally controlled string |
| views.py:28:19:28:25 | request | django.request.HttpRequest | | views_1x.py:7:33:7:35 | bar | externally controlled string |
| views.py:32:19:32:25 | request | django.request.HttpRequest | | views_1x.py:11:20:11:26 | request | django.request.HttpRequest |
| views.py:32:28:32:38 | page_number | externally controlled string | | views_1x.py:15:21:15:27 | request | django.request.HttpRequest |
| views.py:37:24:37:30 | request | django.request.HttpRequest | | views_1x.py:19:21:19:27 | request | django.request.HttpRequest |
| views.py:37:33:37:36 | arg0 | externally controlled string | | views_1x.py:29:20:29:26 | request | django.request.HttpRequest |
| views.py:37:39:37:42 | arg1 | externally controlled string | | views_1x.py:29:29:29:37 | untrusted | externally controlled string |
| views.py:57:15:57:21 | request | django.request.HttpRequest | | views_1x.py:35:19:35:25 | request | django.request.HttpRequest |
| views.py:57:24:57:31 | username | externally controlled string | | views_1x.py:35:28:35:36 | untrusted | externally controlled string |
| views.py:66:30:66:36 | request | django.request.HttpRequest | | views_1x.py:39:19:39:25 | request | django.request.HttpRequest |
| views_1x.py:39:28:39:38 | page_number | externally controlled string |
| views_1x.py:44:24:44:30 | request | django.request.HttpRequest |
| views_1x.py:44:33:44:36 | arg0 | externally controlled string |
| views_1x.py:44:39:44:42 | arg1 | externally controlled string |
| views_1x.py:65:15:65:21 | request | django.request.HttpRequest |
| views_1x.py:65:24:65:31 | username | externally controlled string |
| views_1x.py:74:13:74:19 | request | django.request.HttpRequest |
| views_2x_3x.py:7:19:7:25 | request | django.request.HttpRequest |
| views_2x_3x.py:7:28:7:30 | foo | externally controlled string |
| views_2x_3x.py:7:33:7:35 | bar | externally controlled string |
| views_2x_3x.py:11:20:11:26 | request | django.request.HttpRequest |
| views_2x_3x.py:15:21:15:27 | request | django.request.HttpRequest |
| views_2x_3x.py:19:21:19:27 | request | django.request.HttpRequest |
| views_2x_3x.py:29:20:29:26 | request | django.request.HttpRequest |
| views_2x_3x.py:29:29:29:37 | untrusted | externally controlled string |
| views_2x_3x.py:35:19:35:25 | request | django.request.HttpRequest |
| views_2x_3x.py:35:28:35:36 | untrusted | externally controlled string |
| views_2x_3x.py:39:19:39:25 | request | django.request.HttpRequest |
| views_2x_3x.py:39:28:39:38 | page_number | externally controlled string |
| views_2x_3x.py:44:24:44:30 | request | django.request.HttpRequest |
| views_2x_3x.py:44:33:44:36 | arg0 | externally controlled string |
| views_2x_3x.py:44:39:44:42 | arg1 | externally controlled string |
| views_2x_3x.py:78:17:78:23 | request | django.request.HttpRequest |
| views_2x_3x.py:78:26:78:36 | page_number | externally controlled string |
| views_2x_3x.py:81:17:81:23 | request | django.request.HttpRequest |
| views_2x_3x.py:81:26:81:28 | foo | externally controlled string |
| views_2x_3x.py:81:31:81:33 | bar | externally controlled string |
| views_2x_3x.py:81:36:81:38 | baz | externally controlled string |
| views_2x_3x.py:84:17:84:23 | request | django.request.HttpRequest |
| views_2x_3x.py:84:26:84:28 | foo | externally controlled string |
| views_2x_3x.py:84:31:84:33 | bar | externally controlled string |
| views_2x_3x.py:87:26:87:32 | request | django.request.HttpRequest |

View File

@@ -1,3 +1,4 @@
"""tests for Django 1.x"""
from django.conf.urls import url from django.conf.urls import url
from django.shortcuts import redirect, render from django.shortcuts import redirect, render

View File

@@ -0,0 +1,19 @@
"""tests for Django 2.x and 3.x"""
from django.urls import path
from django.shortcuts import redirect, render
def with_template(request, path='default'):
env = {'path': path}
# We would need to understand django templates to know if this is safe or not
return render(request, 'possibly-vulnerable-template.html', env)
def vuln_redirect(request, path):
return redirect(path)
urlpatterns = [
path('/<path>', with_template),
path('/redirect/<path>', vuln_redirect),
]

View File

@@ -1,3 +1,4 @@
"""test of views for Django 1.x"""
from django.conf.urls import patterns, url from django.conf.urls import patterns, url
from django.http.response import HttpResponse from django.http.response import HttpResponse
from django.views.generic import View from django.views.generic import View
@@ -15,16 +16,22 @@ def post_params_xss(request):
return HttpResponse(request.POST.get("untrusted")) return HttpResponse(request.POST.get("untrusted"))
def http_resp_write(request):
rsp = HttpResponse()
rsp.write(request.GET.get("untrusted"))
return rsp
class Foo(object): class Foo(object):
# Note: since Foo is used as the super type in a class view, it will be able to handle requests. # Note: since Foo is used as the super type in a class view, it will be able to handle requests.
# TODO: Currently we don't flag `untrusted` as a DjangoRequestParameter
def post(self, request, untrusted): def post(self, request, untrusted):
return HttpResponse('Foo post: {}'.format(untrusted)) return HttpResponse('Foo post: {}'.format(untrusted))
class ClassView(View, Foo): class ClassView(View, Foo):
# TODO: Currently we don't flag `untrusted` as a DjangoRequestParameter
def get(self, request, untrusted): def get(self, request, untrusted):
return HttpResponse('ClassView get: {}'.format(untrusted)) return HttpResponse('ClassView get: {}'.format(untrusted))
@@ -42,6 +49,7 @@ urlpatterns = [
url(r'^url_match/(?P<foo>[^/]+)/(?P<bar>[^/]+)$', url_match_xss), url(r'^url_match/(?P<foo>[^/]+)/(?P<bar>[^/]+)$', url_match_xss),
url(r'^get_params$', get_params_xss), url(r'^get_params$', get_params_xss),
url(r'^post_params$', post_params_xss), url(r'^post_params$', post_params_xss),
url(r'^http_resp_write$', http_resp_write),
url(r'^class_view/(?P<untrusted>.+)$', ClassView.as_view()), url(r'^class_view/(?P<untrusted>.+)$', ClassView.as_view()),
# one pattern to support `articles/page-<n>` and ensuring that articles/ goes to page-1 # one pattern to support `articles/page-<n>` and ensuring that articles/ goes to page-1
@@ -51,22 +59,21 @@ urlpatterns = [
url(r'^([^/]+)/(?:foo|bar)/([^/]+)$', xxs_positional_arg, name='xxs_positional_arg'), url(r'^([^/]+)/(?:foo|bar)/([^/]+)$', xxs_positional_arg, name='xxs_positional_arg'),
] ]
################################################################################
# Using patterns() for routing # Using patterns() for routing
def show_user(request, username): def show_user(request, username):
pass return HttpResponse('show_user {}'.format(username))
urlpatterns = patterns(url(r'^users/(?P<username>[^/]+)$', show_user)) urlpatterns = patterns(url(r'^users/(?P<username>[^/]+)$', show_user))
################################################################################
# Show we understand the keyword arguments to django.conf.urls.url # Show we understand the keyword arguments to django.conf.urls.url
def we_understand_url_kwargs(request): def kw_args(request):
pass return HttpResponse('kw_args')
urlpatterns = [ urlpatterns = [
url(view=we_understand_url_kwargs, regex=r'^specifying-as-kwargs-is-not-a-problem$') url(view=kw_args, regex=r'^kw_args$')
] ]

View File

@@ -0,0 +1,122 @@
"""testing views for Django 2.x and 3.x"""
from django.urls import path, re_path
from django.http import HttpResponse
from django.views import View
def url_match_xss(request, foo, bar, no_taint=None):
return HttpResponse('url_match_xss: {} {}'.format(foo, bar))
def get_params_xss(request):
return HttpResponse(request.GET.get("untrusted"))
def post_params_xss(request):
return HttpResponse(request.POST.get("untrusted"))
def http_resp_write(request):
rsp = HttpResponse()
rsp.write(request.GET.get("untrusted"))
return rsp
class Foo(object):
# Note: since Foo is used as the super type in a class view, it will be able to handle requests.
def post(self, request, untrusted):
return HttpResponse('Foo post: {}'.format(untrusted))
class ClassView(View, Foo):
def get(self, request, untrusted):
return HttpResponse('ClassView get: {}'.format(untrusted))
def show_articles(request, page_number=1):
page_number = int(page_number)
return HttpResponse('articles page: {}'.format(page_number))
def xxs_positional_arg(request, arg0, arg1, no_taint=None):
return HttpResponse('xxs_positional_arg: {} {}'.format(arg0, arg1))
urlpatterns = [
re_path(r'^url_match/(?P<foo>[^/]+)/(?P<bar>[^/]+)$', url_match_xss),
re_path(r'^get_params$', get_params_xss),
re_path(r'^post_params$', post_params_xss),
re_path(r'^http_resp_write$', http_resp_write),
re_path(r'^class_view/(?P<untrusted>.+)$', ClassView.as_view()),
# one pattern to support `articles/page-<n>` and ensuring that articles/ goes to page-1
re_path(r'articles/^(?:page-(?P<page_number>\d+)/)?$', show_articles),
# passing as positional argument is not the recommended way of doing things, but it is certainly
# possible
re_path(r'^([^/]+)/(?:foo|bar)/([^/]+)$', xxs_positional_arg, name='xxs_positional_arg'),
]
# Show we understand the keyword arguments to from django.urls.re_path
def re_path_kwargs(request):
return HttpResponse('re_path_kwargs')
urlpatterns = [
re_path(view=re_path_kwargs, regex=r'^specifying-as-kwargs-is-not-a-problem$')
]
################################################################################
# Using path
################################################################################
# saying page_number is an externally controlled *string* is a bit strange, when we have an int converter :O
def page_number(request, page_number=1):
return HttpResponse('page_number: {}'.format(page_number))
def foo_bar_baz(request, foo, bar, baz):
return HttpResponse('foo_bar_baz: {} {} {}'.format(foo, bar, baz))
def path_kwargs(request, foo, bar):
return HttpResponse('path_kwargs: {} {} {}'.format(foo, bar))
def not_valid_identifier(request):
return HttpResponse('<foo!>')
urlpatterns = [
path('articles/', page_number),
path('articles/page-<int:page_number>', page_number),
path('<int:foo>/<str:bar>/<baz>', foo_bar_baz, name='foo-bar-baz'),
path(view=path_kwargs, route='<foo>/<bar>'),
# We should not report there is a request parameter called `not_valid!`
path('not_valid/<not_valid!>', not_valid_identifier),
]
################################################################################
# We should abort if a decorator is used. As demonstrated below, anything might happen
# def reverse_kwargs(f):
# @wraps(f)
# def f_(*args, **kwargs):
# new_kwargs = dict()
# for key, value in kwargs.items():
# new_kwargs[key[::-1]] = value
# return f(*args, **new_kwargs)
# return f_
# @reverse_kwargs
# def decorators_can_do_anything(request, oof, foo=None):
# return HttpResponse('This is a mess'[::-1])
# urlpatterns = [
# path('rev/<foo>', decorators_can_do_anything),
# ]

View File

@@ -2,5 +2,6 @@
def url(regex, view, kwargs=None, name=None): def url(regex, view, kwargs=None, name=None):
pass pass
def patterns(*urls): def patterns(*urls):
pass pass

View File

@@ -0,0 +1,8 @@
# see https://docs.djangoproject.com/en/1.11/_modules/django/shortcuts/#redirect
# https://github.com/django/django/blob/86908785076b2bbc31b908781da6b6ad1779b18b/django/shortcuts.py
def render(request, template_name, context=None, content_type=None, status=None, using=None):
pass
def redirect(to, *args, **kwargs):
pass

View File

@@ -0,0 +1,7 @@
from functools import partial
def _path(route, view, kwargs=None, name=None, Pattern=None):
pass
path = partial(_path, Pattern='RoutePattern (but this is a mock)')
re_path = partial(_path, Pattern='RegexPattern (but this is a mock)')

View File

@@ -0,0 +1,3 @@
# For django 2.x and 3.x
class View:
pass

View File

@@ -1,2 +1,3 @@
# For django 1.x
class View: class View:
pass pass