circumvent CGI 1.1 specs (PATH_INFO is quoted)

This commit is contained in:
posativ 2012-10-19 15:51:51 +02:00
parent 3cb623e7c2
commit f2eff22ff7
5 changed files with 42 additions and 15 deletions

View File

@ -20,7 +20,7 @@
# #
# Isso a lightweight Disqus alternative # Isso a lightweight Disqus alternative
__version__ = '0.1' __version__ = '0.2'
import json import json
@ -33,22 +33,24 @@ from werkzeug.exceptions import HTTPException, NotFound, InternalServerError
from isso import admin, comment, db, utils from isso import admin, comment, db, utils
# override default json :func:`dumps`.
_dumps = json.dumps _dumps = json.dumps
setattr(json, 'dumps', lambda obj, **kw: _dumps(obj, cls=utils.IssoEncoder, **kw)) setattr(json, 'dumps', lambda obj, **kw: _dumps(obj, cls=utils.IssoEncoder, **kw))
# yep. lazy.
url = lambda path, endpoint, methods: Rule(path, endpoint=endpoint, methods=methods) url = lambda path, endpoint, methods: Rule(path, endpoint=endpoint, methods=methods)
url_map = Map([ url_map = Map([
# moderation panel # moderation panel
url('/', 'admin.index', ['GET', 'POST']), url('/', 'admin.index', ['GET', 'POST']),
# comments API # comment API, note that the client side quotes the URL, but this is
url('/comment/<string:path>/', 'comment.get', ['GET']), # actually unnecessary. PEP 333 aka WSGI always unquotes PATH_INFO.
url('/comment/<string:path>/new', 'comment.create', ['POST']), url('/comment/<re(".+"):path>/', 'comment.get', ['GET']),
url('/comment/<string:path>/<int:id>', 'comment.get', ['GET']), url('/comment/<re(".+"):path>/new', 'comment.create', ['POST']),
url('/comment/<string:path>/<int:id>', 'comment.modify', ['PUT', 'DELETE']), url('/comment/<re(".+"):path>/<int:id>', 'comment.get', ['GET']),
]) url('/comment/<re(".+"):path>/<int:id>', 'comment.modify', ['PUT', 'DELETE']),
], converters={'re': utils.RegexConverter})
class Isso: class Isso:
@ -59,7 +61,7 @@ class Isso:
SQLITE = None SQLITE = None
HOST = 'http://localhost:8000/' HOST = 'http://localhost:8000/'
MAX_AGE = 15*60 MAX_AGE = 15 * 60
def __init__(self, conf): def __init__(self, conf):
@ -101,5 +103,10 @@ class Isso:
def main(): def main():
app = Isso({'SQLITE': '/tmp/sqlite.db'}) from os.path import join, dirname
run_simple('127.0.0.1', 8080, app) from werkzeug.wsgi import SharedDataMiddleware
app = Isso({'SQLITE': '/tmp/sqlite.db', 'PRODUCTION': False})
app = SharedDataMiddleware(app,{
'/static': join(dirname(__file__), 'static')})
run_simple('127.0.0.1', 8000, app, use_reloader=True)

View File

@ -13,7 +13,7 @@ from isso import json, models, utils
def create(app, environ, request, path): def create(app, environ, request, path):
if app.PRODUCTION and not utils.urlexists(app.HOST, path): if app.PRODUCTION and not utils.urlexists(app.HOST, '/' + path):
return abort(404) return abort(404)
try: try:

View File

@ -99,7 +99,7 @@ class SQLite(Abstract):
def add(self, path, c): def add(self, path, c):
with sqlite3.connect(self.dbpath) as con: with sqlite3.connect(self.dbpath) as con:
keys = ','.join(self.fields) keys = ','.join(self.fields)
values = ','.join('?'*len(self.fields)) values = ','.join('?' * len(self.fields))
con.execute('INSERT INTO comments (%s) VALUES (%s);' % (keys, values), ( con.execute('INSERT INTO comments (%s) VALUES (%s);' % (keys, values), (
0, path, c.created, c.modified, c.text, c.author, c.email, c.website, 0, path, c.created, c.modified, c.text, c.author, c.email, c.website,
c.parent, self.mode) c.parent, self.mode)

View File

@ -8,6 +8,7 @@ import socket
import httplib import httplib
import urlparse import urlparse
import contextlib import contextlib
import werkzeug.routing
from isso.models import Comment from isso.models import Comment
@ -21,6 +22,12 @@ class IssoEncoder(json.JSONEncoder):
return json.JSONEncoder.default(self, obj) return json.JSONEncoder.default(self, obj)
class RegexConverter(werkzeug.routing.BaseConverter):
def __init__(self, url_map, *items):
super(RegexConverter, self).__init__(url_map)
self.regex = items[0]
def urlexists(host, path): def urlexists(host, path):
with contextlib.closing(httplib.HTTPConnection(host)) as con: with contextlib.closing(httplib.HTTPConnection(host)) as con:
try: try:

View File

@ -1,4 +1,5 @@
import urllib
import tempfile import tempfile
import unittest import unittest
@ -64,7 +65,7 @@ class TestComments(unittest.TestCase):
def testGetInvalid(self): def testGetInvalid(self):
assert self.get('/comment/path/123').status_code == 404 assert self.get('/comment/path/123').status_code == 404
assert self.get('/comment/path/spam').status_code == 404 assert self.get('/comment/path/spam/123').status_code == 404
assert self.get('/comment/foo/').status_code == 404 assert self.get('/comment/foo/').status_code == 404
def testUpdate(self): def testUpdate(self):
@ -102,3 +103,15 @@ class TestComments(unittest.TestCase):
assert self.get('/comment/path/1').status_code == 200 assert self.get('/comment/path/1').status_code == 200
assert self.get('/comment/path/2').status_code == 200 assert self.get('/comment/path/2').status_code == 200
def testPathVariations(self):
paths = ['/sub/path/', '/path.html', '/sub/path.html', '%2Fpath/%2F']
for path in paths:
assert self.post('/comment/' + path + '/new',
data=json.dumps(comment(text='...'))).status_code == 201
for path in paths:
assert self.get('/comment/' + path)
assert self.get('/comment/' + path + '/1')