Commit 67eefb95 authored by Stavros Sachtouris's avatar Stavros Sachtouris
Browse files

Modify HTTP logs in clients for safety and clarity

Refs grnet/kamaki#32

Implement method escape_ctrl_chars
Escape control characters in HTTP data logs
Remove misleading separators from HTTP logs
parent 53d0ffe6
...@@ -43,6 +43,8 @@ import ssl ...@@ -43,6 +43,8 @@ import ssl
from kamaki.clients.utils import https from kamaki.clients.utils import https
from kamaki.clients import utils
TIMEOUT = 60.0 # seconds TIMEOUT = 60.0 # seconds
HTTP_METHODS = ['GET', 'POST', 'PUT', 'HEAD', 'DELETE', 'COPY', 'MOVE'] HTTP_METHODS = ['GET', 'POST', 'PUT', 'HEAD', 'DELETE', 'COPY', 'MOVE']
...@@ -148,7 +150,6 @@ class RequestManager(Logged): ...@@ -148,7 +150,6 @@ class RequestManager(Logged):
def dump_log(self): def dump_log(self):
plog = ('\t[%s]' % self) if self.LOG_PID else '' plog = ('\t[%s]' % self) if self.LOG_PID else ''
sendlog.info('- - - - - - -')
sendlog.info('%s %s://%s%s%s' % ( sendlog.info('%s %s://%s%s%s' % (
self.method, self.scheme, self.netloc, self.path, plog)) self.method, self.scheme, self.netloc, self.path, plog))
for key, val in self.headers.items(): for key, val in self.headers.items():
...@@ -158,8 +159,8 @@ class RequestManager(Logged): ...@@ -158,8 +159,8 @@ class RequestManager(Logged):
if self.data: if self.data:
sendlog.info('data size: %s%s' % (len(self.data), plog)) sendlog.info('data size: %s%s' % (len(self.data), plog))
if self.LOG_DATA: if self.LOG_DATA:
sendlog.info(self.data.replace(self._token, '...') if ( sendlog.info(utils.escape_ctrl_chars(self.data.replace(
self._token) else self.data) self._token, '...') if self._token else self.data))
else: else:
sendlog.info('data size: 0%s' % plog) sendlog.info('data size: 0%s' % plog)
...@@ -274,8 +275,7 @@ class ResponseManager(Logged): ...@@ -274,8 +275,7 @@ class ResponseManager(Logged):
self._status_code, self._status = r.status, unquote( self._status_code, self._status = r.status, unquote(
r.reason) r.reason)
recvlog.info( recvlog.info(
'%d %s%s' % ( '%d %s%s' % (self.status_code, self.status, plog))
self.status_code, self.status, plog))
self._headers = dict() self._headers = dict()
r_headers = r.getheaders() r_headers = r.getheaders()
...@@ -291,8 +291,7 @@ class ResponseManager(Logged): ...@@ -291,8 +291,7 @@ class ResponseManager(Logged):
data = '%s%s' % (self._content, plog) data = '%s%s' % (self._content, plog)
if self._token: if self._token:
data = data.replace(self._token, '...') data = data.replace(self._token, '...')
recvlog.info(data) recvlog.info(utils.escape_ctrl_chars(data))
recvlog.info('- - - - - - -')
break break
except Exception as err: except Exception as err:
if isinstance(err, HTTPException): if isinstance(err, HTTPException):
...@@ -382,11 +381,9 @@ class SilentEvent(Thread): ...@@ -382,11 +381,9 @@ class SilentEvent(Thread):
try: try:
self._value = self.method(*(self.args), **(self.kwargs)) self._value = self.method(*(self.args), **(self.kwargs))
except Exception as e: except Exception as e:
estatus = e.status if isinstance(e, ClientError) else ''
recvlog.debug('Thread %s got exception %s\n<%s %s' % ( recvlog.debug('Thread %s got exception %s\n<%s %s' % (
self, self, type(e), estatus, e))
type(e),
e.status if isinstance(e, ClientError) else '',
e))
self._exception = e self._exception = e
......
...@@ -31,6 +31,8 @@ ...@@ -31,6 +31,8 @@
# interpreted as representing official policies, either expressed # interpreted as representing official policies, either expressed
# or implied, of GRNET S.A. # or implied, of GRNET S.A.
import unicodedata
def _matches(val1, val2, exactMath=True): def _matches(val1, val2, exactMath=True):
"""Case Insensitive match""" """Case Insensitive match"""
...@@ -105,3 +107,14 @@ def readall(openfile, size, retries=7): ...@@ -105,3 +107,14 @@ def readall(openfile, size, retries=7):
continue continue
return buf return buf
raise IOError('Failed to read %s bytes from file' % size) raise IOError('Failed to read %s bytes from file' % size)
def escape_ctrl_chars(s):
"""Escape control characters from unicode and string objects."""
if isinstance(s, unicode):
return "".join(ch.encode("unicode_escape") if (
unicodedata.category(ch)[0]) == "C" else ch for ch in s)
if isinstance(s, basestring):
return "".join(
[c if 31 < ord(c) < 127 else c.encode("string_escape") for c in s])
return s
...@@ -31,10 +31,9 @@ ...@@ -31,10 +31,9 @@
# interpreted as representing official policies, either expressed # interpreted as representing official policies, either expressed
# or implied, of GRNET S.A. # or implied, of GRNET S.A.
#from mock import patch, call
from unittest import TestCase from unittest import TestCase
from tempfile import TemporaryFile from tempfile import TemporaryFile
from itertools import product
from kamaki.clients import utils from kamaki.clients import utils
...@@ -140,6 +139,24 @@ class Utils(TestCase): ...@@ -140,6 +139,24 @@ class Utils(TestCase):
self.assertEqual(utils.readall(f, 1), '') self.assertEqual(utils.readall(f, 1), '')
self.assertRaises(IOError, utils.readall, f, 1, 0) self.assertRaises(IOError, utils.readall, f, 1, 0)
def test_escape_ctrl_chars(self):
gr_synnefo = u'\u03c3\u03cd\u03bd\u03bd\u03b5\u03c6\u03bf'
gr_kamaki = u'\u03ba\u03b1\u03bc\u03ac\u03ba\u03b9'
char_pairs = (
('\b', '\\x08'), ('\n', '\\n'), ('\a', '\\x07'), ('\f', '\\x0c'),
('\t', '\\t'), ('\v', '\\x0b'), ('\r', '\\r'), ('\072', ':'),
('\016', '\\x0e'), ('\\', '\\'), ('\\n', '\\n'), ("'", '\''),
('"', '"'), (u'\u039f\x89', u'\u039f\\x89'),
)
for orig_char, esc_char in char_pairs:
for word1, word2 in product(
('synnefo', gr_kamaki), ('kamaki', gr_synnefo)):
orig_str = word1 + orig_char + word2
esc_str = word1 + esc_char + word2
self.assertEqual(utils.escape_ctrl_chars(orig_str), esc_str)
if __name__ == '__main__': if __name__ == '__main__':
from sys import argv from sys import argv
from kamaki.clients.test import runTestCase from kamaki.clients.test import runTestCase
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment