Skip to content

Commit 2dbbf52

Browse files
committed
Python: Model HTTP responses in aiohttp.web
1 parent 735df45 commit 2dbbf52

File tree

3 files changed

+119
-34
lines changed

3 files changed

+119
-34
lines changed

python/ql/src/semmle/python/frameworks/Aiohttp.qll

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -391,4 +391,89 @@ module AiohttpWebModel {
391391
this.(DataFlow::AttrRead).getAttributeName() in ["url", "rel_url"]
392392
}
393393
}
394+
395+
// ---------------------------------------------------------------------------
396+
// aiohttp.web Response modeling
397+
// ---------------------------------------------------------------------------
398+
/**
399+
* An instantiation of `aiohttp.web.Response`.
400+
*
401+
* Note that `aiohttp.web.HTTPException` (and it's subclasses) is a subclass of `aiohttp.web.Response`.
402+
*
403+
* See
404+
* - https://docs.aiohttp.org/en/stable/web_reference.html#aiohttp.web.Response
405+
* - https://docs.aiohttp.org/en/stable/web_quickstart.html#aiohttp-web-exceptions
406+
*/
407+
class AiohttpWebResponseInstantiation extends HTTP::Server::HttpResponse::Range,
408+
DataFlow::CallCfgNode {
409+
AiohttpWebResponseInstantiation() {
410+
this = API::moduleImport("aiohttp").getMember("web").getMember("Response").getACall()
411+
or
412+
exists(string httpExceptionClassName |
413+
httpExceptionClassName in [
414+
"HTTPException", "HTTPSuccessful", "HTTPOk", "HTTPCreated", "HTTPAccepted",
415+
"HTTPNonAuthoritativeInformation", "HTTPNoContent", "HTTPResetContent",
416+
"HTTPPartialContent", "HTTPRedirection", "HTTPMultipleChoices", "HTTPMovedPermanently",
417+
"HTTPFound", "HTTPSeeOther", "HTTPNotModified", "HTTPUseProxy", "HTTPTemporaryRedirect",
418+
"HTTPPermanentRedirect", "HTTPError", "HTTPClientError", "HTTPBadRequest",
419+
"HTTPUnauthorized", "HTTPPaymentRequired", "HTTPForbidden", "HTTPNotFound",
420+
"HTTPMethodNotAllowed", "HTTPNotAcceptable", "HTTPProxyAuthenticationRequired",
421+
"HTTPRequestTimeout", "HTTPConflict", "HTTPGone", "HTTPLengthRequired",
422+
"HTTPPreconditionFailed", "HTTPRequestEntityTooLarge", "HTTPRequestURITooLong",
423+
"HTTPUnsupportedMediaType", "HTTPRequestRangeNotSatisfiable", "HTTPExpectationFailed",
424+
"HTTPMisdirectedRequest", "HTTPUnprocessableEntity", "HTTPFailedDependency",
425+
"HTTPUpgradeRequired", "HTTPPreconditionRequired", "HTTPTooManyRequests",
426+
"HTTPRequestHeaderFieldsTooLarge", "HTTPUnavailableForLegalReasons", "HTTPServerError",
427+
"HTTPInternalServerError", "HTTPNotImplemented", "HTTPBadGateway",
428+
"HTTPServiceUnavailable", "HTTPGatewayTimeout", "HTTPVersionNotSupported",
429+
"HTTPVariantAlsoNegotiates", "HTTPInsufficientStorage", "HTTPNotExtended",
430+
"HTTPNetworkAuthenticationRequired"
431+
] and
432+
this =
433+
API::moduleImport("aiohttp").getMember("web").getMember(httpExceptionClassName).getACall()
434+
)
435+
}
436+
437+
override DataFlow::Node getBody() {
438+
result in [this.getArgByName("text"), this.getArgByName("body")]
439+
}
440+
441+
override DataFlow::Node getMimetypeOrContentTypeArg() {
442+
result = this.getArgByName("content_type")
443+
}
444+
445+
override string getMimetypeDefault() {
446+
exists(this.getArgByName("text")) and
447+
result = "text/plain"
448+
or
449+
not exists(this.getArgByName("text")) and
450+
result = "application/octet-stream"
451+
}
452+
}
453+
454+
/**
455+
* An instantiation of aiohttp.web HTTP redirect exception.
456+
*
457+
* See the part about redirects at https://docs.aiohttp.org/en/stable/web_quickstart.html#aiohttp-web-exceptions
458+
*/
459+
class AiohttpRedirectExceptionInstantiation extends AiohttpWebResponseInstantiation,
460+
HTTP::Server::HttpRedirectResponse::Range {
461+
AiohttpRedirectExceptionInstantiation() {
462+
exists(string httpRedirectExceptionClassName |
463+
httpRedirectExceptionClassName in [
464+
"HTTPMultipleChoices", "HTTPMovedPermanently", "HTTPFound", "HTTPSeeOther",
465+
"HTTPNotModified", "HTTPUseProxy", "HTTPTemporaryRedirect", "HTTPPermanentRedirect"
466+
] and
467+
this =
468+
API::moduleImport("aiohttp")
469+
.getMember("web")
470+
.getMember(httpRedirectExceptionClassName)
471+
.getACall()
472+
)
473+
}
474+
475+
override DataFlow::Node getRedirectLocation() {
476+
result in [this.getArg(0), this.getArgByName("location")]
477+
}
478+
}
394479
}

python/ql/test/library-tests/frameworks/aiohttp/response_test.py

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,64 +6,64 @@
66

77
@routes.get("/raw_text") # $ routeSetup="/raw_text"
88
async def raw_text(request): # $ requestHandler
9-
return web.Response(text="foo") # $ MISSING: HttpResponse
9+
return web.Response(text="foo") # $ HttpResponse mimetype=text/plain responseBody="foo"
1010

1111

1212
@routes.get("/raw_body") # $ routeSetup="/raw_body"
1313
async def raw_body(request): # $ requestHandler
14-
return web.Response(body=b"foo") # $ MISSING: HttpResponse
14+
return web.Response(body=b"foo") # $ HttpResponse mimetype=application/octet-stream responseBody=b"foo"
1515

1616

1717
@routes.get("/html_text") # $ routeSetup="/html_text"
1818
async def html_text(request): # $ requestHandler
19-
return web.Response(text="foo", content_type="text/html") # $ MISSING: HttpResponse
19+
return web.Response(text="foo", content_type="text/html") # $ HttpResponse mimetype=text/html responseBody="foo"
2020

2121

2222
@routes.get("/html_body") # $ routeSetup="/html_body"
2323
async def html_body(request): # $ requestHandler
24-
return web.Response(body=b"foo", content_type="text/html") # $ MISSING: HttpResponse
24+
return web.Response(body=b"foo", content_type="text/html") # $ HttpResponse mimetype=text/html responseBody=b"foo"
2525

2626

2727
@routes.get("/html_body_set_later") # $ routeSetup="/html_body_set_later"
2828
async def html_body_set_later(request): # $ requestHandler
29-
resp = web.Response(body=b"foo") # $ MISSING: HttpResponse
30-
resp.content_type = "text/html"
29+
resp = web.Response(body=b"foo") # $ HttpResponse mimetype=application/octet-stream responseBody=b"foo"
30+
resp.content_type = "text/html" # $ MISSING: mimetype=text/html
3131
return resp
3232

3333
# Each HTTP status code has an exception
3434
# see https://docs.aiohttp.org/en/stable/web_quickstart.html#exceptions
3535

3636
@routes.get("/through_200_exception") # $ routeSetup="/through_200_exception"
3737
async def through_200_exception(request): # $ requestHandler
38-
raise web.HTTPOk(text="foo") # $ MISSING: HttpResponse
38+
raise web.HTTPOk(text="foo") # $ HttpResponse mimetype=text/plain responseBody="foo"
3939

4040

4141
@routes.get("/through_200_exception_html") # $ routeSetup="/through_200_exception_html"
4242
async def through_200_exception(request): # $ requestHandler
43-
exception = web.HTTPOk(text="foo") # $ MISSING: HttpResponse
44-
exception.content_type = "text/html"
43+
exception = web.HTTPOk(text="foo") # $ HttpResponse mimetype=text/plain responseBody="foo"
44+
exception.content_type = "text/html" # $ MISSING: mimetype=text/html
4545
raise exception
4646

4747

4848
@routes.get("/through_404_exception") # $ routeSetup="/through_404_exception"
4949
async def through_404_exception(request): # $ requestHandler
50-
raise web.HTTPNotFound(text="foo") # $ MISSING: HttpResponse
50+
raise web.HTTPNotFound(text="foo") # $ HttpResponse mimetype=text/plain responseBody="foo"
5151

5252

5353
@routes.get("/redirect_301") # $ routeSetup="/redirect_301"
5454
async def redirect_301(request): # $ requestHandler
5555
if not "kwarg" in request.url.query:
56-
raise web.HTTPMovedPermanently("/login") # $ MISSING: HttpResponse HttpRedirectResponse
56+
raise web.HTTPMovedPermanently("/login") # $ HttpResponse HttpRedirectResponse mimetype=application/octet-stream redirectLocation="/login"
5757
else:
58-
raise web.HTTPMovedPermanently(location="/logout") # $ MISSING: HttpResponse HttpRedirectResponse
58+
raise web.HTTPMovedPermanently(location="/logout") # $ HttpResponse HttpRedirectResponse mimetype=application/octet-stream redirectLocation="/logout"
5959

6060

6161
@routes.get("/redirect_302") # $ routeSetup="/redirect_302"
6262
async def redirect_302(request): # $ requestHandler
6363
if not "kwarg" in request.url.query:
64-
raise web.HTTPFound("/login") # $ MISSING: HttpResponse HttpRedirectResponse
64+
raise web.HTTPFound("/login") # $ HttpResponse HttpRedirectResponse mimetype=application/octet-stream redirectLocation="/login"
6565
else:
66-
raise web.HTTPFound(location="/logout") # $ MISSING: HttpResponse HttpRedirectResponse
66+
raise web.HTTPFound(location="/logout") # $ HttpResponse HttpRedirectResponse mimetype=application/octet-stream redirectLocation="/logout"
6767

6868

6969
if __name__ == "__main__":

python/ql/test/library-tests/frameworks/aiohttp/routing_test.py

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,13 @@
1616

1717
# `app.add_routes` with list
1818
async def foo(request): # $ requestHandler
19-
return web.Response(text="foo")
19+
return web.Response(text="foo") # $ HttpResponse
2020

2121
async def foo2(request): # $ requestHandler
22-
return web.Response(text="foo2")
22+
return web.Response(text="foo2") # $ HttpResponse
2323

2424
async def foo3(request): # $ requestHandler
25-
return web.Response(text="foo3")
25+
return web.Response(text="foo3") # $ HttpResponse
2626

2727
app.add_routes([
2828
web.get("/foo", foo), # $ routeSetup="/foo"
@@ -36,32 +36,32 @@ async def foo3(request): # $ requestHandler
3636

3737
@routes.get("/bar") # $ routeSetup="/bar"
3838
async def bar(request): # $ requestHandler
39-
return web.Response(text="bar")
39+
return web.Response(text="bar") # $ HttpResponse
4040

4141
@routes.route("*", "/bar2") # $ routeSetup="/bar2"
4242
async def bar2(request): # $ requestHandler
43-
return web.Response(text="bar2")
43+
return web.Response(text="bar2") # $ HttpResponse
4444

4545
@routes.get(path="/bar3") # $ routeSetup="/bar3"
4646
async def bar3(request): # $ requestHandler
47-
return web.Response(text="bar3")
47+
return web.Response(text="bar3") # $ HttpResponse
4848

4949
app.add_routes(routes)
5050

5151

5252
# `app.router.add_get` / `app.router.add_route`
5353
async def baz(request): # $ requestHandler
54-
return web.Response(text="baz")
54+
return web.Response(text="baz") # $ HttpResponse
5555

5656
app.router.add_get("/baz", baz) # $ routeSetup="/baz"
5757

5858
async def baz2(request): # $ requestHandler
59-
return web.Response(text="baz2")
59+
return web.Response(text="baz2") # $ HttpResponse
6060

6161
app.router.add_route("*", "/baz2", baz2) # $ routeSetup="/baz2"
6262

6363
async def baz3(request): # $ requestHandler
64-
return web.Response(text="baz3")
64+
return web.Response(text="baz3") # $ HttpResponse
6565

6666
app.router.add_get(path="/baz3", handler=baz3) # $ routeSetup="/baz3"
6767

@@ -73,7 +73,7 @@ async def baz3(request): # $ requestHandler
7373
class MyCustomHandlerClass:
7474

7575
async def foo_handler(self, request): # $ MISSING: requestHandler
76-
return web.Response(text="MyCustomHandlerClass.foo")
76+
return web.Response(text="MyCustomHandlerClass.foo") # $ HttpResponse
7777

7878
my_custom_handler = MyCustomHandlerClass()
7979
app.router.add_get("/MyCustomHandlerClass/foo", my_custom_handler.foo_handler) # $ routeSetup="/MyCustomHandlerClass/foo"
@@ -84,7 +84,7 @@ async def foo_handler(self, request): # $ MISSING: requestHandler
8484
# `app.add_routes` with list
8585
class MyWebView1(web.View):
8686
async def get(self): # $ requestHandler
87-
return web.Response(text="MyWebView1.get")
87+
return web.Response(text="MyWebView1.get") # $ HttpResponse
8888

8989
app.add_routes([
9090
web.view("/MyWebView1", MyWebView1) # $ routeSetup="/MyWebView1"
@@ -97,28 +97,28 @@ async def get(self): # $ requestHandler
9797
@routes.view("/MyWebView2") # $ routeSetup="/MyWebView2"
9898
class MyWebView2(web.View):
9999
async def get(self): # $ requestHandler
100-
return web.Response(text="MyWebView2.get")
100+
return web.Response(text="MyWebView2.get") # $ HttpResponse
101101

102102
app.add_routes(routes)
103103

104104

105105
# `app.router.add_view`
106106
class MyWebView3(web.View):
107107
async def get(self): # $ requestHandler
108-
return web.Response(text="MyWebView3.get")
108+
return web.Response(text="MyWebView3.get") # $ HttpResponse
109109

110110
app.router.add_view("/MyWebView3", MyWebView3) # $ routeSetup="/MyWebView3"
111111

112112
# no route-setup
113113
class MyWebViewNoRoute(web.View):
114114
async def get(self): # $ requestHandler
115-
return web.Response(text="MyWebViewNoRoute.get")
115+
return web.Response(text="MyWebViewNoRoute.get") # $ HttpResponse
116116

117117
if len(__name__) < 0: # avoid running, but fool analysis to not consider dead code
118118
# no explicit-view subclass (but route-setup)
119119
class MyWebViewNoSubclassButRoute(somelib.someclass):
120120
async def get(self): # $ requestHandler
121-
return web.Response(text="MyWebViewNoSubclassButRoute.get")
121+
return web.Response(text="MyWebViewNoSubclassButRoute.get") # $ HttpResponse
122122

123123
app.router.add_view("/MyWebViewNoSubclassButRoute", MyWebViewNoSubclassButRoute) # $ routeSetup="/MyWebViewNoSubclassButRoute"
124124

@@ -127,14 +127,14 @@ async def get(self): # $ requestHandler
127127
# for `add_get` only being for async functions.
128128
if True:
129129
async def no_rules(request): # $ requestHandler
130-
return web.Response(text="no_rules")
130+
return web.Response(text="no_rules") # $ HttpResponse
131131

132132
app.router.add_view("/no_rules", no_rules) # $ routeSetup="/no_rules"
133133

134134

135135
class NoRulesView(web.View):
136136
async def get(self): # $ requestHandler
137-
return web.Response(text="NoRulesView.get")
137+
return web.Response(text="NoRulesView.get") # $ HttpResponse
138138

139139
app.router.add_get("/NoRulesView", NoRulesView) # $ routeSetup="/NoRulesView"
140140

@@ -149,7 +149,7 @@ async def get(self): # $ requestHandler
149149
async def matching(request: web.Request): # $ requestHandler
150150
name = request.match_info['name']
151151
number = request.match_info['number']
152-
return web.Response(text="matching name={} number={}".format(name, number))
152+
return web.Response(text="matching name={} number={}".format(name, number)) # $ HttpResponse
153153

154154
app.router.add_get(r"/matching/{name}/{number:\d+}", matching) # $ routeSetup="/matching/{name}/{number:\d+}"
155155

@@ -161,7 +161,7 @@ async def matching(request: web.Request): # $ requestHandler
161161
subapp = web.Application()
162162

163163
async def subapp_handler(request): # $ requestHandler
164-
return web.Response(text="subapp_handler")
164+
return web.Response(text="subapp_handler") # $ HttpResponse
165165

166166
subapp.router.add_get("/subapp_handler", subapp_handler) # $ routeSetup="/subapp_handler"
167167

@@ -177,7 +177,7 @@ async def subapp_handler(request): # $ requestHandler
177177

178178
if True:
179179
async def manual_dispatcher_instance(request): # $ requestHandler
180-
return web.Response(text="manual_dispatcher_instance")
180+
return web.Response(text="manual_dispatcher_instance") # $ HttpResponse
181181

182182
url_dispatcher = web.UrlDispatcher()
183183
url_dispatcher.add_get("/manual_dispatcher_instance", manual_dispatcher_instance) # $ routeSetup="/manual_dispatcher_instance"

0 commit comments

Comments
 (0)