Skip to content

Commit ef0a1d2

Browse files
Implement models for translation methods
1 parent e051526 commit ef0a1d2

File tree

2 files changed

+67
-0
lines changed

2 files changed

+67
-0
lines changed

ruby/ql/lib/codeql/ruby/Frameworks.qll

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ private import codeql.ruby.frameworks.Json
3030
private import codeql.ruby.frameworks.Erb
3131
private import codeql.ruby.frameworks.Slim
3232
private import codeql.ruby.frameworks.Sinatra
33+
private import codeql.ruby.frameworks.Translation
3334
private import codeql.ruby.frameworks.Twirp
3435
private import codeql.ruby.frameworks.Sqlite3
3536
private import codeql.ruby.frameworks.Mysql2
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/** Provides modeling for the `I18n` translation method and the rails translation helpers. */
2+
3+
private import codeql.ruby.ApiGraphs
4+
private import codeql.ruby.dataflow.FlowSummary
5+
private import codeql.ruby.Concepts
6+
private import codeql.ruby.frameworks.ActionView
7+
8+
/** Gets a call to `I18n.translate`, which can use keyword arguments as inputs for string interpolation. */
9+
private MethodCall getI18nTranslateCall() {
10+
result = API::getTopLevelMember("I18n").getAMethodCall(["t", "translate"]).asExpr().getExpr()
11+
}
12+
13+
/**
14+
* Gets a call to the rails view helper translate method, which is a wrapper around `I18n.translate`
15+
* that can also escape html if the key ends in `_html` or `.html`.
16+
*/
17+
private MethodCall getViewHelperTranslateCall() {
18+
result.getMethodName() = ["t", "translate"] and
19+
inActionViewContext(result)
20+
}
21+
22+
/**
23+
* Gets a call to the rails controller helper translate method, which is a wrapper around `I18n.translate`
24+
* that can also escape html if the key ends in `_html` or `.html`.
25+
*/
26+
private MethodCall getControllerHelperTranslateCall() {
27+
result =
28+
API::getTopLevelMember("ActionController")
29+
.getMember("Base")
30+
.getInstance()
31+
.getAMethodCall(["t", "translate"])
32+
.asExpr()
33+
.getExpr()
34+
}
35+
36+
/** Flow summary for translation methods. */
37+
private class TranslateSummary extends SummarizedCallable {
38+
TranslateSummary() { this = "I18n.translate" }
39+
40+
override MethodCall getACall() {
41+
result =
42+
[getI18nTranslateCall(), getViewHelperTranslateCall(), getControllerHelperTranslateCall()]
43+
}
44+
45+
override predicate propagatesFlow(string input, string output, boolean preservesValue) {
46+
input = "Argument[hash-splat].Element[any]" and
47+
output = "ReturnValue" and
48+
preservesValue = false
49+
}
50+
}
51+
52+
/** A call to a translation helper method that escapes HTML. */
53+
private class TranslateHtmlEscape extends Escaping::Range, DataFlow::CallNode {
54+
TranslateHtmlEscape() {
55+
exists(MethodCall mc | mc = this.asExpr().getExpr() |
56+
mc = [getViewHelperTranslateCall(), getControllerHelperTranslateCall()] and
57+
mc.getArgument(0).getConstantValue().getStringlikeValue().matches(["%\\_html", "%.html"])
58+
)
59+
}
60+
61+
override string getKind() { result = Escaping::getHtmlKind() }
62+
63+
override DataFlow::Node getAnInput() { result = this.getKeywordArgument(_) }
64+
65+
override DataFlow::Node getOutput() { result = this }
66+
}

0 commit comments

Comments
 (0)