Skip to content

Commit c9fa1fb

Browse files
committed
Ruby: copy JS version of IncompleteUrlSubstringSanitization.ql
1 parent eeb9a1d commit c9fa1fb

10 files changed

+1292
-0
lines changed
Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
/**
2+
* Contains classes for recognizing array and string inclusion tests.
3+
*/
4+
5+
private import javascript
6+
7+
/**
8+
* An expression that checks if an element is contained in an array
9+
* or is a substring of another string.
10+
*
11+
* Examples:
12+
* ```
13+
* A.includes(B)
14+
* A.indexOf(B) !== -1
15+
* A.indexOf(B) >= 0
16+
* ~A.indexOf(B)
17+
* ```
18+
*/
19+
class InclusionTest extends DataFlow::Node instanceof InclusionTest::Range {
20+
/** Gets the `A` in `A.includes(B)`. */
21+
DataFlow::Node getContainerNode() { result = super.getContainerNode() }
22+
23+
/** Gets the `B` in `A.includes(B)`. */
24+
DataFlow::Node getContainedNode() { result = super.getContainedNode() }
25+
26+
/**
27+
* Gets the polarity of the check.
28+
*
29+
* If the polarity is `false` the check returns `true` if the container does not contain
30+
* the given element.
31+
*/
32+
boolean getPolarity() { result = super.getPolarity() }
33+
}
34+
35+
module InclusionTest {
36+
/**
37+
* A expression that is equivalent to `A.includes(B)` or `!A.includes(B)`.
38+
*
39+
* Note that this also includes calls to the array method named `includes`.
40+
*/
41+
abstract class Range extends DataFlow::Node {
42+
/** Gets the `A` in `A.includes(B)`. */
43+
abstract DataFlow::Node getContainerNode();
44+
45+
/** Gets the `B` in `A.includes(B)`. */
46+
abstract DataFlow::Node getContainedNode();
47+
48+
/**
49+
* Gets the polarity of the check.
50+
*
51+
* If the polarity is `false` the check returns `true` if the container does not contain
52+
* the given element.
53+
*/
54+
boolean getPolarity() { result = true }
55+
}
56+
57+
/**
58+
* A call to a utility function (`callee`) that performs an InclusionTest (`inner`).
59+
*/
60+
private class IndirectInclusionTest extends Range, DataFlow::CallNode {
61+
InclusionTest inner;
62+
Function callee;
63+
64+
IndirectInclusionTest() {
65+
inner.getEnclosingExpr() = unique(Expr ret | ret = callee.getAReturnedExpr()) and
66+
callee = unique(Function f | f = this.getACallee()) and
67+
not this.isImprecise() and
68+
inner.getContainedNode().getALocalSource() = DataFlow::parameterNode(callee.getAParameter()) and
69+
inner.getContainerNode().getALocalSource() = DataFlow::parameterNode(callee.getAParameter())
70+
}
71+
72+
override DataFlow::Node getContainerNode() {
73+
exists(int arg |
74+
inner.getContainerNode().getALocalSource() =
75+
DataFlow::parameterNode(callee.getParameter(arg)) and
76+
result = this.getArgument(arg)
77+
)
78+
}
79+
80+
override DataFlow::Node getContainedNode() {
81+
exists(int arg |
82+
inner.getContainedNode().getALocalSource() =
83+
DataFlow::parameterNode(callee.getParameter(arg)) and
84+
result = this.getArgument(arg)
85+
)
86+
}
87+
88+
override boolean getPolarity() { result = inner.getPolarity() }
89+
}
90+
91+
/**
92+
* A call to a method named `includes`, assumed to refer to `String.prototype.includes`
93+
* or `Array.prototype.includes`.
94+
*/
95+
private class Includes_Native extends Range, DataFlow::MethodCallNode {
96+
Includes_Native() {
97+
this.getMethodName() = "includes" and
98+
this.getNumArgument() = 1
99+
}
100+
101+
override DataFlow::Node getContainerNode() { result = this.getReceiver() }
102+
103+
override DataFlow::Node getContainedNode() { result = this.getArgument(0) }
104+
}
105+
106+
/**
107+
* A call to `_.includes` or similar, assumed to operate on strings.
108+
*/
109+
private class Includes_Library extends Range, DataFlow::CallNode {
110+
Includes_Library() {
111+
exists(string name |
112+
this = LodashUnderscore::member(name).getACall() and
113+
(name = "includes" or name = "include" or name = "contains")
114+
or
115+
this = Closure::moduleImport("goog.string." + name).getACall() and
116+
(name = "contains" or name = "caseInsensitiveContains")
117+
)
118+
}
119+
120+
override DataFlow::Node getContainerNode() { result = this.getArgument(0) }
121+
122+
override DataFlow::Node getContainedNode() { result = this.getArgument(1) }
123+
}
124+
125+
/**
126+
* A check of form `A.indexOf(B) !== -1` or similar.
127+
*/
128+
private class Includes_IndexOfEquals extends Range, DataFlow::ValueNode {
129+
MethodCallExpr indexOf;
130+
override EqualityTest astNode;
131+
132+
Includes_IndexOfEquals() {
133+
exists(Expr index | astNode.hasOperands(indexOf, index) |
134+
// one operand is of the form `whitelist.indexOf(x)`
135+
indexOf.getMethodName() = "indexOf" and
136+
// and the other one is -1
137+
index.getIntValue() = -1
138+
)
139+
}
140+
141+
override DataFlow::Node getContainerNode() { result = indexOf.getReceiver().flow() }
142+
143+
override DataFlow::Node getContainedNode() { result = indexOf.getArgument(0).flow() }
144+
145+
override boolean getPolarity() { result = astNode.getPolarity().booleanNot() }
146+
}
147+
148+
/**
149+
* A check of form `A.indexOf(B) >= 0` or similar.
150+
*/
151+
private class Includes_IndexOfRelational extends Range, DataFlow::ValueNode {
152+
MethodCallExpr indexOf;
153+
override RelationalComparison astNode;
154+
boolean polarity;
155+
156+
Includes_IndexOfRelational() {
157+
exists(Expr lesser, Expr greater |
158+
astNode.getLesserOperand() = lesser and
159+
astNode.getGreaterOperand() = greater and
160+
indexOf.getMethodName() = "indexOf" and
161+
indexOf.getNumArgument() = 1
162+
|
163+
polarity = true and
164+
greater = indexOf and
165+
(
166+
lesser.getIntValue() = 0 and astNode.isInclusive()
167+
or
168+
lesser.getIntValue() = -1 and not astNode.isInclusive()
169+
)
170+
or
171+
polarity = false and
172+
lesser = indexOf and
173+
(
174+
greater.getIntValue() = -1 and astNode.isInclusive()
175+
or
176+
greater.getIntValue() = 0 and not astNode.isInclusive()
177+
)
178+
)
179+
}
180+
181+
override DataFlow::Node getContainerNode() { result = indexOf.getReceiver().flow() }
182+
183+
override DataFlow::Node getContainedNode() { result = indexOf.getArgument(0).flow() }
184+
185+
override boolean getPolarity() { result = polarity }
186+
}
187+
188+
/**
189+
* An expression of form `~A.indexOf(B)` which, when coerced to a boolean, is equivalent to `A.includes(B)`.
190+
*/
191+
private class Includes_IndexOfBitwise extends Range, DataFlow::ValueNode {
192+
MethodCallExpr indexOf;
193+
override BitNotExpr astNode;
194+
195+
Includes_IndexOfBitwise() {
196+
astNode.getOperand() = indexOf and
197+
indexOf.getMethodName() = "indexOf"
198+
}
199+
200+
override DataFlow::Node getContainerNode() { result = indexOf.getReceiver().flow() }
201+
202+
override DataFlow::Node getContainedNode() { result = indexOf.getArgument(0).flow() }
203+
}
204+
}

0 commit comments

Comments
 (0)