Python: Model CookieWrite for aiohttp

This commit is contained in:
Rasmus Wriedt Larsen
2021-06-24 15:14:59 +02:00
parent e1af1f11ee
commit 226425e831
2 changed files with 133 additions and 29 deletions

View File

@@ -295,6 +295,36 @@ module AiohttpWebModel {
DataFlow::Node instance() { instance(DataFlow::TypeTracker::end()).flowsTo(result) }
}
/**
* Provides models for the `aiohttp.web.Response` class
*
* See https://docs.aiohttp.org/en/stable/web_reference.html#response-classes
*/
module Response {
/**
* A source of instances of `aiohttp.web.Response`, extend this class to model new instances.
*
* This can include instantiations of the class, return values from function
* calls, or a special parameter that will be set when functions are called by an external
* library.
*
* Use `Response::instance()` predicate to get
* references to instances of `aiohttp.web.Response`.
*/
abstract class InstanceSource extends DataFlow::LocalSourceNode { }
/** Gets a reference to an instance of `aiohttp.web.Response`. */
private DataFlow::LocalSourceNode instance(DataFlow::TypeTracker t) {
t.start() and
result instanceof InstanceSource
or
exists(DataFlow::TypeTracker t2 | result = instance(t2).track(t2, t))
}
/** Gets a reference to an instance of `aiohttp.web.Response`. */
DataFlow::Node instance() { instance(DataFlow::TypeTracker::end()).flowsTo(result) }
}
/**
* Provides models for the `aiohttp.StreamReader` class
*
@@ -488,35 +518,46 @@ module AiohttpWebModel {
* - https://docs.aiohttp.org/en/stable/web_quickstart.html#aiohttp-web-exceptions
*/
class AiohttpWebResponseInstantiation extends HTTP::Server::HttpResponse::Range,
DataFlow::CallCfgNode {
Response::InstanceSource, DataFlow::CallCfgNode {
API::Node apiNode;
AiohttpWebResponseInstantiation() {
this = API::moduleImport("aiohttp").getMember("web").getMember("Response").getACall()
or
exists(string httpExceptionClassName |
httpExceptionClassName in [
"HTTPException", "HTTPSuccessful", "HTTPOk", "HTTPCreated", "HTTPAccepted",
"HTTPNonAuthoritativeInformation", "HTTPNoContent", "HTTPResetContent",
"HTTPPartialContent", "HTTPRedirection", "HTTPMultipleChoices", "HTTPMovedPermanently",
"HTTPFound", "HTTPSeeOther", "HTTPNotModified", "HTTPUseProxy", "HTTPTemporaryRedirect",
"HTTPPermanentRedirect", "HTTPError", "HTTPClientError", "HTTPBadRequest",
"HTTPUnauthorized", "HTTPPaymentRequired", "HTTPForbidden", "HTTPNotFound",
"HTTPMethodNotAllowed", "HTTPNotAcceptable", "HTTPProxyAuthenticationRequired",
"HTTPRequestTimeout", "HTTPConflict", "HTTPGone", "HTTPLengthRequired",
"HTTPPreconditionFailed", "HTTPRequestEntityTooLarge", "HTTPRequestURITooLong",
"HTTPUnsupportedMediaType", "HTTPRequestRangeNotSatisfiable", "HTTPExpectationFailed",
"HTTPMisdirectedRequest", "HTTPUnprocessableEntity", "HTTPFailedDependency",
"HTTPUpgradeRequired", "HTTPPreconditionRequired", "HTTPTooManyRequests",
"HTTPRequestHeaderFieldsTooLarge", "HTTPUnavailableForLegalReasons", "HTTPServerError",
"HTTPInternalServerError", "HTTPNotImplemented", "HTTPBadGateway",
"HTTPServiceUnavailable", "HTTPGatewayTimeout", "HTTPVersionNotSupported",
"HTTPVariantAlsoNegotiates", "HTTPInsufficientStorage", "HTTPNotExtended",
"HTTPNetworkAuthenticationRequired"
] and
this =
API::moduleImport("aiohttp").getMember("web").getMember(httpExceptionClassName).getACall()
this = apiNode.getACall() and
(
apiNode = API::moduleImport("aiohttp").getMember("web").getMember("Response")
or
exists(string httpExceptionClassName |
httpExceptionClassName in [
"HTTPException", "HTTPSuccessful", "HTTPOk", "HTTPCreated", "HTTPAccepted",
"HTTPNonAuthoritativeInformation", "HTTPNoContent", "HTTPResetContent",
"HTTPPartialContent", "HTTPRedirection", "HTTPMultipleChoices",
"HTTPMovedPermanently", "HTTPFound", "HTTPSeeOther", "HTTPNotModified",
"HTTPUseProxy", "HTTPTemporaryRedirect", "HTTPPermanentRedirect", "HTTPError",
"HTTPClientError", "HTTPBadRequest", "HTTPUnauthorized", "HTTPPaymentRequired",
"HTTPForbidden", "HTTPNotFound", "HTTPMethodNotAllowed", "HTTPNotAcceptable",
"HTTPProxyAuthenticationRequired", "HTTPRequestTimeout", "HTTPConflict", "HTTPGone",
"HTTPLengthRequired", "HTTPPreconditionFailed", "HTTPRequestEntityTooLarge",
"HTTPRequestURITooLong", "HTTPUnsupportedMediaType", "HTTPRequestRangeNotSatisfiable",
"HTTPExpectationFailed", "HTTPMisdirectedRequest", "HTTPUnprocessableEntity",
"HTTPFailedDependency", "HTTPUpgradeRequired", "HTTPPreconditionRequired",
"HTTPTooManyRequests", "HTTPRequestHeaderFieldsTooLarge",
"HTTPUnavailableForLegalReasons", "HTTPServerError", "HTTPInternalServerError",
"HTTPNotImplemented", "HTTPBadGateway", "HTTPServiceUnavailable",
"HTTPGatewayTimeout", "HTTPVersionNotSupported", "HTTPVariantAlsoNegotiates",
"HTTPInsufficientStorage", "HTTPNotExtended", "HTTPNetworkAuthenticationRequired"
] and
apiNode = API::moduleImport("aiohttp").getMember("web").getMember(httpExceptionClassName)
)
)
}
/**
* INTERNAL: Do not use.
*
* Get the internal `API::Node` that this is call of.
*/
API::Node getApiNode() { result = apiNode }
override DataFlow::Node getBody() {
result in [this.getArgByName("text"), this.getArgByName("body")]
}
@@ -534,6 +575,11 @@ module AiohttpWebModel {
}
}
/** Gets an HTTP response instance. */
private API::Node aiohttpResponseInstance() {
result = any(AiohttpWebResponseInstantiation call).getApiNode().getReturn()
}
/**
* An instantiation of aiohttp.web HTTP redirect exception.
*
@@ -559,4 +605,62 @@ module AiohttpWebModel {
result in [this.getArg(0), this.getArgByName("location")]
}
}
/**
* A call to `set_cookie` on a HTTP Response.
*/
class AiohttpResponseSetCookieCall extends HTTP::Server::CookieWrite::Range, DataFlow::CallCfgNode {
AiohttpResponseSetCookieCall() {
this = aiohttpResponseInstance().getMember("set_cookie").getACall()
}
override DataFlow::Node getHeaderArg() { none() }
override DataFlow::Node getNameArg() { result in [this.getArg(0), this.getArgByName("name")] }
override DataFlow::Node getValueArg() { result in [this.getArg(1), this.getArgByName("value")] }
}
/**
* A call to `del_cookie` on a HTTP Response.
*/
class AiohttpResponseDelCookieCall extends HTTP::Server::CookieWrite::Range, DataFlow::CallCfgNode {
AiohttpResponseDelCookieCall() {
this = aiohttpResponseInstance().getMember("del_cookie").getACall()
}
override DataFlow::Node getHeaderArg() { none() }
override DataFlow::Node getNameArg() { result in [this.getArg(0), this.getArgByName("name")] }
override DataFlow::Node getValueArg() { none() }
}
/**
* A dict-like write to an item of the `cookies` attribute on a HTTP response, such as
* `response.cookies[name] = value`.
*/
class AiohttpResponseCookieSubscriptWrite extends HTTP::Server::CookieWrite::Range {
DataFlow::Node index;
DataFlow::Node value;
AiohttpResponseCookieSubscriptWrite() {
exists(Assign assign, Subscript subscript |
// there doesn't seem to be any _good_ choice for `this`, so just picking the
// whole subscript...
this.asExpr() = subscript
|
assign.getATarget() = subscript and
subscript.getObject() = aiohttpResponseInstance().getMember("cookies").getAUse().asExpr() and
index.asExpr() = subscript.getIndex() and
value.asExpr() = assign.getValue()
)
}
override DataFlow::Node getHeaderArg() { none() }
override DataFlow::Node getNameArg() { result = index }
override DataFlow::Node getValueArg() { result = value }
}
}

View File

@@ -72,11 +72,11 @@ async def redirect_302(request): # $ requestHandler
@routes.get("/setting_cookie") # $ routeSetup="/setting_cookie"
async def setting_cookie(request): # $ requestHandler
resp = web.Response(text="foo") # $ HttpResponse mimetype=text/plain responseBody="foo"
resp.cookies["key"] = "value" # $ MISSING: CookieWrite CookieName="key" CookieValue="value"
resp.cookies["key"] = "value" # $ CookieWrite CookieName="key" CookieValue="value"
resp.headers["Set-Cookie"] = "key2=value2" # $ MISSING: CookieWrite CookieRawHeader="key2=value2"
resp.set_cookie("key3", "value3") # $ MISSING: CookieWrite CookieName="key3" CookieValue="value3"
resp.set_cookie(name="key3", value="value3") # $ MISSING: CookieWrite CookieName="key3" CookieValue="value3"
resp.del_cookie("key4") # $ MISSING: CookieWrite CookieName="key4"
resp.set_cookie("key3", "value3") # $ CookieWrite CookieName="key3" CookieValue="value3"
resp.set_cookie(name="key3", value="value3") # $ CookieWrite CookieName="key3" CookieValue="value3"
resp.del_cookie("key4") # $ CookieWrite CookieName="key4"
return resp