=== trac/db/sqlite_backend.py ================================================================== --- trac/db/sqlite_backend.py (revision 8584) +++ trac/db/sqlite_backend.py (local) @@ -21,6 +21,10 @@ from trac.db.api import IDatabaseConnector from trac.db.util import ConnectionWrapper +def escape_percent(sql): + import re + return re.sub("'((?:[^']|(?:''))*)'", lambda m: m.group(0).replace('%', '%%'), sql) + try: import pysqlite2.dbapi2 as sqlite have_pysqlite = 2 @@ -34,12 +38,12 @@ raise def execute(self, sql, args=None): if args: - sql = sql % (('?',) * len(args)) + sql = escape_percent(sql) % (('?',) * len(args)) return self._rollback_on_error(sqlite.Cursor.execute, sql, args or []) def executemany(self, sql, args=None): if args: - sql = sql % (('?',) * len(args[0])) + sql = escape_percent(sql) % (('?',) * len(args[0])) return self._rollback_on_error(sqlite.Cursor.executemany, sql, args or []) def _convert_row(self, row): @@ -61,6 +65,20 @@ try: import sqlite have_pysqlite = 1 + + class EscapeCursor(sqlite.Cursor): + + def execute(self, sql, args=None): + # use [] instead of calling "execute" without args to make sure + # the '%%' escapes are converted back to a single '%' + return sqlite.Cursor.execute(self, escape_percent(sql), + args or []) + + def executemany(self, sql, args=None): + # see note regarding args for "execute" + return sqlite.Cursor.executemany(self, escape_percent(sql), + args or []) + except ImportError: have_pysqlite = 0 === trac/ticket/report.py ================================================================== --- trac/ticket/report.py (revision 8584) +++ trac/ticket/report.py (local) @@ -448,18 +448,29 @@ def sql_sub_vars(self, req, sql, args): values = [] - def repl(match): - aname = match.group(1) + def add_value(aname): try: arg = args[aname] except KeyError: raise util.TracError("Dynamic variable '$%s' not defined." % aname) req.hdf['report.var.' + aname] = arg values.append(arg) + + def repl(match): + add_value(match.group(1)) return '%s' - return re.sub("'?\$([A-Z]+)'?", repl, sql), values + def repl_literal(match): + add_value(match.group(1)) + return "' || %s || '" # break literal string & concatenate with arg + var_re = re.compile("[$]([A-Z]+)") + sql = [var_re.sub(s.startswith("'") and repl_literal or repl, s) + for s in re.split("('(?:[^']|(?:''))*')", sql)] + sql = ''.join(sql) + self.log.debug(sql) + return sql, values + def _render_csv(self, req, cols, rows, sep=','): req.send_response(200) req.send_header('Content-Type', 'text/plain;charset=utf-8')