/** * Provides classes for working with jQuery code. */ import javascript /** * Gets a data flow node that may refer to the jQuery `$` function. */ predicate jquery = JQuery::dollar/0; /** * An internal version of `JQueryObject` that may be used to retain * backwards compatibility without triggering a deprecation warning. */ abstract private class JQueryObjectInternal extends Expr { } /** * A jQuery object created from a jQuery method. * * This class is defined using the legacy API in order to retain the * behavior of `JQueryObject`. */ private class OrdinaryJQueryObject extends JQueryObjectInternal { OrdinaryJQueryObject() { exists(JQuery::MethodCall jq | this.flow().getALocalSource() = jq and returnsAJQueryObject(jq, jq.getMethodName()) ) } } /** * Holds if the jQuery method call `call`, with name `methodName`, returns a JQuery object. * * The `call` parameter has type `DataFlow::CallNode` instead of `JQuery::MethodCall` to avoid non-monotonic recursion. * The not is placed inside the predicate to avoid non-monotonic recursion. */ bindingset[methodName, call] private predicate returnsAJQueryObject(DataFlow::CallNode call, string methodName) { not ( neverReturnsJQuery(methodName) or methodName = "val" and call.getNumArgument() = 0 // `jQuery.val()` or methodName = ["html", "text"] and call.getNumArgument() = 0 // `jQuery.html()`/`jQuery.text()` or // `jQuery.attr(key)`/`jQuery.prop(key)` methodName = ["attr", "prop"] and call.getNumArgument() = 1 and call.getArgument(0).mayHaveStringValue(_) ) } /** * Holds if a jQuery method named `name` never returns a JQuery object. */ private predicate neverReturnsJQuery(string name) { forex(ExternalMemberDecl decl | decl.getBaseName() = "jQuery" and decl.getName() = name | not decl.getDocumentation() .getATagByTitle("return") .getType() .getAnUnderlyingType() .hasQualifiedName("jQuery") ) } /** * A call to `jQuery.parseXML`. */ private class JQueryParseXmlCall extends XML::ParserInvocation { JQueryParseXmlCall() { this.flow().(JQuery::MethodCall).getMethodName() = "parseXML" } override Expr getSourceArgument() { result = this.getArgument(0) } override predicate resolvesEntities(XML::EntityKind kind) { kind = XML::InternalEntity() } } /** * A call to `$(...)` that constructs a wrapped DOM element, such as `$("
")`. */ private class JQueryDomElementDefinition extends DOM::ElementDefinition, @call_expr { string tagName; CallExpr call; JQueryDomElementDefinition() { this = call and call = jquery().getACall().asExpr() and exists(string s | s = call.getArgument(0).getStringValue() | // match an opening angle bracket followed by a tag name, followed by arbitrary // text and a closing angle bracket, potentially with whitespace in between tagName = s.regexpCapture("\\s*<\\s*(\\w+)\\b[^>]*>\\s*", 1).toLowerCase() ) } override string getName() { result = tagName } /** * Gets a data flow node specifying the attributes of the constructed DOM element. * * For example, in `$("", { href: "https://semmle.com" })` the second argument * specifies the attributes of the new `` element. */ DataFlow::SourceNode getAttributes() { result.flowsToExpr(call.getArgument(1)) } override DOM::ElementDefinition getParent() { none() } } /** * An attribute defined using jQuery APIs. */ abstract private class JQueryAttributeDefinition extends DOM::AttributeDefinition { } /** * An attribute definition supplied when constructing a DOM element using `$(...)`. * * For example, in `$("