Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 8848ee2

Browse files
committed
JS: Extract HTML from inline templates
1 parent 6bf9345 commit 8848ee2

9 files changed

Lines changed: 170 additions & 41 deletions

File tree

javascript/extractor/src/com/semmle/js/extractor/ASTExtractor.java

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.semmle.js.extractor;
22

3+
import java.nio.file.Path;
34
import java.util.ArrayList;
45
import java.util.Collections;
56
import java.util.List;
@@ -166,8 +167,10 @@ public class ASTExtractor {
166167
private final Label toplevelLabel;
167168
private final LexicalExtractor lexicalExtractor;
168169
private final RegExpExtractor regexpExtractor;
170+
private final ExtractorConfig config;
169171

170-
public ASTExtractor(LexicalExtractor lexicalExtractor, ScopeManager scopeManager) {
172+
public ASTExtractor(ExtractorConfig config, LexicalExtractor lexicalExtractor, ScopeManager scopeManager) {
173+
this.config = config;
171174
this.trapwriter = lexicalExtractor.getTrapwriter();
172175
this.locationManager = lexicalExtractor.getLocationManager();
173176
this.contextManager = new SyntacticContextManager();
@@ -1136,9 +1139,70 @@ public Label visit(Property nd, Context c) {
11361139
visit(nd.getDefaultValue(), propkey, 2, IdContext.varBind);
11371140
if (nd.isComputed()) trapwriter.addTuple("is_computed", propkey);
11381141
if (nd.isMethod()) trapwriter.addTuple("is_method", propkey);
1142+
1143+
// Extract the value of a property named `template` as HTML, in order to support
1144+
// Angular2 components with an inline template.
1145+
if (!nd.isComputed() && "template".equals(tryGetIdentifierName(nd.getKey()))) {
1146+
extractStringValueAsHtml(nd.getValue());
1147+
}
1148+
11391149
return propkey;
11401150
}
11411151

1152+
/**
1153+
* Extracts the string value of <code>expr</code> as an HTML snippet.
1154+
*/
1155+
private void extractStringValueAsHtml(Expression expr) {
1156+
TextualExtractor textualExtractor = lexicalExtractor.getTextualExtractor();
1157+
if (textualExtractor.isSnippet()) {
1158+
return; // do not create nested snippets
1159+
}
1160+
String source = tryGetStringValueFromExpression(expr);
1161+
if (source == null) {
1162+
return;
1163+
}
1164+
SourceLocation loc = expr.getLoc();
1165+
Path originalFile = textualExtractor.getExtractedFile().toPath();
1166+
Path vfile = originalFile.resolveSibling(originalFile.getFileName().toString() + "." + loc.getStart().getLine() + "." + loc.getStart().getColumn() + ".html");
1167+
LocationManager innerLocationManager = new LocationManager(
1168+
locationManager.getSourceFile(),
1169+
locationManager.getTrapWriter(),
1170+
locationManager.getFileLabel());
1171+
innerLocationManager.setStart(loc.getStart().getLine(), loc.getStart().getColumn());
1172+
TextualExtractor innerTextualExtractor = new TextualExtractor(
1173+
trapwriter,
1174+
innerLocationManager,
1175+
source,
1176+
false,
1177+
getMetrics(),
1178+
vfile.toFile());
1179+
HTMLExtractor html = HTMLExtractor.forEmbeddedHtml(config);
1180+
html.extract(innerTextualExtractor);
1181+
}
1182+
1183+
private String tryGetIdentifierName(Expression e) {
1184+
return e instanceof Identifier ? ((Identifier)e).getName() : null;
1185+
}
1186+
1187+
private String tryGetStringValueFromExpression(Expression e) {
1188+
if (e instanceof Literal) {
1189+
Literal lit = (Literal) e;
1190+
return lit.isStringLiteral() ? (String) lit.getValue() : null;
1191+
}
1192+
if (e instanceof TemplateLiteral) {
1193+
TemplateLiteral lit = (TemplateLiteral) e;
1194+
if (!lit.getExpressions().isEmpty()) {
1195+
return null;
1196+
}
1197+
StringBuilder sb = new StringBuilder();
1198+
for (TemplateElement elm : lit.getQuasis()) {
1199+
sb.append(elm.getCooked());
1200+
}
1201+
return sb.toString();
1202+
}
1203+
return null;
1204+
}
1205+
11421206
@Override
11431207
public Label visit(IfStatement nd, Context c) {
11441208
Label key = super.visit(nd, c);

javascript/extractor/src/com/semmle/js/extractor/HTMLExtractor.java

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -166,10 +166,21 @@ private boolean isAngularTemplateAttributeName(String name) {
166166

167167
private final ExtractorConfig config;
168168
private final ExtractorState state;
169+
private final boolean isEmbedded;
169170

170-
public HTMLExtractor(ExtractorConfig config, ExtractorState state) {
171+
public HTMLExtractor(ExtractorConfig config, ExtractorState state, boolean isEmbedded) {
171172
this.config = config.withPlatform(Platform.WEB);
172173
this.state = state;
174+
this.isEmbedded = isEmbedded;
175+
}
176+
177+
public HTMLExtractor(ExtractorConfig config, ExtractorState state) {
178+
this(config, state, false);
179+
}
180+
181+
/** Creates an HTML extractor for embedded HTML snippets. */
182+
public static HTMLExtractor forEmbeddedHtml(ExtractorConfig config) {
183+
return new HTMLExtractor(config, null, true);
173184
}
174185

175186
@Override
@@ -179,12 +190,15 @@ public LoCInfo extract(TextualExtractor textualExtractor) {
179190
Attributes.setDefaultMaxErrorCount(100);
180191
JavaScriptHTMLElementHandler eltHandler = new JavaScriptHTMLElementHandler(textualExtractor);
181192

193+
LocationManager locationManager = textualExtractor.getLocationManager();
182194
HtmlPopulator extractor =
183195
new HtmlPopulator(
184196
this.config.getHtmlHandling(),
185197
textualExtractor.getSource(),
186198
textualExtractor.getTrapwriter(),
187-
textualExtractor.getLocationManager().getFileLabel());
199+
locationManager.getFileLabel());
200+
201+
extractor.setStartOffset(locationManager.getStartLine() - 1, locationManager.getStartColumn() - 1);
188202

189203
extractor.doit(Option.some(eltHandler));
190204

@@ -266,6 +280,9 @@ private LoCInfo extractSnippet(
266280
int column,
267281
boolean isTypeScript) {
268282
if (isTypeScript) {
283+
if (isEmbedded) {
284+
return null; // Do not extract files from HTML embedded in other files.
285+
}
269286
Path file = textualExtractor.getExtractedFile().toPath();
270287
FileSnippet snippet =
271288
new FileSnippet(file, line, column, toplevelKind, config.getSourceType());

javascript/extractor/src/com/semmle/js/extractor/JSExtractor.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ public Pair<Label, LoCInfo> extract(
106106

107107
lexicalExtractor =
108108
new LexicalExtractor(textualExtractor, parserRes.getTokens(), parserRes.getComments());
109-
ASTExtractor scriptExtractor = new ASTExtractor(lexicalExtractor, scopeManager);
109+
ASTExtractor scriptExtractor = new ASTExtractor(config, lexicalExtractor, scopeManager);
110110
toplevelLabel = scriptExtractor.getToplevelLabel();
111111
lexicalExtractor.extractComments(toplevelLabel);
112112
loc = lexicalExtractor.extractLines(parserRes.getSource(), toplevelLabel);
@@ -119,7 +119,7 @@ public Pair<Label, LoCInfo> extract(
119119
} else {
120120
lexicalExtractor =
121121
new LexicalExtractor(textualExtractor, new ArrayList<Token>(), new ArrayList<Comment>());
122-
ASTExtractor scriptExtractor = new ASTExtractor(lexicalExtractor, null);
122+
ASTExtractor scriptExtractor = new ASTExtractor(config, lexicalExtractor, null);
123123
toplevelLabel = scriptExtractor.getToplevelLabel();
124124

125125
trapwriter.addTuple("toplevels", toplevelLabel, toplevelKind.getValue());

javascript/extractor/src/com/semmle/js/extractor/LexicalExtractor.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
package com.semmle.js.extractor;
22

3+
import java.util.List;
4+
35
import com.semmle.js.ast.Comment;
46
import com.semmle.js.ast.Position;
57
import com.semmle.js.ast.SourceElement;
68
import com.semmle.js.ast.Token;
79
import com.semmle.js.extractor.ExtractionMetrics.ExtractionPhase;
810
import com.semmle.util.trap.TrapWriter;
911
import com.semmle.util.trap.TrapWriter.Label;
10-
import java.util.List;
1112

1213
/**
1314
* Extractor for populating lexical information about a JavaScript file, including comments and
@@ -28,7 +29,11 @@ public LexicalExtractor(
2829
this.tokens = tokens;
2930
this.comments = comments;
3031
}
31-
32+
33+
public TextualExtractor getTextualExtractor() {
34+
return textualExtractor;
35+
}
36+
3237
public TrapWriter getTrapwriter() {
3338
return trapwriter;
3439
}

javascript/ql/src/semmle/javascript/HTML.qll

Lines changed: 33 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,32 @@ module HTML {
8484
override string getAPrimaryQlClass() { result = "HTML::Element" }
8585
}
8686

87+
/**
88+
* Gets the inline script of the given attribute, if any.
89+
*/
90+
CodeInAttribute getCodeInAttribute(XMLAttribute attribute) {
91+
exists(
92+
string f, Location l1, int sl1, int sc1, int el1, int ec1, Location l2, int sl2, int sc2,
93+
int el2, int ec2
94+
|
95+
l1 = attribute.getLocation() and
96+
l2 = result.getLocation() and
97+
l1.hasLocationInfo(f, sl1, sc1, el1, ec1) and
98+
l2.hasLocationInfo(f, sl2, sc2, el2, ec2)
99+
|
100+
(
101+
sl1 = sl2 and sc1 < sc2
102+
or
103+
sl1 < sl2
104+
) and
105+
(
106+
el1 = el2 and ec1 > ec2
107+
or
108+
el1 > el2
109+
)
110+
)
111+
}
112+
87113
/**
88114
* An attribute of an HTML element.
89115
*
@@ -101,6 +127,13 @@ module HTML {
101127

102128
override Location getLocation() { xmllocations(this, result) }
103129

130+
/**
131+
* Gets the inline script of this attribute, if any.
132+
*/
133+
CodeInAttribute getCodeInAttribute() {
134+
result = getCodeInAttribute(this)
135+
}
136+
104137
/**
105138
* Gets the element to which this attribute belongs.
106139
*/
@@ -127,32 +160,6 @@ module HTML {
127160

128161
override string toString() { result = getName() + "=" + getValue() }
129162

130-
/**
131-
* Gets the inline script of this attribute, if any.
132-
*/
133-
CodeInAttribute getCodeInAttribute() {
134-
exists(
135-
string f, Location l1, int sl1, int sc1, int el1, int ec1, Location l2, int sl2, int sc2,
136-
int el2, int ec2
137-
|
138-
l1 = getLocation() and
139-
l2 = result.getLocation() and
140-
l1.hasLocationInfo(f, sl1, sc1, el1, ec1) and
141-
l2.hasLocationInfo(f, sl2, sc2, el2, ec2)
142-
|
143-
(
144-
sl1 = sl2 and sc1 < sc2
145-
or
146-
sl1 < sl2
147-
) and
148-
(
149-
el1 = el2 and ec1 > ec2
150-
or
151-
el1 > el2
152-
)
153-
)
154-
}
155-
156163
override string getAPrimaryQlClass() { result = "HTML::Attribute" }
157164
}
158165

javascript/ql/src/semmle/javascript/frameworks/Angular2.qll

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -355,14 +355,30 @@ module Angular2 {
355355
result = decorator.getOptionArgument(0, "templateUrl").asExpr().(PathExpr).resolve()
356356
}
357357

358+
pragma[noinline]
359+
private Location getInlineTemplateLocation() {
360+
result = decorator.getOptionArgument(0, "template").asExpr().getLocation()
361+
}
362+
363+
private XMLAttribute getAnAttributeInInlineTemplate() {
364+
exists(Location templateLoc, Location attribLoc |
365+
templateLoc = getInlineTemplateLocation() and
366+
attribLoc = result.getLocation() and
367+
templateLoc.getFile() = attribLoc.getFile()
368+
// TODO: check line/column - though in practice checking the file is enough
369+
)
370+
}
371+
358372
/**
359373
* Gets an access to the variable `name` in the template body.
360374
*/
361375
DataFlow::Node getATemplateVarAccess(string name) {
362-
exists(HTML::Attribute attrib |
363-
attrib.getFile() = getTemplateFile() and
376+
exists(XMLAttribute attrib |
377+
attrib.getLocation().getFile() = getTemplateFile() or
378+
attrib = getAnAttributeInInlineTemplate()
379+
|
364380
isAngularExpressionAttribute(attrib) and
365-
result = getAGlobalVarAccessInAttribute(attrib.getCodeInAttribute(), name).flow()
381+
result = getAGlobalVarAccessInAttribute(HTML::getCodeInAttribute(attrib), name).flow()
366382
)
367383
}
368384
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { Input, Component } from '@angular/core';
2+
3+
@Component({
4+
selector: 'mid-component',
5+
template: `
6+
<sink-component [sink7]="taint"></sink-component>
7+
8+
\n<sink-component [sink7]="taint"></sink-component>
9+
`
10+
})
11+
export class InlineComponent {
12+
taint: string;
13+
14+
constructor() {
15+
this.taint = source();
16+
}
17+
}

javascript/ql/test/library-tests/frameworks/Angular2/sink.component.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export class SinkComponent {
1212
sink4: string;
1313
sink5: string;
1414
sink6: string;
15+
sink7: string;
1516

1617
constructor(private sanitizer: DomSanitizer) {}
1718

@@ -22,5 +23,6 @@ export class SinkComponent {
2223
this.sanitizer.bypassSecurityTrustHtml(this.sink4);
2324
this.sanitizer.bypassSecurityTrustHtml(this.sink5);
2425
this.sanitizer.bypassSecurityTrustHtml(this.sink6);
26+
this.sanitizer.bypassSecurityTrustHtml(this.sink7);
2527
}
2628
}

javascript/ql/test/library-tests/frameworks/Angular2/test.expected

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,9 @@ pipeClassRef
2222
| TestPipe.ts:4:8:9:1 | class T ... ;\\n }\\n} | source.component.html:5:22:5:29 | testPipe |
2323
| TestPipe.ts:4:8:9:1 | class T ... ;\\n }\\n} | source.component.html:6:19:6:26 | testPipe |
2424
taintFlow
25-
| source.component.ts:13:22:13:29 | source() | sink.component.ts:19:48:19:57 | this.sink1 |
26-
| source.component.ts:13:22:13:29 | source() | sink.component.ts:22:48:22:57 | this.sink4 |
27-
| source.component.ts:13:22:13:29 | source() | sink.component.ts:23:48:23:57 | this.sink5 |
28-
| source.component.ts:13:22:13:29 | source() | sink.component.ts:24:48:24:57 | this.sink6 |
29-
| source.component.ts:14:33:14:40 | source() | sink.component.ts:19:48:19:57 | this.sink1 |
25+
| inline.component.ts:15:22:15:29 | source() | sink.component.ts:26:48:26:57 | this.sink7 |
26+
| source.component.ts:13:22:13:29 | source() | sink.component.ts:20:48:20:57 | this.sink1 |
27+
| source.component.ts:13:22:13:29 | source() | sink.component.ts:23:48:23:57 | this.sink4 |
28+
| source.component.ts:13:22:13:29 | source() | sink.component.ts:24:48:24:57 | this.sink5 |
29+
| source.component.ts:13:22:13:29 | source() | sink.component.ts:25:48:25:57 | this.sink6 |
30+
| source.component.ts:14:33:14:40 | source() | sink.component.ts:20:48:20:57 | this.sink1 |

0 commit comments

Comments
 (0)