Skip to content

Commit 8a1998e

Browse files
Add additional header write models for aiohttp and tornado + added qldoc
1 parent a1945c3 commit 8a1998e

File tree

6 files changed

+105
-4
lines changed

6 files changed

+105
-4
lines changed

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

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -706,6 +706,33 @@ module AiohttpWebModel {
706706

707707
override DataFlow::Node getValueArg() { result = value }
708708
}
709+
710+
/**
711+
* A dict-like write to an item of the `headers` attribute on a HTTP response, such as
712+
* `response.headers[name] = value`.
713+
*/
714+
class AiohttpResponseHeaderSubscriptWrite extends Http::Server::ResponseHeaderWrite::Range {
715+
DataFlow::Node index;
716+
DataFlow::Node value;
717+
718+
AiohttpResponseHeaderSubscriptWrite() {
719+
exists(API::Node i |
720+
value = aiohttpResponseInstance().getMember("headers").getSubscriptAt(i).asSink() and
721+
index = i.asSink() and
722+
// To give `this` a value, we need to choose between either LHS or RHS,
723+
// and just go with the RHS as it is readily available
724+
this = value
725+
)
726+
}
727+
728+
override DataFlow::Node getNameArg() { result = index }
729+
730+
override DataFlow::Node getValueArg() { result = value }
731+
732+
override predicate nameAllowsNewline() { none() }
733+
734+
override predicate valueAllowsNewline() { none() }
735+
}
709736
}
710737

711738
/**

python/ql/lib/semmle/python/frameworks/Django.qll

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2240,6 +2240,10 @@ module PrivateDjango {
22402240
override DataFlow::Node getValueArg() { result = value }
22412241
}
22422242

2243+
/**
2244+
* A dict-like write to an item of the `headers` attribute on a HTTP response, such as
2245+
* `response.headers[name] = value`.
2246+
*/
22432247
class DjangoResponseHeaderSubscriptWrite extends Http::Server::ResponseHeaderWrite::Range {
22442248
DataFlow::Node index;
22452249
DataFlow::Node value;

python/ql/lib/semmle/python/frameworks/FastApi.qll

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,10 @@ module FastApi {
384384
override predicate valueAllowsNewline() { none() }
385385
}
386386

387+
/**
388+
* A dict-like write to an item of the `headers` attribute on a HTTP response, such as
389+
* `response.headers[name] = value`.
390+
*/
387391
class HeaderSubscriptWrite extends Http::Server::ResponseHeaderWrite::Range {
388392
DataFlow::Node index;
389393
DataFlow::Node value;

python/ql/lib/semmle/python/frameworks/Tornado.qll

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,50 @@ module Tornado {
6363

6464
override string getAsyncMethodName() { none() }
6565
}
66+
67+
/**
68+
* A dict-like write to an item of an `HTTPHeaders` object.
69+
*/
70+
private class TornadoHeaderSubscriptWrite extends Http::Server::ResponseHeaderWrite::Range {
71+
DataFlow::Node index;
72+
DataFlow::Node value;
73+
74+
TornadoHeaderSubscriptWrite() {
75+
exists(SubscriptNode subscript |
76+
subscript.getObject() = instance().asCfgNode() and
77+
value.asCfgNode() = subscript.(DefinitionNode).getValue() and
78+
index.asCfgNode() = subscript.getIndex() and
79+
this.asCfgNode() = subscript
80+
)
81+
}
82+
83+
override DataFlow::Node getNameArg() { result = index }
84+
85+
override DataFlow::Node getValueArg() { result = value }
86+
87+
override predicate nameAllowsNewline() { none() }
88+
89+
override predicate valueAllowsNewline() { none() }
90+
}
91+
92+
/**
93+
* A call to `HTTPHeaders.add`.
94+
*/
95+
private class TornadoHeadersAppendCall extends Http::Server::ResponseHeaderWrite::Range,
96+
DataFlow::MethodCallNode
97+
{
98+
TornadoHeadersAppendCall() { this.calls(instance(), "append") }
99+
100+
override DataFlow::Node getNameArg() { result = [this.getArg(0), this.getArgByName("name")] }
101+
102+
override DataFlow::Node getValueArg() {
103+
result in [this.getArg(1), this.getArgByName("value")]
104+
}
105+
106+
override predicate nameAllowsNewline() { none() }
107+
108+
override predicate valueAllowsNewline() { none() }
109+
}
66110
}
67111

68112
// ---------------------------------------------------------------------------
@@ -209,6 +253,25 @@ module Tornado {
209253
this.(DataFlow::AttrRead).getAttributeName() = "request"
210254
}
211255
}
256+
257+
/** A call to `RequestHandler.set_header` or `RequestHandler.add_header` */
258+
private class TornadoSetHeaderCall extends Http::Server::ResponseHeaderWrite::Range,
259+
DataFlow::MethodCallNode
260+
{
261+
TornadoSetHeaderCall() { this.calls(instance(), ["set_header", "add_header"]) }
262+
263+
override DataFlow::Node getNameArg() {
264+
result = [this.getArg(0), this.getArgByName("name")]
265+
}
266+
267+
override DataFlow::Node getValueArg() {
268+
result in [this.getArg(1), this.getArgByName("value")]
269+
}
270+
271+
override predicate nameAllowsNewline() { none() }
272+
273+
override predicate valueAllowsNewline() { none() }
274+
}
212275
}
213276

214277
/**

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ async def streaming_response(request): # $ requestHandler
9696
async def setting_cookie(request): # $ requestHandler
9797
resp = web.Response(text="foo") # $ HttpResponse mimetype=text/plain responseBody="foo"
9898
resp.cookies["key"] = "value" # $ CookieWrite CookieName="key" CookieValue="value"
99-
resp.headers["Set-Cookie"] = "key2=value2" # $ MISSING: CookieWrite CookieRawHeader="key2=value2"
99+
resp.headers["Set-Cookie"] = "key2=value2" # $ headerWriteName="Set-Cookie" headerWriteValue="key2=value2" CookieWrite CookieRawHeader="key2=value2"
100100
resp.set_cookie("key3", "value3") # $ CookieWrite CookieName="key3" CookieValue="value3"
101101
resp.set_cookie(name="key3", value="value3") # $ CookieWrite CookieName="key3" CookieValue="value3"
102102
resp.del_cookie("key4") # $ CookieWrite CookieName="key4"

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

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,10 @@ def get(self): # $ requestHandler
2424
# what matters.
2525

2626
self.write("foo") # $ HttpResponse mimetype=text/html responseBody="foo"
27-
self.set_header("Content-Type", "text/plain; charset=utf-8")
27+
self.set_header("Content-Type", "text/plain; charset=utf-8") # $ headerWriteName="Content-Type" headerWriteValue="text/plain; charset=utf-8"
2828

2929
def post(self): # $ requestHandler
30-
self.set_header("Content-Type", "text/plain; charset=utf-8")
30+
self.set_header("Content-Type", "text/plain; charset=utf-8") # $ headerWriteName="Content-Type" headerWriteValue="text/plain; charset=utf-8"
3131
self.write("foo") # $ HttpResponse responseBody="foo" MISSING: mimetype=text/plain SPURIOUS: mimetype=text/html
3232

3333

@@ -67,7 +67,10 @@ def get(self): # $ requestHandler
6767
self.write("foo") # $ HttpResponse mimetype=text/html responseBody="foo"
6868
self.set_cookie("key", "value") # $ CookieWrite CookieName="key" CookieValue="value"
6969
self.set_cookie(name="key", value="value") # $ CookieWrite CookieName="key" CookieValue="value"
70-
self.set_header("Set-Cookie", "key2=value2") # $ MISSING: CookieWrite CookieRawHeader="key2=value2"
70+
self.set_header("Set-Cookie", "key2=value2") # $ headerWriteName="Set-Cookie" headerWriteValue="key2=value2" CookieWrite CookieRawHeader="key2=value2"
71+
self.add_header("Set-Cookie", "key3=value3") # $ headerWriteName="Set-Cookie" headerWriteValue="key3=value3" CookieWrite CookieRawHeader="key3=value3"
72+
self.request.headers.append("Set-Cookie", "key4=value4") # $ headerWriteName="Set-Cookie" headerWriteValue="key4=value4" CookieWrite CookieRawHeader="key4=value4"
73+
self.request.headers["Set-Cookie"] = "key5=value5" # $ headerWriteName="Set-Cookie" headerWriteValue="key5=value5" CookieWrite CookieRawHeader="key5=value5"
7174

7275

7376
def make_app():

0 commit comments

Comments
 (0)