def delete(self, environ, request, id, key=None): data = request.get_json() for field in set(data.keys()) - API.ACCEPT: data.pop(field) valid, reason = self.authenticate(data) if not valid: return BadRequest(reason) item = self.comments.get(id) if item is None: raise NotFound self.authorize(id, data, item, request.cookies.get(str(id), '')) self.cache.delete('hash', (item['email'] or item['remote_addr']).encode('utf-8')) with self.isso.lock: rv = self.comments.delete(id) if rv: for key in set(rv.keys()) - API.FIELDS: rv.pop(key) self.signal("comments.delete", id) resp = JSON(rv, 200) cookie = functools.partial(dump_cookie, expires=0, max_age=0) resp.headers.add("Set-Cookie", cookie(str(id))) resp.headers.add("X-Set-Cookie", cookie("isso-%i" % id)) return resp
def delete(self, environ, request, id, key=None): try: rv = self.isso.unsign(request.cookies.get(str(id), "")) except (SignatureExpired, BadSignature): raise Forbidden else: if rv[0] != id: raise Forbidden # verify checksum, mallory might skip cookie deletion when he deletes a comment if rv[1] != sha1(self.comments.get(id)["text"]): raise Forbidden item = self.comments.get(id) if item is None: raise NotFound self.cache.delete('hash', (item['email'] or item['remote_addr']).encode('utf-8')) with self.isso.lock: rv = self.comments.delete(id) if rv: for key in set(rv.keys()) - API.FIELDS: rv.pop(key) self.signal("comments.delete", id) resp = JSON(rv, 200) cookie = functools.partial(dump_cookie, expires=0, max_age=0) resp.headers.add("Set-Cookie", cookie(str(id))) resp.headers.add("X-Set-Cookie", cookie("isso-%i" % id)) return resp
def show(self, environ, request): rv = { "version": dist.version, "host": str(local("host")), "origin": str(local("origin")), "moderation": self.moderation, } return Response(json.dumps(rv), 200, content_type="application/json")
def host(name): """ Parse :param name: into `httplib`-compatible host:port. >>> host("http://example.tld/") ('example.tld', 80, False) >>> host("https://example.tld/") ('example.tld', 443, True) >>> host("example.tld") ('example.tld', 80, False) >>> host("example.tld:42") ('example.tld', 42, False) >>> host("https://example.tld:80/") ('example.tld', 80, True) """ if not (isinstance(name, string_types)): name = str(name) if not name.startswith(("http://", "https://")): name = "http://" + name rv = urlparse(name) if rv.scheme == "https" and rv.port is None: return (rv.netloc, 443, True) return (rv.netloc.rsplit(":")[0], rv.port or 80, rv.scheme == "https")
def fetch(self, environ, request, uri): rv = list(self.comments.fetch(uri)) if not rv: raise NotFound for item in rv: key = item['email'] or item['remote_addr'] val = self.cache.get('hash', key.encode('utf-8')) if val is None: val = str(pbkdf2(key, self.isso.salt, 1000, 6)) self.cache.set('hash', key.encode('utf-8'), val) item['hash'] = val for key in set(item.keys()) - API.FIELDS: item.pop(key) if request.args.get('plain', '0') == '0': for item in rv: item['text'] = markdown(item['text']) return JSON(json.dumps(rv), 200)
def fetch(app, environ, request, uri): rv = list(app.db.comments.fetch(uri)) if not rv: raise NotFound for item in rv: key = item['email'] or item['remote_addr'] val = app.cache.get('hash', key.encode('utf-8')) if val is None: val = str(pbkdf2(key, app.salt, 1000, 6)) app.cache.set('hash', key.encode('utf-8'), val) item['hash'] = val for key in set(item.keys()) - FIELDS: item.pop(key) if request.args.get('plain', '0') == '0': for item in rv: item['text'] = app.markdown(item['text']) return Response(json.dumps(rv), 200, content_type='application/json')
def fetch(app, environ, request, uri): rv = list(app.db.comments.fetch(uri)) if not rv: raise NotFound for item in rv: key = item['email'] or item['remote_addr'] val = app.cache.get('hash', key) if val is None: val = str(pbkdf2(key, app.salt, 1000, 6)) app.cache.set('hash', key, val) item['hash'] = val for key in set(item.keys()) - FIELDS: item.pop(key) if request.args.get('plain', '0') == '0': for item in rv: item['text'] = app.markdown(item['text']) return Response(json.dumps(rv), 200, content_type='application/json')
def edit(self, environ, request, id): data = request.get_json() for field in set(data.keys()) - API.ACCEPT: data.pop(field) valid, reason = self.authenticate(data) if not valid: return BadRequest(reason) valid, reason = self.verify(data) if not valid: return BadRequest(reason) comment = self.comments.get(id) if time.time() > comment.get('created') + self.conf.getint('max-age'): raise Forbidden self.authorize(id, data, comment, request.cookies.get(str(id), '')) data['modified'] = time.time() for key in set(data.keys()) - API.FIELDS: data.pop(key) with self.isso.lock: rv = self.comments.update(id, data) for key in set(rv.keys()) - API.FIELDS: rv.pop(key) self.signal("comments.edit", rv) cookie = functools.partial(dump_cookie, value=self.isso.sign( [rv["id"], sha1(rv["text"])]), max_age=self.conf.getint('max-age')) rv["text"] = self.isso.render(rv["text"]) resp = JSON(rv, 200) resp.headers.add("Set-Cookie", cookie(str(rv["id"]))) resp.headers.add("X-Set-Cookie", cookie("isso-%i" % rv["id"])) return resp
def edit(self, environ, request, id): data = request.get_json() for field in set(data.keys()) - API.ACCEPT: data.pop(field) valid, reason = self.authenticate(data) if not valid: return BadRequest(reason) valid, reason = self.verify(data) if not valid: return BadRequest(reason) comment = self.comments.get(id) if time.time() > comment.get('created') + self.conf.getint('max-age'): raise Forbidden self.authorize(id, data, comment, request.cookies.get(str(id), '')) data['modified'] = time.time() for key in set(data.keys()) - API.FIELDS: data.pop(key) with self.isso.lock: rv = self.comments.update(id, data) for key in set(rv.keys()) - API.FIELDS: rv.pop(key) self.signal("comments.edit", rv) cookie = functools.partial(dump_cookie, value=self.isso.sign([rv["id"], sha1(rv["text"])]), max_age=self.conf.getint('max-age')) rv["text"] = self.isso.render(rv["text"]) resp = JSON(rv, 200) resp.headers.add("Set-Cookie", cookie(str(rv["id"]))) resp.headers.add("X-Set-Cookie", cookie("isso-%i" % rv["id"])) return resp
def edit(self, environ, request, id): try: rv = self.isso.unsign(request.cookies.get(str(id), '')) except (SignatureExpired, BadSignature): raise Forbidden if rv[0] != id: raise Forbidden # verify checksum, mallory might skip cookie deletion when he deletes a comment if rv[1] != sha1(self.comments.get(id)["text"]): raise Forbidden data = request.get_json() if "text" not in data or data["text"] is None or len(data["text"]) < 3: raise BadRequest("no text given") for key in set(data.keys()) - set(["text", "author", "website"]): data.pop(key) data['modified'] = time.time() with self.isso.lock: rv = self.comments.update(id, data) for key in set(rv.keys()) - API.FIELDS: rv.pop(key) self.signal("comments.edit", rv) cookie = functools.partial(dump_cookie, value=self.isso.sign( [rv["id"], sha1(rv["text"])]), max_age=self.conf.getint('max-age')) rv["text"] = self.isso.render(rv["text"]) resp = JSON(rv, 200) resp.headers.add("Set-Cookie", cookie(str(rv["id"]))) resp.headers.add("X-Set-Cookie", cookie("isso-%i" % rv["id"])) return resp
def _remote_addr(self, request): """Return the anonymized IP address of the requester. Takes into consideration a potential X-Forwarded-For HTTP header if a necessary server.trusted-proxies configuration entry is set. Recipe source: https://stackoverflow.com/a/22936947/636849 """ remote_addr = request.remote_addr if self.trusted_proxies: route = request.access_route + [remote_addr] remote_addr = next((addr for addr in reversed(route) if addr not in self.trusted_proxies), remote_addr) return utils.anonymize(str(remote_addr))
def dislike(self, environ, request, id): nv = self.comments.vote( False, id, utils.anonymize(str(request.remote_addr))) return JSON(nv, 200)
def new(self, environ, request, uri): data = request.get_json() for field in set(data.keys()) - API.ACCEPT: data.pop(field) for key in ("author", "email", "website", "parent"): data.setdefault(key, None) valid, reason = API.verify(data) if not valid: return BadRequest(reason) for field in ("author", "email", "website"): if data.get(field) is not None: data[field] = cgi.escape(data[field]) if data.get("website"): data["website"] = normalize(data["website"]) data['mode'] = 2 if self.moderated else 1 data['remote_addr'] = utils.anonymize(str(request.remote_addr)) with self.isso.lock: if uri not in self.threads: if 'title' not in data: with http.curl('GET', local("origin"), uri) as resp: if resp and resp.status == 200: uri, title = parse.thread(resp.read(), id=uri) else: return NotFound('URI does not exist %s') else: title = data['title'] thread = self.threads.new(uri, title) self.signal("comments.new:new-thread", thread) else: thread = self.threads[uri] # notify extensions that the new comment is about to save self.signal("comments.new:before-save", thread, data) valid, reason = self.guard.validate(uri, data) if not valid: self.signal("comments.new:guard", reason) raise Forbidden(reason) with self.isso.lock: rv = self.comments.add(uri, data) # notify extension, that the new comment has been successfully saved self.signal("comments.new:after-save", thread, rv) cookie = functools.partial(dump_cookie, value=self.isso.sign( [rv["id"], sha1(rv["text"])]), max_age=self.conf.getint('max-age')) rv["text"] = self.isso.render(rv["text"]) rv["hash"] = self.hash(rv['email'] or rv['remote_addr']) self.cache.set( 'hash', (rv['email'] or rv['remote_addr']).encode('utf-8'), rv['hash']) rv = self._add_gravatar_image(rv) for key in set(rv.keys()) - API.FIELDS: rv.pop(key) # success! self.signal("comments.new:finish", thread, rv) resp = JSON(rv, 202 if rv["mode"] == 2 else 201) resp.headers.add("Set-Cookie", cookie(str(rv["id"]))) resp.headers.add("X-Set-Cookie", cookie("isso-%i" % rv["id"])) return resp
def checkip(self, env, req): return Response(utils.anonymize(str(req.remote_addr)), 200)
def new(self, environ, request, uri, key): data = request.get_json() # check access keys rv = self.db.execute(['SELECT uri FROM access WHERE key = ? ;'], (key, )).fetchall() if not rv or rv[0][0] != uri: raise Forbidden for field in set(data.keys()) - API.ACCEPT: data.pop(field) for key in ("author", "parent"): data.setdefault(key, None) if data['parent'] is not None: data.setdefault('place', None) valid, reason = API.verify(data) if not valid: return BadRequest(reason) escaped = dict((key, cgi.escape(value) if value is not None else None) for key, value in data.items()) added = {} if escaped.get("website") is not None: added["website"] = normalize(escaped["website"]) added['mode'] = 2 if self.moderated else 1 prepared = dict(escaped) prepared.update(added) with self.isso.lock: if uri in self._threads: thread = self._threads[uri] else: if 'title' in prepared: title = prepared['title'] else: with http.curl('GET', local("origin"), uri) as resp: if resp and resp.status == 200: uri, title = parse.thread(resp.read(), id=uri) else: return NotFound('URI does not exist %s') thread = self._threads.new(uri, title) self.signal("comments.new:new-thread", thread) # notify extensions that the new comment is about to save self.signal("comments.new:before-save", thread, prepared) valid, reason = self.guard.validate(uri, prepared) if not valid: self.signal("comments.new:guard", reason) raise Forbidden(reason) with self.isso.lock: rv = self.comments.add(uri, prepared) # notify extension, that the new comment has been successfully saved self.signal("comments.new:after-save", thread, rv) cookie = functools.partial(dump_cookie, value=self.isso.sign( [rv["id"], sha1(rv["text"])]), max_age=self.conf.getint('max-age')) rv["text"] = self.isso.render(rv["text"]) rv["hash"] = self.hash(rv['remote_addr']) self.cache.set('hash', (rv['remote_addr']).encode('utf-8'), rv['hash']) for key in set(rv.keys()) - API.FIELDS: rv.pop(key) # success! self.signal("comments.new:finish", thread, rv) resp = JSON(rv, 202 if rv["mode"] == 2 else 201) resp.headers.add("Set-Cookie", cookie(str(rv["id"]))) resp.headers.add("X-Set-Cookie", cookie("isso-%i" % rv["id"])) return resp
def like(self, environ, request, id): nv = self.comments.vote(True, id, str(request.remote_addr)) return JSON(nv, 200)
def dislike(app, environ, request, id): nv = app.db.comments.vote(False, id, utils.anonymize(str(request.remote_addr))) return Response(json.dumps(nv), 200)
def new(self, environ, request, uri): logger.debug("got uri :%s" % uri) logger.debug("got eviron :%s" % str(environ)) logger.debug("got request :%s" % pprint.pformat(request.__dict__)) data = request.get_json() logger.debug("got data :%s" % data) for field in set(data.keys()) - API.ACCEPT: f = data.pop(field) logger.debug("skip data %s:%s" % (field, f )) for key in ("author", "email", "website", "parent"): data.setdefault(key, None) valid, reason = API.verify(data) if not valid: return BadRequest(reason) for field in ("author", "email", "website"): if data.get(field) is not None: data[field] = cgi.escape(data[field]) if data.get("website"): data["website"] = normalize(data["website"]) data['mode'] = 2 if self.moderated else 1 logger.debug("request.remote_addr %s" % (request.remote_addr )) data['remote_addr'] = utils.anonymize(str(request.remote_addr)) with self.isso.lock: if uri not in self.threads: org = str(local("origin")) if org == '<LocalProxy unbound>': org = environ['HTTP_ORIGIN'] logger.debug("got origin %s" % (org)) with http.curl('GET', org, uri) as resp: if resp and resp.status == 200: uri, title = parse.thread(resp.read(), id=uri) else: return NotFound('URI does not exist') thread = self.threads.new(uri, title) self.signal("comments.new:new-thread", thread) else: thread = self.threads[uri] # notify extensions that the new comment is about to save self.signal("comments.new:before-save", thread, data) valid, reason = self.guard.validate(uri, data) if not valid: self.signal("comments.new:guard", reason) raise Forbidden(reason) with self.isso.lock: rv = self.comments.add(uri, data) # notify extension, that the new comment has been successfully saved self.signal("comments.new:after-save", thread, rv) cookie = functools.partial(dump_cookie, value=self.isso.sign([rv["id"], sha1(rv["text"])]), max_age=self.conf.getint('max-age')) rv["text"] = self.isso.render(rv["text"]) rv["hash"] = self.hash(rv['email'] or rv['remote_addr']) self.cache.set('hash', (rv['email'] or rv['remote_addr']).encode('utf-8'), rv['hash']) for key in set(rv.keys()) - API.FIELDS: rv.pop(key) # success! self.signal("comments.new:finish", thread, rv) resp = JSON(rv, 202 if rv["mode"] == 2 else 201) resp.headers.add("Set-Cookie", cookie(str(rv["id"]))) resp.headers.add("X-Set-Cookie", cookie("isso-%i" % rv["id"])) return resp
def single(app, environ, request, id): if request.method == 'GET': rv = app.db.comments.get(id) if rv is None: raise NotFound for key in set(rv.keys()) - FIELDS: rv.pop(key) if request.args.get('plain', '0') == '0': rv['text'] = app.markdown(rv['text']) return Response(json.dumps(rv), 200, content_type='application/json') try: rv = app.unsign(request.cookies.get(str(id), '')) except (SignatureExpired, BadSignature): try: rv = app.unsign(request.cookies.get('admin', '')) except (SignatureExpired, BadSignature): raise Forbidden if rv[0] != id: 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(): raise Forbidden if request.method == 'PUT': data = request.get_json() if "text" not in data or data["text"] is None or len(data["text"]) < 3: raise BadRequest("no text given") for key in set(data.keys()) - set(["text", "author", "website"]): data.pop(key) data['modified'] = time.time() with app.lock: rv = app.db.comments.update(id, data) for key in set(rv.keys()) - FIELDS: rv.pop(key) logger.info('comment %i edited: %s', id, json.dumps(rv)) checksum = hashlib.md5(rv["text"].encode('utf-8')).hexdigest() rv["text"] = app.markdown(rv["text"]) cookie = functools.partial(dump_cookie, value=app.sign([rv["id"], checksum]), max_age=app.conf.getint('general', 'max-age')) resp = Response(json.dumps(rv), 200, content_type='application/json') resp.headers.add("Set-Cookie", cookie(str(rv["id"]))) resp.headers.add("X-Set-Cookie", cookie("isso-%i" % rv["id"])) return resp if request.method == 'DELETE': item = app.db.comments.get(id) app.cache.delete('hash', (item['email'] or item['remote_addr']).encode('utf-8')) rv = app.db.comments.delete(id) if rv: for key in set(rv.keys()) - FIELDS: rv.pop(key) logger.info('comment %i deleted', id) cookie = functools.partial(dump_cookie, expires=0, max_age=0) resp = Response(json.dumps(rv), 200, content_type='application/json') resp.headers.add("Set-Cookie", cookie(str(id))) resp.headers.add("X-Set-Cookie", cookie("isso-%i" % id)) return resp
def like(self, environ, request, id): nv = self.comments.vote(True, id, utils.anonymize(str(request.remote_addr))) return Response(json.dumps(nv), 200)
def dislike(self, environ, request, id): nv = self.comments.vote(False, id, utils.anonymize(str(request.remote_addr))) return JSON(nv, 200)
def new(self, environ, request, uri): data = request.get_json() for field in set(data.keys()) - API.ACCEPT: data.pop(field) for key in ("author", "email", "website", "parent"): data.setdefault(key, None) valid, reason = API.verify(data) if not valid: return BadRequest(reason) for field in ("author", "email"): if data.get(field) is not None: data[field] = cgi.escape(data[field]) data['mode'] = 2 if self.moderated else 1 data['remote_addr'] = utils.anonymize(str(request.remote_addr)) with self.isso.lock: if uri not in self.threads: with http.curl('GET', local("origin"), uri) as resp: if resp and resp.status == 200: uri, title = parse.thread(resp.read(), id=uri) else: return NotFound('URI does not exist') thread = self.threads.new(uri, title) self.signal("comments.new:new-thread", thread) else: thread = self.threads[uri] # notify extensions that the new comment is about to save self.signal("comments.new:before-save", thread, data) valid, reason = self.guard.validate(uri, data) if not valid: self.signal("comments.new:guard", reason) raise Forbidden(reason) with self.isso.lock: rv = self.comments.add(uri, data) # notify extension, that the new comment has been successfully saved self.signal("comments.new:after-save", thread, rv) cookie = functools.partial(dump_cookie, value=self.isso.sign([rv["id"], sha1(rv["text"])]), max_age=self.conf.getint('max-age')) rv["text"] = self.isso.render(rv["text"]) rv["hash"] = pbkdf2(rv['email'] or rv['remote_addr'], self.isso.salt, 1000, 6).decode("utf-8") self.cache.set('hash', (rv['email'] or rv['remote_addr']).encode('utf-8'), rv['hash']) for key in set(rv.keys()) - API.FIELDS: rv.pop(key) # success! self.signal("comments.new:finish", thread, rv) resp = JSON(rv, 202 if rv["mode"] == 2 else 201) resp.headers.add("Set-Cookie", cookie(str(rv["id"]))) resp.headers.add("X-Set-Cookie", cookie("isso-%i" % rv["id"])) return resp
def new(app, environ, request, uri): data = request.get_json() for field in set(data.keys()) - set(['text', 'author', 'website', 'email', 'parent']): data.pop(field) if "text" not in data or data["text"] is None or len(data["text"]) < 3: raise BadRequest("no text given") if "id" in data and not isinstance(data["id"], int): raise BadRequest("parent id must be an integer") if len(data.get("email") or "") > 254: raise BadRequest("http://tools.ietf.org/html/rfc5321#section-4.5.3") for field in ("author", "email"): if data.get(field): data[field] = cgi.escape(data[field]) data['mode'] = (app.conf.getboolean('moderation', 'enabled') and 2) or 1 data['remote_addr'] = utils.anonymize(str(request.remote_addr)) with app.lock: if uri not in app.db.threads: for host in app.conf.getiter('general', 'host'): with http.curl('GET', host, uri) as resp: if resp and resp.status == 200: title = parse.title(resp.read()) break else: return Response('URI does not exist', 404) app.db.threads.new(uri, title) logger.info('new thread: %s -> %s', uri, title) else: title = app.db.threads[uri].title try: with app.lock: rv = app.db.comments.add(uri, data) except db.IssoDBException: raise Forbidden host = list(app.conf.getiter('general', 'host'))[0].rstrip("/") href = host + uri + "#isso-%i" % rv["id"] deletion = host + environ["SCRIPT_NAME"] + "/delete/" + app.sign(str(rv["id"])) activation = None if app.conf.getboolean('moderation', 'enabled'): activation = host + environ["SCRIPT_NAME"] + "/activate/" + app.sign(str(rv["id"])) app.notify(title, notify.format(rv, href, utils.anonymize(str(request.remote_addr)), activation_key=activation, deletion_key=deletion)) # save checksum of text into cookie, so mallory can't modify/delete a comment, if # he add a comment, then removed it but not the signed cookie. checksum = hashlib.md5(rv["text"].encode('utf-8')).hexdigest() rv["text"] = app.markdown(rv["text"]) rv["hash"] = str(pbkdf2(rv['email'] or rv['remote_addr'], app.salt, 1000, 6)) app.cache.set('hash', (rv['email'] or rv['remote_addr']).encode('utf-8'), rv['hash']) for key in set(rv.keys()) - FIELDS: rv.pop(key) # success! logger.info('comment created: %s', json.dumps(rv)) cookie = functools.partial(dump_cookie, value=app.sign([rv["id"], checksum]), max_age=app.conf.getint('general', 'max-age')) resp = Response(json.dumps(rv), 202 if rv["mode"] == 2 else 201, content_type='application/json') resp.headers.add("Set-Cookie", cookie(str(rv["id"]))) resp.headers.add("X-Set-Cookie", cookie("isso-%i" % rv["id"])) return resp
def single(app, environ, request, id): if request.method == 'GET': rv = app.db.comments.get(id) if rv is None: raise NotFound for key in set(rv.keys()) - FIELDS: rv.pop(key) if request.args.get('plain', '0') == '0': rv['text'] = app.markdown(rv['text']) return Response(json.dumps(rv), 200, content_type='application/json') try: rv = app.unsign(request.cookies.get(str(id), '')) except (SignatureExpired, BadSignature): try: rv = app.unsign(request.cookies.get('admin', '')) except (SignatureExpired, BadSignature): raise Forbidden if rv[0] != id: 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(): raise Forbidden if request.method == 'PUT': data = request.get_json() if data.get("text") is not None and len(data['text']) < 3: raise BadRequest("no text given") for key in set(data.keys()) - set(["text", "author", "website"]): data.pop(key) data['modified'] = time.time() with app.lock: rv = app.db.comments.update(id, data) for key in set(rv.keys()) - FIELDS: rv.pop(key) logger.info('comment %i edited: %s', id, json.dumps(rv)) checksum = hashlib.md5(rv["text"].encode('utf-8')).hexdigest() rv["text"] = app.markdown(rv["text"]) resp = Response(json.dumps(rv), 200, content_type='application/json') resp.set_cookie(str(rv["id"]), app.sign([rv["id"], checksum]), max_age=app.conf.getint('general', 'max-age')) return resp if request.method == 'DELETE': item = app.db.comments.get(id) app.cache.delete('hash', item['email'] or item['remote_addr']) rv = app.db.comments.delete(id) if rv: for key in set(rv.keys()) - FIELDS: rv.pop(key) logger.info('comment %i deleted', id) resp = Response(json.dumps(rv), 200, content_type='application/json') resp.delete_cookie(str(id), path='/') return resp
def new(app, environ, request, uri): data = request.get_json() for field in set(data.keys()) - set( ['text', 'author', 'website', 'email', 'parent']): data.pop(field) if not data.get("text"): raise BadRequest("no text given") if "id" in data and not isinstance(data["id"], int): raise BadRequest("parent id must be an integer") for field in ("author", "email"): if data.get(field): data[field] = cgi.escape(data[field]) data['mode'] = (app.conf.getboolean('moderation', 'enabled') and 2) or 1 data['remote_addr'] = utils.anonymize(str(request.remote_addr)) with app.lock: if uri not in app.db.threads: for host in app.conf.getiter('general', 'host'): with http.curl('GET', host, uri) as resp: if resp and resp.status == 200: title = parse.title(resp.read()) break else: return Response('URI does not exist', 404) app.db.threads.new(uri, title) logger.info('new thread: %s -> %s', uri, title) else: title = app.db.threads[uri].title try: with app.lock: rv = app.db.comments.add(uri, data) except db.IssoDBException: raise Forbidden host = list(app.conf.getiter('general', 'host'))[0].rstrip("/") href = host + uri + "#isso-%i" % rv["id"] deletion = host + environ["SCRIPT_NAME"] + "/delete/" + app.sign( str(rv["id"])) activation = None if app.conf.getboolean('moderation', 'enabled'): activation = host + environ["SCRIPT_NAME"] + "/activate/" + app.sign( str(rv["id"])) app.notify( title, notify.format(rv, href, utils.anonymize(str(request.remote_addr)), activation_key=activation, deletion_key=deletion)) # save checksum of text into cookie, so mallory can't modify/delete a comment, if # he add a comment, then removed it but not the signed cookie. checksum = hashlib.md5(rv["text"].encode('utf-8')).hexdigest() rv["text"] = app.markdown(rv["text"]) rv["hash"] = str( pbkdf2(rv.get('email') or rv['remote_addr'], app.salt, 1000, 6)) for key in set(rv.keys()) - FIELDS: rv.pop(key) # success! logger.info('comment created: %s', json.dumps(rv)) resp = Response(json.dumps(rv), 202 if rv["mode"] == 2 else 201, content_type='application/json') resp.set_cookie(str(rv["id"]), app.sign([rv["id"], checksum]), max_age=app.conf.getint('general', 'max-age')) return resp