@@ -2,39 +2,142 @@ import python
22import semmle.python.regex
33import semmle.python.web.Http
44
5- predicate django_route ( CallNode call , ControlFlowNode regex , FunctionValue view ) {
6- exists ( FunctionValue url |
7- Value:: named ( "django.conf.urls.url" ) = url and
8- url .getArgumentForCall ( call , 0 ) = regex and
9- url .getArgumentForCall ( call , 1 ) .pointsTo ( view )
5+ // TODO: Since django uses `path = partial(...)`, our analysis doesn't understand this is
6+ // a FunctionValue, so we can't use `FunctionValue.getArgumentForCall`
7+ // https://github.com/django/django/blob/master/django/urls/conf.py#L76
8+ abstract class DjangoRoute extends CallNode {
9+ DjangoViewHandler getViewHandler ( ) {
10+ result = view_handler_from_view_arg ( this .getArg ( 1 ) )
11+ or
12+ result = view_handler_from_view_arg ( this .getArgByName ( "view" ) )
13+ }
14+
15+ abstract string getANamedArgument ( ) ;
16+
17+ /**
18+ * Get the number of positional arguments that will be passed to the view.
19+ * Will only return a result if there are no named arguments.
20+ */
21+ abstract int getNumPositionalArguments ( ) ;
22+ }
23+
24+ /**
25+ * For function based views -- also see `DjangoClassBasedViewHandler`
26+ * https://docs.djangoproject.com/en/1.11/topics/http/views/
27+ * https://docs.djangoproject.com/en/3.0/topics/http/views/
28+ */
29+ class DjangoViewHandler extends PythonFunctionValue {
30+
31+ /** Gets the index of the 'request' argument */
32+ int getRequestArgIndex ( ) {
33+ result = 0
34+ }
35+ }
36+
37+ /**
38+ * Class based views
39+ * https://docs.djangoproject.com/en/1.11/topics/class-based-views/
40+ * https://docs.djangoproject.com/en/3.0/topics/class-based-views/
41+ */
42+ private class DjangoViewClass extends ClassValue {
43+ DjangoViewClass ( ) {
44+ Value:: named ( "django.views.generic.View" ) = this .getASuperType ( )
45+ or
46+ Value:: named ( "django.views.View" ) = this .getASuperType ( )
47+ }
48+ }
49+
50+ class DjangoClassBasedViewHandler extends DjangoViewHandler {
51+ DjangoClassBasedViewHandler ( ) {
52+ exists ( DjangoViewClass cls |
53+ cls .lookup ( httpVerbLower ( ) ) = this
54+ )
55+ }
56+
57+ override int getRequestArgIndex ( ) {
58+ // due to `self` being the first parameter
59+ result = 1
60+ }
61+ }
62+
63+ /**
64+ * Gets the function that will handle requests when `view_arg` is used as the view argument to a
65+ * django route. That is, this methods handles Class-based Views and its `as_view()` function.
66+ */
67+ private DjangoViewHandler view_handler_from_view_arg ( ControlFlowNode view_arg ) {
68+ // Function-based view
69+ result = view_arg .pointsTo ( )
70+ or
71+ // Class-based view
72+ exists ( ClassValue cls |
73+ cls = view_arg .( CallNode ) .getFunction ( ) .( AttrNode ) .getObject ( "as_view" ) .pointsTo ( ) and
74+ result = cls .lookup ( httpVerbLower ( ) )
1075 )
1176}
1277
78+ // We need this "dummy" class, since otherwise the regex argument would not be considered
79+ // a regex (RegexString is abstract)
1380class DjangoRouteRegex extends RegexString {
14- DjangoRouteRegex ( ) { django_route ( _ , this .getAFlowNode ( ) , _ ) }
81+ DjangoRouteRegex ( ) { exists ( DjangoRegexRoute route | route . getRouteArg ( ) = this .getAFlowNode ( ) ) }
1582}
1683
17- class DjangoRoute extends CallNode {
18- DjangoRoute ( ) { django_route ( this , _, _) }
84+ class DjangoRegexRoute extends DjangoRoute {
85+ ControlFlowNode route ;
86+
87+ DjangoRegexRoute ( ) {
88+ exists ( FunctionValue route_maker |
89+ // Django 1.x: https://docs.djangoproject.com/en/1.11/ref/urls/#django.conf.urls.url
90+ Value:: named ( "django.conf.urls.url" ) = route_maker and
91+ route_maker .getArgumentForCall ( this , 0 ) = route
92+ )
93+ or
94+ // Django 2.x and 3.x: https://docs.djangoproject.com/en/3.0/ref/urls/#re-path
95+ this = Value:: named ( "django.urls.re_path" ) .getACall ( ) and
96+ (
97+ route = this .getArg ( 0 )
98+ or
99+ route = this .getArgByName ( "route" )
100+ )
101+ }
19102
20- FunctionValue getViewFunction ( ) { django_route ( this , _ , result ) }
103+ ControlFlowNode getRouteArg ( ) { result = route }
21104
22- string getNamedArgument ( ) {
23- exists ( DjangoRouteRegex regex |
24- django_route ( this , regex .getAFlowNode ( ) , _) and
25- regex .getGroupName ( _, _) = result
105+ override string getANamedArgument ( ) {
106+ exists ( DjangoRouteRegex regex | regex .getAFlowNode ( ) = route |
107+ result = regex .getGroupName ( _, _)
26108 )
27109 }
28110
29- /**
30- * Get the number of positional arguments that will be passed to the view.
31- * Will only return a result if there are no named arguments.
32- */
33- int getNumPositionalArguments ( ) {
34- exists ( DjangoRouteRegex regex |
35- django_route ( this , regex .getAFlowNode ( ) , _) and
36- not exists ( string s | s = regex .getGroupName ( _, _) ) and
111+ override int getNumPositionalArguments ( ) {
112+ not exists ( this .getANamedArgument ( ) ) and
113+ exists ( DjangoRouteRegex regex | regex .getAFlowNode ( ) = route |
37114 result = count ( regex .getGroupNumber ( _, _) )
38115 )
39116 }
40117}
118+
119+ class DjangoPathRoute extends DjangoRoute {
120+ ControlFlowNode route ;
121+
122+ DjangoPathRoute ( ) {
123+ // Django 2.x and 3.x: https://docs.djangoproject.com/en/3.0/ref/urls/#path
124+ this = Value:: named ( "django.urls.path" ) .getACall ( ) and
125+ (
126+ route = this .getArg ( 0 )
127+ or
128+ route = this .getArgByName ( "route" )
129+ )
130+ }
131+
132+ override string getANamedArgument ( ) {
133+ // regexp taken from django:
134+ // https://github.com/django/django/blob/7d1bf29977bb368d7c28e7c6eb146db3b3009ae7/django/urls/resolvers.py#L199
135+ exists ( StrConst route_str , string match |
136+ route_str = route .getNode ( ) and
137+ match = route_str .getText ( ) .regexpFind ( "<(?:(?<converter>[^>:]+):)?(?<parameter>\\w+)>" , _, _) and
138+ result = match .regexpCapture ( "<(?:(?<converter>[^>:]+):)?(?<parameter>\\w+)>" , 2 )
139+ )
140+ }
141+
142+ override int getNumPositionalArguments ( ) { none ( ) }
143+ }
0 commit comments