diff --git a/woodwind/api.py b/woodwind/api.py
index 3c1860b..f2eeb78 100644
--- a/woodwind/api.py
+++ b/woodwind/api.py
@@ -1,7 +1,7 @@
import flask
import flask.ext.login as flask_login
import requests
-
+from woodwind import util
api = flask.Blueprint('api', __name__)
@@ -13,6 +13,9 @@ def publish():
content = flask.request.form.get('content')
syndicate_to = flask.request.form.getlist('syndicate-to[]')
+ if syndicate_to:
+ syndicate_to = [util.html_unescape(id) for id in syndicate_to]
+
data = {
'h': 'entry',
'syndicate-to[]': syndicate_to,
diff --git a/woodwind/templates/_reply.jinja2 b/woodwind/templates/_reply.jinja2
index fc30ae4..5b30a96 100644
--- a/woodwind/templates/_reply.jinja2
+++ b/woodwind/templates/_reply.jinja2
@@ -23,7 +23,7 @@
{% for target in current_user.get_setting('syndicate-to', []) %}
-
+
diff --git a/woodwind/util.py b/woodwind/util.py
index 681d3bf..2acd188 100644
--- a/woodwind/util.py
+++ b/woodwind/util.py
@@ -1,5 +1,6 @@
import pickle
import re
+from xml.sax import saxutils
from flask import current_app
from redis import StrictRedis
@@ -56,3 +57,13 @@ def clean(text):
if text is not None:
text = re.sub('', '', text, flags=re.DOTALL)
return bleach.clean(text, strip=True)
+
+
+def html_escape(text):
+ # https://wiki.python.org/moin/EscapingHtml
+ return saxutils.escape(text, {'"': '"', "'": '''})
+
+
+def html_unescape(text):
+ # https://wiki.python.org/moin/EscapingHtml
+ return saxutils.unescape(text, {'"': '"', ''': "'"})
diff --git a/woodwind/views.py b/woodwind/views.py
index 2ec325c..88e7065 100644
--- a/woodwind/views.py
+++ b/woodwind/views.py
@@ -16,7 +16,6 @@ import pyquerystring
import requests
import re
import urllib
-import cgi
import sqlalchemy
import sqlalchemy.sql.expression
@@ -257,11 +256,11 @@ def login():
@micropub.authenticated_handler
def login_callback(resp):
if not resp.me:
- flask.flash(cgi.escape('Login error: ' + resp.error))
+ flask.flash(util.html_escape('Login error: ' + resp.error))
return flask.redirect(flask.url_for('.index'))
if resp.error:
- flask.flash(cgi.escape('Warning: ' + resp.error))
+ flask.flash(util.html_escape('Warning: ' + resp.error))
user = load_user(resp.me)
if not user:
@@ -287,12 +286,12 @@ def authorize():
@micropub.authorized_handler
def micropub_callback(resp):
if not resp.me or resp.error:
- flask.flash(cgi.escape('Authorize error: ' + resp.error))
+ flask.flash(util.html_escape('Authorize error: ' + resp.error))
return flask.redirect(flask.url_for('.index'))
user = load_user(resp.me)
if not user:
- flask.flash(cgi.escape('Unknown user for url: ' + resp.me))
+ flask.flash(util.html_escape('Unknown user for url: ' + resp.me))
return flask.redirect(flask.url_for('.index'))
user.micropub_endpoint = resp.micropub_endpoint
@@ -764,21 +763,25 @@ def font_awesome_class_for_service(service):
return 'fa fa-send'
-@views.app_template_filter('syndication_target_id')
-def syndication_target_id(target):
+@views.app_template_filter('render_syndication_target_id')
+def render_syndication_target_id(target):
if isinstance(target, dict):
- return target.get('uid') or target.get('id')
- return target
+ id = target.get('uid') or target.get('id')
+ else:
+ id = target
+ return util.html_escape(id)
@views.app_template_filter('render_syndication_target')
def render_syndication_target(target):
if isinstance(target, dict):
full_name = target.get('name')
- return full_name
+ return util.html_escape(full_name)
return '
{}'.format(
- favicon_for_url(target), target, prettify_url(target))
+ favicon_for_url(target),
+ util.html_escape(target),
+ util.html_escape(prettify_url(target)))
@views.app_template_test('syndicated_to')