mirror of
https://github.com/github/codeql.git
synced 2026-01-24 20:02:58 +01:00
The API graph entry point depended on API::Node. This was due to depending on the the TComponent newtype which has a branch that depends on API::Node
709 lines
23 KiB
Plaintext
709 lines
23 KiB
Plaintext
/**
|
|
* Provides classes for working with Vue code.
|
|
*/
|
|
|
|
import javascript
|
|
|
|
module Vue {
|
|
/** The global variable `Vue`, as an API graph entry point. */
|
|
private class GlobalVueEntryPoint extends API::EntryPoint {
|
|
GlobalVueEntryPoint() { this = "VueEntryPoint" }
|
|
|
|
override DataFlow::SourceNode getASource() { result = DataFlow::globalVarRef("Vue") }
|
|
}
|
|
|
|
/**
|
|
* A value exported from a `.vue` file.
|
|
*
|
|
* This `EntryPoint` is used by `SingleFileComponent::getOwnOptions()`.
|
|
*/
|
|
private class VueExportEntryPoint extends API::EntryPoint {
|
|
VueExportEntryPoint() { this = "VueExportEntryPoint" }
|
|
|
|
override DataFlow::Node getASink() { result = getModuleFromVueFile(_).getDefaultOrBulkExport() }
|
|
}
|
|
|
|
/**
|
|
* Gets a reference to the `Vue` object.
|
|
*/
|
|
API::Node vueLibrary() {
|
|
result = API::moduleImport("vue")
|
|
or
|
|
result = any(GlobalVueEntryPoint e).getANode()
|
|
}
|
|
|
|
/**
|
|
* Gets a reference to the 'Vue' object.
|
|
*/
|
|
DataFlow::SourceNode vue() { result = vueLibrary().asSource() }
|
|
|
|
/** Gets an API node referring to a component or `Vue`. */
|
|
private API::Node component() {
|
|
result = vueLibrary()
|
|
or
|
|
result = component().getMember("extend").getReturn()
|
|
or
|
|
result = vueLibrary().getMember("component").getReturn()
|
|
or
|
|
result = any(VueFileImportEntryPoint e).getANode()
|
|
}
|
|
|
|
/**
|
|
* A call to `Vue.extend` or `extend` on a component.
|
|
*/
|
|
private class VueExtendCall extends API::CallNode {
|
|
VueExtendCall() { this = component().getMember("extend").getACall() }
|
|
}
|
|
|
|
/** A component created by an explicit or implicit call to `Vue.extend`. */
|
|
private newtype TComponent =
|
|
MkComponentExtension(VueExtendCall extend) or
|
|
MkComponentInstantiation(API::NewNode sub) { sub = component().getAnInstantiation() } or
|
|
MkComponentRegistration(API::CallNode def) {
|
|
def = vueLibrary().getMember("component").getACall()
|
|
} or
|
|
MkSingleFileComponent(VueFile file)
|
|
|
|
/** Gets the name of a lifecycle hook method. */
|
|
private string lifecycleHookName() {
|
|
result =
|
|
[
|
|
"beforeCreate", "created", "beforeMount", "mounted", "beforeUpdate", "updated", "activated",
|
|
"deactivated", "beforeDestroy", "destroyed", "errorCaptured", "beforeRouteEnter",
|
|
"beforeRouteUpdate", "beforeRouteLeave"
|
|
]
|
|
}
|
|
|
|
/** Gets a value that can be used as a `@Component` decorator. */
|
|
private DataFlow::SourceNode componentDecorator() {
|
|
result = DataFlow::moduleImport("vue-class-component")
|
|
or
|
|
result = DataFlow::moduleMember("vue-property-decorator", "Component")
|
|
}
|
|
|
|
/**
|
|
* A class with a `@Component` decorator, making it usable as an "options" object in Vue.
|
|
*/
|
|
class ClassComponent extends DataFlow::ClassNode {
|
|
DataFlow::Node decorator;
|
|
|
|
ClassComponent() {
|
|
exists(ClassDefinition cls |
|
|
this = cls.flow() and
|
|
cls.getADecorator().getExpression() = decorator.asExpr() and
|
|
(
|
|
componentDecorator().flowsTo(decorator)
|
|
or
|
|
componentDecorator().getACall() = decorator
|
|
)
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Gets the options object passed to the `@Component` decorator, if any.
|
|
*
|
|
* These options correspond to the options one would pass to `new Vue({...})` or similar.
|
|
*/
|
|
API::Node getDecoratorOptions() { result = decorator.(API::CallNode).getParameter(0) }
|
|
}
|
|
|
|
private string memberKindVerb(DataFlow::MemberKind kind) {
|
|
kind = DataFlow::MemberKind::getter() and result = "get"
|
|
or
|
|
kind = DataFlow::MemberKind::setter() and result = "set"
|
|
}
|
|
|
|
/**
|
|
* A Vue component, such as a `new Vue({ ... })` call or a `.vue` file.
|
|
*
|
|
* Generally speaking, a component is always created by calling `Vue.extend()` or
|
|
* calling `extend` on another component.
|
|
* Often the `Vue.extend()` call is performed by the Vue
|
|
* framework, however, so the call is not always visible in the user code.
|
|
* For instance, `new Vue(obj)` is shorthand for `new (Vue.extend(obj))`.
|
|
*
|
|
* This class covers both the explicit `Vue.extend()` calls an those implicit in the framework.
|
|
*
|
|
* The following types of components are recognized:
|
|
* - `new Vue({...})`
|
|
* - `Vue.extend({...})`
|
|
* - `new ExtendedVue({...})`
|
|
* - `Vue.component("my-component", {...})`
|
|
* - single file components in .vue files
|
|
*/
|
|
class Component extends TComponent {
|
|
/** Gets a textual representation of this element. */
|
|
string toString() { none() } // overridden in subclasses
|
|
|
|
/**
|
|
* 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://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
|
|
*/
|
|
predicate hasLocationInfo(
|
|
string filepath, int startline, int startcolumn, int endline, int endcolumn
|
|
) {
|
|
filepath = "" and
|
|
startline = 0 and
|
|
startcolumn = 0 and
|
|
endline = 0 and
|
|
endcolumn = 0
|
|
}
|
|
|
|
/**
|
|
* Gets an API node referring to the component itself.
|
|
*/
|
|
API::Node getComponentRef() { none() } // overridden in subclass
|
|
|
|
/**
|
|
* Gets an API node referring to the options passed to the Vue object,
|
|
* such as the object literal `{...}` in `new Vue{{...})` or the default export of a single-file component.
|
|
*/
|
|
API::Node getOwnOptions() { none() } // overridden in subclass
|
|
|
|
/** Gets a component which is extended by this one. */
|
|
Component getABaseComponent() {
|
|
result.getComponentRef().getAValueReachableFromSource() =
|
|
this.getOwnOptions().getMember(["extends", "mixins"]).asSink()
|
|
}
|
|
|
|
/**
|
|
* Gets an API node referring to the options passed to the Vue object or one
|
|
* of its base component.
|
|
*/
|
|
API::Node getOptions() {
|
|
result = this.getOwnOptions()
|
|
or
|
|
result = this.getOwnOptions().getMember(["extends", "mixins"]).getAMember()
|
|
or
|
|
result = this.getABaseComponent().getOptions()
|
|
or
|
|
result = this.getAsClassComponent().getDecoratorOptions()
|
|
}
|
|
|
|
/**
|
|
* DEPRECATED. Use `getOwnOptions().getASink()`.
|
|
*
|
|
* Gets the options passed to the Vue object, such as the object literal `{...}` in `new Vue{{...})`
|
|
* or the default export of a single-file component.
|
|
*/
|
|
deprecated DataFlow::Node getOwnOptionsObject() { result = this.getOwnOptions().asSink() }
|
|
|
|
/**
|
|
* Gets the class implementing this Vue component, if any.
|
|
*
|
|
* Specifically, this is a class annotated with `@Component` which flows to the options
|
|
* object of this Vue component.
|
|
*/
|
|
ClassComponent getAsClassComponent() { result = this.getOwnOptions().getAValueReachingSink() }
|
|
|
|
/**
|
|
* Gets the node for option `name` for this component, not including
|
|
* those from extended objects and mixins.
|
|
*/
|
|
DataFlow::Node getOwnOption(string name) {
|
|
result = this.getOwnOptions().getMember(name).asSink()
|
|
}
|
|
|
|
/**
|
|
* Gets the node for option `name` for this component, including those from
|
|
* extended objects and mixins.
|
|
*/
|
|
DataFlow::Node getOption(string name) { result = this.getOptions().getMember(name).asSink() }
|
|
|
|
/**
|
|
* Gets a source node flowing into the option `name` of this component, including those from
|
|
* extended objects and mixins.
|
|
*/
|
|
pragma[nomagic]
|
|
DataFlow::SourceNode getOptionSource(string name) {
|
|
result = this.getOptions().getMember(name).getAValueReachingSink()
|
|
}
|
|
|
|
/**
|
|
* Gets the template element used by this component, if any.
|
|
*/
|
|
Template::Element getTemplateElement() { none() } // overridden in subclasses
|
|
|
|
/**
|
|
* Gets the node for the `data` option object of this component.
|
|
*/
|
|
DataFlow::Node getData() {
|
|
result = this.getOption("data")
|
|
or
|
|
result = this.getOptionSource("data").(DataFlow::FunctionNode).getReturnNode()
|
|
or
|
|
result = this.getAsClassComponent().getAReceiverNode()
|
|
or
|
|
result = this.getAsClassComponent().getInstanceMethod("data").getAReturn()
|
|
}
|
|
|
|
/**
|
|
* Gets the node for the `template` option of this component.
|
|
*/
|
|
pragma[nomagic]
|
|
DataFlow::SourceNode getTemplate() { result = this.getOptionSource("template") }
|
|
|
|
/**
|
|
* Gets the node for the `render` option of this component.
|
|
*/
|
|
pragma[nomagic]
|
|
DataFlow::SourceNode getRender() {
|
|
result = this.getOptionSource("render")
|
|
or
|
|
result = this.getAsClassComponent().getInstanceMethod("render")
|
|
}
|
|
|
|
/**
|
|
* Gets the node for the `methods` option of this component.
|
|
*/
|
|
pragma[nomagic]
|
|
DataFlow::SourceNode getMethods() { result = this.getOptionSource("methods") }
|
|
|
|
/**
|
|
* Gets the node for the `computed` option of this component.
|
|
*/
|
|
pragma[nomagic]
|
|
DataFlow::SourceNode getComputed() { result = this.getOptionSource("computed") }
|
|
|
|
/**
|
|
* Gets the node for the `watch` option of this component.
|
|
*/
|
|
pragma[nomagic]
|
|
DataFlow::SourceNode getWatch() { result = this.getOptionSource("watch") }
|
|
|
|
/**
|
|
* Gets the function responding to changes to the given `propName`.
|
|
*/
|
|
DataFlow::FunctionNode getWatchHandler(string propName) {
|
|
exists(API::Node propWatch |
|
|
propWatch = this.getOptions().getMember("watch").getMember(propName) and
|
|
result = [propWatch, propWatch.getMember("handler")].getAValueReachingSink()
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Gets a node for a member `name` of the `computed` option of this component that matches `kind`.
|
|
*/
|
|
private DataFlow::SourceNode getAccessor(string name, DataFlow::MemberKind kind) {
|
|
result = this.getComputed().getAPropertySource(name) and kind = DataFlow::MemberKind::getter()
|
|
or
|
|
result = this.getComputed().getAPropertySource(name).getAPropertySource(memberKindVerb(kind))
|
|
or
|
|
result = this.getAsClassComponent().getInstanceMember(name, kind) and
|
|
kind.isAccessor()
|
|
}
|
|
|
|
/**
|
|
* Gets the node for the life cycle hook of the `hookName` option of this component.
|
|
*/
|
|
pragma[nomagic]
|
|
DataFlow::SourceNode getALifecycleHook(string hookName) {
|
|
hookName = lifecycleHookName() and
|
|
(
|
|
result = this.getOptionSource(hookName)
|
|
or
|
|
result = this.getAsClassComponent().getInstanceMethod(hookName)
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Gets a node for a function that will be invoked with `this` bound to this component.
|
|
*/
|
|
DataFlow::FunctionNode getABoundFunction() {
|
|
result = this.getOptions().getAMember+().getAValueReachingSink()
|
|
or
|
|
result = this.getAsClassComponent().getAnInstanceMember()
|
|
}
|
|
|
|
/** Gets an API node referring to an instance of this component. */
|
|
API::Node getInstance() { result.asSource() = this.getABoundFunction().getReceiver() }
|
|
|
|
/** Gets a data flow node referring to an instance of this component. */
|
|
DataFlow::SourceNode getAnInstanceRef() { result = this.getInstance().asSource() }
|
|
|
|
pragma[noinline]
|
|
private DataFlow::PropWrite getAPropertyValueWrite(string name) {
|
|
result = this.getData().getALocalSource().getAPropertyWrite(name)
|
|
or
|
|
result = this.getAnInstanceRef().getAPropertyWrite(name)
|
|
}
|
|
|
|
/**
|
|
* Gets the data flow node that flows into the property `name` of this component, or is
|
|
* returned form a getter defining that property.
|
|
*/
|
|
DataFlow::Node getAPropertyValue(string name) {
|
|
result = this.getAPropertyValueWrite(name).getRhs()
|
|
or
|
|
exists(DataFlow::FunctionNode getter |
|
|
getter.flowsTo(this.getAccessor(name, DataFlow::MemberKind::getter())) and
|
|
result = getter.getAReturn()
|
|
)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A Vue component created implicitly at an invocation of form `new Vue({...})` or `new CustomComponent({...})`.
|
|
*/
|
|
private class ComponentInstantiation extends Component, MkComponentInstantiation {
|
|
API::NewNode def;
|
|
|
|
ComponentInstantiation() { this = MkComponentInstantiation(def) }
|
|
|
|
override string toString() { result = def.toString() }
|
|
|
|
override predicate hasLocationInfo(
|
|
string filepath, int startline, int startcolumn, int endline, int endcolumn
|
|
) {
|
|
def.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
|
|
}
|
|
|
|
override API::Node getComponentRef() {
|
|
// The Vue.extend call is made in the Vue framework; there is no explicit reference
|
|
// to the component in user code.
|
|
none()
|
|
}
|
|
|
|
override API::Node getOwnOptions() { result = def.getParameter(0) }
|
|
|
|
override Template::Element getTemplateElement() { none() }
|
|
|
|
override Component getABaseComponent() {
|
|
result = Component.super.getABaseComponent()
|
|
or
|
|
result.getComponentRef().getAnInstantiation() = def
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A component created via an explicit call to `Vue.extend({...})` or `CustomComponent.extend({...})`.
|
|
*/
|
|
class ComponentExtension extends Component, MkComponentExtension {
|
|
VueExtendCall extend;
|
|
|
|
ComponentExtension() { this = MkComponentExtension(extend) }
|
|
|
|
override string toString() { result = extend.toString() }
|
|
|
|
override predicate hasLocationInfo(
|
|
string filepath, int startline, int startcolumn, int endline, int endcolumn
|
|
) {
|
|
extend.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
|
|
}
|
|
|
|
override API::Node getComponentRef() { result = extend.getReturn() }
|
|
|
|
override API::Node getOwnOptions() { result = extend.getParameter(0) }
|
|
|
|
override Template::Element getTemplateElement() { none() }
|
|
|
|
override Component getABaseComponent() {
|
|
result = Component.super.getABaseComponent()
|
|
or
|
|
result.getComponentRef().getMember("extend").getACall() = extend
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A Vue component from `Vue.component("my-component", { ... })`.
|
|
*/
|
|
class ComponentRegistration extends Component, MkComponentRegistration {
|
|
API::CallNode def;
|
|
|
|
ComponentRegistration() { this = MkComponentRegistration(def) }
|
|
|
|
override string toString() { result = def.toString() }
|
|
|
|
override predicate hasLocationInfo(
|
|
string filepath, int startline, int startcolumn, int endline, int endcolumn
|
|
) {
|
|
def.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
|
|
}
|
|
|
|
override API::Node getComponentRef() {
|
|
// The component can be obtained via 1-argument calls to `Vue.component()` with the
|
|
// same name, but we don't model this at the moment.
|
|
none()
|
|
}
|
|
|
|
override API::Node getOwnOptions() { result = def.getParameter(1) }
|
|
|
|
override Template::Element getTemplateElement() { none() }
|
|
}
|
|
|
|
/**
|
|
* An import referring to a `.vue` file, seen as an API entry point.
|
|
*
|
|
* Concretely, such an import receives the Vue component generated from the .vue file,
|
|
* not the actual exports of the script tag in the file.
|
|
*
|
|
* This entry point is used in `SingleFileComponent::getComponentRef()`.
|
|
*/
|
|
private class VueFileImportEntryPoint extends API::EntryPoint {
|
|
VueFileImportEntryPoint() { this = "VueFileImportEntryPoint" }
|
|
|
|
override DataFlow::SourceNode getASource() {
|
|
exists(Import imprt |
|
|
imprt.getImportedPath().resolve() instanceof VueFile and
|
|
result = imprt.getImportedModuleNode()
|
|
)
|
|
}
|
|
}
|
|
|
|
private Module getModuleFromVueFile(VueFile file) {
|
|
exists(HTML::ScriptElement elem |
|
|
xmlElements(elem, _, _, _, file) and // Avoid materializing all of Locatable.getFile()
|
|
result.getTopLevel() = elem.getScript()
|
|
)
|
|
}
|
|
|
|
/**
|
|
* A single file Vue component in a `.vue` file.
|
|
*/
|
|
class SingleFileComponent extends Component, MkSingleFileComponent {
|
|
VueFile file;
|
|
|
|
SingleFileComponent() { this = MkSingleFileComponent(file) }
|
|
|
|
override Template::Element getTemplateElement() {
|
|
exists(HTML::Element e | result.(Template::HtmlElement).getElement() = e |
|
|
e.getFile() = file and
|
|
e.getName() = "template" and
|
|
e.isTopLevel()
|
|
)
|
|
}
|
|
|
|
override predicate hasLocationInfo(
|
|
string filepath, int startline, int startcolumn, int endline, int endcolumn
|
|
) {
|
|
filepath = file.getAbsolutePath() and
|
|
startline = 0 and
|
|
startcolumn = 0 and
|
|
endline = 0 and
|
|
endcolumn = 0
|
|
}
|
|
|
|
/** Gets the module defined by the `script` tag in this .vue file, if any. */
|
|
Module getModule() { result = getModuleFromVueFile(file) }
|
|
|
|
override API::Node getComponentRef() {
|
|
// There is no explicit `new Vue()` call in .vue files, so instead get all the imports
|
|
// of the .vue file.
|
|
exists(Import imprt |
|
|
imprt.getImportedPath().resolve() = file and
|
|
result.asSource() = imprt.getImportedModuleNode()
|
|
)
|
|
}
|
|
|
|
override API::Node getOwnOptions() {
|
|
// Use the entry point generated by `VueExportEntryPoint`
|
|
result.asSink() = this.getModule().getDefaultOrBulkExport()
|
|
}
|
|
|
|
override string toString() { result = file.toString() }
|
|
}
|
|
|
|
/**
|
|
* A `.vue` file.
|
|
*/
|
|
class VueFile extends File {
|
|
VueFile() { this.getExtension() = "vue" }
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private DataFlow::Node propStepPred(Component comp, string name) {
|
|
result = comp.getAPropertyValue(name)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private DataFlow::Node propStepSucc(Component comp, string name) {
|
|
result = comp.getAnInstanceRef().getAPropertyRead(name)
|
|
}
|
|
|
|
/**
|
|
* A taint propagating data flow edge through a Vue instance property.
|
|
*/
|
|
private class PropStep extends TaintTracking::SharedTaintStep {
|
|
override predicate viewComponentStep(DataFlow::Node pred, DataFlow::Node succ) {
|
|
exists(Component comp, string name |
|
|
pred = propStepPred(comp, name) and
|
|
succ = propStepSucc(comp, name)
|
|
)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A Vue `v-html` attribute.
|
|
*/
|
|
class VHtmlAttribute extends DataFlow::Node {
|
|
HTML::Attribute attr;
|
|
|
|
VHtmlAttribute() {
|
|
this.(DataFlow::HtmlAttributeNode).getAttribute() = attr and attr.getName() = "v-html"
|
|
}
|
|
|
|
/**
|
|
* Gets the HTML attribute of this sink.
|
|
*/
|
|
HTML::Attribute getAttr() { result = attr }
|
|
}
|
|
|
|
/**
|
|
* A taint propagating data flow edge through a string interpolation of a
|
|
* Vue instance property to a `v-html` attribute.
|
|
*
|
|
* As an example, `<div v-html="prop"/>` reads the `prop` property
|
|
* of `inst = new Vue({ ..., data: { prop: source } })`, if the
|
|
* `div` element is part of the template for `inst`.
|
|
*/
|
|
private class VHtmlAttributeStep extends TaintTracking::SharedTaintStep {
|
|
override predicate viewComponentStep(DataFlow::Node pred, DataFlow::Node succ) {
|
|
exists(Component component, string expr, VHtmlAttribute attr |
|
|
attr.getAttr().getRoot() =
|
|
component.getTemplateElement().(Template::HtmlElement).getElement() and
|
|
expr = attr.getAttr().getValue() and
|
|
// only support for simple identifier expressions
|
|
expr.regexpMatch("(?i)[a-z0-9_]+") and
|
|
pred = component.getAPropertyValue(expr) and
|
|
succ = attr
|
|
)
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Provides classes for working with Vue templates.
|
|
*/
|
|
|
|
module Template {
|
|
// Currently only supports HTML elements, but it may be possible to parse simple string templates later
|
|
private newtype TElement = MkHtmlElement(HTML::Element e) { e.getFile() instanceof VueFile }
|
|
|
|
/**
|
|
* An element of a template.
|
|
*/
|
|
abstract class Element extends TElement {
|
|
/** Gets a textual representation of this element. */
|
|
string toString() { result = "<" + this.getName() + ">...</>" }
|
|
|
|
/**
|
|
* 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://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
|
|
*/
|
|
predicate hasLocationInfo(
|
|
string filepath, int startline, int startcolumn, int endline, int endcolumn
|
|
) {
|
|
filepath = "" and
|
|
startline = 0 and
|
|
startcolumn = 0 and
|
|
endline = 0 and
|
|
endcolumn = 0
|
|
}
|
|
|
|
/**
|
|
* Gets the name of this element.
|
|
*
|
|
* For example, the name of `<br>` is `br`.
|
|
*/
|
|
abstract string getName();
|
|
}
|
|
|
|
/**
|
|
* An HTML element as a template element.
|
|
*/
|
|
class HtmlElement extends Element, MkHtmlElement {
|
|
HTML::Element elem;
|
|
|
|
HtmlElement() { this = MkHtmlElement(elem) }
|
|
|
|
override predicate hasLocationInfo(
|
|
string filepath, int startline, int startcolumn, int endline, int endcolumn
|
|
) {
|
|
elem.getLocation().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
|
|
}
|
|
|
|
override string getName() { result = elem.getName() }
|
|
|
|
/**
|
|
* Gets the HTML element of this element.
|
|
*/
|
|
HTML::Element getElement() { result = elem }
|
|
}
|
|
}
|
|
|
|
/** Gets an API node referring to a `RouteConfig` being passed to `vue-router`. */
|
|
private API::Node routeConfig() {
|
|
result = API::moduleImport("vue-router").getParameter(0).getMember("routes").getAMember()
|
|
or
|
|
result = routeConfig().getMember("children").getAMember()
|
|
}
|
|
|
|
/** Gets a data flow node that refers to a `Route` object from `vue-router`. */
|
|
private DataFlow::SourceNode routeObject(DataFlow::TypeTracker t) {
|
|
t.start() and
|
|
(
|
|
exists(API::Node router | router = API::moduleImport("vue-router") |
|
|
result = router.getInstance().getMember("currentRoute").asSource()
|
|
or
|
|
result =
|
|
router
|
|
.getInstance()
|
|
.getMember(["beforeEach", "beforeResolve", "afterEach"])
|
|
.getParameter(0)
|
|
.getParameter([0, 1])
|
|
.asSource()
|
|
or
|
|
result = router.getParameter(0).getMember("scrollBehavior").getParameter([0, 1]).asSource()
|
|
)
|
|
or
|
|
result = routeConfig().getMember("beforeEnter").getParameter([0, 1]).asSource()
|
|
or
|
|
exists(Component c |
|
|
result = c.getABoundFunction().getAFunctionValue().getReceiver().getAPropertyRead("$route")
|
|
or
|
|
result =
|
|
c.getALifecycleHook(["beforeRouteEnter", "beforeRouteUpdate", "beforeRouteLeave"])
|
|
.getAFunctionValue()
|
|
.getParameter([0, 1])
|
|
or
|
|
result = c.getWatchHandler("$route").getParameter([0, 1])
|
|
)
|
|
)
|
|
or
|
|
exists(DataFlow::TypeTracker t2 | result = routeObject(t2).track(t2, t))
|
|
}
|
|
|
|
/** Gets a data flow node that refers to a `Route` object from `vue-router`. */
|
|
DataFlow::SourceNode routeObject() { result = routeObject(DataFlow::TypeTracker::end()) }
|
|
|
|
private class VueRouterFlowSource extends ClientSideRemoteFlowSource {
|
|
ClientSideRemoteFlowKind kind;
|
|
|
|
VueRouterFlowSource() {
|
|
exists(string name |
|
|
this = routeObject().getAPropertyRead(name)
|
|
or
|
|
exists(string prop |
|
|
this = any(Component c).getWatchHandler(prop).getParameter([0, 1]) and
|
|
name = prop.regexpCapture("\\$route\\.(params|query|hash|path|fullPath)\\b.*", 1)
|
|
)
|
|
|
|
|
name = ["params", "path", "fullPath"] and kind.isPath()
|
|
or
|
|
name = "query" and kind.isQuery()
|
|
or
|
|
name = "hash" and kind.isFragment()
|
|
)
|
|
}
|
|
|
|
override string getSourceType() { result = "Vue route parameter" }
|
|
|
|
override ClientSideRemoteFlowKind getKind() { result = kind }
|
|
}
|
|
}
|