JS: Move ClientRequest classes into a module and publish them

This commit is contained in:
Asger F
2019-08-02 23:02:43 +01:00
parent 55ad3bb65f
commit 0950b4d0f7

View File

@@ -69,6 +69,8 @@ class ClientRequest extends DataFlow::InvokeNode {
DataFlow::Node getAResponseDataNode() { result = getAResponseDataNode(_, _) }
}
deprecated class CustomClientRequest = ClientRequest::Range;
module ClientRequest {
/**
* A call that performs a request to a URL.
@@ -101,411 +103,402 @@ module ClientRequest {
*/
DataFlow::Node getAResponseDataNode(string responseType, boolean promise) { none() }
}
}
deprecated class CustomClientRequest = ClientRequest::Range;
/**
* Gets name of an HTTP request method, in all-lowercase.
*/
private string httpMethodName() { result = any(HTTP::RequestMethodName m).toLowerCase() }
/**
* Gets name of an HTTP request method, in all-lowercase.
*/
private string httpMethodName() { result = any(HTTP::RequestMethodName m).toLowerCase() }
/**
* A model of a URL request made using the `request` library.
*/
class RequestUrlRequest extends ClientRequest::Range, DataFlow::CallNode {
boolean promise;
/**
* Gets the name of a property that likely contains a URL value.
*/
private string urlPropertyName() {
result = "uri" or
result = "url"
}
/**
* A model of a URL request made using the `request` library.
*/
private class RequestUrlRequest extends ClientRequest::Range, DataFlow::CallNode {
boolean promise;
RequestUrlRequest() {
exists(string moduleName, DataFlow::SourceNode callee | this = callee.getACall() |
(
promise = false and
moduleName = "request"
or
promise = true and
RequestUrlRequest() {
exists(string moduleName, DataFlow::SourceNode callee | this = callee.getACall() |
(
moduleName = "request-promise" or
moduleName = "request-promise-any" or
moduleName = "request-promise-native"
promise = false and
moduleName = "request"
or
promise = true and
(
moduleName = "request-promise" or
moduleName = "request-promise-any" or
moduleName = "request-promise-native"
)
) and
(
callee = DataFlow::moduleImport(moduleName) or
callee = DataFlow::moduleMember(moduleName, httpMethodName())
)
) and
(
callee = DataFlow::moduleImport(moduleName) or
callee = DataFlow::moduleMember(moduleName, httpMethodName())
)
)
}
}
override DataFlow::Node getUrl() {
result = getArgument(0) or
result = getOptionArgument(0, urlPropertyName())
}
override DataFlow::Node getUrl() {
result = getArgument(0) or
result = getOptionArgument(0, urlPropertyName())
}
override DataFlow::Node getHost() { none() }
override DataFlow::Node getHost() { none() }
string getResponseFormat() {
if getOptionArgument(0, "json").mayHaveBooleanValue(true) then
result = "json"
else
result = "text"
}
string getResponseFormat() {
if getOptionArgument(0, "json").mayHaveBooleanValue(true) then
result = "json"
else
result = "text"
}
override DataFlow::Node getAResponseDataNode(string responseType, boolean pr) {
responseType = getResponseFormat() and
promise = true and
pr = true and
result = this
or
responseType = getResponseFormat() and
promise = false and
pr = false and
(
result = getCallback([1..2]).getParameter(2)
override DataFlow::Node getAResponseDataNode(string responseType, boolean pr) {
responseType = getResponseFormat() and
promise = true and
pr = true and
result = this
or
result = getCallback([1..2]).getParameter(1).getAPropertyRead("body")
)
}
override DataFlow::Node getADataNode() { result = getArgument(1) }
}
/**
* A model of a URL request made using the `axios` library.
*/
private class AxiosUrlRequest extends ClientRequest::Range {
string method;
AxiosUrlRequest() {
exists(string moduleName, DataFlow::SourceNode callee | this = callee.getACall() |
moduleName = "axios" and
responseType = getResponseFormat() and
promise = false and
pr = false and
(
callee = DataFlow::moduleImport(moduleName) and method = "request"
result = getCallback([1..2]).getParameter(2)
or
callee = DataFlow::moduleMember(moduleName, method) and
(method = httpMethodName() or method = "request")
result = getCallback([1..2]).getParameter(1).getAPropertyRead("body")
)
)
}
override DataFlow::Node getADataNode() { result = getArgument(1) }
}
private DataFlow::Node getOptionArgument(string name) {
// depends on the method name and the call arity, over-approximating slightly in the name of simplicity
result = getOptionArgument([0 .. 2], name)
}
/**
* A model of a URL request made using the `axios` library.
*/
class AxiosUrlRequest extends ClientRequest::Range {
string method;
override DataFlow::Node getUrl() {
result = getArgument(0) or
result = getOptionArgument(urlPropertyName())
}
AxiosUrlRequest() {
exists(string moduleName, DataFlow::SourceNode callee | this = callee.getACall() |
moduleName = "axios" and
(
callee = DataFlow::moduleImport(moduleName) and method = "request"
or
callee = DataFlow::moduleMember(moduleName, method) and
(method = httpMethodName() or method = "request")
)
)
}
override DataFlow::Node getHost() { result = getOptionArgument("host") }
override DataFlow::Node getADataNode() {
method = "request" and
result = getOptionArgument(0, "data")
or
(method = "post" or method = "put" or method = "put") and
(result = getArgument(1) or result = getOptionArgument(2, "data"))
or
exists(string name | name = "headers" or name = "params" |
private DataFlow::Node getOptionArgument(string name) {
// depends on the method name and the call arity, over-approximating slightly in the name of simplicity
result = getOptionArgument([0 .. 2], name)
)
}
}
string getResponseFormat() {
exists(DataFlow::Node option | option = getOptionArgument([0 .. 2], "responseType") |
result = option.getStringValue()
override DataFlow::Node getUrl() {
result = getArgument(0) or
result = getOptionArgument(urlPropertyName())
}
override DataFlow::Node getHost() { result = getOptionArgument("host") }
override DataFlow::Node getADataNode() {
method = "request" and
result = getOptionArgument(0, "data")
or
not exists(option.getStringValue()) and
result = ""
)
or
not exists(getOptionArgument([0 .. 2], "responseType")) and
result = "json"
}
override DataFlow::Node getAResponseDataNode(string responseType, boolean promise) {
responseType = getResponseFormat() and
promise = true and
result = this
}
}
/**
* A model of a URL request made using an implementation of the `fetch` API.
*/
private class FetchUrlRequest extends ClientRequest::Range {
DataFlow::Node url;
FetchUrlRequest() {
exists(string moduleName, DataFlow::SourceNode callee | this = callee.getACall() |
(
moduleName = "node-fetch" or
moduleName = "cross-fetch" or
moduleName = "isomorphic-fetch"
) and
callee = DataFlow::moduleImport(moduleName) and
url = getArgument(0)
)
or
this = DataFlow::globalVarRef("fetch").getACall() and
url = getArgument(0)
}
override DataFlow::Node getUrl() { result = url }
override DataFlow::Node getHost() { none() }
override DataFlow::Node getADataNode() {
exists(string name | name = "headers" or name = "body" | result = getOptionArgument(1, name))
}
override DataFlow::Node getAResponseDataNode(string responseType, boolean promise) {
responseType = "fetch.response" and
promise = true and
result = this
}
}
/**
* A model of a URL request made using the `got` library.
*/
private class GotUrlRequest extends ClientRequest::Range {
GotUrlRequest() {
exists(string moduleName, DataFlow::SourceNode callee | this = callee.getACall() |
moduleName = "got" and
(
callee = DataFlow::moduleImport(moduleName) or
callee = DataFlow::moduleMember(moduleName, "stream")
(method = "post" or method = "put" or method = "put") and
(result = getArgument(1) or result = getOptionArgument(2, "data"))
or
exists(string name | name = "headers" or name = "params" |
result = getOptionArgument([0 .. 2], name)
)
)
}
}
override DataFlow::Node getUrl() {
result = getArgument(0) and
not exists(getOptionArgument(1, "baseUrl"))
}
override DataFlow::Node getHost() {
exists(string name |
name = "host" or
name = "hostname"
|
result = getOptionArgument(1, name)
)
}
override DataFlow::Node getADataNode() {
exists(string name | name = "headers" or name = "body" or name = "query" |
result = getOptionArgument(1, name)
)
}
predicate isStream() {
getOptionArgument(1, "stream").mayHaveBooleanValue(true)
or
this = DataFlow::moduleMember("got", "stream").getACall()
}
predicate isJson() {
getOptionArgument(1, "json").mayHaveBooleanValue(true)
}
override DataFlow::Node getAResponseDataNode(string responseType, boolean promise) {
result = this and
(
isStream() and
responseType = "stream" and
promise = false
string getResponseFormat() {
exists(DataFlow::Node option | option = getOptionArgument([0 .. 2], "responseType") |
result = option.getStringValue()
or
not exists(option.getStringValue()) and
result = ""
)
or
isJson() and
responseType = "json" and
promise = true
or
not isStream() and
not isJson() and
responseType = "text" and
promise = true
)
not exists(getOptionArgument([0 .. 2], "responseType")) and
result = "json"
}
override DataFlow::Node getAResponseDataNode(string responseType, boolean promise) {
responseType = getResponseFormat() and
promise = true and
result = this
}
}
}
/**
* A model of a URL request made using the `superagent` library.
*/
private class SuperAgentUrlRequest extends ClientRequest::Range {
DataFlow::Node url;
/**
* A model of a URL request made using an implementation of the `fetch` API.
*/
class FetchUrlRequest extends ClientRequest::Range {
DataFlow::Node url;
SuperAgentUrlRequest() {
exists(string moduleName, DataFlow::SourceNode callee | this = callee.getACall() |
moduleName = "superagent" and
callee = DataFlow::moduleMember(moduleName, httpMethodName()) and
FetchUrlRequest() {
exists(string moduleName, DataFlow::SourceNode callee | this = callee.getACall() |
(
moduleName = "node-fetch" or
moduleName = "cross-fetch" or
moduleName = "isomorphic-fetch"
) and
callee = DataFlow::moduleImport(moduleName) and
url = getArgument(0)
)
or
this = DataFlow::globalVarRef("fetch").getACall() and
url = getArgument(0)
)
}
override DataFlow::Node getUrl() { result = url }
override DataFlow::Node getHost() { none() }
override DataFlow::Node getADataNode() {
exists(string name | name = "headers" or name = "body" | result = getOptionArgument(1, name))
}
override DataFlow::Node getAResponseDataNode(string responseType, boolean promise) {
responseType = "fetch.response" and
promise = true and
result = this
}
}
override DataFlow::Node getUrl() { result = url }
/**
* A model of a URL request made using the `got` library.
*/
class GotUrlRequest extends ClientRequest::Range {
GotUrlRequest() {
exists(string moduleName, DataFlow::SourceNode callee | this = callee.getACall() |
moduleName = "got" and
(
callee = DataFlow::moduleImport(moduleName) or
callee = DataFlow::moduleMember(moduleName, "stream")
)
)
}
override DataFlow::Node getHost() { none() }
override DataFlow::Node getUrl() {
result = getArgument(0) and
not exists(getOptionArgument(1, "baseUrl"))
}
override DataFlow::Node getADataNode() {
exists(string name | name = "set" or name = "send" or name = "query" |
result = this.getAChainedMethodCall(name).getAnArgument()
)
override DataFlow::Node getHost() {
exists(string name |
name = "host" or
name = "hostname"
|
result = getOptionArgument(1, name)
)
}
override DataFlow::Node getADataNode() {
exists(string name | name = "headers" or name = "body" or name = "query" |
result = getOptionArgument(1, name)
)
}
predicate isStream() {
getOptionArgument(1, "stream").mayHaveBooleanValue(true)
or
this = DataFlow::moduleMember("got", "stream").getACall()
}
predicate isJson() {
getOptionArgument(1, "json").mayHaveBooleanValue(true)
}
override DataFlow::Node getAResponseDataNode(string responseType, boolean promise) {
result = this and
(
isStream() and
responseType = "stream" and
promise = false
or
isJson() and
responseType = "json" and
promise = true
or
not isStream() and
not isJson() and
responseType = "text" and
promise = true
)
}
}
override DataFlow::Node getAResponseDataNode(string responseType, boolean promise) {
responseType = "text" and
promise = true and
result = this
or
exists(DataFlow::FunctionNode callback |
callback = getAChainedMethodCall("end").getCallback(0) and
/**
* A model of a URL request made using the `superagent` library.
*/
class SuperAgentUrlRequest extends ClientRequest::Range {
DataFlow::Node url;
SuperAgentUrlRequest() {
exists(string moduleName, DataFlow::SourceNode callee | this = callee.getACall() |
moduleName = "superagent" and
callee = DataFlow::moduleMember(moduleName, httpMethodName()) and
url = getArgument(0)
)
}
override DataFlow::Node getUrl() { result = url }
override DataFlow::Node getHost() { none() }
override DataFlow::Node getADataNode() {
exists(string name | name = "set" or name = "send" or name = "query" |
result = this.getAChainedMethodCall(name).getAnArgument()
)
}
override DataFlow::Node getAResponseDataNode(string responseType, boolean promise) {
responseType = "text" and
promise = true and
result = this
or
exists(DataFlow::FunctionNode callback |
callback = getAChainedMethodCall("end").getCallback(0) and
promise = false and
(
responseType = "error" and result = callback.getParameter(0)
or
responseType = "text" and result = callback.getParameter(1)
)
)
}
}
/**
* A model of a URL request made using the `XMLHttpRequest` browser class.
*/
class XMLHttpRequest extends ClientRequest::Range {
XMLHttpRequest() {
this = DataFlow::globalVarRef("XMLHttpRequest").getAnInstantiation()
or
// closure shim for XMLHttpRequest
this = Closure::moduleImport("goog.net.XmlHttp").getAnInvocation()
}
override DataFlow::Node getUrl() { result = getAMethodCall("open").getArgument(1) }
override DataFlow::Node getHost() { none() }
override DataFlow::Node getADataNode() { result = getAMethodCall("send").getArgument(0) }
private string getAssignedResponseType() {
getAPropertyWrite("responseType").mayHaveStringValue(result)
or
getAPropertyWrite("responseType").mayHaveStringValue("") and
result = "text"
or
not exists(getAPropertyWrite("responseType")) and
result = "text"
}
DataFlow::FunctionNode getAnEventListener() {
result = getAPropertyWrite("on" + any(string s)).getRhs().getAFunctionValue()
or
result = getAMethodCall("addEventListener").getArgument(1).getAFunctionValue()
}
DataFlow::SourceNode getAnAlias() {
result = this
or
// The value of `this` in an event listener refers to the XHR object
result = getAnEventListener().getReceiver()
}
override DataFlow::Node getAResponseDataNode(string responseType, boolean promise) {
promise = false and
(
responseType = "error" and result = callback.getParameter(0)
exists(string prop | result = getAnAlias().getAPropertyRead(prop) |
prop = "response" and responseType = getAssignedResponseType()
or
prop = "responseText" and responseType = "text"
or
prop = "statusText" and responseType = "text"
or
prop = "responseXML" and responseType = "document"
)
or
responseType = "text" and result = callback.getParameter(1)
responseType = "text" and
exists(string method | result = getAnAlias().getAMethodCall(method) |
method = "getAllResponseHeaders" or
method = "getResponseHeader"
)
)
)
}
}
/**
* A model of a URL request made using the `XMLHttpRequest` browser class.
*/
private class XMLHttpRequest extends ClientRequest::Range {
XMLHttpRequest() {
this = DataFlow::globalVarRef("XMLHttpRequest").getAnInstantiation()
or
// closure shim for XMLHttpRequest
this = Closure::moduleImport("goog.net.XmlHttp").getAnInvocation()
}
}
override DataFlow::Node getUrl() { result = getAMethodCall("open").getArgument(1) }
/**
* A model of a URL request made using the `XhrIo` class from the closure library.
*/
class ClosureXhrIoRequest extends ClientRequest::Range {
DataFlow::SourceNode xhrIo;
boolean static;
override DataFlow::Node getHost() { none() }
override DataFlow::Node getADataNode() { result = getAMethodCall("send").getArgument(0) }
private string getAssignedResponseType() {
getAPropertyWrite("responseType").mayHaveStringValue(result)
or
getAPropertyWrite("responseType").mayHaveStringValue("") and
result = "text"
or
not exists(getAPropertyWrite("responseType")) and
result = "text"
}
private DataFlow::FunctionNode getAnEventListener() {
result = getAPropertyWrite("on" + any(string s)).getRhs().getAFunctionValue()
or
result = getAMethodCall("addEventListener").getArgument(1).getAFunctionValue()
}
private DataFlow::SourceNode getAnAlias() {
result = this
or
// The value of `this` in an event listener refers to the XHR object
result = getAnEventListener().getReceiver()
}
override DataFlow::Node getAResponseDataNode(string responseType, boolean promise) {
promise = false and
(
exists(string prop | result = getAnAlias().getAPropertyRead(prop) |
prop = "response" and responseType = getAssignedResponseType()
ClosureXhrIoRequest() {
xhrIo = Closure::moduleImport("goog.net.XhrIo") and
(
this = xhrIo.getAMethodCall("send") and static = true
or
prop = "responseText" and responseType = "text"
or
prop = "statusText" and responseType = "text"
or
prop = "responseXML" and responseType = "document"
this = xhrIo.getAnInstantiation().getAMethodCall("send") and static = false
)
}
override DataFlow::Node getUrl() { result = getArgument(0) }
override DataFlow::Node getHost() { none() }
override DataFlow::Node getADataNode() {
result = getArgument(2) or
result = getArgument(3)
}
/** Gets an event listener with `this` bound to this object. */
DataFlow::FunctionNode getAnEventListener() {
result = getAnArgument().getAFunctionValue()
or
responseType = "text" and
static = false and
exists(DataFlow::MethodCallNode listen, string name |
listen = getAMethodCall(name) and
(name = "listen" or name = "listenOnce") and
xhrIo.flowsTo(listen.getArgument(3)) and
result = listen
)
}
DataFlow::SourceNode getAnAlias() {
static = false and
result = xhrIo
or
result = getAnEventListener().getReceiver()
}
private string getAssignedResponseType() {
getAMethodCall("setResponseType").getArgument(0).mayHaveStringValue(result)
or
not exists(getAMethodCall("setResponseType")) and
result = "text"
}
override DataFlow::Node getAResponseDataNode(string responseType, boolean promise) {
promise = false and
exists(string method | result = getAnAlias().getAMethodCall(method) |
method = "getAllResponseHeaders" or
method = "getResponseHeader"
method = "getResponse" and responseType = getAssignedResponseType()
or
method = "getResponseHeader" and responseType = "text"
or
method = "getResponseJson" and responseType = "json"
or
method = "getResponseText" and responseType = "text"
or
method = "getResponseXml" and responseType = "document"
or
method = "getStatusText" and responseType = "text"
)
)
}
}
/**
* A model of a URL request made using the `XhrIo` class from the closure library.
*/
private class ClosureXhrIoRequest extends ClientRequest::Range {
DataFlow::SourceNode xhrIo;
boolean static;
ClosureXhrIoRequest() {
xhrIo = Closure::moduleImport("goog.net.XhrIo") and
(
this = xhrIo.getAMethodCall("send") and static = true
or
this = xhrIo.getAnInstantiation().getAMethodCall("send") and static = false
)
}
override DataFlow::Node getUrl() { result = getArgument(0) }
override DataFlow::Node getHost() { none() }
override DataFlow::Node getADataNode() {
result = getArgument(2) or
result = getArgument(3)
}
/** Gets an event listener with `this` bound to this object. */
private DataFlow::FunctionNode getAnEventListener() {
result = getAnArgument().getAFunctionValue()
or
static = false and
exists(DataFlow::MethodCallNode listen, string name |
listen = getAMethodCall(name) and
(name = "listen" or name = "listenOnce") and
xhrIo.flowsTo(listen.getArgument(3)) and
result = listen
)
}
private DataFlow::SourceNode getAnAlias() {
static = false and
result = xhrIo
or
result = getAnEventListener().getReceiver()
}
private string getAssignedResponseType() {
getAMethodCall("setResponseType").getArgument(0).mayHaveStringValue(result)
or
not exists(getAMethodCall("setResponseType")) and
result = "text"
}
override DataFlow::Node getAResponseDataNode(string responseType, boolean promise) {
promise = false and
exists(string method | result = getAnAlias().getAMethodCall(method) |
method = "getResponse" and responseType = getAssignedResponseType()
or
method = "getResponseHeader" and responseType = "text"
or
method = "getResponseJson" and responseType = "json"
or
method = "getResponseText" and responseType = "text"
or
method = "getResponseXml" and responseType = "document"
or
method = "getStatusText" and responseType = "text"
)
}
}
}