replace abort(code) with proper exceptions and refactor request dispatch
This commit is contained in:
parent
2794734258
commit
74ab58167e
@ -32,7 +32,6 @@ dist = pkg_resources.get_distribution("isso")
|
||||
|
||||
import sys
|
||||
import os
|
||||
import socket
|
||||
import logging
|
||||
|
||||
from os.path import dirname, join
|
||||
@ -47,8 +46,7 @@ import misaka
|
||||
from itsdangerous import URLSafeTimedSerializer
|
||||
|
||||
from werkzeug.routing import Map, Rule
|
||||
from werkzeug.wrappers import Response, Request
|
||||
from werkzeug.exceptions import HTTPException, NotFound, MethodNotAllowed
|
||||
from werkzeug.exceptions import HTTPException, InternalServerError
|
||||
|
||||
from werkzeug.wsgi import SharedDataMiddleware
|
||||
from werkzeug.serving import run_simple
|
||||
@ -56,7 +54,7 @@ from werkzeug.contrib.fixers import ProxyFix
|
||||
|
||||
from isso import db, migrate, views, wsgi
|
||||
from isso.core import ThreadedMixin, uWSGIMixin, Config
|
||||
from isso.utils import parse, http
|
||||
from isso.utils import parse, http, JSONRequest
|
||||
from isso.views import comment
|
||||
|
||||
logging.getLogger('werkzeug').setLevel(logging.ERROR)
|
||||
@ -67,7 +65,10 @@ logging.basicConfig(
|
||||
logger = logging.getLogger("isso")
|
||||
|
||||
|
||||
rules = Map([
|
||||
class Isso(object):
|
||||
|
||||
salt = b"Eech7co8Ohloopo9Ol6baimi"
|
||||
urls = Map([
|
||||
Rule('/new', methods=['POST'], endpoint=views.comment.new),
|
||||
|
||||
Rule('/id/<int:id>', methods=['GET', 'PUT', 'DELETE'], endpoint=views.comment.single),
|
||||
@ -82,11 +83,6 @@ rules = Map([
|
||||
Rule('/check-ip', endpoint=views.comment.checkip)
|
||||
])
|
||||
|
||||
|
||||
class Isso(object):
|
||||
|
||||
salt = b"Eech7co8Ohloopo9Ol6baimi"
|
||||
|
||||
def __init__(self, conf):
|
||||
|
||||
self.conf = conf
|
||||
@ -102,30 +98,27 @@ class Isso(object):
|
||||
return self.signer.loads(obj, max_age=max_age or self.conf.getint('general', 'max-age'))
|
||||
|
||||
def markdown(self, text):
|
||||
return misaka.html(text, extensions=misaka.EXT_STRIKETHROUGH \
|
||||
| misaka.EXT_SUPERSCRIPT | misaka.EXT_AUTOLINK \
|
||||
return misaka.html(text, extensions=misaka.EXT_STRIKETHROUGH
|
||||
| misaka.EXT_SUPERSCRIPT | misaka.EXT_AUTOLINK
|
||||
| misaka.HTML_SKIP_HTML | misaka.HTML_SKIP_IMAGES | misaka.HTML_SAFELINK)
|
||||
|
||||
def dispatch(self, request, start_response):
|
||||
adapter = rules.bind_to_environ(request.environ)
|
||||
def dispatch(self, request):
|
||||
adapter = Isso.urls.bind_to_environ(request.environ)
|
||||
try:
|
||||
handler, values = adapter.match()
|
||||
return handler(self, request.environ, request, **values)
|
||||
except NotFound:
|
||||
return Response('Not Found', 404)
|
||||
except MethodNotAllowed:
|
||||
return Response("Yup.", 200)
|
||||
except HTTPException as e:
|
||||
return e
|
||||
else:
|
||||
try:
|
||||
response = handler(self, request.environ, request, **values)
|
||||
except HTTPException as e:
|
||||
return e
|
||||
except Exception:
|
||||
logger.exception("%s %s", request.method, request.environ["PATH_INFO"])
|
||||
return InternalServerError()
|
||||
|
||||
def wsgi_app(self, environ, start_response):
|
||||
|
||||
response = self.dispatch(Request(environ), start_response)
|
||||
|
||||
# add CORS header
|
||||
if hasattr(response, 'headers') and 'HTTP_ORIGN' in environ:
|
||||
for host in self.conf.getiter('general', 'host'):
|
||||
if environ["HTTP_ORIGIN"] == host.rstrip("/"):
|
||||
if request.environ.get("HTTP_ORIGIN", None) == host.rstrip("/"):
|
||||
origin = host.rstrip("/")
|
||||
break
|
||||
else:
|
||||
@ -137,6 +130,11 @@ class Isso(object):
|
||||
hdrs["Access-Control-Allow-Credentials"] = "true"
|
||||
hdrs["Access-Control-Allow-Methods"] = "GET, POST, PUT, DELETE"
|
||||
|
||||
return response
|
||||
|
||||
def wsgi_app(self, environ, start_response):
|
||||
|
||||
response = self.dispatch(JSONRequest(environ))
|
||||
return response(environ, start_response)
|
||||
|
||||
def __call__(self, environ, start_response):
|
||||
|
@ -2,11 +2,15 @@
|
||||
|
||||
from __future__ import division
|
||||
|
||||
import json
|
||||
import random
|
||||
import hashlib
|
||||
|
||||
from string import ascii_letters, digits
|
||||
|
||||
from werkzeug.wrappers import Request
|
||||
from werkzeug.exceptions import BadRequest
|
||||
|
||||
import ipaddress
|
||||
|
||||
|
||||
@ -83,3 +87,12 @@ class Bloomfilter:
|
||||
|
||||
def __len__(self):
|
||||
return self.elements
|
||||
|
||||
|
||||
class JSONRequest(Request):
|
||||
|
||||
def get_json(self):
|
||||
try:
|
||||
return json.loads(self.get_data().decode('utf-8'))
|
||||
except ValueError:
|
||||
raise BadRequest('Unable to read JSON request')
|
||||
|
@ -10,7 +10,7 @@ import sqlite3
|
||||
from itsdangerous import SignatureExpired, BadSignature
|
||||
|
||||
from werkzeug.wrappers import Response
|
||||
from werkzeug.exceptions import abort, BadRequest
|
||||
from werkzeug.exceptions import BadRequest, Forbidden, NotFound
|
||||
|
||||
from isso.compat import text_type as str
|
||||
|
||||
@ -49,19 +49,16 @@ class requires:
|
||||
@requires(str, 'uri')
|
||||
def new(app, environ, request, uri):
|
||||
|
||||
try:
|
||||
data = json.loads(request.get_data().decode('utf-8'))
|
||||
except ValueError:
|
||||
return Response("No JSON object could be decoded", 400)
|
||||
data = request.get_json()
|
||||
|
||||
for field in set(data.keys()) - set(['text', 'author', 'website', 'email', 'parent']):
|
||||
data.pop(field)
|
||||
|
||||
if not data.get("text"):
|
||||
return Response("No text given.", 400)
|
||||
raise BadRequest("no text given")
|
||||
|
||||
if "id" in data and not isinstance(data["id"], int):
|
||||
return Response("Parent ID must be an integer.")
|
||||
raise BadRequest("parent id must be an integer")
|
||||
|
||||
for field in ("author", "email"):
|
||||
if data.get(field):
|
||||
@ -88,11 +85,8 @@ def new(app, environ, request, uri):
|
||||
try:
|
||||
with app.lock:
|
||||
rv = app.db.comments.add(uri, data)
|
||||
except sqlite3.Error:
|
||||
logging.exception('uncaught SQLite3 exception')
|
||||
abort(400)
|
||||
except db.IssoDBException:
|
||||
abort(403)
|
||||
raise Forbidden
|
||||
|
||||
host = list(app.conf.getiter('general', 'host'))[0].rstrip("/")
|
||||
href = host + uri + "#isso-%i" % rv["id"]
|
||||
@ -130,7 +124,7 @@ def single(app, environ, request, id):
|
||||
if request.method == 'GET':
|
||||
rv = app.db.comments.get(id)
|
||||
if rv is None:
|
||||
abort(404)
|
||||
raise NotFound
|
||||
|
||||
for key in set(rv.keys()) - FIELDS:
|
||||
rv.pop(key)
|
||||
@ -146,23 +140,20 @@ def single(app, environ, request, id):
|
||||
try:
|
||||
rv = app.unsign(request.cookies.get('admin', ''))
|
||||
except (SignatureExpired, BadSignature):
|
||||
abort(403)
|
||||
raise Forbidden
|
||||
|
||||
if rv[0] != id:
|
||||
abort(403)
|
||||
raise Forbidden
|
||||
|
||||
# verify checksum, mallory might skip cookie deletion when he deletes a comment
|
||||
if rv[1] != hashlib.md5(app.db.comments.get(id)["text"].encode('utf-8')).hexdigest():
|
||||
abort(403)
|
||||
raise Forbidden
|
||||
|
||||
if request.method == 'PUT':
|
||||
try:
|
||||
data = json.loads(request.get_data().decode('utf-8'))
|
||||
except ValueError:
|
||||
return Response("No JSON object could be decoded", 400)
|
||||
data = request.get_json()
|
||||
|
||||
if data.get("text") is not None and len(data['text']) < 3:
|
||||
return Response("No text given.", 400)
|
||||
raise BadRequest("no text given")
|
||||
|
||||
for key in set(data.keys()) - set(["text", "author", "website"]):
|
||||
data.pop(key)
|
||||
@ -203,7 +194,7 @@ def fetch(app, environ, request, uri):
|
||||
|
||||
rv = list(app.db.comments.fetch(uri))
|
||||
if not rv:
|
||||
abort(404)
|
||||
raise NotFound
|
||||
|
||||
for item in rv:
|
||||
|
||||
@ -237,7 +228,7 @@ def count(app, environ, request, uri):
|
||||
rv = app.db.comments.count(uri)[0]
|
||||
|
||||
if rv == 0:
|
||||
abort(404)
|
||||
raise NotFound
|
||||
|
||||
return Response(json.dumps(rv), 200, content_type='application/json')
|
||||
|
||||
@ -247,7 +238,7 @@ def activate(app, environ, request, auth):
|
||||
try:
|
||||
id = app.unsign(auth, max_age=2**32)
|
||||
except (BadSignature, SignatureExpired):
|
||||
abort(403)
|
||||
raise Forbidden
|
||||
|
||||
with app.lock:
|
||||
app.db.comments.activate(id)
|
||||
@ -260,7 +251,7 @@ def delete(app, environ, request, auth):
|
||||
try:
|
||||
id = app.unsign(auth, max_age=2**32)
|
||||
except (BadSignature, SignatureExpired):
|
||||
abort(403)
|
||||
raise Forbidden
|
||||
|
||||
with app.lock:
|
||||
app.db.comments.delete(id)
|
||||
|
Loading…
Reference in New Issue
Block a user