Merge pull request #975 from sebres/gh-973-fix

BF: binding parameter error (unsupported type) (closes gh-973) ...
pull/985/head
Yaroslav Halchenko 2015-03-05 22:47:45 -05:00
commit daa2a9e5d8
4 changed files with 81 additions and 16 deletions

View File

@ -38,6 +38,8 @@ ver. 0.9.2 (2014/XX/XXX) - wanna-be-released
system authentication issues system authentication issues
* fail2ban-regex reads filter file(s) completely, incl. '.local' file etc. (gh-954) * fail2ban-regex reads filter file(s) completely, incl. '.local' file etc. (gh-954)
* firewallcmd-* actions: split output into separate lines for grepping (gh-908) * firewallcmd-* actions: split output into separate lines for grepping (gh-908)
* Guard unicode encode/decode issues while storing records in the database.
Fixes "binding parameter error (unsupported type)" (gh-973), thanks to kot for reporting
- New Features: - New Features:
- New filters: - New filters:

View File

@ -37,17 +37,49 @@ from ..helpers import getLogger
logSys = getLogger(__name__) logSys = getLogger(__name__)
if sys.version_info >= (3,): if sys.version_info >= (3,):
sqlite3.register_adapter( def _json_dumps_safe(x):
dict, try:
lambda x: json.dumps(x, ensure_ascii=False).encode( x = json.dumps(x, ensure_ascii=False).encode(
locale.getpreferredencoding(), 'replace')) locale.getpreferredencoding(), 'replace')
sqlite3.register_converter( except Exception, e: # pragma: no cover
"JSON", logSys.error('json dumps failed: %s', e)
lambda x: json.loads(x.decode( x = '{}'
locale.getpreferredencoding(), 'replace'))) return x
def _json_loads_safe(x):
try:
x = json.loads(x.decode(locale.getpreferredencoding(), 'replace'))
except Exception, e: # pragma: no cover
logSys.error('json loads failed: %s', e)
x = {}
return x
else: else:
sqlite3.register_adapter(dict, json.dumps) def _normalize(x):
sqlite3.register_converter("JSON", json.loads) if isinstance(x, dict):
return dict((_normalize(k), _normalize(v)) for k, v in x.iteritems())
elif isinstance(x, list):
return [_normalize(element) for element in x]
elif isinstance(x, unicode):
return x.encode(locale.getpreferredencoding())
else:
return x
def _json_dumps_safe(x):
try:
x = json.dumps(_normalize(x), ensure_ascii=False).decode(
locale.getpreferredencoding(), 'replace')
except Exception, e: # pragma: no cover
logSys.error('json dumps failed: %s', e)
x = '{}'
return x
def _json_loads_safe(x):
try:
x = _normalize(json.loads(x.decode(locale.getpreferredencoding(), 'replace')))
except Exception, e: # pragma: no cover
logSys.error('json loads failed: %s', e)
x = {}
return x
sqlite3.register_adapter(dict, _json_dumps_safe)
sqlite3.register_converter("JSON", _json_loads_safe)
def commitandrollback(f): def commitandrollback(f):
@wraps(f) @wraps(f)
@ -431,8 +463,8 @@ class Fail2BanDb(object):
tickets = [] tickets = []
for ip, timeofban, data in self._getBans(**kwargs): for ip, timeofban, data in self._getBans(**kwargs):
#TODO: Implement data parts once arbitrary match keys completed #TODO: Implement data parts once arbitrary match keys completed
tickets.append(FailTicket(ip, timeofban, data['matches'])) tickets.append(FailTicket(ip, timeofban, data.get('matches')))
tickets[-1].setAttempt(data['failures']) tickets[-1].setAttempt(data.get('failures', 1))
return tickets return tickets
def getBansMerged(self, ip=None, jail=None, bantime=None): def getBansMerged(self, ip=None, jail=None, bantime=None):
@ -484,8 +516,8 @@ class Fail2BanDb(object):
prev_banip = banip prev_banip = banip
matches = [] matches = []
failures = 0 failures = 0
matches.extend(data['matches']) matches.extend(data.get('matches', []))
failures += data['failures'] failures += data.get('failures', 1)
prev_timeofban = timeofban prev_timeofban = timeofban
ticket = FailTicket(banip, prev_timeofban, matches) ticket = FailTicket(banip, prev_timeofban, matches)
ticket.setAttempt(failures) ticket.setAttempt(failures)

View File

@ -802,8 +802,8 @@ class FileContainer:
" encoding) for this jail. Continuing" " encoding) for this jail. Continuing"
" to process line ignoring invalid characters: %r" % " to process line ignoring invalid characters: %r" %
(self.getFileName(), self.getEncoding(), line)) (self.getFileName(), self.getEncoding(), line))
if sys.version_info >= (3,): # In python3, must be decoded # decode with replacing error chars:
line = line.decode(self.getEncoding(), 'ignore') line = line.decode(self.getEncoding(), 'replace')
return line return line
def close(self): def close(self):

View File

@ -177,6 +177,37 @@ class DatabaseTest(LogCaptureTestCase):
self.assertTrue( self.assertTrue(
isinstance(self.db.getBans(jail=self.jail)[0], FailTicket)) isinstance(self.db.getBans(jail=self.jail)[0], FailTicket))
def testAddBanInvalidEncoded(self):
if Fail2BanDb is None: # pragma: no cover
return
self.testAddJail()
# invalid + valid, invalid + valid unicode, invalid + valid dual converted (like in filter:readline by fallback) ...
tickets = [
FailTicket("127.0.0.1", 0, ['user "\xd1\xe2\xe5\xf2\xe0"', 'user "\xc3\xa4\xc3\xb6\xc3\xbc\xc3\x9f"']),
FailTicket("127.0.0.2", 0, ['user "\xd1\xe2\xe5\xf2\xe0"', u'user "\xc3\xa4\xc3\xb6\xc3\xbc\xc3\x9f"']),
FailTicket("127.0.0.3", 0, ['user "\xd1\xe2\xe5\xf2\xe0"', b'user "\xc3\xa4\xc3\xb6\xc3\xbc\xc3\x9f"'.decode('utf-8', 'replace')])
]
self.db.addBan(self.jail, tickets[0])
self.db.addBan(self.jail, tickets[1])
self.db.addBan(self.jail, tickets[2])
readtickets = self.db.getBans(jail=self.jail)
self.assertEqual(len(readtickets), 3)
## python 2 or 3 :
invstr = u'user "\ufffd\ufffd\ufffd\ufffd\ufffd"'.encode('utf-8', 'replace')
self.assertTrue(
readtickets[0] == FailTicket("127.0.0.1", 0, [invstr, 'user "\xc3\xa4\xc3\xb6\xc3\xbc\xc3\x9f"'])
or readtickets[0] == tickets[0]
)
self.assertTrue(
readtickets[1] == FailTicket("127.0.0.2", 0, [invstr, u'user "\xc3\xa4\xc3\xb6\xc3\xbc\xc3\x9f"'.encode('utf-8', 'replace')])
or readtickets[1] == tickets[1]
)
self.assertTrue(
readtickets[2] == FailTicket("127.0.0.3", 0, [invstr, 'user "\xc3\xa4\xc3\xb6\xc3\xbc\xc3\x9f"'])
or readtickets[2] == tickets[2]
)
def testDelBan(self): def testDelBan(self):
self.testAddBan() self.testAddBan()
ticket = self.db.getBans(jail=self.jail)[0] ticket = self.db.getBans(jail=self.jail)[0]