Skip to content

Commit

Permalink
Merge 15d2ec0 into 4b52619
Browse files Browse the repository at this point in the history
  • Loading branch information
Coffeemaker committed Mar 7, 2016
2 parents 4b52619 + 15d2ec0 commit 64ed131
Show file tree
Hide file tree
Showing 3 changed files with 95 additions and 5 deletions.
61 changes: 61 additions & 0 deletions test/test_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from txtorcon.util import ip_from_int
from txtorcon.util import find_tor_binary
from txtorcon.util import maybe_ip_addr
from txtorcon.util import unescape_quoted_string

import os
import tempfile
Expand Down Expand Up @@ -277,3 +278,63 @@ def foo(blam):
raise ValueError('testing')
ipaddr.IPAddress.side_effect = foo
ip = maybe_ip_addr('1.2.3.4')


class TestUnescapeQuotedString(unittest.TestCase):
'''
Test cases for the function unescape_quoted_string.
'''
def test_valid_string_unescaping(self):
unescapeable = {
'\\\\': '\\', # \\ -> \
r'\"': r'"', # \" -> "
r'\\\"': r'\"', # \\\" -> \"
r'\\\\\"': r'\\"', # \\\\\" -> \\"
'\\"\\\\': '"\\', # \"\\ -> "\
"\\'": "'", # \' -> '
"\\\\\\'": "\\'", # \\\' -> \
r'some\"text': 'some"text',
'some\\word': 'someword',
'\\delete\\ al\\l un\\used \\backslashes': 'delete all unused backslashes',
'\\n\\r\\t': '\n\r\t',
'\\x00 \\x0123': 'x00 x0123',
'\\\\x00 \\\\x00': '\\x00 \\x00',
'\\\\\\x00 \\\\\\x00': '\\x00 \\x00'
}

for escaped, correct_unescaped in unescapeable.items():
escaped = '"{}"'.format(escaped)
unescaped = unescape_quoted_string(escaped)
msg = "Wrong unescape: {escaped} -> {unescaped} instead of {correct}"
msg = msg.format(unescaped=unescaped, escaped=escaped,
correct=correct_unescaped)
self.assertEqual(unescaped, correct_unescaped, msg=msg)

def test_string_unescape_octals(self):
'''
Octal numbers can be escaped by a backslash:
\0 is interpreted as a byte with the value 0
'''
for number in range(1000):
escaped = '\\{}'.format(number)
result = unescape_quoted_string('"{}"'.format(escaped))

expected = escaped.decode('string-escape')
if expected[0] == '\\' and len(expected) > 1:
expected = expected[1:]

msg = "Number not decoded correctly: {escaped} -> {result} instead of {expected}"
msg = msg.format(escaped=escaped, result=repr(result), expected=repr(expected))
self.assertEquals(result, expected, msg=msg)


def test_invalid_string_unescaping(self):
invalid_escaped = [
'"""', # " - unescaped quote
'"\\"', # \ - unescaped backslash
'"\\\\\\"', # \\\ - uneven backslashes
'"\\\\""', # \\" - quotes not escaped
]

for invalid_string in invalid_escaped:
self.assertRaises(ValueError, unescape_quoted_string, invalid_string)
9 changes: 4 additions & 5 deletions txtorcon/torcontrolprotocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

from zope.interface import implementer

from txtorcon.util import hmac_sha256, compare_via_hash
from txtorcon.util import hmac_sha256, compare_via_hash, unescape_quoted_string
from txtorcon.log import txtorlog

from txtorcon.interface import ITorControlProtocol
Expand Down Expand Up @@ -663,7 +663,6 @@ def _do_authenticate(self, protoinfo):
Callback on PROTOCOLINFO to actually authenticate once we know
what's supported.
"""

methods = None
cookie_auth = False
for line in protoinfo.split('\n'):
Expand All @@ -676,11 +675,11 @@ def _do_authenticate(self, protoinfo):
)

if 'SAFECOOKIE' in methods or 'COOKIE' in methods:
cookiefile_match = re.search(r'COOKIEFILE="((?:[^"\\]|\\.)*)"', protoinfo)
cookiefile_match = re.search(r'COOKIEFILE=("(?:[^"\\]|\\.)*")',
protoinfo)
if cookiefile_match:
cookiefile = cookiefile_match.group(1)
cookiefile = cookiefile.replace('\\\\', '\\')
cookiefile = cookiefile.replace('\\"', '"')
cookiefile = unescape_quoted_string(cookiefile)
try:
self._read_cookie(cookiefile)
except IOError as why:
Expand Down
30 changes: 30 additions & 0 deletions txtorcon/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import socket
import subprocess
import struct
import re

from twisted.internet import defer
from twisted.internet.interfaces import IProtocolFactory
Expand Down Expand Up @@ -292,3 +293,32 @@ def available_tcp_port(reactor):
address = port.getHost()
yield port.stopListening()
defer.returnValue(address.port)


def unescape_quoted_string(string):
r'''
This function implementes the recommended functionality described in the
tor control-spec to be compatible with older tor versions:
* Read \\n \\t \\r and \\0 ... \\377 as C escapes.
* Treat a backslash followed by any other character as that character.
Except the legacy support for the escape sequences above this function
implements parsing of QuotedString using qcontent from
QuotedString = DQUOTE *qcontent DQUOTE
:param string: The escaped quoted string.
:returns: The unescaped string.
:raises ValueError: If the string is in a invalid form
(e.g. a single backslash)
'''
match = re.match(r'''^"((?:[^"\\]|\\.)*)"$''', string)
if not match:
raise ValueError("Invalid quoted string", string)
string = match.group(1)
# remove backslash before all characters which should not be
# handeled as escape codes by string.decode('string-escape').
# This is needed so e.g. '\x00' is not unescaped as '\0'
string = re.sub(r'((?:^|[^\\])(?:\\\\)*)\\([^ntr0-7\\])', r'\1\2', string)
return string.decode('string-escape')

0 comments on commit 64ed131

Please sign in to comment.