`—a detective puzzle that introduces recursion
Further resources
-----------------
diff --git a/java/ql/src/Violations of Best Practice/Boxed Types/BoxedVariable.ql b/java/ql/src/Violations of Best Practice/Boxed Types/BoxedVariable.ql
index 4f8ed49e010..a5523845c24 100644
--- a/java/ql/src/Violations of Best Practice/Boxed Types/BoxedVariable.ql
+++ b/java/ql/src/Violations of Best Practice/Boxed Types/BoxedVariable.ql
@@ -39,6 +39,9 @@ predicate notDeliberatelyBoxed(LocalBoxedVar v) {
)
}
+pragma[nomagic]
+int callableGetNumberOfParameters(Callable c) { result = c.getNumberOfParameters() }
+
/**
* Replacing the type of a boxed variable with the corresponding primitive type may affect
* overload resolution. If this is the case then the boxing is most likely intentional and
@@ -52,7 +55,7 @@ predicate affectsOverload(LocalBoxedVar v) {
c1.getParameterType(i) instanceof RefType and
c2.getParameterType(i) instanceof PrimitiveType and
c1.getName() = c2.getName() and
- c1.getNumberOfParameters() = c2.getNumberOfParameters()
+ callableGetNumberOfParameters(c1) = callableGetNumberOfParameters(c2)
)
}
diff --git a/java/ql/src/semmle/code/Location.qll b/java/ql/src/semmle/code/Location.qll
index 92b92fc69ed..a34bba9ddc7 100755
--- a/java/ql/src/semmle/code/Location.qll
+++ b/java/ql/src/semmle/code/Location.qll
@@ -84,6 +84,7 @@ class Top extends @top {
int getNumberOfCommentLines() { numlines(this, _, _, result) }
/** Gets a textual representation of this element. */
+ cached
string toString() { hasName(this, result) }
}
diff --git a/java/ql/src/semmle/code/java/deadcode/DeadCode.qll b/java/ql/src/semmle/code/java/deadcode/DeadCode.qll
index 9c7d7ba4a53..9749413a23c 100644
--- a/java/ql/src/semmle/code/java/deadcode/DeadCode.qll
+++ b/java/ql/src/semmle/code/java/deadcode/DeadCode.qll
@@ -301,7 +301,7 @@ class RootdefCallable extends Callable {
}
}
-pragma[noinline]
+pragma[nomagic]
private predicate overrideAccess(Callable c, int i) {
exists(Method m | m.overridesOrInstantiates+(c) | exists(m.getParameter(i).getAnAccess()))
}
diff --git a/java/ql/src/semmle/code/java/frameworks/gwt/GWT.qll b/java/ql/src/semmle/code/java/frameworks/gwt/GWT.qll
index ed68cd395fb..c704320657f 100644
--- a/java/ql/src/semmle/code/java/frameworks/gwt/GWT.qll
+++ b/java/ql/src/semmle/code/java/frameworks/gwt/GWT.qll
@@ -61,19 +61,29 @@ class ClientSideGwtCompilationUnit extends GwtCompilationUnit {
}
}
-/** Auxiliary predicate: `jsni` is a JSNI comment associated with method `m`. */
-private predicate jsniComment(Javadoc jsni, Method m) {
+private predicate jsni(Javadoc jsni, File file, int startline) {
// The comment must start with `-{` ...
jsni.getChild(0).getText().matches("-{%") and
// ... and it must end with `}-`.
jsni.getChild(jsni.getNumChild() - 1).getText().matches("%}-") and
- // The associated callable must be marked as `native` ...
+ file = jsni.getFile() and
+ startline = jsni.getLocation().getStartLine()
+}
+
+private predicate nativeMethodLines(Method m, File file, int line) {
m.isNative() and
- // ... and the comment has to be contained in `m`.
- jsni.getFile() = m.getFile() and
- jsni.getLocation().getStartLine() in [m.getLocation().getStartLine() .. m
- .getLocation()
- .getEndLine()]
+ file = m.getFile() and
+ line in [m.getLocation().getStartLine() .. m.getLocation().getEndLine()]
+}
+
+/** Auxiliary predicate: `jsni` is a JSNI comment associated with method `m`. */
+private predicate jsniComment(Javadoc jsni, Method m) {
+ exists(File file, int line |
+ jsni(jsni, file, line) and
+ // The associated callable must be marked as `native`
+ // and the comment has to be contained in `m`.
+ nativeMethodLines(m, file, line)
+ )
}
/**
diff --git a/java/ql/src/semmle/code/xml/XML.qll b/java/ql/src/semmle/code/xml/XML.qll
index b4fe2c83417..c6d55c8190b 100755
--- a/java/ql/src/semmle/code/xml/XML.qll
+++ b/java/ql/src/semmle/code/xml/XML.qll
@@ -2,7 +2,7 @@
* Provides classes and predicates for working with XML files and their content.
*/
-import semmle.code.Location
+import semmle.files.FileSystem
/** An XML element that has a location. */
abstract class XMLLocatable extends @xmllocatable {
@@ -34,6 +34,12 @@ abstract class XMLLocatable extends @xmllocatable {
* both of which can contain other elements.
*/
class XMLParent extends @xmlparent {
+ XMLParent() {
+ // explicitly restrict `this` to be either an `XMLElement` or an `XMLFile`;
+ // the type `@xmlparent` currently also includes non-XML files
+ this instanceof @xmlelement or xmlEncoding(this, _)
+ }
+
/**
* Gets a printable representation of this XML parent.
* (Intended to be overridden in subclasses.)
@@ -82,7 +88,10 @@ class XMLParent extends @xmlparent {
)
}
- /** Append all the character sequences of this XML parent from left to right, separated by a space. */
+ /**
+ * Gets the result of appending all the character sequences of this XML parent from
+ * left to right, separated by a space.
+ */
string allCharactersString() {
result = concat(string chars, int pos |
xmlChars(_, chars, this, pos, _, _)
@@ -108,6 +117,20 @@ class XMLFile extends XMLParent, File {
/** Gets the name of this XML file. */
override string getName() { result = File.super.getAbsolutePath() }
+ /**
+ * DEPRECATED: Use `getAbsolutePath()` instead.
+ *
+ * Gets the path of this XML file.
+ */
+ deprecated string getPath() { result = getAbsolutePath() }
+
+ /**
+ * DEPRECATED: Use `getParentContainer().getAbsolutePath()` instead.
+ *
+ * Gets the path of the folder that contains this XML file.
+ */
+ deprecated string getFolder() { result = getParentContainer().getAbsolutePath() }
+
/** Gets the encoding of this XML file. */
string getEncoding() { xmlEncoding(this, result) }
@@ -121,7 +144,17 @@ class XMLFile extends XMLParent, File {
XMLDTD getADTD() { xmlDTDs(result, _, _, _, this) }
}
-/** A "Document Type Definition" of an XML file. */
+/**
+ * An XML document type definition (DTD).
+ *
+ * Example:
+ *
+ * ```
+ *
+ *
+ *
+ * ```
+ */
class XMLDTD extends @xmldtd {
/** Gets the name of the root element of this DTD. */
string getRoot() { xmlDTDs(this, result, _, _, _) }
@@ -148,7 +181,17 @@ class XMLDTD extends @xmldtd {
}
}
-/** An XML tag in an XML file. */
+/**
+ * An XML element in an XML file.
+ *
+ * Example:
+ *
+ * ```
+ *
+ *
+ * ```
+ */
class XMLElement extends @xmlelement, XMLParent, XMLLocatable {
/** Holds if this XML element has the given `name`. */
predicate hasName(string name) { name = getName() }
@@ -193,7 +236,16 @@ class XMLElement extends @xmlelement, XMLParent, XMLLocatable {
override string toString() { result = XMLParent.super.toString() }
}
-/** An attribute that occurs inside an XML element. */
+/**
+ * An attribute that occurs inside an XML element.
+ *
+ * Examples:
+ *
+ * ```
+ * package="com.example.exampleapp"
+ * android:versionCode="1"
+ * ```
+ */
class XMLAttribute extends @xmlattribute, XMLLocatable {
/** Gets the name of this attribute. */
string getName() { xmlAttrs(this, _, result, _, _, _) }
@@ -214,7 +266,15 @@ class XMLAttribute extends @xmlattribute, XMLLocatable {
override string toString() { result = this.getName() + "=" + this.getValue() }
}
-/** A namespace used in an XML file */
+/**
+ * A namespace used in an XML file.
+ *
+ * Example:
+ *
+ * ```
+ * xmlns:android="http://schemas.android.com/apk/res/android"
+ * ```
+ */
class XMLNamespace extends @xmlnamespace {
/** Gets the prefix of this namespace. */
string getPrefix() { xmlNs(this, result, _, _) }
@@ -233,7 +293,15 @@ class XMLNamespace extends @xmlnamespace {
}
}
-/** A comment of the form `` is an XML comment. */
+/**
+ * A comment in an XML file.
+ *
+ * Example:
+ *
+ * ```
+ *
+ * ```
+ */
class XMLComment extends @xmlcomment, XMLLocatable {
/** Gets the text content of this XML comment. */
string getText() { xmlComments(this, result, _, _) }
@@ -248,6 +316,12 @@ class XMLComment extends @xmlcomment, XMLLocatable {
/**
* A sequence of characters that occurs between opening and
* closing tags of an XML element, excluding other elements.
+ *
+ * Example:
+ *
+ * ```
+ * This is a sequence of characters.
+ * ```
*/
class XMLCharacters extends @xmlcharacters, XMLLocatable {
/** Gets the content of this character sequence. */
diff --git a/javascript/extractor/src/com/semmle/jcorn/ESNextParser.java b/javascript/extractor/src/com/semmle/jcorn/ESNextParser.java
index 93295a519db..f750013067b 100644
--- a/javascript/extractor/src/com/semmle/jcorn/ESNextParser.java
+++ b/javascript/extractor/src/com/semmle/jcorn/ESNextParser.java
@@ -240,6 +240,9 @@ public class ESNextParser extends JSXParser {
if (this.type == TokenType._import) {
Position startLoc = this.startLoc;
this.next();
+ if (this.eat(TokenType.dot)) {
+ return parseImportMeta(startLoc);
+ }
this.expect(TokenType.parenL);
return parseDynamicImport(startLoc);
}
@@ -413,6 +416,19 @@ public class ESNextParser extends JSXParser {
}
}
+ /**
+ * Parses an import.meta expression, assuming that the initial "import" and "." has been consumed.
+ */
+ private MetaProperty parseImportMeta(Position loc) {
+ Position propertyLoc = this.startLoc;
+ Identifier property = this.parseIdent(true);
+ if (!property.getName().equals("meta")) {
+ this.unexpected(propertyLoc);
+ }
+ return this.finishNode(
+ new MetaProperty(new SourceLocation(loc), new Identifier(new SourceLocation(loc), "import"), property));
+ }
+
/**
* Parses a dynamic import, assuming that the keyword `import` and the opening parenthesis have
* already been consumed.
diff --git a/javascript/extractor/src/com/semmle/js/ast/MetaProperty.java b/javascript/extractor/src/com/semmle/js/ast/MetaProperty.java
index 1a1154a53a5..cc98b94b406 100644
--- a/javascript/extractor/src/com/semmle/js/ast/MetaProperty.java
+++ b/javascript/extractor/src/com/semmle/js/ast/MetaProperty.java
@@ -3,8 +3,8 @@ package com.semmle.js.ast;
/**
* A meta property access (cf. ECMAScript 2015 Language Specification, Chapter 12.3.8).
*
- * Currently the only recognised meta properties are new.target
and
- * function.sent
.
+ *
Currently the only recognised meta properties are new.target
,
+ * import.meta
and function.sent
.
*/
public class MetaProperty extends Expression {
private final Identifier meta, property;
diff --git a/javascript/ql/src/semmle/javascript/XML.qll b/javascript/ql/src/semmle/javascript/XML.qll
index af06cedcbcd..c6d55c8190b 100755
--- a/javascript/ql/src/semmle/javascript/XML.qll
+++ b/javascript/ql/src/semmle/javascript/XML.qll
@@ -2,7 +2,7 @@
* Provides classes and predicates for working with XML files and their content.
*/
-import javascript
+import semmle.files.FileSystem
/** An XML element that has a location. */
abstract class XMLLocatable extends @xmllocatable {
@@ -88,7 +88,10 @@ class XMLParent extends @xmlparent {
)
}
- /** Append all the character sequences of this XML parent from left to right, separated by a space. */
+ /**
+ * Gets the result of appending all the character sequences of this XML parent from
+ * left to right, separated by a space.
+ */
string allCharactersString() {
result = concat(string chars, int pos |
xmlChars(_, chars, this, pos, _, _)
@@ -114,6 +117,20 @@ class XMLFile extends XMLParent, File {
/** Gets the name of this XML file. */
override string getName() { result = File.super.getAbsolutePath() }
+ /**
+ * DEPRECATED: Use `getAbsolutePath()` instead.
+ *
+ * Gets the path of this XML file.
+ */
+ deprecated string getPath() { result = getAbsolutePath() }
+
+ /**
+ * DEPRECATED: Use `getParentContainer().getAbsolutePath()` instead.
+ *
+ * Gets the path of the folder that contains this XML file.
+ */
+ deprecated string getFolder() { result = getParentContainer().getAbsolutePath() }
+
/** Gets the encoding of this XML file. */
string getEncoding() { xmlEncoding(this, result) }
diff --git a/javascript/ql/test/library-tests/Modules/ImportMeta.qll b/javascript/ql/test/library-tests/Modules/ImportMeta.qll
new file mode 100644
index 00000000000..81a4ba1a086
--- /dev/null
+++ b/javascript/ql/test/library-tests/Modules/ImportMeta.qll
@@ -0,0 +1,5 @@
+import semmle.javascript.ES2015Modules
+
+query predicate test_ImportMetaExpr(ImportMetaExpr meta) {
+ any()
+}
diff --git a/javascript/ql/test/library-tests/Modules/importmeta.js b/javascript/ql/test/library-tests/Modules/importmeta.js
new file mode 100644
index 00000000000..05bc2f96e49
--- /dev/null
+++ b/javascript/ql/test/library-tests/Modules/importmeta.js
@@ -0,0 +1 @@
+var foo = new URL('../cli.svg', import.meta.url).pathname;
\ No newline at end of file
diff --git a/javascript/ql/test/library-tests/Modules/tests.expected b/javascript/ql/test/library-tests/Modules/tests.expected
index 9de217ec11b..120038a8a24 100644
--- a/javascript/ql/test/library-tests/Modules/tests.expected
+++ b/javascript/ql/test/library-tests/Modules/tests.expected
@@ -26,6 +26,8 @@ test_getExportedName
| m/c.js:5:10:5:15 | g as h | g |
test_OtherImports
| es2015_require.js:1:11:1:24 | require('./d') | d.js:1:1:5:0 | |
+test_ImportMetaExpr
+| importmeta.js:1:33:1:43 | import.meta |
test_ReExportDeclarations
| b.js:7:1:7:21 | export ... './a'; | b.js:7:16:7:20 | './a' |
| d.js:4:1:4:20 | export * from 'm/c'; | d.js:4:15:4:19 | 'm/c' |
@@ -102,6 +104,7 @@ test_NamedImportSpecifier
test_GlobalVariableRef
| a.js:5:31:5:31 | o |
| exports.js:3:9:3:15 | exports |
+| importmeta.js:1:15:1:17 | URL |
| tst.html:6:3:6:7 | alert |
test_BulkReExportDeclarations
| d.js:4:1:4:20 | export * from 'm/c'; |
diff --git a/javascript/ql/test/library-tests/Modules/tests.ql b/javascript/ql/test/library-tests/Modules/tests.ql
index 7f1652f60ae..447cfd97496 100644
--- a/javascript/ql/test/library-tests/Modules/tests.ql
+++ b/javascript/ql/test/library-tests/Modules/tests.ql
@@ -2,6 +2,7 @@ import ImportSpecifiers
import getLocalName
import getExportedName
import OtherImports
+import ImportMeta
import ReExportDeclarations
import ImportDefaultSpecifiers
import getImportedName
diff --git a/python/ql/src/semmle/python/objects/ObjectAPI.qll b/python/ql/src/semmle/python/objects/ObjectAPI.qll
index c6eb6db6517..ac5348264cf 100644
--- a/python/ql/src/semmle/python/objects/ObjectAPI.qll
+++ b/python/ql/src/semmle/python/objects/ObjectAPI.qll
@@ -211,7 +211,7 @@ module Value {
}
/** Gets the `Value` for the integer constant `i`, if it exists.
- * There will be no `Value` for most integers, but the following are
+ * There will be no `Value` for most integers, but the following are
* guaranteed to exist:
* * From zero to 511 inclusive.
* * All powers of 2 (up to 2**30)
@@ -486,6 +486,11 @@ class PythonFunctionValue extends FunctionValue {
)
}
+ /** Gets a control flow node corresponding to a return statement in this function */
+ ControlFlowNode getAReturnedNode() {
+ result = this.getScope().getAReturnValueFlowNode()
+ }
+
}
/** Class representing builtin functions, such as `len` or `print` */
diff --git a/python/ql/src/semmle/python/web/flask/Response.qll b/python/ql/src/semmle/python/web/flask/Response.qll
index 2ae78d2cdfc..0828c180e9a 100644
--- a/python/ql/src/semmle/python/web/flask/Response.qll
+++ b/python/ql/src/semmle/python/web/flask/Response.qll
@@ -9,8 +9,8 @@ import semmle.python.web.flask.General
*/
class FlaskRoutedResponse extends HttpResponseTaintSink {
FlaskRoutedResponse() {
- exists(PyFunctionObject response |
- flask_routing(_, response.getFunction()) and
+ exists(PythonFunctionValue response |
+ flask_routing(_, response.getScope()) and
this = response.getAReturnedNode()
)
}
diff --git a/python/ql/src/semmle/python/web/pyramid/Response.qll b/python/ql/src/semmle/python/web/pyramid/Response.qll
index 511d1e28322..37dc4be783c 100644
--- a/python/ql/src/semmle/python/web/pyramid/Response.qll
+++ b/python/ql/src/semmle/python/web/pyramid/Response.qll
@@ -11,8 +11,8 @@ private import semmle.python.web.Http
*/
class PyramidRoutedResponse extends HttpResponseTaintSink {
PyramidRoutedResponse() {
- exists(PyFunctionObject view |
- is_pyramid_view_function(view.getFunction()) and
+ exists(PythonFunctionValue view |
+ is_pyramid_view_function(view.getScope()) and
this = view.getAReturnedNode()
)
}
diff --git a/python/ql/src/semmle/python/web/twisted/Request.qll b/python/ql/src/semmle/python/web/twisted/Request.qll
index 287a33ab1fe..67bb8cedbfe 100644
--- a/python/ql/src/semmle/python/web/twisted/Request.qll
+++ b/python/ql/src/semmle/python/web/twisted/Request.qll
@@ -1,53 +1,35 @@
import python
-
import semmle.python.security.TaintTracking
import semmle.python.web.Http
import Twisted
/** A twisted.web.http.Request object */
class TwistedRequest extends TaintKind {
-
- TwistedRequest() {
- this = "twisted.request.http.Request"
- }
+ TwistedRequest() { this = "twisted.request.http.Request" }
override TaintKind getTaintOfAttribute(string name) {
result instanceof ExternalStringSequenceDictKind and
- (
- name = "args"
- )
+ name = "args"
or
result instanceof ExternalStringKind and
- (
- name = "uri"
- )
+ name = "uri"
}
override TaintKind getTaintOfMethodResult(string name) {
- (
- name = "getHeader" or
- name = "getCookie" or
- name = "getUser" or
- name = "getPassword"
- ) and
- result instanceof ExternalStringKind
+ (
+ name = "getHeader" or
+ name = "getCookie" or
+ name = "getUser" or
+ name = "getPassword"
+ ) and
+ result instanceof ExternalStringKind
}
-
}
-
class TwistedRequestSource extends TaintSource {
+ TwistedRequestSource() { isTwistedRequestInstance(this) }
- TwistedRequestSource() {
- isTwistedRequestInstance(this)
- }
-
- override string toString() {
- result = "Twisted request source"
- }
-
- override predicate isSourceOf(TaintKind kind) {
- kind instanceof TwistedRequest
- }
+ override string toString() { result = "Twisted request source" }
+ override predicate isSourceOf(TaintKind kind) { kind instanceof TwistedRequest }
}
diff --git a/python/ql/src/semmle/python/web/twisted/Response.qll b/python/ql/src/semmle/python/web/twisted/Response.qll
index cdf35933e61..435960c40ab 100644
--- a/python/ql/src/semmle/python/web/twisted/Response.qll
+++ b/python/ql/src/semmle/python/web/twisted/Response.qll
@@ -1,5 +1,4 @@
import python
-
import semmle.python.security.TaintTracking
import semmle.python.web.Http
import semmle.python.security.strings.Basic
@@ -8,7 +7,7 @@ import Request
class TwistedResponse extends TaintSink {
TwistedResponse() {
- exists(PyFunctionObject func, string name |
+ exists(PythonFunctionValue func, string name, Return ret |
isKnownRequestHandlerMethodName(name) and
name = func.getName() and
func = getTwistedRequestHandlerMethod(name) and
@@ -16,13 +15,9 @@ class TwistedResponse extends TaintSink {
)
}
- override predicate sinks(TaintKind kind) {
- kind instanceof ExternalStringKind
- }
+ override predicate sinks(TaintKind kind) { kind instanceof ExternalStringKind }
- override string toString() {
- result = "Twisted response"
- }
+ override string toString() { result = "Twisted response" }
}
/**
@@ -30,7 +25,7 @@ class TwistedResponse extends TaintSink {
* object, which affects the properties of the subsequent response sent to this
* request.
*/
- class TwistedRequestSetter extends HttpResponseTaintSink {
+class TwistedRequestSetter extends HttpResponseTaintSink {
TwistedRequestSetter() {
exists(CallNode call, ControlFlowNode node, string name |
(
@@ -44,11 +39,7 @@ class TwistedResponse extends TaintSink {
)
}
- override predicate sinks(TaintKind kind) {
- kind instanceof ExternalStringKind
- }
+ override predicate sinks(TaintKind kind) { kind instanceof ExternalStringKind }
- override string toString() {
- result = "Twisted request setter"
- }
-}
\ No newline at end of file
+ override string toString() { result = "Twisted request setter" }
+}
diff --git a/python/ql/src/semmle/python/web/twisted/Twisted.qll b/python/ql/src/semmle/python/web/twisted/Twisted.qll
index 25912ad523e..e3b0ab0f9be 100644
--- a/python/ql/src/semmle/python/web/twisted/Twisted.qll
+++ b/python/ql/src/semmle/python/web/twisted/Twisted.qll
@@ -1,20 +1,17 @@
import python
-
import semmle.python.security.TaintTracking
-private ClassObject theTwistedHttpRequestClass() {
- result = ModuleObject::named("twisted.web.http").attr("Request")
+private ClassValue theTwistedHttpRequestClass() {
+ result = Value::named("twisted.web.http.Request")
}
-private ClassObject theTwistedHttpResourceClass() {
- result = ModuleObject::named("twisted.web.resource").attr("Resource")
+private ClassValue theTwistedHttpResourceClass() {
+ result = Value::named("twisted.web.resource.Resource")
}
-ClassObject aTwistedRequestHandlerClass() {
- result.getASuperType() = theTwistedHttpResourceClass()
-}
+ClassValue aTwistedRequestHandlerClass() { result.getABaseType+() = theTwistedHttpResourceClass() }
-FunctionObject getTwistedRequestHandlerMethod(string name) {
+FunctionValue getTwistedRequestHandlerMethod(string name) {
result = aTwistedRequestHandlerClass().declaredAttribute(name)
}
@@ -24,29 +21,30 @@ predicate isKnownRequestHandlerMethodName(string name) {
name.matches("render_%")
}
-/** Holds if `node` is likely to refer to an instance of the twisted
+/**
+ * Holds if `node` is likely to refer to an instance of the twisted
* `Request` class.
*/
predicate isTwistedRequestInstance(NameNode node) {
- node.refersTo(_, theTwistedHttpRequestClass(), _)
+ node.pointsTo().getClass() = theTwistedHttpRequestClass()
or
- /* In points-to analysis cannot infer that a given object is an instance of
+ /*
+ * In points-to analysis cannot infer that a given object is an instance of
* the `twisted.web.http.Request` class, we also include any parameter
* called `request` that appears inside a subclass of a request handler
* class, and the appropriate arguments of known request handler methods.
*/
- exists(Function func | func = node.getScope() |
- func.getEnclosingScope().(Class).getClassObject() = aTwistedRequestHandlerClass()
- ) and
- (
- /* Any parameter called `request` */
- node.getId() = "request" and
- node.isParameter()
- or
- /* Any request parameter of a known request handler method */
- exists(FunctionObject func | node.getScope() = func.getFunction() |
+
+ exists(Function func |
+ func = node.getScope() and
+ func.getEnclosingScope() = aTwistedRequestHandlerClass().getScope()
+ |
+ /* Any parameter called `request` */
+ node.getId() = "request" and
+ node.isParameter()
+ or
+ /* Any request parameter of a known request handler method */
isKnownRequestHandlerMethodName(func.getName()) and
- node.getNode() = func.getFunction().getArg(1)
- )
+ node.getNode() = func.getArg(1)
)
}
diff --git a/python/ql/src/semmle/python/xml/XML.qll b/python/ql/src/semmle/python/xml/XML.qll
index 13cc8d84f37..c6d55c8190b 100755
--- a/python/ql/src/semmle/python/xml/XML.qll
+++ b/python/ql/src/semmle/python/xml/XML.qll
@@ -1,26 +1,31 @@
/**
- * A library for working with XML files and their content.
+ * Provides classes and predicates for working with XML files and their content.
*/
-import semmle.python.Files
+import semmle.files.FileSystem
/** An XML element that has a location. */
abstract class XMLLocatable extends @xmllocatable {
- /** The source location for this element. */
- Location getLocation() { xmllocations(this,result) }
+ /** Gets the source location for this element. */
+ Location getLocation() { xmllocations(this, result) }
/**
- * Whether this element has the specified location information,
- * including file path, start line, start column, end line and end column.
+ * Holds if this element is at the specified location.
+ * The location spans column `startcolumn` of line `startline` to
+ * column `endcolumn` of line `endline` in file `filepath`.
+ * For more information, see
+ * [Locations](https://help.semmle.com/QL/learn-ql/ql/locations.html).
*/
- predicate hasLocationInfo(string filepath, int startline, int startcolumn, int endline, int endcolumn) {
+ predicate hasLocationInfo(
+ string filepath, int startline, int startcolumn, int endline, int endcolumn
+ ) {
exists(File f, Location l | l = this.getLocation() |
- locations_default(l,f,startline,startcolumn,endline,endcolumn) and
- filepath = f.getName()
+ locations_default(l, f, startline, startcolumn, endline, endcolumn) and
+ filepath = f.getAbsolutePath()
)
}
- /** A printable representation of this element. */
+ /** Gets a textual representation of this element. */
abstract string toString();
}
@@ -29,254 +34,305 @@ abstract class XMLLocatable extends @xmllocatable {
* both of which can contain other elements.
*/
class XMLParent extends @xmlparent {
+ XMLParent() {
+ // explicitly restrict `this` to be either an `XMLElement` or an `XMLFile`;
+ // the type `@xmlparent` currently also includes non-XML files
+ this instanceof @xmlelement or xmlEncoding(this, _)
+ }
+
/**
- * A printable representation of this XML parent.
+ * Gets a printable representation of this XML parent.
* (Intended to be overridden in subclasses.)
*/
- /*abstract*/ string getName() { result = "parent" }
+ abstract string getName();
- /** The file to which this XML parent belongs. */
- XMLFile getFile() { result = this or xmlElements(this,_,_,_,result) }
+ /** Gets the file to which this XML parent belongs. */
+ XMLFile getFile() { result = this or xmlElements(this, _, _, _, result) }
- /** The child element at a specified index of this XML parent. */
+ /** Gets the child element at a specified index of this XML parent. */
XMLElement getChild(int index) { xmlElements(result, _, this, index, _) }
- /** A child element of this XML parent. */
- XMLElement getAChild() { xmlElements(result,_,this,_,_) }
+ /** Gets a child element of this XML parent. */
+ XMLElement getAChild() { xmlElements(result, _, this, _, _) }
- /** A child element of this XML parent with the given `name`. */
- XMLElement getAChild(string name) { xmlElements(result,_,this,_,_) and result.hasName(name) }
+ /** Gets a child element of this XML parent with the given `name`. */
+ XMLElement getAChild(string name) { xmlElements(result, _, this, _, _) and result.hasName(name) }
- /** A comment that is a child of this XML parent. */
- XMLComment getAComment() { xmlComments(result,_,this,_) }
+ /** Gets a comment that is a child of this XML parent. */
+ XMLComment getAComment() { xmlComments(result, _, this, _) }
- /** A character sequence that is a child of this XML parent. */
- XMLCharacters getACharactersSet() { xmlChars(result,_,this,_,_,_) }
+ /** Gets a character sequence that is a child of this XML parent. */
+ XMLCharacters getACharactersSet() { xmlChars(result, _, this, _, _, _) }
- /** The depth in the tree. (Overridden in XMLElement.) */
+ /** Gets the depth in the tree. (Overridden in XMLElement.) */
int getDepth() { result = 0 }
- /** The number of child XML elements of this XML parent. */
- int getNumberOfChildren() {
- result = count(XMLElement e | xmlElements(e,_,this,_,_))
- }
+ /** Gets the number of child XML elements of this XML parent. */
+ int getNumberOfChildren() { result = count(XMLElement e | xmlElements(e, _, this, _, _)) }
- /** The number of places in the body of this XML parent where text occurs. */
- int getNumberOfCharacterSets() {
- result = count(int pos | xmlChars(_,_,this,pos,_,_))
- }
+ /** Gets the number of places in the body of this XML parent where text occurs. */
+ int getNumberOfCharacterSets() { result = count(int pos | xmlChars(_, _, this, pos, _, _)) }
/**
+ * DEPRECATED: Internal.
+ *
* Append the character sequences of this XML parent from left to right, separated by a space,
* up to a specified (zero-based) index.
*/
- string charsSetUpTo(int n) {
- (n = 0 and xmlChars(_,result,this,0,_,_)) or
- (n > 0 and exists(string chars | xmlChars(_,chars,this,n,_,_) |
- result = this.charsSetUpTo(n-1) + " " + chars))
- }
-
- /** Append all the character sequences of this XML parent from left to right, separated by a space. */
- string allCharactersString() {
- exists(int n | n = this.getNumberOfCharacterSets() |
- (n = 0 and result = "") or
- (n > 0 and result = this.charsSetUpTo(n-1))
+ deprecated string charsSetUpTo(int n) {
+ n = 0 and xmlChars(_, result, this, 0, _, _)
+ or
+ n > 0 and
+ exists(string chars | xmlChars(_, chars, this, n, _, _) |
+ result = this.charsSetUpTo(n - 1) + " " + chars
)
}
- /** The text value contained in this XML parent. */
- string getTextValue() {
- result = allCharactersString()
+ /**
+ * Gets the result of appending all the character sequences of this XML parent from
+ * left to right, separated by a space.
+ */
+ string allCharactersString() {
+ result = concat(string chars, int pos |
+ xmlChars(_, chars, this, pos, _, _)
+ |
+ chars, " " order by pos
+ )
}
- /** A printable representation of this XML parent. */
+ /** Gets the text value contained in this XML parent. */
+ string getTextValue() { result = allCharactersString() }
+
+ /** Gets a printable representation of this XML parent. */
string toString() { result = this.getName() }
}
/** An XML file. */
class XMLFile extends XMLParent, File {
- XMLFile() {
- xmlEncoding(this,_)
- }
+ XMLFile() { xmlEncoding(this, _) }
- /** A printable representation of this XML file. */
- override
- string toString() { result = XMLParent.super.toString() }
+ /** Gets a printable representation of this XML file. */
+ override string toString() { result = XMLParent.super.toString() }
- /** The name of this XML file. */
- override
- string getName() { files(this,result,_,_,_) }
+ /** Gets the name of this XML file. */
+ override string getName() { result = File.super.getAbsolutePath() }
- /** The path of this XML file. */
- string getPath() { files(this,_,result,_,_) }
+ /**
+ * DEPRECATED: Use `getAbsolutePath()` instead.
+ *
+ * Gets the path of this XML file.
+ */
+ deprecated string getPath() { result = getAbsolutePath() }
- /** The path of the folder that contains this XML file. */
- string getFolder() {
- result = this.getPath().substring(0, this.getPath().length()-this.getName().length())
- }
+ /**
+ * DEPRECATED: Use `getParentContainer().getAbsolutePath()` instead.
+ *
+ * Gets the path of the folder that contains this XML file.
+ */
+ deprecated string getFolder() { result = getParentContainer().getAbsolutePath() }
- /** The encoding of this XML file. */
- string getEncoding() { xmlEncoding(this,result) }
+ /** Gets the encoding of this XML file. */
+ string getEncoding() { xmlEncoding(this, result) }
- /** The XML file itself. */
- override
- XMLFile getFile() { result = this }
+ /** Gets the XML file itself. */
+ override XMLFile getFile() { result = this }
- /** A top-most element in an XML file. */
+ /** Gets a top-most element in an XML file. */
XMLElement getARootElement() { result = this.getAChild() }
- /** A DTD associated with this XML file. */
- XMLDTD getADTD() { xmlDTDs(result,_,_,_,this) }
+ /** Gets a DTD associated with this XML file. */
+ XMLDTD getADTD() { xmlDTDs(result, _, _, _, this) }
}
-/** A "Document Type Definition" of an XML file. */
+/**
+ * An XML document type definition (DTD).
+ *
+ * Example:
+ *
+ * ```
+ *
+ *
+ *
+ * ```
+ */
class XMLDTD extends @xmldtd {
- /** The name of the root element of this DTD. */
- string getRoot() { xmlDTDs(this,result,_,_,_) }
+ /** Gets the name of the root element of this DTD. */
+ string getRoot() { xmlDTDs(this, result, _, _, _) }
- /** The public ID of this DTD. */
- string getPublicId() { xmlDTDs(this,_,result,_,_) }
+ /** Gets the public ID of this DTD. */
+ string getPublicId() { xmlDTDs(this, _, result, _, _) }
- /** The system ID of this DTD. */
- string getSystemId() { xmlDTDs(this,_,_,result,_) }
+ /** Gets the system ID of this DTD. */
+ string getSystemId() { xmlDTDs(this, _, _, result, _) }
- /** Whether this DTD is public. */
- predicate isPublic() { not xmlDTDs(this,_,"",_,_) }
+ /** Holds if this DTD is public. */
+ predicate isPublic() { not xmlDTDs(this, _, "", _, _) }
- /** The parent of this DTD. */
- XMLParent getParent() { xmlDTDs(this,_,_,_,result) }
+ /** Gets the parent of this DTD. */
+ XMLParent getParent() { xmlDTDs(this, _, _, _, result) }
- /** A printable representation of this DTD. */
+ /** Gets a printable representation of this DTD. */
string toString() {
- (this.isPublic() and result = this.getRoot() + " PUBLIC '" +
- this.getPublicId() + "' '" +
- this.getSystemId() + "'") or
- (not this.isPublic() and result = this.getRoot() +
- " SYSTEM '" +
- this.getSystemId() + "'")
+ this.isPublic() and
+ result = this.getRoot() + " PUBLIC '" + this.getPublicId() + "' '" + this.getSystemId() + "'"
+ or
+ not this.isPublic() and
+ result = this.getRoot() + " SYSTEM '" + this.getSystemId() + "'"
}
}
-/** An XML tag in an XML file. */
+/**
+ * An XML element in an XML file.
+ *
+ * Example:
+ *
+ * ```
+ *
+ *
+ * ```
+ */
class XMLElement extends @xmlelement, XMLParent, XMLLocatable {
- /** Whether this XML element has the given `name`. */
+ /** Holds if this XML element has the given `name`. */
predicate hasName(string name) { name = getName() }
- /** The name of this XML element. */
- override
- string getName() { xmlElements(this,result,_,_,_) }
+ /** Gets the name of this XML element. */
+ override string getName() { xmlElements(this, result, _, _, _) }
- /** The XML file in which this XML element occurs. */
- override
- XMLFile getFile() { xmlElements(this,_,_,_,result) }
+ /** Gets the XML file in which this XML element occurs. */
+ override XMLFile getFile() { xmlElements(this, _, _, _, result) }
- /** The parent of this XML element. */
- XMLParent getParent() { xmlElements(this,_,result,_,_) }
+ /** Gets the parent of this XML element. */
+ XMLParent getParent() { xmlElements(this, _, result, _, _) }
- /** The index of this XML element among its parent's children. */
+ /** Gets the index of this XML element among its parent's children. */
int getIndex() { xmlElements(this, _, _, result, _) }
- /** Whether this XML element has a namespace. */
- predicate hasNamespace() { xmlHasNs(this,_,_) }
+ /** Holds if this XML element has a namespace. */
+ predicate hasNamespace() { xmlHasNs(this, _, _) }
- /** The namespace of this XML element, if any. */
- XMLNamespace getNamespace() { xmlHasNs(this,result,_) }
+ /** Gets the namespace of this XML element, if any. */
+ XMLNamespace getNamespace() { xmlHasNs(this, result, _) }
- /** The index of this XML element among its parent's children. */
- int getElementPositionIndex() { xmlElements(this,_,_,result,_) }
+ /** Gets the index of this XML element among its parent's children. */
+ int getElementPositionIndex() { xmlElements(this, _, _, result, _) }
- /** The depth of this element within the XML file tree structure. */
- override
- int getDepth() { result = this.getParent().getDepth() + 1 }
+ /** Gets the depth of this element within the XML file tree structure. */
+ override int getDepth() { result = this.getParent().getDepth() + 1 }
- /** An XML attribute of this XML element. */
+ /** Gets an XML attribute of this XML element. */
XMLAttribute getAnAttribute() { result.getElement() = this }
- /** The attribute with the specified `name`, if any. */
- XMLAttribute getAttribute(string name) {
- result.getElement() = this and result.getName() = name
- }
+ /** Gets the attribute with the specified `name`, if any. */
+ XMLAttribute getAttribute(string name) { result.getElement() = this and result.getName() = name }
- /** Whether this XML element has an attribute with the specified `name`. */
- predicate hasAttribute(string name) {
- exists(XMLAttribute a| a = this.getAttribute(name))
- }
+ /** Holds if this XML element has an attribute with the specified `name`. */
+ predicate hasAttribute(string name) { exists(XMLAttribute a | a = this.getAttribute(name)) }
- /** The value of the attribute with the specified `name`, if any. */
- string getAttributeValue(string name) {
- result = this.getAttribute(name).getValue()
- }
+ /** Gets the value of the attribute with the specified `name`, if any. */
+ string getAttributeValue(string name) { result = this.getAttribute(name).getValue() }
- /** A printable representation of this XML element. */
- override
- string toString() { result = XMLParent.super.toString() }
+ /** Gets a printable representation of this XML element. */
+ override string toString() { result = XMLParent.super.toString() }
}
-/** An attribute that occurs inside an XML element. */
+/**
+ * An attribute that occurs inside an XML element.
+ *
+ * Examples:
+ *
+ * ```
+ * package="com.example.exampleapp"
+ * android:versionCode="1"
+ * ```
+ */
class XMLAttribute extends @xmlattribute, XMLLocatable {
- /** The name of this attribute. */
- string getName() { xmlAttrs(this,_,result,_,_,_) }
+ /** Gets the name of this attribute. */
+ string getName() { xmlAttrs(this, _, result, _, _, _) }
- /** The XML element to which this attribute belongs. */
- XMLElement getElement() { xmlAttrs(this,result,_,_,_,_) }
+ /** Gets the XML element to which this attribute belongs. */
+ XMLElement getElement() { xmlAttrs(this, result, _, _, _, _) }
- /** Whether this attribute has a namespace. */
- predicate hasNamespace() { xmlHasNs(this,_,_) }
+ /** Holds if this attribute has a namespace. */
+ predicate hasNamespace() { xmlHasNs(this, _, _) }
- /** The namespace of this attribute, if any. */
- XMLNamespace getNamespace() { xmlHasNs(this,result,_) }
+ /** Gets the namespace of this attribute, if any. */
+ XMLNamespace getNamespace() { xmlHasNs(this, result, _) }
- /** The value of this attribute. */
- string getValue() { xmlAttrs(this,_,_,result,_,_) }
+ /** Gets the value of this attribute. */
+ string getValue() { xmlAttrs(this, _, _, result, _, _) }
- /** A printable representation of this XML attribute. */
+ /** Gets a printable representation of this XML attribute. */
override string toString() { result = this.getName() + "=" + this.getValue() }
}
-/** A namespace used in an XML file */
+/**
+ * A namespace used in an XML file.
+ *
+ * Example:
+ *
+ * ```
+ * xmlns:android="http://schemas.android.com/apk/res/android"
+ * ```
+ */
class XMLNamespace extends @xmlnamespace {
- /** The prefix of this namespace. */
- string getPrefix() { xmlNs(this,result,_,_) }
+ /** Gets the prefix of this namespace. */
+ string getPrefix() { xmlNs(this, result, _, _) }
- /** The URI of this namespace. */
- string getURI() { xmlNs(this,_,result,_) }
+ /** Gets the URI of this namespace. */
+ string getURI() { xmlNs(this, _, result, _) }
- /** Whether this namespace has no prefix. */
+ /** Holds if this namespace has no prefix. */
predicate isDefault() { this.getPrefix() = "" }
- /** A printable representation of this XML namespace. */
+ /** Gets a printable representation of this XML namespace. */
string toString() {
- (this.isDefault() and result = this.getURI()) or
- (not this.isDefault() and result = this.getPrefix() + ":" + this.getURI())
+ this.isDefault() and result = this.getURI()
+ or
+ not this.isDefault() and result = this.getPrefix() + ":" + this.getURI()
}
}
-/** A comment of the form `` is an XML comment. */
+/**
+ * A comment in an XML file.
+ *
+ * Example:
+ *
+ * ```
+ *
+ * ```
+ */
class XMLComment extends @xmlcomment, XMLLocatable {
- /** The text content of this XML comment. */
- string getText() { xmlComments(this,result,_,_) }
+ /** Gets the text content of this XML comment. */
+ string getText() { xmlComments(this, result, _, _) }
- /** The parent of this XML comment. */
- XMLParent getParent() { xmlComments(this,_,result,_) }
+ /** Gets the parent of this XML comment. */
+ XMLParent getParent() { xmlComments(this, _, result, _) }
- /** A printable representation of this XML comment. */
+ /** Gets a printable representation of this XML comment. */
override string toString() { result = this.getText() }
}
/**
* A sequence of characters that occurs between opening and
* closing tags of an XML element, excluding other elements.
+ *
+ * Example:
+ *
+ * ```
+ * This is a sequence of characters.
+ * ```
*/
class XMLCharacters extends @xmlcharacters, XMLLocatable {
- /** The content of this character sequence. */
- string getCharacters() { xmlChars(this,result,_,_,_,_) }
+ /** Gets the content of this character sequence. */
+ string getCharacters() { xmlChars(this, result, _, _, _, _) }
- /** The parent of this character sequence. */
- XMLParent getParent() { xmlChars(this,_,result,_,_,_) }
+ /** Gets the parent of this character sequence. */
+ XMLParent getParent() { xmlChars(this, _, result, _, _, _) }
- /** Whether this character sequence is CDATA. */
- predicate isCDATA() { xmlChars(this,_,_,_,1,_) }
+ /** Holds if this character sequence is CDATA. */
+ predicate isCDATA() { xmlChars(this, _, _, _, 1, _) }
- /** A printable representation of this XML character sequence. */
+ /** Gets a printable representation of this XML character sequence. */
override string toString() { result = this.getCharacters() }
}
diff --git a/python/ql/test/library-tests/web/twisted/Classes.expected b/python/ql/test/library-tests/web/twisted/Classes.expected
new file mode 100644
index 00000000000..7713130d705
--- /dev/null
+++ b/python/ql/test/library-tests/web/twisted/Classes.expected
@@ -0,0 +1,5 @@
+| class MyRequestHandler1 | test.py:3 |
+| class MyRequestHandler2 | test.py:23 |
+| class MyRequestHandler3 | test.py:27 |
+| class MyRequestHandler4 | test.py:38 |
+| class MyRequestHandler5 | test.py:42 |
diff --git a/python/ql/test/library-tests/web/twisted/Classes.ql b/python/ql/test/library-tests/web/twisted/Classes.ql
new file mode 100644
index 00000000000..ccc3618b61e
--- /dev/null
+++ b/python/ql/test/library-tests/web/twisted/Classes.ql
@@ -0,0 +1,7 @@
+import python
+import semmle.python.TestUtils
+import semmle.python.web.twisted.Twisted
+
+from ClassValue cls
+where cls = aTwistedRequestHandlerClass()
+select cls.toString(), remove_library_prefix(cls.getScope().getLocation())
diff --git a/python/ql/test/library-tests/web/twisted/Methods.expected b/python/ql/test/library-tests/web/twisted/Methods.expected
new file mode 100644
index 00000000000..e4ca6605ac9
--- /dev/null
+++ b/python/ql/test/library-tests/web/twisted/Methods.expected
@@ -0,0 +1,8 @@
+| myrender | Function MyRequestHandler2.myrender | test.py:24 |
+| render | Function MyRequestHandler1.render | test.py:4 |
+| render | Function MyRequestHandler3.render | test.py:28 |
+| render | Function MyRequestHandler4.render | test.py:39 |
+| render | Function MyRequestHandler5.render | test.py:43 |
+| render_GET | Function MyRequestHandler1.render_GET | test.py:9 |
+| render_POST | Function MyRequestHandler1.render_POST | test.py:16 |
+| render_POST | Function MyRequestHandler3.render_POST | test.py:31 |
diff --git a/python/ql/test/library-tests/web/twisted/Methods.ql b/python/ql/test/library-tests/web/twisted/Methods.ql
new file mode 100644
index 00000000000..f997b7deef3
--- /dev/null
+++ b/python/ql/test/library-tests/web/twisted/Methods.ql
@@ -0,0 +1,7 @@
+import python
+import semmle.python.TestUtils
+import semmle.python.web.twisted.Twisted
+
+from FunctionValue func, string name
+where func = getTwistedRequestHandlerMethod(name)
+select name, func.toString(), remove_library_prefix(func.getScope().getLocation())
diff --git a/python/ql/test/library-tests/web/twisted/Sinks.expected b/python/ql/test/library-tests/web/twisted/Sinks.expected
new file mode 100644
index 00000000000..f416a03e4b7
--- /dev/null
+++ b/python/ql/test/library-tests/web/twisted/Sinks.expected
@@ -0,0 +1,8 @@
+| test.py:7 | response | externally controlled string |
+| test.py:14 | response | externally controlled string |
+| test.py:21 | response | externally controlled string |
+| test.py:36 | do_stuff_with() | externally controlled string |
+| test.py:40 | Str | externally controlled string |
+| test.py:44 | Str | externally controlled string |
+| test.py:45 | Str | externally controlled string |
+| test.py:46 | Str | externally controlled string |
diff --git a/python/ql/test/library-tests/web/twisted/Sinks.ql b/python/ql/test/library-tests/web/twisted/Sinks.ql
new file mode 100644
index 00000000000..1045e9dda6b
--- /dev/null
+++ b/python/ql/test/library-tests/web/twisted/Sinks.ql
@@ -0,0 +1,10 @@
+import python
+
+import semmle.python.web.HttpRequest
+import semmle.python.web.HttpResponse
+import semmle.python.security.strings.Untrusted
+import semmle.python.TestUtils
+
+from TaintSink sink, TaintKind kind
+where sink.sinks(kind)
+select remove_library_prefix(sink.getLocation()), sink.(ControlFlowNode).getNode().toString(), kind
diff --git a/python/ql/test/library-tests/web/twisted/Sources.expected b/python/ql/test/library-tests/web/twisted/Sources.expected
new file mode 100644
index 00000000000..3015951d32b
--- /dev/null
+++ b/python/ql/test/library-tests/web/twisted/Sources.expected
@@ -0,0 +1,8 @@
+| test.py:4 | request | twisted.request.http.Request |
+| test.py:9 | request | twisted.request.http.Request |
+| test.py:16 | request | twisted.request.http.Request |
+| test.py:24 | request | twisted.request.http.Request |
+| test.py:28 | myrequest | twisted.request.http.Request |
+| test.py:31 | postrequest | twisted.request.http.Request |
+| test.py:39 | request | twisted.request.http.Request |
+| test.py:43 | request | twisted.request.http.Request |
diff --git a/python/ql/test/library-tests/web/twisted/Sources.ql b/python/ql/test/library-tests/web/twisted/Sources.ql
new file mode 100644
index 00000000000..bded6087ed2
--- /dev/null
+++ b/python/ql/test/library-tests/web/twisted/Sources.ql
@@ -0,0 +1,11 @@
+import python
+import semmle.python.TestUtils
+
+import semmle.python.web.HttpRequest
+import semmle.python.web.HttpResponse
+import semmle.python.security.strings.Untrusted
+
+
+from TaintSource src, TaintKind kind
+where src.isSourceOf(kind)
+select remove_library_prefix(src.getLocation()), src.(ControlFlowNode).getNode().toString(), kind
diff --git a/python/ql/test/library-tests/web/twisted/Taint.expected b/python/ql/test/library-tests/web/twisted/Taint.expected
new file mode 100644
index 00000000000..1c793c973bd
--- /dev/null
+++ b/python/ql/test/library-tests/web/twisted/Taint.expected
@@ -0,0 +1,41 @@
+| test.py:4 | request | twisted.request.http.Request |
+| test.py:5 | Attribute | externally controlled string |
+| test.py:5 | request | twisted.request.http.Request |
+| test.py:6 | request | twisted.request.http.Request |
+| test.py:9 | request | twisted.request.http.Request |
+| test.py:10 | request | twisted.request.http.Request |
+| test.py:11 | Attribute | externally controlled string |
+| test.py:11 | x | twisted.request.http.Request |
+| test.py:12 | request | twisted.request.http.Request |
+| test.py:13 | request | twisted.request.http.Request |
+| test.py:16 | request | twisted.request.http.Request |
+| test.py:17 | Attribute | {[externally controlled string]} |
+| test.py:17 | request | twisted.request.http.Request |
+| test.py:18 | Attribute | {[externally controlled string]} |
+| test.py:18 | Attribute() | [externally controlled string] |
+| test.py:18 | request | twisted.request.http.Request |
+| test.py:19 | Subscript | externally controlled string |
+| test.py:19 | foo | [externally controlled string] |
+| test.py:20 | quux | externally controlled string |
+| test.py:24 | request | twisted.request.http.Request |
+| test.py:25 | request | twisted.request.http.Request |
+| test.py:28 | myrequest | twisted.request.http.Request |
+| test.py:29 | myrequest | twisted.request.http.Request |
+| test.py:31 | postrequest | twisted.request.http.Request |
+| test.py:32 | Attribute() | externally controlled string |
+| test.py:32 | postrequest | twisted.request.http.Request |
+| test.py:33 | Attribute() | externally controlled string |
+| test.py:33 | postrequest | twisted.request.http.Request |
+| test.py:34 | Attribute() | externally controlled string |
+| test.py:34 | postrequest | twisted.request.http.Request |
+| test.py:35 | Attribute() | externally controlled string |
+| test.py:35 | postrequest | twisted.request.http.Request |
+| test.py:36 | w | externally controlled string |
+| test.py:36 | x | externally controlled string |
+| test.py:36 | y | externally controlled string |
+| test.py:36 | z | externally controlled string |
+| test.py:39 | request | twisted.request.http.Request |
+| test.py:40 | request | twisted.request.http.Request |
+| test.py:43 | request | twisted.request.http.Request |
+| test.py:44 | request | twisted.request.http.Request |
+| test.py:45 | request | twisted.request.http.Request |
diff --git a/python/ql/test/library-tests/web/twisted/Taint.ql b/python/ql/test/library-tests/web/twisted/Taint.ql
new file mode 100644
index 00000000000..f0a95299113
--- /dev/null
+++ b/python/ql/test/library-tests/web/twisted/Taint.ql
@@ -0,0 +1,11 @@
+import python
+import semmle.python.TestUtils
+
+import semmle.python.web.HttpRequest
+import semmle.python.web.HttpResponse
+import semmle.python.security.strings.Untrusted
+
+from TaintedNode node
+
+select remove_library_prefix(node.getLocation()), node.getAstNode().toString(), node.getTaintKind()
+
diff --git a/python/ql/test/library-tests/web/twisted/options b/python/ql/test/library-tests/web/twisted/options
new file mode 100644
index 00000000000..6979a743e58
--- /dev/null
+++ b/python/ql/test/library-tests/web/twisted/options
@@ -0,0 +1,2 @@
+semmle-extractor-options: --max-import-depth=1 -p ../../../query-tests/Security/lib/
+optimize: true
diff --git a/python/ql/test/library-tests/web/twisted/test.py b/python/ql/test/library-tests/web/twisted/test.py
new file mode 100644
index 00000000000..f6691b84d95
--- /dev/null
+++ b/python/ql/test/library-tests/web/twisted/test.py
@@ -0,0 +1,51 @@
+from twisted.web import resource
+
+class MyRequestHandler1(resource.Resource):
+ def render(self, request):
+ foo(request.uri)
+ response = do_stuff_with(request)
+ return response
+
+ def render_GET(self, request):
+ x = request
+ bar(x.uri)
+ do_stuff_with(request)
+ response = do_stuff_with(request)
+ return response
+
+ def render_POST(self, request):
+ baz(request.args)
+ foo = request.args.get("baz")
+ quux = foo[5]
+ response = do_stuff_with(quux)
+ return response
+
+class MyRequestHandler2(resource.Resource):
+ def myrender(self, request):
+ do_stuff_with(request)
+
+class MyRequestHandler3(resource.Resource):
+ def render(self, myrequest):
+ do_stuff_with(myrequest)
+
+ def render_POST(self, postrequest):
+ x = postrequest.getHeader("someheader")
+ y = postrequest.getCookie("somecookie")
+ z = postrequest.getUser()
+ w = postrequest.getPassword()
+ return do_stuff_with(x,y,z,w)
+
+class MyRequestHandler4(resource.Resource):
+ def render(self, request):
+ request.write("Foobar")
+
+class MyRequestHandler5(resource.Resource):
+ def render(self, request):
+ request.setHeader("foo", "bar")
+ request.addCookie("key", "value")
+ return "This is my response."
+
+class NotATwistedRequestHandler(object):
+ def render(self, request):
+ return do_stuff_with(request)
+
diff --git a/python/ql/test/library-tests/web/zope/Test.expected b/python/ql/test/library-tests/web/zope/Test.expected
new file mode 100644
index 00000000000..bb3ca1f441e
--- /dev/null
+++ b/python/ql/test/library-tests/web/zope/Test.expected
@@ -0,0 +1,3 @@
+| 12 | ControlFlowNode for implementer | class implementer | ../../../query-tests/Security/lib/zope/interface/__init__.py:5 |
+| 13 | ControlFlowNode for IThing | class IThing | test.py:4 |
+| 14 | ControlFlowNode for Thing | class Thing | test.py:9 |
diff --git a/python/ql/test/library-tests/web/zope/Test.ql b/python/ql/test/library-tests/web/zope/Test.ql
new file mode 100644
index 00000000000..e694883237b
--- /dev/null
+++ b/python/ql/test/library-tests/web/zope/Test.ql
@@ -0,0 +1,10 @@
+import python
+import semmle.python.TestUtils
+
+from ControlFlowNode f, Value v, ControlFlowNode x
+where
+ exists(ExprStmt s | s.getValue().getAFlowNode() = f) and
+ f.pointsTo(v, x) and
+ f.getLocation().getFile().getBaseName() = "test.py"
+select f.getLocation().getStartLine(), f.toString(), v.toString(),
+ remove_library_prefix(x.getLocation())
diff --git a/python/ql/test/library-tests/web/zope/options b/python/ql/test/library-tests/web/zope/options
new file mode 100644
index 00000000000..7fb713d5924
--- /dev/null
+++ b/python/ql/test/library-tests/web/zope/options
@@ -0,0 +1 @@
+semmle-extractor-options: --max-import-depth=3 -p ../../../query-tests/Security/lib/
diff --git a/python/ql/test/library-tests/web/zope/test.py b/python/ql/test/library-tests/web/zope/test.py
new file mode 100644
index 00000000000..64ac9138d7e
--- /dev/null
+++ b/python/ql/test/library-tests/web/zope/test.py
@@ -0,0 +1,14 @@
+
+from zope.interface import Interface, implementer
+
+class IThing(Interface):
+ pass
+
+
+@implementer(IThing)
+class Thing(object):
+ pass
+
+implementer
+IThing
+Thing
diff --git a/python/ql/test/query-tests/Security/lib/twisted/__init__.py b/python/ql/test/query-tests/Security/lib/twisted/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/python/ql/test/query-tests/Security/lib/twisted/web/__init__.py b/python/ql/test/query-tests/Security/lib/twisted/web/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/python/ql/test/query-tests/Security/lib/twisted/web/http.py b/python/ql/test/query-tests/Security/lib/twisted/web/http.py
new file mode 100644
index 00000000000..c7ac65eaf05
--- /dev/null
+++ b/python/ql/test/query-tests/Security/lib/twisted/web/http.py
@@ -0,0 +1,2 @@
+class Request(object):
+ pass
diff --git a/python/ql/test/query-tests/Security/lib/twisted/web/resource.py b/python/ql/test/query-tests/Security/lib/twisted/web/resource.py
new file mode 100644
index 00000000000..1fe186864cb
--- /dev/null
+++ b/python/ql/test/query-tests/Security/lib/twisted/web/resource.py
@@ -0,0 +1,2 @@
+class Resource(object):
+ pass
diff --git a/python/ql/test/query-tests/Security/lib/zope/__init__.py b/python/ql/test/query-tests/Security/lib/zope/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/python/ql/test/query-tests/Security/lib/zope/interface/__init__.py b/python/ql/test/query-tests/Security/lib/zope/interface/__init__.py
new file mode 100644
index 00000000000..423c7b58341
--- /dev/null
+++ b/python/ql/test/query-tests/Security/lib/zope/interface/__init__.py
@@ -0,0 +1,9 @@
+class Interface():
+ pass
+
+
+class implementer:
+
+ def __call__(self, ob):
+ ...
+ return ob