diff --git a/datasette/app.py b/datasette/app.py
index da40df1e..337fcdae 100644
--- a/datasette/app.py
+++ b/datasette/app.py
@@ -524,10 +524,11 @@ class RowTableShared(BaseView):
cells.append({
'column': 'Link',
'value': jinja2.Markup(
- '{flat_pks}'.format(
+ '{flat_pks}'.format(
database=database,
table=urllib.parse.quote_plus(table),
- flat_pks=path_from_row_pks(row, pks, not pks),
+ flat_pks=str(jinja2.escape(path_from_row_pks(row, pks, not pks, False))),
+ flat_pks_quoted=path_from_row_pks(row, pks, not pks)
)
),
})
diff --git a/datasette/utils.py b/datasette/utils.py
index 1f296a0b..7ba663f0 100644
--- a/datasette/utils.py
+++ b/datasette/utils.py
@@ -38,14 +38,19 @@ def urlsafe_components(token):
]
-def path_from_row_pks(row, pks, use_rowid):
+def path_from_row_pks(row, pks, use_rowid, quote=True):
+ """ Generate an optionally URL-quoted unique identifier
+ for a row from its primary keys."""
if use_rowid:
- return urllib.parse.quote_plus(str(row['rowid']))
- bits = []
- for pk in pks:
- bits.append(
- urllib.parse.quote_plus(str(row[pk]))
- )
+ bits = [row['rowid']]
+ else:
+ bits = [row[pk] for pk in pks]
+
+ if quote:
+ bits = [urllib.parse.quote_plus(str(bit)) for bit in bits]
+ else:
+ bits = [str(bit) for bit in bits]
+
return ','.join(bits)