Skip to content

Commit e39a831

Browse files
committed
Merged
2 parents da15bf4 + 5b8d733 commit e39a831

File tree

8 files changed

+55
-15
lines changed

8 files changed

+55
-15
lines changed

.travis.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,8 @@ before_install:
6565
- cd $TRAVIS_BUILD_DIR
6666

6767
install:
68-
- pip install gpg mysqlclient psycopg2 pytz whoosh
68+
- pip install mysqlclient==1.3.13
69+
- pip install gpg psycopg2 pytz whoosh
6970
- pip install pytest-cov codecov
7071

7172
before_script:

CHANGES.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,10 @@ Fixed:
100100
HTTP_X-REQUESTED-WITH to HTTP_X_REQUESTED_WITH. The last is
101101
correct. Also fix roundup-server to produce the latter form. (Patch
102102
by C�dric Krier, reviewed/applied John Rouillard.)
103+
- issue2551035 - fix XSS issue in wsgi and cgi when handing url not
104+
found/404. Reported by hannob at
105+
https://github.com/python/bugs.python.org/issues/34, issue opened by
106+
JulienPalard.
103107

104108
2018-07-13 1.6.0
105109

frontends/roundup.cgi

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ def main(out, err):
181181
request.send_response(404)
182182
request.send_header('Content-Type', 'text/html')
183183
request.end_headers()
184-
out.write(s2b('Not found: %s'%client.path))
184+
out.write(s2b('Not found: %s'%cgi.escape(client.path)))
185185

186186
else:
187187
from roundup.anypy import urllib_

roundup/cgi/client.py

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -237,15 +237,13 @@ class BinaryFieldStorage(cgi.FieldStorage):
237237
needed for handling json and xml data blobs under python
238238
3. Under python 2, str and binary are interchangable, not so
239239
under 3.
240-
241-
Note that there may be places where this should support text mode.
242-
(e.g. a large text file upload??). None are known, but this could be
243-
a problem.
244240
'''
245241
def make_file(self, mode=None):
246242
''' work around https://bugs.python.org/issue27777 '''
247243
import tempfile
248-
return tempfile.TemporaryFile("wb+")
244+
if self.length >= 0:
245+
return tempfile.TemporaryFile("wb+")
246+
return super().make_file()
249247

250248
class Client:
251249
"""Instantiate to handle one CGI request.
@@ -527,7 +525,16 @@ def handle_rest(self):
527525
self.determine_language()
528526
# Open the database as the correct user.
529527
# TODO: add everything to RestfulDispatcher
530-
self.determine_user()
528+
try:
529+
self.determine_user()
530+
except LoginError as err:
531+
self.response_code = http_.client.UNAUTHORIZED
532+
output = b"Invalid Login\n"
533+
self.setHeader("Content-Length", str(len(output)))
534+
self.setHeader("Content-Type", "text/plain")
535+
self.write(output)
536+
return
537+
531538
self.check_anonymous_access()
532539

533540
# Call rest library to handle the request

roundup/cgi/wsgi_handler.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ def __call__(self, environ, start_response):
6969
client.main()
7070
except roundup.cgi.client.NotFound:
7171
request.start_response([('Content-Type', 'text/html')], 404)
72-
request.wfile.write(s2b('Not found: %s'%client.path))
72+
request.wfile.write(s2b('Not found: %s'%cgi.escape(client.path)))
7373

7474
# all body data has been written using wfile
7575
return []

roundup/rest.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ def calculate_etag (node, classname="Missing", id="0"):
132132
'''
133133

134134
items = node.items(protected=True) # include every item
135-
etag = md5(bs2b(repr(items))).hexdigest()
135+
etag = md5(bs2b(repr(sorted(items)))).hexdigest()
136136
logger.debug("object=%s%s; tag=%s; repr=%s", classname, id,
137137
etag, repr(node.items(protected=True)))
138138
return etag
@@ -1336,7 +1336,7 @@ def summary(self, input):
13361336
summary.setdefault(status_name, []).append(issue_object)
13371337
messages.append((num, issue_object))
13381338

1339-
messages.sort(reverse=True)
1339+
sorted(messages, key=lambda tup: tup[0], reverse=True)
13401340

13411341
result = {
13421342
'created': created,
@@ -1450,7 +1450,7 @@ def dispatch(self, method, uri, input):
14501450
output = RoundupJSONEncoder(indent=indent).encode(output)
14511451
elif data_type.lower() == "xml" and dicttoxml:
14521452
self.client.setHeader("Content-Type", "application/xml")
1453-
output = dicttoxml(output, root=False)
1453+
output = b2s(dicttoxml(output, root=False))
14541454
else:
14551455
self.client.response_code = 406
14561456
output = "Content type is not accepted by client"

roundup/scripts/roundup_server.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -377,13 +377,15 @@ def inner_run_cgi(self):
377377
env['QUERY_STRING'] = query
378378
if hasattr(self.headers, 'get_content_type'):
379379
# Python 3. We need the raw header contents.
380-
env['CONTENT_TYPE'] = self.headers.get('content-type')
380+
content_type = self.headers.get('content-type')
381381
elif self.headers.typeheader is None:
382382
# Python 2.
383-
env['CONTENT_TYPE'] = self.headers.type
383+
content_type = self.headers.type
384384
else:
385385
# Python 2.
386-
env['CONTENT_TYPE'] = self.headers.typeheader
386+
content_type = self.headers.typeheader
387+
if content_type:
388+
env['CONTENT_TYPE'] = content_type
387389
length = self.headers.get('content-length')
388390
if length:
389391
env['CONTENT_LENGTH'] = length

test/rest_common.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,32 @@ def testPagination(self):
346346
# page_size < 0
347347
# page_index < 0
348348

349+
def notestEtagGeneration(self):
350+
''' Make sure etag generation is stable
351+
352+
FIXME need to mock somehow date.Date() when creating
353+
the target to be mocked. The differening dates makes
354+
this test impossible.
355+
'''
356+
newuser = self.db.user.create(
357+
username='john',
358+
password=password.Password('random1'),
359+
address='[email protected]',
360+
realname='JohnRandom',
361+
roles='User,Admin'
362+
)
363+
364+
node = self.db.user.getnode(self.joeid)
365+
etag = calculate_etag(node)
366+
items = node.items(protected=True) # include every item
367+
print(repr(items))
368+
print(etag)
369+
self.assertEqual(etag, "6adf97f83acf6453d4a6a4b1070f3754")
370+
371+
etag = calculate_etag(self.db.issue.getnode("1"))
372+
print(etag)
373+
self.assertEqual(etag, "6adf97f83acf6453d4a6a4b1070f3754")
374+
349375
def testEtagProcessing(self):
350376
'''
351377
Etags can come from two places:

0 commit comments

Comments
 (0)