rendered paste body=== 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')