Skip to content

Commit f547b60

Browse files
committed
Python: Implement routed parameter for django.urls.re_path
1 parent ff8708d commit f547b60

File tree

2 files changed

+41
-6
lines changed

2 files changed

+41
-6
lines changed

python/ql/src/experimental/semmle/python/frameworks/Django.qll

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ private import python
77
private import experimental.dataflow.DataFlow
88
private import experimental.dataflow.RemoteFlowSources
99
private import experimental.semmle.python.Concepts
10+
import semmle.python.regex
1011

1112
/**
1213
* Provides models for the `django` PyPI package.
@@ -229,6 +230,22 @@ private module Django {
229230
}
230231
}
231232

233+
/**
234+
* A regex that is used in a call to `django.urls.re_path`.
235+
*
236+
* Needs this subclass to be considered a RegexString.
237+
*/
238+
private class DjangoUrlsRePathRegex extends RegexString {
239+
DjangoUrlsRePathCall rePathCall;
240+
241+
DjangoUrlsRePathRegex() {
242+
this instanceof StrConst and
243+
DataFlow::localFlow(DataFlow::exprNode(this), rePathCall.getUrlPatternArg())
244+
}
245+
246+
DjangoUrlsRePathCall getRePathCall() { result = rePathCall }
247+
}
248+
232249
/**
233250
* A call to `django.urls.re_path`.
234251
*
@@ -250,6 +267,29 @@ private module Django {
250267
)
251268
}
252269

253-
override Parameter getARoutedParameter() { none() }
270+
override Parameter getARoutedParameter() {
271+
// If we don't know the URL pattern, we simply mark all parameters as a routed
272+
// parameter. This should give us more RemoteFlowSources but could also lead to
273+
// more FPs. If this turns out to be the wrong tradeoff, we can always change our mind.
274+
exists(DjangoRouteHandler routeHandler | routeHandler = this.getARouteHandler() |
275+
not exists(this.getUrlPattern()) and
276+
result in [routeHandler.getArg(_), routeHandler.getArgByName(_)] and
277+
not result = any(int i | i <= routeHandler.getRequestParamIndex() | routeHandler.getArg(i))
278+
)
279+
or
280+
exists(DjangoRouteHandler routeHandler, DjangoUrlsRePathRegex regex |
281+
routeHandler = this.getARouteHandler() and
282+
regex.getRePathCall() = this
283+
|
284+
// either using named capture groups (passed as keyword arguments) or using
285+
// unnamed capture groups (passed as positional arguments)
286+
not exists(regex.getGroupName(_, _)) and
287+
// first group will have group number 1
288+
result =
289+
routeHandler.getArg(routeHandler.getRequestParamIndex() + regex.getGroupNumber(_, _))
290+
or
291+
result = routeHandler.getArgByName(regex.getGroupName(_, _))
292+
)
293+
}
254294
}
255295
}
Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,2 @@
1-
| routing_test.py:7:55:7:111 | Comment # $routeHandler $routedParameter=foo $routedParameter=bar | Missing result:routedParameter=bar |
2-
| routing_test.py:7:55:7:111 | Comment # $routeHandler $routedParameter=foo $routedParameter=bar | Missing result:routedParameter=foo |
3-
| routing_test.py:39:45:39:88 | Comment # $routeHandler $routedParameter=page_number | Missing result:routedParameter=page_number |
4-
| routing_test.py:44:62:44:120 | Comment # $routeHandler $routedParameter=arg0 $routedParameter=arg1 | Missing result:routedParameter=arg0 |
5-
| routing_test.py:44:62:44:120 | Comment # $routeHandler $routedParameter=arg0 $routedParameter=arg1 | Missing result:routedParameter=arg1 |
61
| testapp/views.py:3:33:3:47 | Comment # $routeHandler | Missing result:routeHandler= |
72
| testapp/views.py:6:37:6:51 | Comment # $routeHandler | Missing result:routeHandler= |

0 commit comments

Comments
 (0)