diff --git a/.gitattributes b/.gitattributes
index fdf3185f2d5..fd2c3bc6065 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -1,5 +1,6 @@
-# Force git not to modify line endings for go files under the ql directory
+# Force git not to modify line endings for go or html files under the ql directory
ql/**/*.go -text
+ql/**/*.html -text
# Force git not to modify line endings for dbschemes
*.dbscheme -text
diff --git a/Makefile b/Makefile
index 29bb3321927..d4631e810cc 100644
--- a/Makefile
+++ b/Makefile
@@ -14,7 +14,7 @@ CODEQL_PLATFORM = osx64
endif
endif
-CODEQL_TOOLS = $(addprefix codeql-tools/,autobuild.cmd autobuild.sh index.cmd index.sh linux64 osx64 win64)
+CODEQL_TOOLS = $(addprefix codeql-tools/,autobuild.cmd autobuild.sh pre-finalize.cmd pre-finalize.sh index.cmd index.sh linux64 osx64 win64)
EXTRACTOR_PACK_OUT = build/codeql-extractor-go
diff --git a/change-notes/2021-03-16-html-tracing.md b/change-notes/2021-03-16-html-tracing.md
new file mode 100644
index 00000000000..664e5f312b6
--- /dev/null
+++ b/change-notes/2021-03-16-html-tracing.md
@@ -0,0 +1,2 @@
+lgtm,codescanning
+* Support for extracting HTML files has been added, alongside support for Raw Revel templates.
diff --git a/codeql-tools/index.cmd b/codeql-tools/index.cmd
index 15d7548c1d9..21c8f64df92 100644
--- a/codeql-tools/index.cmd
+++ b/codeql-tools/index.cmd
@@ -2,6 +2,7 @@
SETLOCAL EnableDelayedExpansion
type NUL && "%CODEQL_EXTRACTOR_GO_ROOT%/tools/%CODEQL_PLATFORM%/go-extractor.exe" -mod=vendor ./...
+type NUL && "%CODEQL_EXTRACTOR_GO_ROOT%/tools/pre-finalize.cmd"
exit /b %ERRORLEVEL%
ENDLOCAL
diff --git a/codeql-tools/index.sh b/codeql-tools/index.sh
index 655fb5eeca3..877400d37f2 100755
--- a/codeql-tools/index.sh
+++ b/codeql-tools/index.sh
@@ -8,3 +8,4 @@ if [ "$CODEQL_PLATFORM" != "linux64" ] && [ "$CODEQL_PLATFORM" != "osx64" ] ; th
fi
"$CODEQL_EXTRACTOR_GO_ROOT/tools/$CODEQL_PLATFORM/go-extractor" -mod=vendor ./...
+"$CODEQL_EXTRACTOR_GO_ROOT/tools/pre-finalize.sh"
diff --git a/codeql-tools/pre-finalize.cmd b/codeql-tools/pre-finalize.cmd
new file mode 100644
index 00000000000..4abac249933
--- /dev/null
+++ b/codeql-tools/pre-finalize.cmd
@@ -0,0 +1,19 @@
+@echo off
+SETLOCAL EnableDelayedExpansion
+
+if NOT "%CODEQL_EXTRACTOR_GO_EXTRACT_HTML%"=="no" (
+ type NUL && "%CODEQL_DIST%/codeql.exe" database index-files ^
+ --working-dir=. ^
+ --include-extension=.htm ^
+ --include-extension=.html ^
+ --include-extension=.xhtm ^
+ --include-extension=.xhtml ^
+ --include-extension=.vue ^
+ --size-limit 10m ^
+ --language html ^
+ -- ^
+ "%CODEQL_EXTRACTOR_GO_WIP_DATABASE%" ^
+ || echo "HTML extraction failed; continuing"
+
+ exit /b %ERRORLEVEL%
+)
diff --git a/codeql-tools/pre-finalize.sh b/codeql-tools/pre-finalize.sh
new file mode 100755
index 00000000000..3a8b31c70a0
--- /dev/null
+++ b/codeql-tools/pre-finalize.sh
@@ -0,0 +1,18 @@
+#!/bin/sh
+
+set -eu
+
+if [ "${CODEQL_EXTRACTOR_GO_EXTRACT_HTML:-yes}" != "no" ]; then
+ "$CODEQL_DIST/codeql" database index-files \
+ --working-dir=. \
+ --include-extension=.htm \
+ --include-extension=.html \
+ --include-extension=.xhtm \
+ --include-extension=.xhtml \
+ --include-extension=.vue \
+ --size-limit 10m \
+ --language html \
+ -- \
+ "$CODEQL_EXTRACTOR_GO_WIP_DATABASE" \
+ || echo "HTML extraction failed; continuing."
+fi
diff --git a/extractor/dbscheme/dbscheme.go b/extractor/dbscheme/dbscheme.go
index 0eadc3efd9b..fddaa3e6e1f 100644
--- a/extractor/dbscheme/dbscheme.go
+++ b/extractor/dbscheme/dbscheme.go
@@ -362,6 +362,15 @@ func NewUnionType(name string, parents ...*UnionType) *UnionType {
return tp
}
+// AddChild adds the type with given `name` to the union type.
+// This is useful if a type defined in a snippet should be a child of a type defined in Go.
+func (parent *UnionType) AddChild(name string) bool {
+ tp := &PrimaryKeyType{name}
+ // don't add tp to types; it's expected that it's already in the db somehow.
+ parent.components = append(parent.components, tp)
+ return true
+}
+
// NewAliasType constructs a new alias type with the given `name` that aliases `underlying`
func NewAliasType(name string, underlying Type) *AliasType {
tp := &AliasType{name, underlying}
diff --git a/extractor/dbscheme/tables.go b/extractor/dbscheme/tables.go
index 3847324b5db..233f087a2b1 100644
--- a/extractor/dbscheme/tables.go
+++ b/extractor/dbscheme/tables.go
@@ -44,12 +44,91 @@ snapshotDate(unique date snapshotDate : date ref);
sourceLocationPrefix(varchar(900) prefix : string ref);
`)
+// Copied directly from the XML dbscheme
+var xmlSnippet = AddDefaultSnippet(`
+/*
+ * XML Files
+ */
+
+xmlEncoding(
+ unique int id: @file ref,
+ string encoding: string ref
+);
+
+xmlDTDs(
+ unique int id: @xmldtd,
+ string root: string ref,
+ string publicId: string ref,
+ string systemId: string ref,
+ int fileid: @file ref
+);
+
+xmlElements(
+ unique int id: @xmlelement,
+ string name: string ref,
+ int parentid: @xmlparent ref,
+ int idx: int ref,
+ int fileid: @file ref
+);
+
+xmlAttrs(
+ unique int id: @xmlattribute,
+ int elementid: @xmlelement ref,
+ string name: string ref,
+ string value: string ref,
+ int idx: int ref,
+ int fileid: @file ref
+);
+
+xmlNs(
+ int id: @xmlnamespace,
+ string prefixName: string ref,
+ string URI: string ref,
+ int fileid: @file ref
+);
+
+xmlHasNs(
+ int elementId: @xmlnamespaceable ref,
+ int nsId: @xmlnamespace ref,
+ int fileid: @file ref
+);
+
+xmlComments(
+ unique int id: @xmlcomment,
+ string text: string ref,
+ int parentid: @xmlparent ref,
+ int fileid: @file ref
+);
+
+xmlChars(
+ unique int id: @xmlcharacters,
+ string text: string ref,
+ int parentid: @xmlparent ref,
+ int idx: int ref,
+ int isCDATA: int ref,
+ int fileid: @file ref
+);
+
+@xmlparent = @file | @xmlelement;
+@xmlnamespaceable = @xmlelement | @xmlattribute;
+
+xmllocations(
+ int xmlElement: @xmllocatable ref,
+ int location: @location_default ref
+);
+
+@xmllocatable = @xmlcharacters | @xmlelement | @xmlcomment | @xmlattribute | @xmldtd | @file | @xmlnamespace;
+`)
+
// ContainerType is the type of files and folders
var ContainerType = NewUnionType("@container")
// LocatableType is the type of program entities that have locations
var LocatableType = NewUnionType("@locatable")
+// Adds xmllocatable as a locatable
+var XmlLocatableAsLocatable = LocatableType.AddChild("@xmllocatable")
+
// NodeType is the type of AST nodes
var NodeType = NewUnionType("@node", LocatableType)
diff --git a/ql/src/Security/CWE-079/ReflectedXss.ql b/ql/src/Security/CWE-079/ReflectedXss.ql
index bc11cbc7ebf..bbedd60a5fe 100644
--- a/ql/src/Security/CWE-079/ReflectedXss.ql
+++ b/ql/src/Security/CWE-079/ReflectedXss.ql
@@ -15,7 +15,22 @@ import go
import semmle.go.security.ReflectedXss::ReflectedXss
import DataFlow::PathGraph
-from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
-where cfg.hasFlowPath(source, sink)
-select sink.getNode(), source, sink, "Cross-site scripting vulnerability due to $@.",
- source.getNode(), "user-provided value"
+from
+ Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink, string msg, string part,
+ Locatable partloc
+where
+ cfg.hasFlowPath(source, sink) and
+ (
+ exists(string kind | kind = sink.getNode().(SharedXss::Sink).getSinkKind() |
+ kind = "rawtemplate" and
+ msg =
+ "Cross-site scripting vulnerability due to $@. This template argument is instantiated raw $@." and
+ part = "here"
+ )
+ or
+ not exists(sink.getNode().(SharedXss::Sink).getSinkKind()) and
+ msg = "Cross-site scripting vulnerability due to $@." and
+ part = ""
+ ) and
+ partloc = sink.getNode().(SharedXss::Sink).getAssociatedLoc()
+select sink.getNode(), source, sink, msg, source.getNode(), "user-provided value", partloc, part
diff --git a/ql/src/go.dbscheme b/ql/src/go.dbscheme
index 4affa49dbe2..2e92b436892 100644
--- a/ql/src/go.dbscheme
+++ b/ql/src/go.dbscheme
@@ -36,6 +36,80 @@ snapshotDate(unique date snapshotDate : date ref);
sourceLocationPrefix(varchar(900) prefix : string ref);
+
+/*
+ * XML Files
+ */
+
+xmlEncoding(
+ unique int id: @file ref,
+ string encoding: string ref
+);
+
+xmlDTDs(
+ unique int id: @xmldtd,
+ string root: string ref,
+ string publicId: string ref,
+ string systemId: string ref,
+ int fileid: @file ref
+);
+
+xmlElements(
+ unique int id: @xmlelement,
+ string name: string ref,
+ int parentid: @xmlparent ref,
+ int idx: int ref,
+ int fileid: @file ref
+);
+
+xmlAttrs(
+ unique int id: @xmlattribute,
+ int elementid: @xmlelement ref,
+ string name: string ref,
+ string value: string ref,
+ int idx: int ref,
+ int fileid: @file ref
+);
+
+xmlNs(
+ int id: @xmlnamespace,
+ string prefixName: string ref,
+ string URI: string ref,
+ int fileid: @file ref
+);
+
+xmlHasNs(
+ int elementId: @xmlnamespaceable ref,
+ int nsId: @xmlnamespace ref,
+ int fileid: @file ref
+);
+
+xmlComments(
+ unique int id: @xmlcomment,
+ string text: string ref,
+ int parentid: @xmlparent ref,
+ int fileid: @file ref
+);
+
+xmlChars(
+ unique int id: @xmlcharacters,
+ string text: string ref,
+ int parentid: @xmlparent ref,
+ int idx: int ref,
+ int isCDATA: int ref,
+ int fileid: @file ref
+);
+
+@xmlparent = @file | @xmlelement;
+@xmlnamespaceable = @xmlelement | @xmlattribute;
+
+xmllocations(
+ int xmlElement: @xmllocatable ref,
+ int location: @location_default ref
+);
+
+@xmllocatable = @xmlcharacters | @xmlelement | @xmlcomment | @xmlattribute | @xmldtd | @file | @xmlnamespace;
+
locations_default(unique int id: @location_default, int file: @file ref, int beginLine: int ref, int beginColumn: int ref,
int endLine: int ref, int endColumn: int ref);
@@ -133,7 +207,7 @@ has_ellipsis(int id: @callorconversionexpr ref);
@container = @file | @folder;
-@locatable = @node | @localscope;
+@locatable = @xmllocatable | @node | @localscope;
@node = @documentable | @exprparent | @modexprparent | @fieldparent | @stmtparent | @declparent | @scopenode
| @comment_group | @comment;
diff --git a/ql/src/go.qll b/ql/src/go.qll
index 1ef249783a4..574268b2627 100644
--- a/ql/src/go.qll
+++ b/ql/src/go.qll
@@ -12,6 +12,7 @@ import semmle.go.Errors
import semmle.go.Expr
import semmle.go.Files
import semmle.go.GoMod
+import semmle.go.HTML
import semmle.go.Locations
import semmle.go.Packages
import semmle.go.Scopes
@@ -19,6 +20,7 @@ import semmle.go.Stmt
import semmle.go.StringOps
import semmle.go.Types
import semmle.go.Util
+import semmle.go.VariableWithFields
import semmle.go.concepts.HTTP
import semmle.go.controlflow.BasicBlocks
import semmle.go.controlflow.ControlFlowGraph
diff --git a/ql/src/semmle/go/HTML.qll b/ql/src/semmle/go/HTML.qll
new file mode 100644
index 00000000000..82c8724cd4b
--- /dev/null
+++ b/ql/src/semmle/go/HTML.qll
@@ -0,0 +1,214 @@
+/** Provides classes for working with HTML documents. */
+
+import go
+
+module HTML {
+ /**
+ * An HTML file.
+ */
+ class HtmlFile extends File {
+ HtmlFile() { this.getExtension().regexpMatch("x?html?") }
+ }
+
+ /**
+ * An HTML element.
+ *
+ * Example:
+ *
+ * ```
+ * Semmle
+ * ```
+ */
+ class Element extends Locatable, @xmlelement {
+ Element() { exists(HtmlFile f | xmlElements(this, _, _, _, f)) }
+
+ override Location getLocation() { xmllocations(this, result) }
+
+ /**
+ * Gets the name of this HTML element.
+ *
+ * For example, the name of ` ` is `br`.
+ */
+ string getName() { xmlElements(this, result, _, _, _) }
+
+ /**
+ * Gets the parent element of this element, if any.
+ */
+ Element getParent() { xmlElements(this, _, result, _, _) }
+
+ /**
+ * Holds if this is a toplevel element, that is, if it does not have a parent element.
+ */
+ predicate isTopLevel() { not exists(getParent()) }
+
+ /**
+ * Gets the root HTML document element in which this element is contained.
+ */
+ DocumentElement getDocument() { result = getRoot() }
+
+ /**
+ * Gets the root element in which this element is contained.
+ */
+ Element getRoot() { if isTopLevel() then result = this else result = getParent().getRoot() }
+
+ /**
+ * Gets the `i`th child element (0-based) of this element.
+ */
+ Element getChild(int i) { xmlElements(result, _, this, i, _) }
+
+ /**
+ * Gets a child element of this element.
+ */
+ Element getChild() { result = getChild(_) }
+
+ /**
+ * Gets the `i`th attribute (0-based) of this element.
+ */
+ Attribute getAttribute(int i) { xmlAttrs(result, this, _, _, i, _) }
+
+ /**
+ * Gets an attribute of this element.
+ */
+ Attribute getAnAttribute() { result = getAttribute(_) }
+
+ /**
+ * Gets an attribute of this element that has the given name.
+ */
+ Attribute getAttributeByName(string name) {
+ result = getAnAttribute() and
+ result.getName() = name
+ }
+
+ /**
+ * Gets the text node associated with this element.
+ */
+ TextNode getTextNode() { result.getParent() = this }
+
+ override string toString() { result = "<" + getName() + ">...>" }
+ }
+
+ /**
+ * An attribute of an HTML element.
+ *
+ * Examples:
+ *
+ * ```
+ *
+ * target=_blank
+ * >Semmle
+ * ```
+ */
+ class Attribute extends Locatable, @xmlattribute {
+ Attribute() { xmlAttrs(this, _, _, _, _, any(HtmlFile f)) }
+
+ override Location getLocation() { xmllocations(this, result) }
+
+ /**
+ * Gets the element to which this attribute belongs.
+ */
+ Element getElement() { xmlAttrs(this, result, _, _, _, _) }
+
+ /**
+ * Gets the root element in which the element to which this attribute
+ * belongs is contained.
+ */
+ Element getRoot() { result = getElement().getRoot() }
+
+ /**
+ * Gets the name of this attribute.
+ */
+ string getName() { xmlAttrs(this, _, result, _, _, _) }
+
+ /**
+ * Gets the value of this attribute.
+ *
+ * For attributes without an explicitly specified value, the
+ * result is the empty string.
+ */
+ string getValue() { xmlAttrs(this, _, _, result, _, _) }
+
+ override string toString() { result = getName() + "=" + getValue() }
+ }
+
+ /**
+ * An HTML `` element.
+ *
+ * Example:
+ *
+ * ```
+ *
+ *
+ * This is a test.
+ *
+ *
+ * ```
+ */
+ class DocumentElement extends Element {
+ DocumentElement() { getName() = "html" }
+ }
+
+ /**
+ * An HTML text node.
+ *
+ * Example:
+ *
+ * ```
+ *
+ * This text is represented as a text node.
+ *
+ * ```
+ */
+ class TextNode extends Locatable, @xmlcharacters {
+ TextNode() { exists(HtmlFile f | xmlChars(this, _, _, _, _, f)) }
+
+ override string toString() { result = getText() }
+
+ /**
+ * Gets the content of this text node.
+ *
+ * Note that entity expansion has been performed already.
+ */
+ string getText() { xmlChars(this, result, _, _, _, _) }
+
+ /**
+ * Gets the parent this text.
+ */
+ Element getParent() { xmlChars(this, _, result, _, _, _) }
+
+ /**
+ * Gets the child index number of this text node.
+ */
+ int getIndex() { xmlChars(this, _, _, result, _, _) }
+
+ /**
+ * Holds if this text node is inside a `CDATA` tag.
+ */
+ predicate isCData() { xmlChars(this, _, _, _, 1, _) }
+
+ override Location getLocation() { xmllocations(this, result) }
+ }
+
+ /**
+ * An HTML comment.
+ *
+ * Example:
+ *
+ * ```
+ *
+ * ```
+ */
+ class CommentNode extends Locatable, @xmlcomment {
+ CommentNode() { exists(HtmlFile f | xmlComments(this, _, _, f)) }
+
+ /** Gets the element in which this comment occurs. */
+ Element getParent() { xmlComments(this, _, result, _) }
+
+ /** Gets the text of this comment, not including delimiters. */
+ string getText() { result = toString().regexpCapture("(?s)", 1) }
+
+ override string toString() { xmlComments(this, result, _, _) }
+
+ override Location getLocation() { xmllocations(this, result) }
+ }
+}
diff --git a/ql/src/semmle/go/VariableWithFields.qll b/ql/src/semmle/go/VariableWithFields.qll
new file mode 100644
index 00000000000..000543b5be1
--- /dev/null
+++ b/ql/src/semmle/go/VariableWithFields.qll
@@ -0,0 +1,198 @@
+/** Provides the `VariableWithFields` class, for working with variables with a chain of field or element accesses chained to it. */
+
+import go
+
+private newtype TVariableWithFields =
+ TVariableRoot(Variable v) or
+ TVariableFieldStep(VariableWithFields base, Field f) {
+ exists(fieldAccessPathAux(base, f)) or exists(fieldWriteAccessPathAux(base, f))
+ } or
+ TVariableElementStep(VariableWithFields base, string e) {
+ exists(elementAccessPathAux(base, e)) or exists(elementWriteAccessPathAux(base, e))
+ }
+
+/**
+ * Gets a representation of the write target `wt` as a variable with fields value if there is one.
+ */
+private TVariableWithFields writeAccessPath(IR::WriteTarget wt) {
+ exists(Variable v | wt = v.getAWrite().getLhs() | result = TVariableRoot(v))
+ or
+ exists(VariableWithFields base, Field f | wt = fieldWriteAccessPathAux(base, f) |
+ result = TVariableFieldStep(base, f)
+ )
+ or
+ exists(VariableWithFields base, string e | wt = elementWriteAccessPathAux(base, e) |
+ result = TVariableElementStep(base, e)
+ )
+}
+
+/**
+ * Gets a representation of `insn` as a variable with fields value if there is one.
+ */
+private TVariableWithFields accessPath(IR::Instruction insn) {
+ exists(Variable v | insn = v.getARead().asInstruction() | result = TVariableRoot(v))
+ or
+ exists(VariableWithFields base, Field f | insn = fieldAccessPathAux(base, f) |
+ result = TVariableFieldStep(base, f)
+ )
+ or
+ exists(VariableWithFields base, string e | insn = elementAccessPathAux(base, e) |
+ result = TVariableElementStep(base, e)
+ )
+}
+
+/**
+ * Gets an IR instruction that reads a field `f` from a node that is represented
+ * by variable with fields value `base`.
+ */
+private IR::Instruction fieldAccessPathAux(TVariableWithFields base, Field f) {
+ exists(IR::FieldReadInstruction fr, IR::EvalInstruction frb |
+ fr.getBase() = frb or
+ fr.getBase() = IR::implicitDerefInstruction(frb.getExpr())
+ |
+ base = accessPath(frb) and
+ f = fr.getField() and
+ result = fr
+ )
+}
+
+/**
+ * Gets an IR write target that represents a field `f` from a node that is represented
+ * by variable with fields value `base`.
+ */
+private IR::WriteTarget fieldWriteAccessPathAux(TVariableWithFields base, Field f) {
+ exists(IR::FieldTarget ft, IR::EvalInstruction ftb |
+ ft.getBase() = ftb or
+ ft.getBase() = IR::implicitDerefInstruction(ftb.getExpr())
+ |
+ base = accessPath(ftb) and
+ ft.getField() = f and
+ result = ft
+ )
+}
+
+/**
+ * Gets an IR instruction that reads an element `e` from a node that is represented
+ * by variable with fields value `base`.
+ */
+private IR::Instruction elementAccessPathAux(TVariableWithFields base, string e) {
+ exists(IR::ElementReadInstruction er, IR::EvalInstruction erb |
+ er.getBase() = erb or
+ er.getBase() = IR::implicitDerefInstruction(erb.getExpr())
+ |
+ base = accessPath(erb) and
+ e = er.getIndex().getExactValue() and
+ result = er
+ )
+}
+
+/**
+ * Gets an IR write target that represents an element `e` from a node that is represented
+ * by variable with fields value `base`.
+ */
+private IR::WriteTarget elementWriteAccessPathAux(TVariableWithFields base, string e) {
+ exists(IR::ElementTarget et, IR::EvalInstruction etb |
+ et.getBase() = etb or
+ et.getBase() = IR::implicitDerefInstruction(etb.getExpr())
+ |
+ base = accessPath(etb) and
+ e = et.getIndex().getExactValue() and
+ result = et
+ )
+}
+
+/** A variable with zero or more fields or elements read from it. */
+class VariableWithFields extends TVariableWithFields {
+ /**
+ * Gets the variable corresponding to the base of this variable with fields.
+ *
+ * For example, the variable corresponding to `a` for the variable with fields
+ * corresponding to `a.b[c]`.
+ */
+ Variable getBaseVariable() { this.getParent*() = TVariableRoot(result) }
+
+ /**
+ * Gets the variable with fields corresponding to the parent of this variable with fields.
+ *
+ * For example, the variable with fields corresponding to `a.b` for the variable with fields
+ * corresponding to `a.b[c]`.
+ */
+ VariableWithFields getParent() {
+ exists(VariableWithFields base |
+ this = TVariableFieldStep(base, _) or this = TVariableElementStep(base, _)
+ |
+ result = base
+ )
+ }
+
+ /** Gets a use that refers to this variable with fields. */
+ DataFlow::Node getAUse() { this = accessPath(result.asInstruction()) }
+
+ /** Gets the type of this variable with fields. */
+ Type getType() {
+ exists(IR::Instruction acc | this = accessPath(acc) | result = acc.getResultType())
+ }
+
+ /** Gets a textual representation of this element. */
+ string toString() {
+ exists(Variable var | this = TVariableRoot(var) | result = "(" + var + ")")
+ or
+ exists(VariableWithFields base, Field f | this = TVariableFieldStep(base, f) |
+ result = base + "." + f.getName()
+ )
+ or
+ exists(VariableWithFields base, string e | this = TVariableElementStep(base, e) |
+ result = base + "[" + e + "]"
+ )
+ }
+
+ /**
+ * Gets the qualified name of the source variable or variable and fields that this represents.
+ *
+ * For example, for the variable with fields that represents the field `a.b[c]`, this would get the string
+ * `"a.b.c"`.
+ */
+ string getQualifiedName() {
+ exists(Variable v | this = TVariableRoot(v) | result = v.getName())
+ or
+ exists(VariableWithFields base, Field f | this = TVariableFieldStep(base, f) |
+ result = base.getQualifiedName() + "." + f.getName()
+ )
+ or
+ exists(VariableWithFields base, string e | this = TVariableElementStep(base, e) |
+ result = base.getQualifiedName() + "." + e.replaceAll(".", "\\.")
+ )
+ }
+
+ /**
+ * Gets a write of this variable with fields.
+ */
+ Write getAWrite() { this = writeAccessPath(result.getLhs()) }
+
+ /**
+ * Gets the field that is the last step of this variable with fields, if any.
+ *
+ * For example, the field `c` for the variable with fields `a.b.c`.
+ */
+ Field getField() { this = TVariableFieldStep(_, result) }
+
+ /**
+ * Gets the element that this variable with fields reads, if any.
+ *
+ * For example, the string value of `c` for the variable with fields `a.b[c]`.
+ */
+ string getElement() { this = TVariableElementStep(_, result) }
+
+ /**
+ * 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
+ ) {
+ this.getBaseVariable().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
+ }
+}
diff --git a/ql/src/semmle/go/concepts/HTTP.qll b/ql/src/semmle/go/concepts/HTTP.qll
index 3a5b9b8ff8b..6763b6e29cb 100644
--- a/ql/src/semmle/go/concepts/HTTP.qll
+++ b/ql/src/semmle/go/concepts/HTTP.qll
@@ -223,6 +223,35 @@ module HTTP {
DataFlow::Node getAContentTypeNode() { result = self.getAContentTypeNode() }
}
+ /** Provides a class for modeling new HTTP template response-body APIs. */
+ module TemplateResponseBody {
+ /**
+ * An expression which is written to an HTTP response body via a template execution.
+ *
+ * Extend this class to model new APIs. If you want to refine existing API models,
+ * extend `HTTP::ResponseBody` instead.
+ */
+ abstract class Range extends ResponseBody::Range {
+ /** Gets the read of the variable inside the template where this value is read. */
+ abstract HtmlTemplate::TemplateRead getRead();
+ }
+ }
+
+ /**
+ * An expression which is written to an HTTP response body via a template execution.
+ *
+ * Extend this class to refine existing API models. If you want to model new APIs,
+ * extend `HTTP::TemplateResponseBody::Range` instead.
+ */
+ class TemplateResponseBody extends ResponseBody {
+ override TemplateResponseBody::Range self;
+
+ TemplateResponseBody() { this = self }
+
+ /** Gets the read of the variable inside the template where this value is read. */
+ HtmlTemplate::TemplateRead getRead() { result = self.getRead() }
+ }
+
/** Provides a class for modeling new HTTP client request APIs. */
module ClientRequest {
/**
diff --git a/ql/src/semmle/go/frameworks/Revel.qll b/ql/src/semmle/go/frameworks/Revel.qll
index 1058c54c713..23cc4edd109 100644
--- a/ql/src/semmle/go/frameworks/Revel.qll
+++ b/ql/src/semmle/go/frameworks/Revel.qll
@@ -104,8 +104,7 @@ module Revel {
* Note these don't actually generate the response, they return a struct which is then returned by the controller
* method, but it is very likely if a string is being rendered that it will end up sent to the user.
*
- * The `Render` and `RenderTemplate` methods are excluded for now because both execute HTML templates, and deciding
- * whether a particular value is exposed unescaped or not requires parsing the template.
+ * The `Render` and `RenderTemplate` methods are handled by `TemplateRender` below.
*
* The `RenderError` method can actually return HTML content, but again only via an HTML template if one exists;
* we assume it falls back to return plain text as this implies there is probably not an injection opportunity
@@ -216,4 +215,114 @@ module Revel {
inp = input and outp = output
}
}
+
+ /**
+ * A read in a Revel template that uses Revel's `raw` function.
+ */
+ class RawTemplateRead extends HtmlTemplate::TemplateRead {
+ RawTemplateRead() { parent.getBody().regexpMatch("(?s)raw\\s.*") }
+ }
+
+ /**
+ * A write to a template argument field that is read raw inside of a template.
+ */
+ private class RawTemplateArgument extends HTTP::TemplateResponseBody::Range {
+ RawTemplateRead read;
+
+ RawTemplateArgument() {
+ exists(TemplateRender render, VariableWithFields var |
+ render.getRenderedFile() = read.getFile() and
+ // if var is a.b.c, any rhs of a write to a, a.b, or a.b.cb
+ this = var.getParent*().getAWrite().getRhs()
+ |
+ var.getParent*() = render.getArgumentVariable() and
+ (
+ var = read.getReadVariable(render.getArgumentVariable())
+ or
+ // if no write or use of that variable exists, no VariableWithFields will be generated
+ // so we try to find a parent VariableWithFields
+ // this isn't covered by the 'getParent*' above because no match would be found at all
+ // for var
+ not exists(read.getReadVariable(render.getArgumentVariable())) and
+ exists(string fieldName | fieldName = read.getFieldName() |
+ var.getQualifiedName() =
+ render.getArgumentVariable().getQualifiedName() +
+ ["." + fieldName.substring(0, fieldName.indexOf(".")), ""]
+ )
+ )
+ or
+ // a revel controller.Render(arg) will set controller.ViewArgs["arg"] = arg
+ exists(Variable arg | arg.getARead() = render.(ControllerRender).getAnArgument() |
+ var.getBaseVariable() = arg and
+ var.getQualifiedName() = read.getFieldName()
+ )
+ )
+ }
+
+ override string getAContentType() { result = "text/html" }
+
+ override HTTP::ResponseWriter getResponseWriter() { none() }
+
+ override HtmlTemplate::TemplateRead getRead() { result = read }
+ }
+
+ /**
+ * A render of a template.
+ */
+ abstract class TemplateRender extends DataFlow::Node, TemplateInstantiation::Range {
+ /** Gets the name of the file that is rendered. */
+ abstract File getRenderedFile();
+
+ /** Gets the variable passed as an argument to the template. */
+ abstract VariableWithFields getArgumentVariable();
+
+ override DataFlow::Node getADataArgument() { result = this.getArgumentVariable().getAUse() }
+ }
+
+ /** A call to `Controller.Render`. */
+ private class ControllerRender extends TemplateRender, DataFlow::MethodCallNode {
+ ControllerRender() { this.getTarget().hasQualifiedName(packagePath(), "Controller", "Render") }
+
+ override DataFlow::Node getTemplateArgument() { none() }
+
+ override File getRenderedFile() {
+ exists(string controllerRe, string handlerRe, string pathRe |
+ controllerRe = "\\Q" + this.getReceiver().getType().getName() + "\\E" and
+ handlerRe = "\\Q" + this.getEnclosingCallable().getName() + "\\E" and
+ // find a file named '/views//(.).html
+ pathRe = "/views/" + controllerRe + "/" + handlerRe + "(\\..*)?\\.html?"
+ |
+ result.getAbsolutePath().regexpMatch("(?i).*" + pathRe)
+ )
+ }
+
+ override VariableWithFields getArgumentVariable() {
+ exists(VariableWithFields base | base.getAUse().getASuccessor*() = this.getReceiver() |
+ result.getParent() = base and
+ result.getField().getName() = "ViewArgs"
+ )
+ }
+ }
+
+ /** A call to `Controller.RenderTemplate`. */
+ private class ControllerRenderTemplate extends TemplateRender, DataFlow::MethodCallNode {
+ ControllerRenderTemplate() {
+ this.getTarget().hasQualifiedName(packagePath(), "Controller", "RenderTemplate")
+ }
+
+ override DataFlow::Node getTemplateArgument() { result = this.getArgument(0) }
+
+ override File getRenderedFile() {
+ exists(string pathRe | pathRe = "\\Q" + this.getTemplateArgument().getStringValue() + "\\E" |
+ result.getAbsolutePath().regexpMatch(".*/" + pathRe)
+ )
+ }
+
+ override VariableWithFields getArgumentVariable() {
+ exists(VariableWithFields base | base.getAUse().getASuccessor*() = this.getReceiver() |
+ result.getParent() = base and
+ result.getField().getName() = "ViewArgs"
+ )
+ }
+ }
}
diff --git a/ql/src/semmle/go/frameworks/stdlib/HtmlTemplate.qll b/ql/src/semmle/go/frameworks/stdlib/HtmlTemplate.qll
index 5560c7cdb97..45a7532afdb 100644
--- a/ql/src/semmle/go/frameworks/stdlib/HtmlTemplate.qll
+++ b/ql/src/semmle/go/frameworks/stdlib/HtmlTemplate.qll
@@ -62,4 +62,111 @@ module HtmlTemplate {
input = inp and output = outp
}
}
+
+ private class MethodModels extends TaintTracking::FunctionModel, Method {
+ FunctionInput inp;
+ FunctionOutput outp;
+
+ MethodModels() {
+ // signature: func (*Template).Execute(wr io.Writer, data interface{}) error
+ hasQualifiedName("html/template", "Template", "Execute") and
+ (inp.isParameter(1) and outp.isParameter(0))
+ or
+ // signature: func (*Template).ExecuteTemplate(wr io.Writer, name string, data interface{}) error
+ hasQualifiedName("html/template", "Template", "ExecuteTemplate") and
+ (inp.isParameter(2) and outp.isParameter(0))
+ }
+
+ override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
+ input = inp and output = outp
+ }
+ }
+
+ private newtype TTemplateStmt =
+ MkTemplateStmt(HTML::TextNode parent, int idx, string text) {
+ text = parent.getText().regexpFind("(?s)\\{\\{.*?\\}\\}", idx, _)
+ }
+
+ /**
+ * A statement inside an HTML template.
+ */
+ class TemplateStmt extends TTemplateStmt {
+ HTML::TextNode parent;
+ string text;
+
+ TemplateStmt() { this = MkTemplateStmt(parent, _, text) }
+
+ /** Gets the text of the body of the template statement. */
+ string getBody() { result = text.regexpCapture("(?s)\\{\\{(.*)\\}\\}", 1) } // matches the inside of the curly bracket delimiters
+
+ /** Gets the file in which this statement appears. */
+ File getFile() { hasLocationInfo(result.getAbsolutePath(), _, _, _, _) }
+
+ /** Gets a textual representation of this statement. */
+ string toString() { result = "HTML template statement" }
+
+ /** Get the HTML element that contains this template statement. */
+ HTML::TextNode getEnclosingTextNode() { result = parent }
+
+ /**
+ * 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
+ ) {
+ parent.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
+ }
+ }
+
+ private newtype TTemplateRead =
+ MkTemplateRead(TemplateStmt parent, int idx, string text) {
+ // matches (.)*
+ text = parent.getBody().regexpFind("(\\s|^)\\.(\\p{L}|\\p{N}|\\.)*\\b", idx, _)
+ }
+
+ /**
+ * A read in an HTML template statement.
+ */
+ class TemplateRead extends TTemplateRead {
+ TemplateStmt parent;
+ string text;
+
+ TemplateRead() { this = MkTemplateRead(parent, _, text) }
+
+ /** Gets the name of the field being read. This may include dots if nested fields are used. */
+ string getFieldName() { result = text.trim().suffix(1) }
+
+ /** Gets the variable with fields that is read if `arg` is passed to this template execution. */
+ VariableWithFields getReadVariable(VariableWithFields arg) {
+ if this.getFieldName() = ""
+ then result = arg
+ else result.getQualifiedName() = arg.getQualifiedName() + "." + this.getFieldName()
+ }
+
+ /** Gets the file in which this read appears. */
+ File getFile() { hasLocationInfo(result.getAbsolutePath(), _, _, _, _) }
+
+ /** Gets a textual representation of this statement. */
+ string toString() { result = "HTML template read of " + text }
+
+ /** Get the HTML element that contains this template read. */
+ HTML::TextNode getEnclosingTextNode() { result = parent.getEnclosingTextNode() }
+
+ /**
+ * 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
+ ) {
+ parent.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
+ }
+ }
}
diff --git a/ql/src/semmle/go/security/Xss.qll b/ql/src/semmle/go/security/Xss.qll
index b438cdfd4f1..0d1e3cd5ed7 100644
--- a/ql/src/semmle/go/security/Xss.qll
+++ b/ql/src/semmle/go/security/Xss.qll
@@ -18,6 +18,14 @@ module SharedXss {
* that do not allow script injection, but injection of other undesirable HTML elements.
*/
string getVulnerabilityKind() { result = "Cross-site scripting" }
+
+ /**
+ * Gets the kind of sink
+ */
+ string getSinkKind() { none() }
+
+ /** Gets an associated locatable, if any. */
+ Locatable getAssociatedLoc() { result = this.getFile() }
}
/** A sanitizer for XSS vulnerabilities. */
@@ -38,6 +46,15 @@ module SharedXss {
HttpResponseBodySink() { not nonHtmlContentType(this) }
}
+ /**
+ * An expression that is rendered as part of a template.
+ */
+ class RawTemplateInstantiationSink extends HttpResponseBodySink, HTTP::TemplateResponseBody {
+ override string getSinkKind() { result = "rawtemplate" }
+
+ override Locatable getAssociatedLoc() { result = this.getRead().getEnclosingTextNode() }
+ }
+
/**
* Holds if `body` may send a response with a content type other than HTML.
*/
diff --git a/ql/src/xml.dbscheme b/ql/src/xml.dbscheme
new file mode 100644
index 00000000000..8e909080bf3
--- /dev/null
+++ b/ql/src/xml.dbscheme
@@ -0,0 +1,144 @@
+/*
+ * External artifacts
+ */
+
+externalData(
+ int id : @externalDataElement,
+ string path : string ref,
+ int column: int ref,
+ string value : string ref
+);
+
+snapshotDate(
+ unique date snapshotDate : date ref
+);
+
+sourceLocationPrefix(
+ string prefix : string ref
+);
+
+/*
+ * Locations and files
+ */
+
+@location = @location_default ;
+
+@locatable = @xmllocatable ;
+
+locations_default(
+ unique int id: @location_default,
+ int file: @file ref,
+ int beginLine: int ref,
+ int beginColumn: int ref,
+ int endLine: int ref,
+ int endColumn: int ref
+);
+
+hasLocation(
+ int locatableid: @locatable ref,
+ int id: @location ref
+);
+
+@sourceline = @locatable ;
+
+#keyset[element_id]
+numlines(
+ int element_id: @sourceline ref,
+ int num_lines: int ref,
+ int num_code: int ref,
+ int num_comment: int ref
+);
+
+files(
+ unique int id: @file,
+ string name: string ref,
+ string simple: string ref,
+ string ext: string ref,
+ int fromSource: int ref // deprecated
+);
+
+folders(
+ unique int id: @folder,
+ string name: string ref,
+ string simple: string ref
+);
+
+@container = @folder | @file
+
+containerparent(
+ int parent: @container ref,
+ unique int child: @container ref
+);
+
+/*
+ * XML Files
+ */
+
+xmlEncoding(
+ unique int id: @file ref,
+ string encoding: string ref
+);
+
+xmlDTDs(
+ unique int id: @xmldtd,
+ string root: string ref,
+ string publicId: string ref,
+ string systemId: string ref,
+ int fileid: @file ref
+);
+
+xmlElements(
+ unique int id: @xmlelement,
+ string name: string ref,
+ int parentid: @xmlparent ref,
+ int idx: int ref,
+ int fileid: @file ref
+);
+
+xmlAttrs(
+ unique int id: @xmlattribute,
+ int elementid: @xmlelement ref,
+ string name: string ref,
+ string value: string ref,
+ int idx: int ref,
+ int fileid: @file ref
+);
+
+xmlNs(
+ int id: @xmlnamespace,
+ string prefixName: string ref,
+ string URI: string ref,
+ int fileid: @file ref
+);
+
+xmlHasNs(
+ int elementId: @xmlnamespaceable ref,
+ int nsId: @xmlnamespace ref,
+ int fileid: @file ref
+);
+
+xmlComments(
+ unique int id: @xmlcomment,
+ string text: string ref,
+ int parentid: @xmlparent ref,
+ int fileid: @file ref
+);
+
+xmlChars(
+ unique int id: @xmlcharacters,
+ string text: string ref,
+ int parentid: @xmlparent ref,
+ int idx: int ref,
+ int isCDATA: int ref,
+ int fileid: @file ref
+);
+
+@xmlparent = @file | @xmlelement;
+@xmlnamespaceable = @xmlelement | @xmlattribute;
+
+xmllocations(
+ int xmlElement: @xmllocatable ref,
+ int location: @location_default ref
+);
+
+@xmllocatable = @xmlcharacters | @xmlelement | @xmlcomment | @xmlattribute | @xmldtd | @file | @xmlnamespace;
diff --git a/ql/test/extractor-tests/html/htmlelements.expected b/ql/test/extractor-tests/html/htmlelements.expected
new file mode 100644
index 00000000000..8b2fc89ace4
--- /dev/null
+++ b/ql/test/extractor-tests/html/htmlelements.expected
@@ -0,0 +1,4 @@
+| test.html:2:1:2:23 |
...> |
+| test.html:3:1:11:5 |
...> |
+| test.html:6:13:6:44 |
...> |
+| test.html:8:13:8:31 |
...> |
diff --git a/ql/test/extractor-tests/html/htmlelements.ql b/ql/test/extractor-tests/html/htmlelements.ql
new file mode 100644
index 00000000000..c86b5fd72e3
--- /dev/null
+++ b/ql/test/extractor-tests/html/htmlelements.ql
@@ -0,0 +1,3 @@
+import go
+
+select any(HTML::Element e)
diff --git a/ql/test/extractor-tests/html/main.go b/ql/test/extractor-tests/html/main.go
new file mode 100644
index 00000000000..06ab7d0f9a3
--- /dev/null
+++ b/ql/test/extractor-tests/html/main.go
@@ -0,0 +1 @@
+package main
diff --git a/ql/test/extractor-tests/html/test.html b/ql/test/extractor-tests/html/test.html
new file mode 100644
index 00000000000..8bd601b604f
--- /dev/null
+++ b/ql/test/extractor-tests/html/test.html
@@ -0,0 +1,11 @@
+
+
{{.PageTitle}}
+
+ {{range .Todos}}
+ {{if .Done}}
+
{{.Title}}
+ {{else}}
+
{{.Title}}
+ {{end}}
+ {{end}}
+
diff --git a/ql/test/library-tests/semmle/go/frameworks/Beego/ReflectedXss.expected b/ql/test/library-tests/semmle/go/frameworks/Beego/ReflectedXss.expected
index a33c31b8d63..2b290e9dacf 100644
--- a/ql/test/library-tests/semmle/go/frameworks/Beego/ReflectedXss.expected
+++ b/ql/test/library-tests/semmle/go/frameworks/Beego/ReflectedXss.expected
@@ -190,56 +190,56 @@ nodes
| test.go:304:21:304:48 | type assertion | semmle.label | type assertion |
| test.go:305:21:305:52 | type assertion | semmle.label | type assertion |
#select
-| test.go:28:13:28:30 | type conversion | test.go:26:6:26:10 | definition of bound : bindMe | test.go:28:13:28:30 | type conversion | Cross-site scripting vulnerability due to $@. | test.go:26:6:26:10 | definition of bound | user-provided value |
-| test.go:29:13:29:27 | type conversion | test.go:26:6:26:10 | definition of bound : bindMe | test.go:29:13:29:27 | type conversion | Cross-site scripting vulnerability due to $@. | test.go:26:6:26:10 | definition of bound | user-provided value |
-| test.go:30:13:30:29 | type conversion | test.go:26:6:26:10 | definition of bound : bindMe | test.go:30:13:30:29 | type conversion | Cross-site scripting vulnerability due to $@. | test.go:26:6:26:10 | definition of bound | user-provided value |
-| test.go:35:13:35:43 | type conversion | test.go:35:20:35:42 | call to Cookie : string | test.go:35:13:35:43 | type conversion | Cross-site scripting vulnerability due to $@. | test.go:35:20:35:42 | call to Cookie | user-provided value |
-| test.go:40:13:40:52 | type conversion | test.go:40:20:40:31 | call to Data : map type | test.go:40:13:40:52 | type conversion | Cross-site scripting vulnerability due to $@. | test.go:40:20:40:31 | call to Data | user-provided value |
-| test.go:45:13:45:53 | type conversion | test.go:45:20:45:43 | call to GetData : interface type | test.go:45:13:45:53 | type conversion | Cross-site scripting vulnerability due to $@. | test.go:45:20:45:43 | call to GetData | user-provided value |
-| test.go:50:13:50:43 | type conversion | test.go:50:20:50:42 | call to Header : string | test.go:50:13:50:43 | type conversion | Cross-site scripting vulnerability due to $@. | test.go:50:20:50:42 | call to Header | user-provided value |
-| test.go:55:13:55:42 | type conversion | test.go:55:20:55:41 | call to Param : string | test.go:55:13:55:42 | type conversion | Cross-site scripting vulnerability due to $@. | test.go:55:20:55:41 | call to Param | user-provided value |
-| test.go:60:13:60:45 | type conversion | test.go:60:20:60:33 | call to Params : map type | test.go:60:13:60:45 | type conversion | Cross-site scripting vulnerability due to $@. | test.go:60:20:60:33 | call to Params | user-provided value |
-| test.go:65:13:65:42 | type conversion | test.go:65:20:65:41 | call to Query : string | test.go:65:13:65:42 | type conversion | Cross-site scripting vulnerability due to $@. | test.go:65:20:65:41 | call to Query | user-provided value |
-| test.go:70:13:70:33 | type conversion | test.go:70:20:70:32 | call to Refer : string | test.go:70:13:70:33 | type conversion | Cross-site scripting vulnerability due to $@. | test.go:70:20:70:32 | call to Refer | user-provided value |
-| test.go:75:13:75:35 | type conversion | test.go:75:20:75:34 | call to Referer : string | test.go:75:13:75:35 | type conversion | Cross-site scripting vulnerability due to $@. | test.go:75:20:75:34 | call to Referer | user-provided value |
-| test.go:80:13:80:31 | type conversion | test.go:80:20:80:30 | call to URI : string | test.go:80:13:80:31 | type conversion | Cross-site scripting vulnerability due to $@. | test.go:80:20:80:30 | call to URI | user-provided value |
-| test.go:85:13:85:31 | type conversion | test.go:85:20:85:30 | call to URL : string | test.go:85:13:85:31 | type conversion | Cross-site scripting vulnerability due to $@. | test.go:85:20:85:30 | call to URL | user-provided value |
-| test.go:90:13:90:37 | type conversion | test.go:90:20:90:36 | call to UserAgent : string | test.go:90:13:90:37 | type conversion | Cross-site scripting vulnerability due to $@. | test.go:90:20:90:36 | call to UserAgent | user-provided value |
-| test.go:95:14:95:45 | type assertion | test.go:95:14:95:25 | call to Data : map type | test.go:95:14:95:45 | type assertion | Cross-site scripting vulnerability due to $@. | test.go:95:14:95:25 | call to Data | user-provided value |
-| test.go:107:14:107:45 | type assertion | test.go:107:14:107:25 | call to Data : map type | test.go:107:14:107:45 | type assertion | Cross-site scripting vulnerability due to $@. | test.go:107:14:107:25 | call to Data | user-provided value |
-| test.go:119:14:119:45 | type assertion | test.go:119:14:119:25 | call to Data : map type | test.go:119:14:119:45 | type assertion | Cross-site scripting vulnerability due to $@. | test.go:119:14:119:25 | call to Data | user-provided value |
-| test.go:136:23:136:62 | type assertion | test.go:136:23:136:42 | call to Data : map type | test.go:136:23:136:62 | type assertion | Cross-site scripting vulnerability due to $@. | test.go:136:23:136:42 | call to Data | user-provided value |
-| test.go:193:14:193:55 | type conversion | test.go:192:15:192:26 | call to Data : map type | test.go:193:14:193:55 | type conversion | Cross-site scripting vulnerability due to $@. | test.go:192:15:192:26 | call to Data | user-provided value |
-| test.go:194:14:194:58 | type conversion | test.go:192:15:192:26 | call to Data : map type | test.go:194:14:194:58 | type conversion | Cross-site scripting vulnerability due to $@. | test.go:192:15:192:26 | call to Data | user-provided value |
-| test.go:196:14:196:28 | type assertion | test.go:192:15:192:26 | call to Data : map type | test.go:196:14:196:28 | type assertion | Cross-site scripting vulnerability due to $@. | test.go:192:15:192:26 | call to Data | user-provided value |
-| test.go:197:14:197:55 | type conversion | test.go:192:15:192:26 | call to Data : map type | test.go:197:14:197:55 | type conversion | Cross-site scripting vulnerability due to $@. | test.go:192:15:192:26 | call to Data | user-provided value |
-| test.go:198:14:198:59 | type conversion | test.go:192:15:192:26 | call to Data : map type | test.go:198:14:198:59 | type conversion | Cross-site scripting vulnerability due to $@. | test.go:192:15:192:26 | call to Data | user-provided value |
-| test.go:202:14:202:28 | type conversion | test.go:201:18:201:33 | selection of Form : Values | test.go:202:14:202:28 | type conversion | Cross-site scripting vulnerability due to $@. | test.go:201:18:201:33 | selection of Form | user-provided value |
-| test.go:217:14:217:32 | type conversion | test.go:216:2:216:34 | ... := ...[1] : pointer type | test.go:217:14:217:32 | type conversion | Cross-site scripting vulnerability due to $@. | test.go:216:2:216:34 | ... := ...[1] | user-provided value |
-| test.go:219:14:219:20 | content | test.go:216:2:216:34 | ... := ...[0] : File | test.go:219:14:219:20 | content | Cross-site scripting vulnerability due to $@. | test.go:216:2:216:34 | ... := ...[0] | user-provided value |
-| test.go:222:14:222:38 | type conversion | test.go:221:2:221:40 | ... := ...[0] : slice type | test.go:222:14:222:38 | type conversion | Cross-site scripting vulnerability due to $@. | test.go:221:2:221:40 | ... := ...[0] | user-provided value |
-| test.go:225:14:225:22 | type conversion | test.go:224:7:224:28 | call to GetString : string | test.go:225:14:225:22 | type conversion | Cross-site scripting vulnerability due to $@. | test.go:224:7:224:28 | call to GetString | user-provided value |
-| test.go:228:14:228:26 | type conversion | test.go:227:8:227:35 | call to GetStrings : slice type | test.go:228:14:228:26 | type conversion | Cross-site scripting vulnerability due to $@. | test.go:227:8:227:35 | call to GetStrings | user-provided value |
-| test.go:231:14:231:27 | type conversion | test.go:230:9:230:17 | call to Input : Values | test.go:231:14:231:27 | type conversion | Cross-site scripting vulnerability due to $@. | test.go:230:9:230:17 | call to Input | user-provided value |
-| test.go:235:14:235:30 | type conversion | test.go:233:6:233:8 | definition of str : myStruct | test.go:235:14:235:30 | type conversion | Cross-site scripting vulnerability due to $@. | test.go:233:6:233:8 | definition of str | user-provided value |
-| test.go:242:21:242:29 | untrusted | test.go:239:15:239:36 | call to GetString : string | test.go:242:21:242:29 | untrusted | Cross-site scripting vulnerability due to $@. | test.go:239:15:239:36 | call to GetString | user-provided value |
-| test.go:252:16:252:45 | type conversion | test.go:252:23:252:44 | call to GetCookie : string | test.go:252:16:252:45 | type conversion | Cross-site scripting vulnerability due to $@. | test.go:252:23:252:44 | call to GetCookie | user-provided value |
-| test.go:257:16:257:37 | call to GetCookie | test.go:257:16:257:37 | call to GetCookie | test.go:257:16:257:37 | call to GetCookie | Cross-site scripting vulnerability due to $@. | test.go:257:16:257:37 | call to GetCookie | user-provided value |
-| test.go:258:15:258:41 | call to GetCookie | test.go:258:15:258:41 | call to GetCookie | test.go:258:15:258:41 | call to GetCookie | Cross-site scripting vulnerability due to $@. | test.go:258:15:258:41 | call to GetCookie | user-provided value |
-| test.go:263:55:263:84 | type conversion | test.go:263:62:263:83 | call to GetCookie : string | test.go:263:55:263:84 | type conversion | Cross-site scripting vulnerability due to $@. | test.go:263:62:263:83 | call to GetCookie | user-provided value |
-| test.go:276:21:276:61 | call to GetDisplayString | test.go:268:2:268:40 | ... := ...[0] : slice type | test.go:276:21:276:61 | call to GetDisplayString | Cross-site scripting vulnerability due to $@. | test.go:268:2:268:40 | ... := ...[0] | user-provided value |
-| test.go:277:21:277:92 | selection of Filename | test.go:268:2:268:40 | ... := ...[0] : slice type | test.go:277:21:277:92 | selection of Filename | Cross-site scripting vulnerability due to $@. | test.go:268:2:268:40 | ... := ...[0] | user-provided value |
-| test.go:278:21:278:96 | selection of Filename | test.go:268:2:268:40 | ... := ...[0] : slice type | test.go:278:21:278:96 | selection of Filename | Cross-site scripting vulnerability due to $@. | test.go:268:2:268:40 | ... := ...[0] | user-provided value |
-| test.go:283:3:285:80 | selection of Filename | test.go:268:2:268:40 | ... := ...[0] : slice type | test.go:283:3:285:80 | selection of Filename | Cross-site scripting vulnerability due to $@. | test.go:268:2:268:40 | ... := ...[0] | user-provided value |
-| test.go:286:21:286:101 | selection of Filename | test.go:268:2:268:40 | ... := ...[0] : slice type | test.go:286:21:286:101 | selection of Filename | Cross-site scripting vulnerability due to $@. | test.go:268:2:268:40 | ... := ...[0] | user-provided value |
-| test.go:287:21:287:101 | selection of Filename | test.go:268:2:268:40 | ... := ...[0] : slice type | test.go:287:21:287:101 | selection of Filename | Cross-site scripting vulnerability due to $@. | test.go:268:2:268:40 | ... := ...[0] | user-provided value |
-| test.go:288:21:288:97 | selection of Filename | test.go:268:2:268:40 | ... := ...[0] : slice type | test.go:288:21:288:97 | selection of Filename | Cross-site scripting vulnerability due to $@. | test.go:268:2:268:40 | ... := ...[0] | user-provided value |
-| test.go:289:21:289:97 | selection of Filename | test.go:268:2:268:40 | ... := ...[0] : slice type | test.go:289:21:289:97 | selection of Filename | Cross-site scripting vulnerability due to $@. | test.go:268:2:268:40 | ... := ...[0] | user-provided value |
-| test.go:290:21:290:102 | selection of Filename | test.go:268:2:268:40 | ... := ...[0] : slice type | test.go:290:21:290:102 | selection of Filename | Cross-site scripting vulnerability due to $@. | test.go:268:2:268:40 | ... := ...[0] | user-provided value |
-| test.go:291:21:291:102 | selection of Filename | test.go:268:2:268:40 | ... := ...[0] : slice type | test.go:291:21:291:102 | selection of Filename | Cross-site scripting vulnerability due to $@. | test.go:268:2:268:40 | ... := ...[0] | user-provided value |
-| test.go:292:21:292:82 | selection of Filename | test.go:268:2:268:40 | ... := ...[0] : slice type | test.go:292:21:292:82 | selection of Filename | Cross-site scripting vulnerability due to $@. | test.go:268:2:268:40 | ... := ...[0] | user-provided value |
-| test.go:294:21:294:133 | selection of Filename | test.go:268:2:268:40 | ... := ...[0] : slice type | test.go:294:21:294:133 | selection of Filename | Cross-site scripting vulnerability due to $@. | test.go:268:2:268:40 | ... := ...[0] | user-provided value |
-| test.go:295:21:295:88 | selection of Filename | test.go:268:2:268:40 | ... := ...[0] : slice type | test.go:295:21:295:88 | selection of Filename | Cross-site scripting vulnerability due to $@. | test.go:268:2:268:40 | ... := ...[0] | user-provided value |
-| test.go:296:21:296:87 | selection of Filename | test.go:268:2:268:40 | ... := ...[0] : slice type | test.go:296:21:296:87 | selection of Filename | Cross-site scripting vulnerability due to $@. | test.go:268:2:268:40 | ... := ...[0] | user-provided value |
-| test.go:304:21:304:48 | type assertion | test.go:302:15:302:36 | call to GetString : string | test.go:304:21:304:48 | type assertion | Cross-site scripting vulnerability due to $@. | test.go:302:15:302:36 | call to GetString | user-provided value |
-| test.go:305:21:305:52 | type assertion | test.go:302:15:302:36 | call to GetString : string | test.go:305:21:305:52 | type assertion | Cross-site scripting vulnerability due to $@. | test.go:302:15:302:36 | call to GetString | user-provided value |
+| test.go:28:13:28:30 | type conversion | test.go:26:6:26:10 | definition of bound : bindMe | test.go:28:13:28:30 | type conversion | Cross-site scripting vulnerability due to $@. | test.go:26:6:26:10 | definition of bound | user-provided value | test.go:0:0:0:0 | test.go | |
+| test.go:29:13:29:27 | type conversion | test.go:26:6:26:10 | definition of bound : bindMe | test.go:29:13:29:27 | type conversion | Cross-site scripting vulnerability due to $@. | test.go:26:6:26:10 | definition of bound | user-provided value | test.go:0:0:0:0 | test.go | |
+| test.go:30:13:30:29 | type conversion | test.go:26:6:26:10 | definition of bound : bindMe | test.go:30:13:30:29 | type conversion | Cross-site scripting vulnerability due to $@. | test.go:26:6:26:10 | definition of bound | user-provided value | test.go:0:0:0:0 | test.go | |
+| test.go:35:13:35:43 | type conversion | test.go:35:20:35:42 | call to Cookie : string | test.go:35:13:35:43 | type conversion | Cross-site scripting vulnerability due to $@. | test.go:35:20:35:42 | call to Cookie | user-provided value | test.go:0:0:0:0 | test.go | |
+| test.go:40:13:40:52 | type conversion | test.go:40:20:40:31 | call to Data : map type | test.go:40:13:40:52 | type conversion | Cross-site scripting vulnerability due to $@. | test.go:40:20:40:31 | call to Data | user-provided value | test.go:0:0:0:0 | test.go | |
+| test.go:45:13:45:53 | type conversion | test.go:45:20:45:43 | call to GetData : interface type | test.go:45:13:45:53 | type conversion | Cross-site scripting vulnerability due to $@. | test.go:45:20:45:43 | call to GetData | user-provided value | test.go:0:0:0:0 | test.go | |
+| test.go:50:13:50:43 | type conversion | test.go:50:20:50:42 | call to Header : string | test.go:50:13:50:43 | type conversion | Cross-site scripting vulnerability due to $@. | test.go:50:20:50:42 | call to Header | user-provided value | test.go:0:0:0:0 | test.go | |
+| test.go:55:13:55:42 | type conversion | test.go:55:20:55:41 | call to Param : string | test.go:55:13:55:42 | type conversion | Cross-site scripting vulnerability due to $@. | test.go:55:20:55:41 | call to Param | user-provided value | test.go:0:0:0:0 | test.go | |
+| test.go:60:13:60:45 | type conversion | test.go:60:20:60:33 | call to Params : map type | test.go:60:13:60:45 | type conversion | Cross-site scripting vulnerability due to $@. | test.go:60:20:60:33 | call to Params | user-provided value | test.go:0:0:0:0 | test.go | |
+| test.go:65:13:65:42 | type conversion | test.go:65:20:65:41 | call to Query : string | test.go:65:13:65:42 | type conversion | Cross-site scripting vulnerability due to $@. | test.go:65:20:65:41 | call to Query | user-provided value | test.go:0:0:0:0 | test.go | |
+| test.go:70:13:70:33 | type conversion | test.go:70:20:70:32 | call to Refer : string | test.go:70:13:70:33 | type conversion | Cross-site scripting vulnerability due to $@. | test.go:70:20:70:32 | call to Refer | user-provided value | test.go:0:0:0:0 | test.go | |
+| test.go:75:13:75:35 | type conversion | test.go:75:20:75:34 | call to Referer : string | test.go:75:13:75:35 | type conversion | Cross-site scripting vulnerability due to $@. | test.go:75:20:75:34 | call to Referer | user-provided value | test.go:0:0:0:0 | test.go | |
+| test.go:80:13:80:31 | type conversion | test.go:80:20:80:30 | call to URI : string | test.go:80:13:80:31 | type conversion | Cross-site scripting vulnerability due to $@. | test.go:80:20:80:30 | call to URI | user-provided value | test.go:0:0:0:0 | test.go | |
+| test.go:85:13:85:31 | type conversion | test.go:85:20:85:30 | call to URL : string | test.go:85:13:85:31 | type conversion | Cross-site scripting vulnerability due to $@. | test.go:85:20:85:30 | call to URL | user-provided value | test.go:0:0:0:0 | test.go | |
+| test.go:90:13:90:37 | type conversion | test.go:90:20:90:36 | call to UserAgent : string | test.go:90:13:90:37 | type conversion | Cross-site scripting vulnerability due to $@. | test.go:90:20:90:36 | call to UserAgent | user-provided value | test.go:0:0:0:0 | test.go | |
+| test.go:95:14:95:45 | type assertion | test.go:95:14:95:25 | call to Data : map type | test.go:95:14:95:45 | type assertion | Cross-site scripting vulnerability due to $@. | test.go:95:14:95:25 | call to Data | user-provided value | test.go:0:0:0:0 | test.go | |
+| test.go:107:14:107:45 | type assertion | test.go:107:14:107:25 | call to Data : map type | test.go:107:14:107:45 | type assertion | Cross-site scripting vulnerability due to $@. | test.go:107:14:107:25 | call to Data | user-provided value | test.go:0:0:0:0 | test.go | |
+| test.go:119:14:119:45 | type assertion | test.go:119:14:119:25 | call to Data : map type | test.go:119:14:119:45 | type assertion | Cross-site scripting vulnerability due to $@. | test.go:119:14:119:25 | call to Data | user-provided value | test.go:0:0:0:0 | test.go | |
+| test.go:136:23:136:62 | type assertion | test.go:136:23:136:42 | call to Data : map type | test.go:136:23:136:62 | type assertion | Cross-site scripting vulnerability due to $@. | test.go:136:23:136:42 | call to Data | user-provided value | test.go:0:0:0:0 | test.go | |
+| test.go:193:14:193:55 | type conversion | test.go:192:15:192:26 | call to Data : map type | test.go:193:14:193:55 | type conversion | Cross-site scripting vulnerability due to $@. | test.go:192:15:192:26 | call to Data | user-provided value | test.go:0:0:0:0 | test.go | |
+| test.go:194:14:194:58 | type conversion | test.go:192:15:192:26 | call to Data : map type | test.go:194:14:194:58 | type conversion | Cross-site scripting vulnerability due to $@. | test.go:192:15:192:26 | call to Data | user-provided value | test.go:0:0:0:0 | test.go | |
+| test.go:196:14:196:28 | type assertion | test.go:192:15:192:26 | call to Data : map type | test.go:196:14:196:28 | type assertion | Cross-site scripting vulnerability due to $@. | test.go:192:15:192:26 | call to Data | user-provided value | test.go:0:0:0:0 | test.go | |
+| test.go:197:14:197:55 | type conversion | test.go:192:15:192:26 | call to Data : map type | test.go:197:14:197:55 | type conversion | Cross-site scripting vulnerability due to $@. | test.go:192:15:192:26 | call to Data | user-provided value | test.go:0:0:0:0 | test.go | |
+| test.go:198:14:198:59 | type conversion | test.go:192:15:192:26 | call to Data : map type | test.go:198:14:198:59 | type conversion | Cross-site scripting vulnerability due to $@. | test.go:192:15:192:26 | call to Data | user-provided value | test.go:0:0:0:0 | test.go | |
+| test.go:202:14:202:28 | type conversion | test.go:201:18:201:33 | selection of Form : Values | test.go:202:14:202:28 | type conversion | Cross-site scripting vulnerability due to $@. | test.go:201:18:201:33 | selection of Form | user-provided value | test.go:0:0:0:0 | test.go | |
+| test.go:217:14:217:32 | type conversion | test.go:216:2:216:34 | ... := ...[1] : pointer type | test.go:217:14:217:32 | type conversion | Cross-site scripting vulnerability due to $@. | test.go:216:2:216:34 | ... := ...[1] | user-provided value | test.go:0:0:0:0 | test.go | |
+| test.go:219:14:219:20 | content | test.go:216:2:216:34 | ... := ...[0] : File | test.go:219:14:219:20 | content | Cross-site scripting vulnerability due to $@. | test.go:216:2:216:34 | ... := ...[0] | user-provided value | test.go:0:0:0:0 | test.go | |
+| test.go:222:14:222:38 | type conversion | test.go:221:2:221:40 | ... := ...[0] : slice type | test.go:222:14:222:38 | type conversion | Cross-site scripting vulnerability due to $@. | test.go:221:2:221:40 | ... := ...[0] | user-provided value | test.go:0:0:0:0 | test.go | |
+| test.go:225:14:225:22 | type conversion | test.go:224:7:224:28 | call to GetString : string | test.go:225:14:225:22 | type conversion | Cross-site scripting vulnerability due to $@. | test.go:224:7:224:28 | call to GetString | user-provided value | test.go:0:0:0:0 | test.go | |
+| test.go:228:14:228:26 | type conversion | test.go:227:8:227:35 | call to GetStrings : slice type | test.go:228:14:228:26 | type conversion | Cross-site scripting vulnerability due to $@. | test.go:227:8:227:35 | call to GetStrings | user-provided value | test.go:0:0:0:0 | test.go | |
+| test.go:231:14:231:27 | type conversion | test.go:230:9:230:17 | call to Input : Values | test.go:231:14:231:27 | type conversion | Cross-site scripting vulnerability due to $@. | test.go:230:9:230:17 | call to Input | user-provided value | test.go:0:0:0:0 | test.go | |
+| test.go:235:14:235:30 | type conversion | test.go:233:6:233:8 | definition of str : myStruct | test.go:235:14:235:30 | type conversion | Cross-site scripting vulnerability due to $@. | test.go:233:6:233:8 | definition of str | user-provided value | test.go:0:0:0:0 | test.go | |
+| test.go:242:21:242:29 | untrusted | test.go:239:15:239:36 | call to GetString : string | test.go:242:21:242:29 | untrusted | Cross-site scripting vulnerability due to $@. | test.go:239:15:239:36 | call to GetString | user-provided value | test.go:0:0:0:0 | test.go | |
+| test.go:252:16:252:45 | type conversion | test.go:252:23:252:44 | call to GetCookie : string | test.go:252:16:252:45 | type conversion | Cross-site scripting vulnerability due to $@. | test.go:252:23:252:44 | call to GetCookie | user-provided value | test.go:0:0:0:0 | test.go | |
+| test.go:257:16:257:37 | call to GetCookie | test.go:257:16:257:37 | call to GetCookie | test.go:257:16:257:37 | call to GetCookie | Cross-site scripting vulnerability due to $@. | test.go:257:16:257:37 | call to GetCookie | user-provided value | test.go:0:0:0:0 | test.go | |
+| test.go:258:15:258:41 | call to GetCookie | test.go:258:15:258:41 | call to GetCookie | test.go:258:15:258:41 | call to GetCookie | Cross-site scripting vulnerability due to $@. | test.go:258:15:258:41 | call to GetCookie | user-provided value | test.go:0:0:0:0 | test.go | |
+| test.go:263:55:263:84 | type conversion | test.go:263:62:263:83 | call to GetCookie : string | test.go:263:55:263:84 | type conversion | Cross-site scripting vulnerability due to $@. | test.go:263:62:263:83 | call to GetCookie | user-provided value | test.go:0:0:0:0 | test.go | |
+| test.go:276:21:276:61 | call to GetDisplayString | test.go:268:2:268:40 | ... := ...[0] : slice type | test.go:276:21:276:61 | call to GetDisplayString | Cross-site scripting vulnerability due to $@. | test.go:268:2:268:40 | ... := ...[0] | user-provided value | test.go:0:0:0:0 | test.go | |
+| test.go:277:21:277:92 | selection of Filename | test.go:268:2:268:40 | ... := ...[0] : slice type | test.go:277:21:277:92 | selection of Filename | Cross-site scripting vulnerability due to $@. | test.go:268:2:268:40 | ... := ...[0] | user-provided value | test.go:0:0:0:0 | test.go | |
+| test.go:278:21:278:96 | selection of Filename | test.go:268:2:268:40 | ... := ...[0] : slice type | test.go:278:21:278:96 | selection of Filename | Cross-site scripting vulnerability due to $@. | test.go:268:2:268:40 | ... := ...[0] | user-provided value | test.go:0:0:0:0 | test.go | |
+| test.go:283:3:285:80 | selection of Filename | test.go:268:2:268:40 | ... := ...[0] : slice type | test.go:283:3:285:80 | selection of Filename | Cross-site scripting vulnerability due to $@. | test.go:268:2:268:40 | ... := ...[0] | user-provided value | test.go:0:0:0:0 | test.go | |
+| test.go:286:21:286:101 | selection of Filename | test.go:268:2:268:40 | ... := ...[0] : slice type | test.go:286:21:286:101 | selection of Filename | Cross-site scripting vulnerability due to $@. | test.go:268:2:268:40 | ... := ...[0] | user-provided value | test.go:0:0:0:0 | test.go | |
+| test.go:287:21:287:101 | selection of Filename | test.go:268:2:268:40 | ... := ...[0] : slice type | test.go:287:21:287:101 | selection of Filename | Cross-site scripting vulnerability due to $@. | test.go:268:2:268:40 | ... := ...[0] | user-provided value | test.go:0:0:0:0 | test.go | |
+| test.go:288:21:288:97 | selection of Filename | test.go:268:2:268:40 | ... := ...[0] : slice type | test.go:288:21:288:97 | selection of Filename | Cross-site scripting vulnerability due to $@. | test.go:268:2:268:40 | ... := ...[0] | user-provided value | test.go:0:0:0:0 | test.go | |
+| test.go:289:21:289:97 | selection of Filename | test.go:268:2:268:40 | ... := ...[0] : slice type | test.go:289:21:289:97 | selection of Filename | Cross-site scripting vulnerability due to $@. | test.go:268:2:268:40 | ... := ...[0] | user-provided value | test.go:0:0:0:0 | test.go | |
+| test.go:290:21:290:102 | selection of Filename | test.go:268:2:268:40 | ... := ...[0] : slice type | test.go:290:21:290:102 | selection of Filename | Cross-site scripting vulnerability due to $@. | test.go:268:2:268:40 | ... := ...[0] | user-provided value | test.go:0:0:0:0 | test.go | |
+| test.go:291:21:291:102 | selection of Filename | test.go:268:2:268:40 | ... := ...[0] : slice type | test.go:291:21:291:102 | selection of Filename | Cross-site scripting vulnerability due to $@. | test.go:268:2:268:40 | ... := ...[0] | user-provided value | test.go:0:0:0:0 | test.go | |
+| test.go:292:21:292:82 | selection of Filename | test.go:268:2:268:40 | ... := ...[0] : slice type | test.go:292:21:292:82 | selection of Filename | Cross-site scripting vulnerability due to $@. | test.go:268:2:268:40 | ... := ...[0] | user-provided value | test.go:0:0:0:0 | test.go | |
+| test.go:294:21:294:133 | selection of Filename | test.go:268:2:268:40 | ... := ...[0] : slice type | test.go:294:21:294:133 | selection of Filename | Cross-site scripting vulnerability due to $@. | test.go:268:2:268:40 | ... := ...[0] | user-provided value | test.go:0:0:0:0 | test.go | |
+| test.go:295:21:295:88 | selection of Filename | test.go:268:2:268:40 | ... := ...[0] : slice type | test.go:295:21:295:88 | selection of Filename | Cross-site scripting vulnerability due to $@. | test.go:268:2:268:40 | ... := ...[0] | user-provided value | test.go:0:0:0:0 | test.go | |
+| test.go:296:21:296:87 | selection of Filename | test.go:268:2:268:40 | ... := ...[0] : slice type | test.go:296:21:296:87 | selection of Filename | Cross-site scripting vulnerability due to $@. | test.go:268:2:268:40 | ... := ...[0] | user-provided value | test.go:0:0:0:0 | test.go | |
+| test.go:304:21:304:48 | type assertion | test.go:302:15:302:36 | call to GetString : string | test.go:304:21:304:48 | type assertion | Cross-site scripting vulnerability due to $@. | test.go:302:15:302:36 | call to GetString | user-provided value | test.go:0:0:0:0 | test.go | |
+| test.go:305:21:305:52 | type assertion | test.go:302:15:302:36 | call to GetString : string | test.go:305:21:305:52 | type assertion | Cross-site scripting vulnerability due to $@. | test.go:302:15:302:36 | call to GetString | user-provided value | test.go:0:0:0:0 | test.go | |
diff --git a/ql/test/library-tests/semmle/go/frameworks/Chi/ReflectedXss.expected b/ql/test/library-tests/semmle/go/frameworks/Chi/ReflectedXss.expected
index 68cf414f652..7d5a43de231 100644
--- a/ql/test/library-tests/semmle/go/frameworks/Chi/ReflectedXss.expected
+++ b/ql/test/library-tests/semmle/go/frameworks/Chi/ReflectedXss.expected
@@ -23,7 +23,7 @@ nodes
| test.go:24:11:24:72 | type conversion | semmle.label | type conversion |
| test.go:24:18:24:71 | call to URLParam : string | semmle.label | call to URLParam : string |
#select
-| test.go:21:11:21:24 | type conversion | test.go:13:12:13:16 | selection of URL : pointer type | test.go:21:11:21:24 | type conversion | Cross-site scripting vulnerability due to $@. | test.go:13:12:13:16 | selection of URL | user-provided value |
-| test.go:22:11:22:46 | type conversion | test.go:22:18:22:45 | call to URLParam : string | test.go:22:11:22:46 | type conversion | Cross-site scripting vulnerability due to $@. | test.go:22:18:22:45 | call to URLParam | user-provided value |
-| test.go:23:11:23:61 | type conversion | test.go:23:18:23:60 | call to URLParamFromCtx : string | test.go:23:11:23:61 | type conversion | Cross-site scripting vulnerability due to $@. | test.go:23:18:23:60 | call to URLParamFromCtx | user-provided value |
-| test.go:24:11:24:72 | type conversion | test.go:24:18:24:71 | call to URLParam : string | test.go:24:11:24:72 | type conversion | Cross-site scripting vulnerability due to $@. | test.go:24:18:24:71 | call to URLParam | user-provided value |
+| test.go:21:11:21:24 | type conversion | test.go:13:12:13:16 | selection of URL : pointer type | test.go:21:11:21:24 | type conversion | Cross-site scripting vulnerability due to $@. | test.go:13:12:13:16 | selection of URL | user-provided value | test.go:0:0:0:0 | test.go | |
+| test.go:22:11:22:46 | type conversion | test.go:22:18:22:45 | call to URLParam : string | test.go:22:11:22:46 | type conversion | Cross-site scripting vulnerability due to $@. | test.go:22:18:22:45 | call to URLParam | user-provided value | test.go:0:0:0:0 | test.go | |
+| test.go:23:11:23:61 | type conversion | test.go:23:18:23:60 | call to URLParamFromCtx : string | test.go:23:11:23:61 | type conversion | Cross-site scripting vulnerability due to $@. | test.go:23:18:23:60 | call to URLParamFromCtx | user-provided value | test.go:0:0:0:0 | test.go | |
+| test.go:24:11:24:72 | type conversion | test.go:24:18:24:71 | call to URLParam : string | test.go:24:11:24:72 | type conversion | Cross-site scripting vulnerability due to $@. | test.go:24:18:24:71 | call to URLParam | user-provided value | test.go:0:0:0:0 | test.go | |
diff --git a/ql/test/library-tests/semmle/go/frameworks/Echo/ReflectedXss.expected b/ql/test/library-tests/semmle/go/frameworks/Echo/ReflectedXss.expected
index 941aa382b0c..5badcb7a391 100644
--- a/ql/test/library-tests/semmle/go/frameworks/Echo/ReflectedXss.expected
+++ b/ql/test/library-tests/semmle/go/frameworks/Echo/ReflectedXss.expected
@@ -74,22 +74,22 @@ nodes
| test.go:162:11:162:32 | call to Param : string | semmle.label | call to Param : string |
| test.go:163:23:163:35 | type conversion | semmle.label | type conversion |
#select
-| test.go:14:16:14:20 | param | test.go:13:11:13:32 | call to Param : string | test.go:14:16:14:20 | param | Cross-site scripting vulnerability due to $@. | test.go:13:11:13:32 | call to Param | user-provided value |
-| test.go:20:16:20:20 | param | test.go:19:11:19:27 | call to ParamValues : slice type | test.go:20:16:20:20 | param | Cross-site scripting vulnerability due to $@. | test.go:19:11:19:27 | call to ParamValues | user-provided value |
-| test.go:26:16:26:20 | param | test.go:25:11:25:37 | call to QueryParam : string | test.go:26:16:26:20 | param | Cross-site scripting vulnerability due to $@. | test.go:25:11:25:37 | call to QueryParam | user-provided value |
-| test.go:32:16:32:20 | param | test.go:31:11:31:27 | call to QueryParams : Values | test.go:32:16:32:20 | param | Cross-site scripting vulnerability due to $@. | test.go:31:11:31:27 | call to QueryParams | user-provided value |
-| test.go:38:16:38:19 | qstr | test.go:37:10:37:26 | call to QueryString : string | test.go:38:16:38:19 | qstr | Cross-site scripting vulnerability due to $@. | test.go:37:10:37:26 | call to QueryString | user-provided value |
-| test.go:44:16:44:18 | val | test.go:43:9:43:34 | call to FormValue : string | test.go:44:16:44:18 | val | Cross-site scripting vulnerability due to $@. | test.go:43:9:43:34 | call to FormValue | user-provided value |
-| test.go:50:16:50:37 | index expression | test.go:49:2:49:30 | ... := ...[0] : Values | test.go:50:16:50:37 | index expression | Cross-site scripting vulnerability due to $@. | test.go:49:2:49:30 | ... := ...[0] | user-provided value |
-| test.go:59:20:59:25 | buffer | test.go:55:2:55:46 | ... := ...[0] : pointer type | test.go:59:20:59:25 | buffer | Cross-site scripting vulnerability due to $@. | test.go:55:2:55:46 | ... := ...[0] | user-provided value |
-| test.go:65:16:65:41 | index expression | test.go:64:2:64:31 | ... := ...[0] : pointer type | test.go:65:16:65:41 | index expression | Cross-site scripting vulnerability due to $@. | test.go:64:2:64:31 | ... := ...[0] | user-provided value |
-| test.go:75:20:75:25 | buffer | test.go:70:2:70:31 | ... := ...[0] : pointer type | test.go:75:20:75:25 | buffer | Cross-site scripting vulnerability due to $@. | test.go:70:2:70:31 | ... := ...[0] | user-provided value |
-| test.go:81:16:81:24 | selection of Value | test.go:80:2:80:32 | ... := ...[0] : pointer type | test.go:81:16:81:24 | selection of Value | Cross-site scripting vulnerability due to $@. | test.go:80:2:80:32 | ... := ...[0] | user-provided value |
-| test.go:87:16:87:31 | selection of Value | test.go:86:13:86:25 | call to Cookies : slice type | test.go:87:16:87:31 | selection of Value | Cross-site scripting vulnerability due to $@. | test.go:86:13:86:25 | call to Cookies | user-provided value |
-| test.go:98:16:98:21 | selection of s | test.go:97:11:97:15 | &... : pointer type | test.go:98:16:98:21 | selection of s | Cross-site scripting vulnerability due to $@. | test.go:97:11:97:15 | &... | user-provided value |
-| test.go:112:16:112:42 | type assertion | test.go:111:21:111:42 | call to Param : string | test.go:112:16:112:42 | type assertion | Cross-site scripting vulnerability due to $@. | test.go:111:21:111:42 | call to Param | user-provided value |
-| test.go:123:16:123:20 | param | test.go:122:11:122:32 | call to Param : string | test.go:123:16:123:20 | param | Cross-site scripting vulnerability due to $@. | test.go:122:11:122:32 | call to Param | user-provided value |
-| test.go:129:20:129:32 | type conversion | test.go:128:11:128:32 | call to Param : string | test.go:129:20:129:32 | type conversion | Cross-site scripting vulnerability due to $@. | test.go:128:11:128:32 | call to Param | user-provided value |
-| test.go:135:29:135:41 | type conversion | test.go:134:11:134:32 | call to Param : string | test.go:135:29:135:41 | type conversion | Cross-site scripting vulnerability due to $@. | test.go:134:11:134:32 | call to Param | user-provided value |
-| test.go:148:31:148:36 | reader | test.go:146:11:146:32 | call to Param : string | test.go:148:31:148:36 | reader | Cross-site scripting vulnerability due to $@. | test.go:146:11:146:32 | call to Param | user-provided value |
-| test.go:163:23:163:35 | type conversion | test.go:162:11:162:32 | call to Param : string | test.go:163:23:163:35 | type conversion | Cross-site scripting vulnerability due to $@. | test.go:162:11:162:32 | call to Param | user-provided value |
+| test.go:14:16:14:20 | param | test.go:13:11:13:32 | call to Param : string | test.go:14:16:14:20 | param | Cross-site scripting vulnerability due to $@. | test.go:13:11:13:32 | call to Param | user-provided value | test.go:0:0:0:0 | test.go | |
+| test.go:20:16:20:20 | param | test.go:19:11:19:27 | call to ParamValues : slice type | test.go:20:16:20:20 | param | Cross-site scripting vulnerability due to $@. | test.go:19:11:19:27 | call to ParamValues | user-provided value | test.go:0:0:0:0 | test.go | |
+| test.go:26:16:26:20 | param | test.go:25:11:25:37 | call to QueryParam : string | test.go:26:16:26:20 | param | Cross-site scripting vulnerability due to $@. | test.go:25:11:25:37 | call to QueryParam | user-provided value | test.go:0:0:0:0 | test.go | |
+| test.go:32:16:32:20 | param | test.go:31:11:31:27 | call to QueryParams : Values | test.go:32:16:32:20 | param | Cross-site scripting vulnerability due to $@. | test.go:31:11:31:27 | call to QueryParams | user-provided value | test.go:0:0:0:0 | test.go | |
+| test.go:38:16:38:19 | qstr | test.go:37:10:37:26 | call to QueryString : string | test.go:38:16:38:19 | qstr | Cross-site scripting vulnerability due to $@. | test.go:37:10:37:26 | call to QueryString | user-provided value | test.go:0:0:0:0 | test.go | |
+| test.go:44:16:44:18 | val | test.go:43:9:43:34 | call to FormValue : string | test.go:44:16:44:18 | val | Cross-site scripting vulnerability due to $@. | test.go:43:9:43:34 | call to FormValue | user-provided value | test.go:0:0:0:0 | test.go | |
+| test.go:50:16:50:37 | index expression | test.go:49:2:49:30 | ... := ...[0] : Values | test.go:50:16:50:37 | index expression | Cross-site scripting vulnerability due to $@. | test.go:49:2:49:30 | ... := ...[0] | user-provided value | test.go:0:0:0:0 | test.go | |
+| test.go:59:20:59:25 | buffer | test.go:55:2:55:46 | ... := ...[0] : pointer type | test.go:59:20:59:25 | buffer | Cross-site scripting vulnerability due to $@. | test.go:55:2:55:46 | ... := ...[0] | user-provided value | test.go:0:0:0:0 | test.go | |
+| test.go:65:16:65:41 | index expression | test.go:64:2:64:31 | ... := ...[0] : pointer type | test.go:65:16:65:41 | index expression | Cross-site scripting vulnerability due to $@. | test.go:64:2:64:31 | ... := ...[0] | user-provided value | test.go:0:0:0:0 | test.go | |
+| test.go:75:20:75:25 | buffer | test.go:70:2:70:31 | ... := ...[0] : pointer type | test.go:75:20:75:25 | buffer | Cross-site scripting vulnerability due to $@. | test.go:70:2:70:31 | ... := ...[0] | user-provided value | test.go:0:0:0:0 | test.go | |
+| test.go:81:16:81:24 | selection of Value | test.go:80:2:80:32 | ... := ...[0] : pointer type | test.go:81:16:81:24 | selection of Value | Cross-site scripting vulnerability due to $@. | test.go:80:2:80:32 | ... := ...[0] | user-provided value | test.go:0:0:0:0 | test.go | |
+| test.go:87:16:87:31 | selection of Value | test.go:86:13:86:25 | call to Cookies : slice type | test.go:87:16:87:31 | selection of Value | Cross-site scripting vulnerability due to $@. | test.go:86:13:86:25 | call to Cookies | user-provided value | test.go:0:0:0:0 | test.go | |
+| test.go:98:16:98:21 | selection of s | test.go:97:11:97:15 | &... : pointer type | test.go:98:16:98:21 | selection of s | Cross-site scripting vulnerability due to $@. | test.go:97:11:97:15 | &... | user-provided value | test.go:0:0:0:0 | test.go | |
+| test.go:112:16:112:42 | type assertion | test.go:111:21:111:42 | call to Param : string | test.go:112:16:112:42 | type assertion | Cross-site scripting vulnerability due to $@. | test.go:111:21:111:42 | call to Param | user-provided value | test.go:0:0:0:0 | test.go | |
+| test.go:123:16:123:20 | param | test.go:122:11:122:32 | call to Param : string | test.go:123:16:123:20 | param | Cross-site scripting vulnerability due to $@. | test.go:122:11:122:32 | call to Param | user-provided value | test.go:0:0:0:0 | test.go | |
+| test.go:129:20:129:32 | type conversion | test.go:128:11:128:32 | call to Param : string | test.go:129:20:129:32 | type conversion | Cross-site scripting vulnerability due to $@. | test.go:128:11:128:32 | call to Param | user-provided value | test.go:0:0:0:0 | test.go | |
+| test.go:135:29:135:41 | type conversion | test.go:134:11:134:32 | call to Param : string | test.go:135:29:135:41 | type conversion | Cross-site scripting vulnerability due to $@. | test.go:134:11:134:32 | call to Param | user-provided value | test.go:0:0:0:0 | test.go | |
+| test.go:148:31:148:36 | reader | test.go:146:11:146:32 | call to Param : string | test.go:148:31:148:36 | reader | Cross-site scripting vulnerability due to $@. | test.go:146:11:146:32 | call to Param | user-provided value | test.go:0:0:0:0 | test.go | |
+| test.go:163:23:163:35 | type conversion | test.go:162:11:162:32 | call to Param : string | test.go:163:23:163:35 | type conversion | Cross-site scripting vulnerability due to $@. | test.go:162:11:162:32 | call to Param | user-provided value | test.go:0:0:0:0 | test.go | |
diff --git a/ql/test/library-tests/semmle/go/frameworks/Revel/EndToEnd.go b/ql/test/library-tests/semmle/go/frameworks/Revel/EndToEnd.go
index 5e275412b73..05cac01673f 100644
--- a/ql/test/library-tests/semmle/go/frameworks/Revel/EndToEnd.go
+++ b/ql/test/library-tests/semmle/go/frameworks/Revel/EndToEnd.go
@@ -27,69 +27,69 @@ type MyRoute struct {
func (c MyRoute) Handler1() revel.Result {
// GOOD:Â the Render function is likely to properly escape the user-controlled parameter.
- return c.Render("someviewparam", c.Params.Form.Get("someField"))
+ return c.Render("someviewparam", c.Params.Form.Get("someField")) // $source=selection of Params
}
func (c MyRoute) Handler2() revel.Result {
// BAD:Â the RenderBinary function copies an `io.Reader` to the user's browser.
buf := &bytes.Buffer{}
- buf.WriteString(c.Params.Form.Get("someField"))
- return c.RenderBinary(buf, "index.html", revel.Inline, time.Now())
+ buf.WriteString(c.Params.Form.Get("someField")) // $source=selection of Params
+ return c.RenderBinary(buf, "index.html", revel.Inline, time.Now()) // $responsebody=buf
}
func (c MyRoute) Handler3() revel.Result {
// GOOD:Â the RenderBinary function copies an `io.Reader` to the user's browser, but the filename
// means it will be given a safe content-type.
buf := &bytes.Buffer{}
- buf.WriteString(c.Params.Form.Get("someField"))
- return c.RenderBinary(buf, "index.txt", revel.Inline, time.Now())
+ buf.WriteString(c.Params.Form.Get("someField")) // $source=selection of Params
+ return c.RenderBinary(buf, "index.txt", revel.Inline, time.Now()) // $responsebody=buf
}
func (c MyRoute) Handler4() revel.Result {
// GOOD: the RenderError function either uses an HTML template with probable escaping,
// or it uses content-type text/plain.
- err := errors.New(c.Params.Form.Get("someField"))
- return c.RenderError(err)
+ err := errors.New(c.Params.Form.Get("someField")) // $source=selection of Params
+ return c.RenderError(err) // $responsebody=err
}
func (c MyRoute) Handler5() revel.Result {
// BAD: returning an arbitrary file (but this is detected at the os.Open call, not
// due to modelling Revel)
- f, _ := os.Open(c.Params.Form.Get("someField"))
+ f, _ := os.Open(c.Params.Form.Get("someField")) // $source=selection of Params
return c.RenderFile(f, revel.Inline)
}
func (c MyRoute) Handler6() revel.Result {
// BAD: returning an arbitrary file (detected as a user-controlled file-op, not XSS)
- return c.RenderFileName(c.Params.Form.Get("someField"), revel.Inline)
+ return c.RenderFileName(c.Params.Form.Get("someField"), revel.Inline) // $source=selection of Params
}
func (c MyRoute) Handler7() revel.Result {
// BAD: straightforward XSS
- return c.RenderHTML(c.Params.Form.Get("someField"))
+ return c.RenderHTML(c.Params.Form.Get("someField")) // $responsebody=call to Get $source=selection of Params
}
func (c MyRoute) Handler8() revel.Result {
// GOOD: uses JSON content-type
- return c.RenderJSON(c.Params.Form.Get("someField"))
+ return c.RenderJSON(c.Params.Form.Get("someField")) // $responsebody=call to Get $source=selection of Params
}
func (c MyRoute) Handler9() revel.Result {
// GOOD: uses Javascript content-type
- return c.RenderJSONP("callback", c.Params.Form.Get("someField"))
+ return c.RenderJSONP("callback", c.Params.Form.Get("someField")) // $responsebody=call to Get $source=selection of Params
}
func (c MyRoute) Handler10() revel.Result {
// GOOD: uses text content-type
- return c.RenderText(c.Params.Form.Get("someField"))
+ return c.RenderText(c.Params.Form.Get("someField")) // $responsebody=call to Get $source=selection of Params
}
func (c MyRoute) Handler11() revel.Result {
// GOOD: uses xml content-type
- return c.RenderXML(c.Params.Form.Get("someField"))
+ return c.RenderXML(c.Params.Form.Get("someField")) // $responsebody=call to Get $source=selection of Params
}
func (c MyRoute) Handler12() revel.Result {
// BAD: open redirect
- return c.Redirect(c.Params.Form.Get("someField"))
+ return c.Redirect(c.Params.Form.Get("someField")) // $source=selection of Params
}
diff --git a/ql/test/library-tests/semmle/go/frameworks/Revel/ReflectedXss.expected b/ql/test/library-tests/semmle/go/frameworks/Revel/ReflectedXss.expected
index e6b6e77a9c9..92839e115e9 100644
--- a/ql/test/library-tests/semmle/go/frameworks/Revel/ReflectedXss.expected
+++ b/ql/test/library-tests/semmle/go/frameworks/Revel/ReflectedXss.expected
@@ -11,6 +11,24 @@ edges
| EndToEnd.go:69:22:69:29 | selection of Params : pointer type | EndToEnd.go:69:22:69:29 | implicit dereference : Params |
| EndToEnd.go:69:22:69:29 | selection of Params : pointer type | EndToEnd.go:69:22:69:29 | selection of Params : pointer type |
| EndToEnd.go:69:22:69:29 | selection of Params : pointer type | EndToEnd.go:69:22:69:51 | call to Get |
+| Revel.go:70:22:70:29 | implicit dereference : Params | Revel.go:70:22:70:29 | implicit dereference : Params |
+| Revel.go:70:22:70:29 | implicit dereference : Params | Revel.go:70:22:70:29 | selection of Params : pointer type |
+| Revel.go:70:22:70:29 | implicit dereference : Params | Revel.go:70:22:70:35 | selection of Query |
+| Revel.go:70:22:70:29 | selection of Params : pointer type | Revel.go:70:22:70:29 | implicit dereference : Params |
+| Revel.go:70:22:70:29 | selection of Params : pointer type | Revel.go:70:22:70:29 | selection of Params : pointer type |
+| Revel.go:70:22:70:29 | selection of Params : pointer type | Revel.go:70:22:70:35 | selection of Query |
+| examples/booking/app/init.go:36:44:36:48 | implicit dereference : URL | examples/booking/app/init.go:36:44:36:48 | implicit dereference : URL |
+| examples/booking/app/init.go:36:44:36:48 | implicit dereference : URL | examples/booking/app/init.go:36:44:36:48 | selection of URL : pointer type |
+| examples/booking/app/init.go:36:44:36:48 | implicit dereference : URL | examples/booking/app/init.go:36:44:36:53 | selection of Path |
+| examples/booking/app/init.go:36:44:36:48 | selection of URL : pointer type | examples/booking/app/init.go:36:44:36:48 | implicit dereference : URL |
+| examples/booking/app/init.go:36:44:36:48 | selection of URL : pointer type | examples/booking/app/init.go:36:44:36:48 | selection of URL : pointer type |
+| examples/booking/app/init.go:36:44:36:48 | selection of URL : pointer type | examples/booking/app/init.go:36:44:36:53 | selection of Path |
+| examples/booking/app/init.go:40:49:40:53 | implicit dereference : URL | examples/booking/app/init.go:40:49:40:53 | implicit dereference : URL |
+| examples/booking/app/init.go:40:49:40:53 | implicit dereference : URL | examples/booking/app/init.go:40:49:40:53 | selection of URL : pointer type |
+| examples/booking/app/init.go:40:49:40:53 | implicit dereference : URL | examples/booking/app/init.go:40:49:40:58 | selection of Path |
+| examples/booking/app/init.go:40:49:40:53 | selection of URL : pointer type | examples/booking/app/init.go:40:49:40:53 | implicit dereference : URL |
+| examples/booking/app/init.go:40:49:40:53 | selection of URL : pointer type | examples/booking/app/init.go:40:49:40:53 | selection of URL : pointer type |
+| examples/booking/app/init.go:40:49:40:53 | selection of URL : pointer type | examples/booking/app/init.go:40:49:40:58 | selection of Path |
nodes
| EndToEnd.go:36:18:36:25 | implicit dereference : Params | semmle.label | implicit dereference : Params |
| EndToEnd.go:36:18:36:25 | selection of Params : pointer type | semmle.label | selection of Params : pointer type |
@@ -18,6 +36,18 @@ nodes
| EndToEnd.go:69:22:69:29 | implicit dereference : Params | semmle.label | implicit dereference : Params |
| EndToEnd.go:69:22:69:29 | selection of Params : pointer type | semmle.label | selection of Params : pointer type |
| EndToEnd.go:69:22:69:51 | call to Get | semmle.label | call to Get |
+| Revel.go:70:22:70:29 | implicit dereference : Params | semmle.label | implicit dereference : Params |
+| Revel.go:70:22:70:29 | selection of Params : pointer type | semmle.label | selection of Params : pointer type |
+| Revel.go:70:22:70:35 | selection of Query | semmle.label | selection of Query |
+| examples/booking/app/init.go:36:44:36:48 | implicit dereference : URL | semmle.label | implicit dereference : URL |
+| examples/booking/app/init.go:36:44:36:48 | selection of URL : pointer type | semmle.label | selection of URL : pointer type |
+| examples/booking/app/init.go:36:44:36:53 | selection of Path | semmle.label | selection of Path |
+| examples/booking/app/init.go:40:49:40:53 | implicit dereference : URL | semmle.label | implicit dereference : URL |
+| examples/booking/app/init.go:40:49:40:53 | selection of URL : pointer type | semmle.label | selection of URL : pointer type |
+| examples/booking/app/init.go:40:49:40:58 | selection of Path | semmle.label | selection of Path |
#select
-| EndToEnd.go:37:24:37:26 | buf | EndToEnd.go:36:18:36:25 | selection of Params : pointer type | EndToEnd.go:37:24:37:26 | buf | Cross-site scripting vulnerability due to $@. | EndToEnd.go:36:18:36:25 | selection of Params | user-provided value |
-| EndToEnd.go:69:22:69:51 | call to Get | EndToEnd.go:69:22:69:29 | selection of Params : pointer type | EndToEnd.go:69:22:69:51 | call to Get | Cross-site scripting vulnerability due to $@. | EndToEnd.go:69:22:69:29 | selection of Params | user-provided value |
+| EndToEnd.go:37:24:37:26 | buf | EndToEnd.go:36:18:36:25 | selection of Params : pointer type | EndToEnd.go:37:24:37:26 | buf | Cross-site scripting vulnerability due to $@. | EndToEnd.go:36:18:36:25 | selection of Params | user-provided value | EndToEnd.go:0:0:0:0 | EndToEnd.go | |
+| EndToEnd.go:69:22:69:51 | call to Get | EndToEnd.go:69:22:69:29 | selection of Params : pointer type | EndToEnd.go:69:22:69:51 | call to Get | Cross-site scripting vulnerability due to $@. | EndToEnd.go:69:22:69:29 | selection of Params | user-provided value | EndToEnd.go:0:0:0:0 | EndToEnd.go | |
+| Revel.go:70:22:70:35 | selection of Query | Revel.go:70:22:70:29 | selection of Params : pointer type | Revel.go:70:22:70:35 | selection of Query | Cross-site scripting vulnerability due to $@. This template argument is instantiated raw $@. | Revel.go:70:22:70:29 | selection of Params | user-provided value | views/myAppController/rawRead.html:1:1:2:9 | {{raw .Foo}}\n{{.Bar}}\n | here |
+| examples/booking/app/init.go:36:44:36:53 | selection of Path | examples/booking/app/init.go:36:44:36:48 | selection of URL : pointer type | examples/booking/app/init.go:36:44:36:53 | selection of Path | Cross-site scripting vulnerability due to $@. | examples/booking/app/init.go:36:44:36:48 | selection of URL | user-provided value | examples/booking/app/init.go:0:0:0:0 | examples/booking/app/init.go | |
+| examples/booking/app/init.go:40:49:40:58 | selection of Path | examples/booking/app/init.go:40:49:40:53 | selection of URL : pointer type | examples/booking/app/init.go:40:49:40:58 | selection of Path | Cross-site scripting vulnerability due to $@. | examples/booking/app/init.go:40:49:40:53 | selection of URL | user-provided value | examples/booking/app/init.go:0:0:0:0 | examples/booking/app/init.go | |
diff --git a/ql/test/library-tests/semmle/go/frameworks/Revel/Revel.go b/ql/test/library-tests/semmle/go/frameworks/Revel/Revel.go
index 7391b2506f2..be4c08e6725 100644
--- a/ql/test/library-tests/semmle/go/frameworks/Revel/Revel.go
+++ b/ql/test/library-tests/semmle/go/frameworks/Revel/Revel.go
@@ -4,8 +4,6 @@ package main
import (
"io/ioutil"
- "mime/multipart"
- "net/url"
"github.com/revel/revel"
)
@@ -22,115 +20,111 @@ type person struct {
Address string `form:"address"`
}
-func useString(val string) {}
-
-func useFiles(val *multipart.FileHeader) {}
-
-func useJSON(val []byte) {}
-
-func useURLValues(v url.Values) {
- useString(v["key"][0])
- useString(v.Get("key"))
-}
-
-func usePerson(p person) {}
+func sink(_ ...interface{}) {}
func (c myAppController) accessingParamsDirectlyIsUnsafe() {
- useString(c.Params.Get("key")) // NOT OK
- useURLValues(c.Params.Values) // NOT OK
+ sink(c.Params.Get("key"))
+ sink(c.Params.Values) // $source=selection of Params
val4 := ""
- c.Params.Bind(&val4, "key") // NOT OK
- useString(val4)
+ c.Params.Bind(&val4, "key") // $source=selection of Params
+ sink(val4)
- useString(c.Request.FormValue("key")) // NOT OK
+ sink(c.Request.FormValue("key"))
}
func (c myAppController) accessingFixedIsSafe(mainRouter *revel.Router) {
- useURLValues(c.Params.Fixed) // OK
- useString(mainRouter.Route(c.Request).FixedParams[0]) // OK
+ sink(c.Params.Fixed.Get("key")) // $noflow
+ sink(mainRouter.Route(c.Request).FixedParams[0]) // $noflow
}
func (c myAppController) accessingRouteIsUnsafe(mainRouter *revel.Router) {
- useURLValues(c.Params.Route) // NOT OK
- useURLValues(mainRouter.Route(c.Request).Params) // NOT OK
+ sink(c.Params.Route["key"][0])
+ sink(mainRouter.Route(c.Request).Params["key"][0])
}
func (c myAppController) accessingParamsQueryIsUnsafe() {
- useURLValues(c.Params.Query) // NOT OK
+ sink(c.Params.Query["key"][0])
}
func (c myAppController) accessingParamsFormIsUnsafe() {
- useURLValues(c.Params.Form) // NOT OK
- useString(c.Request.PostFormValue("key")) // NOT OK
+ sink(c.Params.Form["key"][0])
+ sink(c.Request.PostFormValue("key"))
}
func (c myAppController) accessingParamsFilesIsUnsafe() {
- useFiles(c.Params.Files["key"][0]) // NOT OK
+ sink(c.Params.Files["key"][0])
}
func (c myAppController) accessingParamsJSONIsUnsafe() {
- useJSON(c.Params.JSON) // NOT OK
+ sink(c.Params.JSON)
var val2 map[string]interface{}
- c.Params.BindJSON(&val2) // NOT OK
- useString(val2["name"].(string))
+ c.Params.BindJSON(&val2)
+ sink(val2["name"].(string))
+}
+
+func (c myAppController) rawRead() { // $responsebody=argument corresponding to c
+ c.ViewArgs["Foo"] = "
raw HTML
" // $responsebody="
raw HTML
"
+ c.ViewArgs["Bar"] = "
not raw HTML
"
+ c.ViewArgs["Foo"] = c.Params.Query // $responsebody=selection of Query
+ c.Render()
}
func accessingRequestDirectlyIsUnsafe(c *revel.Controller) {
- useURLValues(c.Request.GetQuery()) // NOT OK
- useURLValues(c.Request.Form) // NOT OK
- useURLValues(c.Request.MultipartForm.Value) // NOT OK
- useString(c.Request.ContentType) // NOT OK
- useString(c.Request.AcceptLanguages[0].Language) // NOT OK
- useString(c.Request.Locale) // NOT OK
+ sink(c.Request.GetQuery()["key"][0])
+ sink(c.Request.Form["key"][0])
+ sink(c.Request.MultipartForm.Value["key"][0])
+ sink(c.Request.ContentType)
+ sink(c.Request.AcceptLanguages[0].Language)
+ sink(c.Request.Locale)
- form, _ := c.Request.GetForm() // NOT OK
- useURLValues(form)
+ form, _ := c.Request.GetForm()
+ sink(form["key"][0])
- smp1, _ := c.Request.GetMultipartForm() // NOT OK
- useURLValues(smp1.GetValues())
+ smp1, _ := c.Request.GetMultipartForm()
+ sink(smp1.GetValues()["key"][0])
- smp2, _ := c.Request.GetMultipartForm() // NOT OK
- useFiles(smp2.GetFiles()["key"][0])
+ smp2, _ := c.Request.GetMultipartForm()
+ sink(smp2.GetFiles()["key"][0])
- useFiles(c.Request.MultipartForm.File["key"][0]) // NOT OK
+ sink(c.Request.MultipartForm.File["key"][0])
- json, _ := ioutil.ReadAll(c.Request.GetBody()) // NOT OK
- useJSON(json)
+ json, _ := ioutil.ReadAll(c.Request.GetBody())
+ sink(json)
cookie, _ := c.Request.Cookie("abc")
- useString(cookie.GetValue()) // NOT OK
+ sink(cookie.GetValue())
- useString(c.Request.GetHttpHeader("headername")) // NOT OK
+ sink(c.Request.GetHttpHeader("headername"))
- useString(c.Request.GetRequestURI()) // NOT OK
+ sink(c.Request.GetRequestURI())
reader, _ := c.Request.MultipartReader()
part, _ := reader.NextPart()
partbody := make([]byte, 100)
part.Read(partbody)
- useString(string(partbody)) // NOT OK
+ sink(string(partbody))
- useString(c.Request.Referer()) // NOT OK
+ sink(c.Request.Referer())
- useString(c.Request.UserAgent()) // NOT OK
+ sink(c.Request.UserAgent())
}
func accessingServerRequest(c *revel.Controller) {
var message string
- c.Request.WebSocket.MessageReceive(&message) // NOT OK
- useString(message)
+ c.Request.WebSocket.MessageReceive(&message)
+ sink(message)
var p person
- c.Request.WebSocket.MessageReceiveJSON(&p) // NOT OK
- usePerson(p)
+ c.Request.WebSocket.MessageReceiveJSON(&p)
+ sink(p)
}
func accessingHeaders(c *revel.Controller) {
- tainted := c.Request.Header.Get("somekey") // NOT OK
- useString(tainted)
+ tainted := c.Request.Header.Get("somekey")
+ sink(tainted)
- tainted2 := c.Request.Header.GetAll("somekey") // NOT OK
- useString(tainted2[0])
+ tainted2 := c.Request.Header.GetAll("somekey")
+ sink(tainted2[0])
}
diff --git a/ql/test/library-tests/semmle/go/frameworks/Revel/TaintFlows.expected b/ql/test/library-tests/semmle/go/frameworks/Revel/TaintFlows.expected
deleted file mode 100644
index fe8b9115a71..00000000000
--- a/ql/test/library-tests/semmle/go/frameworks/Revel/TaintFlows.expected
+++ /dev/null
@@ -1,43 +0,0 @@
-| Revel.go:39:12:39:19 | selection of Params : pointer type | Revel.go:39:12:39:30 | call to Get | 39 |
-| Revel.go:40:15:40:22 | selection of Params : pointer type | Revel.go:32:12:32:22 | index expression | 40 |
-| Revel.go:40:15:40:22 | selection of Params : pointer type | Revel.go:33:12:33:23 | call to Get | 40 |
-| Revel.go:43:2:43:9 | selection of Params : pointer type | Revel.go:44:12:44:15 | val4 | 43 |
-| Revel.go:46:12:46:37 | call to FormValue | Revel.go:46:12:46:37 | call to FormValue | 46 |
-| Revel.go:55:15:55:22 | selection of Params : pointer type | Revel.go:32:12:32:22 | index expression | 55 |
-| Revel.go:55:15:55:22 | selection of Params : pointer type | Revel.go:33:12:33:23 | call to Get | 55 |
-| Revel.go:56:15:56:48 | selection of Params : map type | Revel.go:32:12:32:22 | index expression | 56 |
-| Revel.go:56:15:56:48 | selection of Params : map type | Revel.go:33:12:33:23 | call to Get | 56 |
-| Revel.go:60:15:60:22 | selection of Params : pointer type | Revel.go:32:12:32:22 | index expression | 60 |
-| Revel.go:60:15:60:22 | selection of Params : pointer type | Revel.go:33:12:33:23 | call to Get | 60 |
-| Revel.go:64:15:64:22 | selection of Params : pointer type | Revel.go:32:12:32:22 | index expression | 64 |
-| Revel.go:64:15:64:22 | selection of Params : pointer type | Revel.go:33:12:33:23 | call to Get | 64 |
-| Revel.go:65:12:65:41 | call to PostFormValue | Revel.go:65:12:65:41 | call to PostFormValue | 65 |
-| Revel.go:69:11:69:18 | selection of Params : pointer type | Revel.go:69:11:69:34 | index expression | 69 |
-| Revel.go:73:10:73:17 | selection of Params : pointer type | Revel.go:73:10:73:22 | selection of JSON | 73 |
-| Revel.go:76:2:76:9 | selection of Params : pointer type | Revel.go:77:12:77:32 | type assertion | 76 |
-| Revel.go:81:15:81:34 | call to GetQuery : Values | Revel.go:32:12:32:22 | index expression | 81 |
-| Revel.go:81:15:81:34 | call to GetQuery : Values | Revel.go:33:12:33:23 | call to Get | 81 |
-| Revel.go:82:15:82:28 | selection of Form : Values | Revel.go:32:12:32:22 | index expression | 82 |
-| Revel.go:82:15:82:28 | selection of Form : Values | Revel.go:33:12:33:23 | call to Get | 82 |
-| Revel.go:83:15:83:37 | selection of MultipartForm : pointer type | Revel.go:32:12:32:22 | index expression | 83 |
-| Revel.go:83:15:83:37 | selection of MultipartForm : pointer type | Revel.go:33:12:33:23 | call to Get | 83 |
-| Revel.go:84:12:84:32 | selection of ContentType | Revel.go:84:12:84:32 | selection of ContentType | 84 |
-| Revel.go:85:12:85:36 | selection of AcceptLanguages : AcceptLanguages | Revel.go:85:12:85:48 | selection of Language | 85 |
-| Revel.go:86:12:86:27 | selection of Locale | Revel.go:86:12:86:27 | selection of Locale | 86 |
-| Revel.go:88:13:88:31 | call to GetForm : tuple type | Revel.go:32:12:32:22 | index expression | 88 |
-| Revel.go:88:13:88:31 | call to GetForm : tuple type | Revel.go:33:12:33:23 | call to Get | 88 |
-| Revel.go:91:13:91:40 | call to GetMultipartForm : tuple type | Revel.go:32:12:32:22 | index expression | 91 |
-| Revel.go:91:13:91:40 | call to GetMultipartForm : tuple type | Revel.go:33:12:33:23 | call to Get | 91 |
-| Revel.go:94:13:94:40 | call to GetMultipartForm : tuple type | Revel.go:95:11:95:35 | index expression | 94 |
-| Revel.go:97:11:97:33 | selection of MultipartForm : pointer type | Revel.go:97:11:97:48 | index expression | 97 |
-| Revel.go:99:28:99:46 | call to GetBody : Reader | Revel.go:100:10:100:13 | json | 99 |
-| Revel.go:102:15:102:37 | call to Cookie : tuple type | Revel.go:103:12:103:28 | call to GetValue | 102 |
-| Revel.go:105:12:105:48 | call to GetHttpHeader | Revel.go:105:12:105:48 | call to GetHttpHeader | 105 |
-| Revel.go:107:12:107:36 | call to GetRequestURI | Revel.go:107:12:107:36 | call to GetRequestURI | 107 |
-| Revel.go:109:15:109:41 | call to MultipartReader : tuple type | Revel.go:113:12:113:27 | type conversion | 109 |
-| Revel.go:115:12:115:30 | call to Referer | Revel.go:115:12:115:30 | call to Referer | 115 |
-| Revel.go:117:12:117:32 | call to UserAgent | Revel.go:117:12:117:32 | call to UserAgent | 117 |
-| Revel.go:122:37:122:44 | &... : pointer type | Revel.go:123:12:123:18 | message | 122 |
-| Revel.go:126:41:126:42 | &... : pointer type | Revel.go:127:12:127:12 | p | 126 |
-| Revel.go:131:13:131:28 | selection of Header : pointer type | Revel.go:132:12:132:18 | tainted | 131 |
-| Revel.go:134:14:134:29 | selection of Header : pointer type | Revel.go:135:12:135:22 | index expression | 134 |
diff --git a/ql/test/library-tests/semmle/go/frameworks/Revel/TaintFlows.ql b/ql/test/library-tests/semmle/go/frameworks/Revel/TaintFlows.ql
deleted file mode 100644
index e41767f0a97..00000000000
--- a/ql/test/library-tests/semmle/go/frameworks/Revel/TaintFlows.ql
+++ /dev/null
@@ -1,19 +0,0 @@
-import go
-
-class SinkFunction extends Function {
- SinkFunction() { this.getName() = ["useFiles", "useJSON", "usePerson", "useString"] }
-}
-
-class TestConfig extends TaintTracking::Configuration {
- TestConfig() { this = "testconfig" }
-
- override predicate isSource(DataFlow::Node source) { source instanceof UntrustedFlowSource }
-
- override predicate isSink(DataFlow::Node sink) {
- sink = any(SinkFunction f).getACall().getAnArgument()
- }
-}
-
-from TaintTracking::Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink, int i
-where config.hasFlowPath(source, sink) and source.hasLocationInfo(_, i, _, _, _)
-select source, sink, i order by i
diff --git a/ql/test/library-tests/semmle/go/frameworks/Revel/examples/LICENSE b/ql/test/library-tests/semmle/go/frameworks/Revel/examples/LICENSE
new file mode 100644
index 00000000000..92246e3cc4b
--- /dev/null
+++ b/ql/test/library-tests/semmle/go/frameworks/Revel/examples/LICENSE
@@ -0,0 +1,20 @@
+The MIT License (MIT)
+
+Copyright (C) 2012-2018 The Revel Framework Authors.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/ql/test/library-tests/semmle/go/frameworks/Revel/examples/README.md b/ql/test/library-tests/semmle/go/frameworks/Revel/examples/README.md
new file mode 100644
index 00000000000..6f836ac7e00
--- /dev/null
+++ b/ql/test/library-tests/semmle/go/frameworks/Revel/examples/README.md
@@ -0,0 +1,3 @@
+Revel example adapted from [revel-examples](https://github.com/revel/revel-examples).
+
+See `LICENSE` for license information.
diff --git a/ql/test/library-tests/semmle/go/frameworks/Revel/examples/booking/app/controllers/app.go b/ql/test/library-tests/semmle/go/frameworks/Revel/examples/booking/app/controllers/app.go
new file mode 100644
index 00000000000..8a3f01a316d
--- /dev/null
+++ b/ql/test/library-tests/semmle/go/frameworks/Revel/examples/booking/app/controllers/app.go
@@ -0,0 +1,98 @@
+package controllers
+
+import (
+ "github.com/revel/revel"
+
+ "codeql-go-tests/frameworks/Revel/examples/booking/app/models"
+ "github.com/revel/modules/orm/gorp/app/controllers"
+)
+
+type Application struct {
+ gorpController.Controller
+}
+
+func (c Application) AddUser() revel.Result {
+ if user := c.connected(); user != nil {
+ c.ViewArgs["user"] = user
+ }
+ return nil
+}
+
+func (c Application) connected() *models.User {
+ if c.ViewArgs["user"] != nil {
+ return c.ViewArgs["user"].(*models.User)
+ }
+ if username, ok := c.Session["user"]; ok {
+ return c.getUser(username.(string))
+ }
+
+ return nil
+}
+
+func (c Application) getUser(username string) (user *models.User) {
+ user = &models.User{}
+ c.Session.GetInto("fulluser", user, false)
+ if user.Username == username {
+ return user
+ }
+
+ c.Session["fulluser"] = user
+ return
+}
+
+func (c Application) Index() revel.Result {
+ if c.connected() != nil {
+ return c.Redirect(nil)
+ }
+ c.Flash.Error("Please log in first")
+ return c.Render()
+}
+
+func (c Application) Register() revel.Result {
+ return c.Render()
+}
+
+func (c Application) SaveUser(user models.User, verifyPassword string) revel.Result {
+ c.Validation.Required(verifyPassword)
+ c.Validation.Required(verifyPassword == user.Password).
+ MessageKey("Password does not match")
+ user.Validate(c.Validation)
+
+ if c.Validation.HasErrors() {
+ c.Validation.Keep()
+ c.FlashParams()
+ return c.Redirect(nil)
+ }
+
+ c.Session["user"] = user.Username
+ c.Flash.Success("Welcome, " + user.Name)
+ return c.Redirect(nil)
+}
+
+func (c Application) Login(username, password string, remember bool) revel.Result {
+ user := c.getUser(username)
+ if user != nil {
+ var err error
+ if err == nil {
+ c.Session["user"] = username
+ if remember {
+ c.Session.SetDefaultExpiration()
+ } else {
+ c.Session.SetNoExpiration()
+ }
+ c.Flash.Success("Welcome, " + username)
+ return c.Redirect(nil)
+ }
+ }
+
+ c.Flash.Out["username"] = username
+ c.Flash.Error("Login failed")
+ return c.Redirect(nil)
+}
+
+func (c Application) Logout() revel.Result {
+ for k := range c.Session {
+ delete(c.Session, k)
+ }
+ return c.Redirect(nil)
+}
diff --git a/ql/test/library-tests/semmle/go/frameworks/Revel/examples/booking/app/controllers/hotels.go b/ql/test/library-tests/semmle/go/frameworks/Revel/examples/booking/app/controllers/hotels.go
new file mode 100644
index 00000000000..b4752fa8fc9
--- /dev/null
+++ b/ql/test/library-tests/semmle/go/frameworks/Revel/examples/booking/app/controllers/hotels.go
@@ -0,0 +1,196 @@
+//go:generate swagger generate spec -o swagger.json
+
+// Package classification Swagger Hotel Example.
+// Swagger Hotel Example
+//
+//
+//
+// Schemes: https
+// Host: hotel.example.revelframework.com
+// BasePath: /
+// Version: 1.0.0
+// License: MIT http://opensource.org/licenses/MIT
+// Contact: Name https://www.somewhere.com
+//
+// Consumes:
+// - application/json
+// - application/x-www-form-urlencoded
+//
+// Produces:
+// - text/html
+//
+//
+//
+//
+// swagger:meta
+
+package controllers
+
+import (
+ "fmt"
+ "strings"
+
+ "codeql-go-tests/frameworks/Revel/examples/booking/app/models"
+ "github.com/revel/revel"
+)
+
+type Hotels struct {
+ Application
+}
+
+func (c Hotels) checkUser() revel.Result {
+ if user := c.connected(); user == nil {
+ c.Flash.Error("Please log in first")
+ return c.Redirect(nil)
+ }
+ return nil
+}
+
+func (c Hotels) Index() revel.Result {
+ c.Log.Info("Fetching index")
+ var bookings []*models.Booking
+
+ return c.Render(bookings)
+}
+
+// swagger:route GET /hotels/ListJson enter demo
+//
+// Enter Demo
+//
+//
+// Consumes:
+// - application/x-www-form-urlencoded
+//
+// Produces:
+// - text/html
+//
+// Schemes: https
+//
+//
+// Responses:
+// 200: Success
+// 401: Invalid User
+
+// swagger:operation GET /demo demo
+//
+// Enter Demo
+//
+//
+// ---
+// produces:
+// - text/html
+// parameters:
+// - name: user
+// in: formData
+// description: user
+// required: true
+// type: string
+// - name: demo
+// in: formData
+// description: demo
+// required: true
+// type: string
+// responses:
+// '200':
+// description: Success
+// '401':
+// description: Invalid User
+func (c Hotels) ListJson(search string, size, page uint64) revel.Result {
+ if page == 0 {
+ page = 1
+ }
+ nextPage := page + 1
+ search = strings.TrimSpace(search)
+
+ var hotels []*models.Hotel
+
+ return c.RenderJSON(map[string]interface{}{"hotels": hotels, "search": search, "size": size, "page": page, "nextPage": nextPage}) // $responsebody=map literal
+}
+func (c Hotels) List(search string, size, page uint64) revel.Result {
+ if page == 0 {
+ page = 1
+ }
+ nextPage := page + 1
+ search = strings.TrimSpace(search)
+
+ var hotels []*models.Hotel
+
+ return c.Render(hotels, search, size, page, nextPage)
+}
+
+func (c Hotels) loadHotelById(id int) *models.Hotel {
+ var h interface{}
+ if h == nil {
+ return nil
+ }
+ return h.(*models.Hotel)
+}
+
+func (c Hotels) Show(id int) revel.Result {
+ hotel := c.loadHotelById(id)
+ if hotel == nil {
+ return c.NotFound("Hotel %d does not exist", id)
+ }
+ title := hotel.Name
+ return c.Render(title, hotel)
+}
+
+func (c Hotels) Settings() revel.Result {
+ return c.Render()
+}
+
+func (c Hotels) SaveSettings(password, verifyPassword string) revel.Result {
+ models.ValidatePassword(c.Validation, password)
+ c.Validation.Required(verifyPassword).
+ Message("Please verify your password")
+ c.Validation.Required(verifyPassword == password).
+ Message("Your password doesn't match")
+ if c.Validation.HasErrors() {
+ c.Validation.Keep()
+ return c.Redirect(nil)
+ }
+
+ c.Flash.Success("Password updated")
+ return c.Redirect(nil)
+}
+
+func (c Hotels) ConfirmBooking(id int, booking models.Booking) revel.Result {
+ hotel := c.loadHotelById(id) // $responsebody=call to loadHotelById
+ if hotel == nil {
+ return c.NotFound("Hotel %d does not exist", id)
+ }
+
+ title := fmt.Sprintf("Confirm %s booking", hotel.Name)
+ booking.Hotel = hotel
+ booking.User = c.connected()
+ booking.Validate(c.Validation)
+
+ if c.Validation.HasErrors() || c.Params.Get("revise") != "" {
+ c.Validation.Keep()
+ c.FlashParams()
+ return c.Redirect(nil)
+ }
+
+ if c.Params.Get("confirm") != "" {
+ c.Flash.Success("Thank you, %s, your confirmation number for %s is %d",
+ booking.User.Name, hotel.Name, booking.BookingId)
+ return c.Redirect(nil)
+ }
+
+ return c.Render(title, hotel, booking)
+}
+
+func (c Hotels) CancelBooking(id int) revel.Result {
+ c.Flash.Success(fmt.Sprintln("Booking cancelled for confirmation number", id))
+ return c.Redirect(nil)
+}
+
+func (c Hotels) Book(id int) revel.Result {
+ hotel := c.loadHotelById(id)
+ if hotel == nil {
+ return c.NotFound("Hotel %d does not exist", id)
+ }
+
+ title := "Book " + hotel.Name
+ return c.Render(title, hotel)
+}
diff --git a/ql/test/library-tests/semmle/go/frameworks/Revel/examples/booking/app/controllers/init.go b/ql/test/library-tests/semmle/go/frameworks/Revel/examples/booking/app/controllers/init.go
new file mode 100644
index 00000000000..c119d9664e9
--- /dev/null
+++ b/ql/test/library-tests/semmle/go/frameworks/Revel/examples/booking/app/controllers/init.go
@@ -0,0 +1,8 @@
+package controllers
+
+import "github.com/revel/revel"
+
+func init() {
+ revel.InterceptMethod(Application.AddUser, revel.BEFORE)
+ revel.InterceptMethod(Hotels.checkUser, revel.BEFORE)
+}
diff --git a/ql/test/library-tests/semmle/go/frameworks/Revel/examples/booking/app/init.go b/ql/test/library-tests/semmle/go/frameworks/Revel/examples/booking/app/init.go
new file mode 100644
index 00000000000..ef5b74fa507
--- /dev/null
+++ b/ql/test/library-tests/semmle/go/frameworks/Revel/examples/booking/app/init.go
@@ -0,0 +1,57 @@
+package app
+
+import (
+ "fmt"
+ "github.com/revel/revel"
+ "github.com/revel/revel/logger"
+ "net/http"
+ "os"
+)
+
+func init() {
+ // Filters is the default set of global filters.
+ revel.Filters = []revel.Filter{
+ revel.PanicFilter, // Recover from panics and display an error page instead.
+ revel.RouterFilter, // Use the routing table to select the right Action
+ revel.FilterConfiguringFilter, // A hook for adding or removing per-Action filters.
+ revel.ParamsFilter, // Parse parameters into Controller.Params.
+ revel.SessionFilter, // Restore and write the session cookie.
+ revel.FlashFilter, // Restore and write the flash cookie.
+ revel.ValidationFilter, // Restore kept validation errors and save new ones from cookie.
+ revel.I18nFilter, // Resolve the requested language
+ HeaderFilter, // Add some security based headers
+ revel.InterceptorFilter, // Run interceptors around the action.
+ revel.CompressFilter, // Compress the result.
+ revel.ActionInvoker, // Invoke the action.
+ }
+ logger.LogFunctionMap["stdoutjson"] =
+ func(c *logger.CompositeMultiHandler, options *logger.LogOptions) {
+ // Set the json formatter to os.Stdout, replace any existing handlers for the level specified
+ c.SetJson(os.Stdout, options)
+ }
+ revel.AddInitEventHandler(func(event revel.Event, i interface{}) revel.EventResponse {
+ switch event {
+ case revel.ENGINE_BEFORE_INITIALIZED:
+ revel.AddHTTPMux("/this/is/a/test", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ fmt.Fprintln(w, "Hi there, it worked", r.URL.Path) // $responsebody=selection of Path $responsebody="Hi there, it worked"
+ w.WriteHeader(200)
+ }))
+ revel.AddHTTPMux("/this/is/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ fmt.Fprintln(w, "Hi there, shorter prefix", r.URL.Path) // $responsebody=selection of Path $responsebody="Hi there, shorter prefix"
+ w.WriteHeader(200)
+ }))
+ }
+ return 0
+ })
+
+ revel.OnAppStart(func() {}, 5)
+}
+
+var HeaderFilter = func(c *revel.Controller, fc []revel.Filter) {
+ // Add some common security headers
+ c.Response.Out.Header().Add("X-Frame-Options", "SAMEORIGIN")
+ c.Response.Out.Header().Add("X-XSS-Protection", "1; mode=block")
+ c.Response.Out.Header().Add("X-Content-Type-Options", "nosniff")
+
+ fc[0](c, fc[1:]) // Execute the next filter stage.
+}
diff --git a/ql/test/library-tests/semmle/go/frameworks/Revel/examples/booking/app/models/booking.go b/ql/test/library-tests/semmle/go/frameworks/Revel/examples/booking/app/models/booking.go
new file mode 100644
index 00000000000..6c162f2f28d
--- /dev/null
+++ b/ql/test/library-tests/semmle/go/frameworks/Revel/examples/booking/app/models/booking.go
@@ -0,0 +1,100 @@
+package models
+
+import (
+ "fmt"
+ "github.com/revel/revel"
+ "regexp"
+ "time"
+)
+
+type Booking struct {
+ BookingId int
+ UserId int
+ HotelId int
+ CheckInStr string
+ CheckOutStr string
+ CardNumber string
+ NameOnCard string
+ CardExpMonth int
+ CardExpYear int
+ Smoking bool
+ Beds int
+
+ // Transient
+ CheckInDate time.Time
+ CheckOutDate time.Time
+ User *User
+ Hotel *Hotel
+}
+
+// TODO: Make an interface for Validate() and then validation can pass in the
+// key prefix ("booking.")
+func (booking Booking) Validate(v *revel.Validation) {
+ v.Required(booking.User)
+ v.Required(booking.Hotel)
+ v.Required(booking.CheckInDate)
+ v.Required(booking.CheckOutDate)
+
+ v.Match(booking.CardNumber, regexp.MustCompile(`\d{16}`)).
+ Message("Credit card number must be numeric and 16 digits")
+
+ v.Check(booking.NameOnCard,
+ revel.Required{},
+ revel.MinSize{3},
+ revel.MaxSize{70},
+ )
+}
+
+func (b Booking) Total() int {
+ return b.Hotel.Price * b.Nights()
+}
+
+func (b Booking) Nights() int {
+ return int((b.CheckOutDate.Unix() - b.CheckInDate.Unix()) / 60 / 60 / 24)
+}
+
+const (
+ DATE_FORMAT = "Jan _2, 2006"
+ SQL_DATE_FORMAT = "2006-01-02"
+)
+
+func (b Booking) Description() string {
+ if b.Hotel == nil {
+ return ""
+ }
+
+ return fmt.Sprintf("%s, %s to %s",
+ b.Hotel.Name,
+ b.CheckInDate.Format(DATE_FORMAT),
+ b.CheckOutDate.Format(DATE_FORMAT))
+}
+
+func (b Booking) String() string {
+ return fmt.Sprintf("Booking(%s,%s)", b.User, b.Hotel.Name)
+}
+
+// These hooks work around two things:
+// - Gorp's lack of support for loading relations automatically.
+// - Sqlite's lack of support for datetimes.
+
+func (b *Booking) PreInsert(_ interface{}) error {
+ b.UserId = b.User.UserId
+ b.HotelId = b.Hotel.HotelId
+ b.CheckInStr = b.CheckInDate.Format(SQL_DATE_FORMAT)
+ b.CheckOutStr = b.CheckOutDate.Format(SQL_DATE_FORMAT)
+ return nil
+}
+
+func (b *Booking) PostGet(exe interface{}) error {
+ var (
+ err error
+ )
+
+ if b.CheckInDate, err = time.Parse(SQL_DATE_FORMAT, b.CheckInStr); err != nil {
+ return fmt.Errorf("Error parsing check in date '%s' %s:", b.CheckInStr, err.Error())
+ }
+ if b.CheckOutDate, err = time.Parse(SQL_DATE_FORMAT, b.CheckOutStr); err != nil {
+ return fmt.Errorf("Error parsing check out date '%s' %s:", b.CheckOutStr, err.Error())
+ }
+ return nil
+}
diff --git a/ql/test/library-tests/semmle/go/frameworks/Revel/examples/booking/app/models/hotel.go b/ql/test/library-tests/semmle/go/frameworks/Revel/examples/booking/app/models/hotel.go
new file mode 100644
index 00000000000..448cb813fa5
--- /dev/null
+++ b/ql/test/library-tests/semmle/go/frameworks/Revel/examples/booking/app/models/hotel.go
@@ -0,0 +1,45 @@
+package models
+
+import (
+ "github.com/revel/revel"
+)
+
+type Hotel struct {
+ HotelId int
+ Name, Address string
+ City, State, Zip string
+ Country string
+ Price int
+}
+
+func (hotel *Hotel) Validate(v *revel.Validation) {
+ v.Check(hotel.Name,
+ revel.Required{},
+ revel.MaxSize{50},
+ )
+
+ v.MaxSize(hotel.Address, 100)
+
+ v.Check(hotel.City,
+ revel.Required{},
+ revel.MaxSize{40},
+ )
+
+ v.Check(hotel.State,
+ revel.Required{},
+ revel.MaxSize{6},
+ revel.MinSize{2},
+ )
+
+ v.Check(hotel.Zip,
+ revel.Required{},
+ revel.MaxSize{6},
+ revel.MinSize{5},
+ )
+
+ v.Check(hotel.Country,
+ revel.Required{},
+ revel.MaxSize{40},
+ revel.MinSize{2},
+ )
+}
diff --git a/ql/test/library-tests/semmle/go/frameworks/Revel/examples/booking/app/models/user.go b/ql/test/library-tests/semmle/go/frameworks/Revel/examples/booking/app/models/user.go
new file mode 100644
index 00000000000..8dcfc22118f
--- /dev/null
+++ b/ql/test/library-tests/semmle/go/frameworks/Revel/examples/booking/app/models/user.go
@@ -0,0 +1,45 @@
+package models
+
+import (
+ "fmt"
+ "github.com/revel/revel"
+ "regexp"
+)
+
+type User struct {
+ UserId int
+ Name string
+ Username, Password string
+ HashedPassword []byte
+}
+
+func (u *User) String() string {
+ return fmt.Sprintf("User(%s)", u.Username)
+}
+
+var userRegex = regexp.MustCompile("^\\w*$")
+
+func (user *User) Validate(v *revel.Validation) {
+ v.Check(user.Username,
+ revel.Required{},
+ revel.MaxSize{15},
+ revel.MinSize{4},
+ revel.Match{userRegex},
+ )
+
+ ValidatePassword(v, user.Password).
+ Key("user.Password")
+
+ v.Check(user.Name,
+ revel.Required{},
+ revel.MaxSize{100},
+ )
+}
+
+func ValidatePassword(v *revel.Validation, password string) *revel.ValidationResult {
+ return v.Check(password,
+ revel.Required{},
+ revel.MaxSize{15},
+ revel.MinSize{5},
+ )
+}
diff --git a/ql/test/library-tests/semmle/go/frameworks/Revel/examples/booking/app/views/Hotels/book.html b/ql/test/library-tests/semmle/go/frameworks/Revel/examples/booking/app/views/Hotels/book.html
new file mode 100644
index 00000000000..e6aa371adc2
--- /dev/null
+++ b/ql/test/library-tests/semmle/go/frameworks/Revel/examples/booking/app/views/Hotels/book.html
@@ -0,0 +1,114 @@
+{{append . "moreStyles" "ui-lightness/jquery-ui-1.7.2.custom.css"}}
+{{append . "moreScripts" "js/jquery-ui-1.7.2.custom.min.js"}}
+{{template "header.html" .}}
+
+