Skip to content

Commit

Permalink
Fix parsing of quoted strings
Browse files Browse the repository at this point in the history
When imaputil was parsing quoted strings, it treated "abcd\\"
as incomplete quoted string having escaped quote, rather than
properly-quoted string having escaped backslash.

GitHub issue: #53
Signed-off-by: Eygene Ryabinkin <rea@codelabs.ru>
  • Loading branch information
konvpalto committed Sep 19, 2013
1 parent be1c72e commit 57adfc2
Show file tree
Hide file tree
Showing 2 changed files with 45 additions and 14 deletions.
3 changes: 3 additions & 0 deletions Changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ WIP (add new stuff for the next release)
* Updated bundled imaplib2 to 2.36: it includes support for SSL
version override that was integrated into our code before,
no other changes.
* Fixed parsing of quoted strings in IMAP responses: strings like "\\"
were treated as having \" as the escaped quote, rather than treating
it as the quoted escaped backslash (GitHub#53).

OfflineIMAP v6.5.5-rc1 (2012-09-05)
===================================
Expand Down
56 changes: 42 additions & 14 deletions offlineimap/imaputil.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,6 @@
from offlineimap.ui import getglobalui


# find the first quote in a string
quotere = re.compile(
r"""(?P<quote>"[^\"\\]*(?:\\"|[^"])*") # Quote, possibly containing encoded
# quotation mark
\s*(?P<rest>.*)$ # Whitespace & remainder of string""",
re.VERBOSE)

def debug(*args):
msg = []
for arg in args:
Expand Down Expand Up @@ -144,13 +137,9 @@ def imapsplit(imapstring):
retval.append(parenlist)
elif workstr[0] == '"':
# quoted fragments '"...\"..."'
m = quotere.match(workstr)
if not m:
raise ValueError ("failed to parse "
"quoted component %s " % str(workstr) + \
"while working with %s" % str(imapstring))
retval.append(m.group('quote'))
workstr = m.group('rest')
(quoted, rest) = _split_quoted(workstr)
retval.append(quoted)
workstr = rest
else:
splits = string.split(workstr, maxsplit = 1)
splitslen = len(splits)
Expand Down Expand Up @@ -222,3 +211,42 @@ def getrange(start, end):

retval.append(getrange(start, end)) # Add final range/item
return ",".join(retval)


def _split_quoted(string):
"""
Looks for the ending quote character in the string that starts
with quote character, splitting out quoted component and the
rest of the string (without possible space between these two
parts.
First character of the string is taken to be quote character.
Examples:
- "this is \" a test" (\\None) => ("this is \" a test", (\\None))
- "\\" => ("\\", )
"""

if len(string) == 0:
return ('', '')

q = quoted = string[0]
rest = string[1:]
while True:
next_q = rest.find(q)
if next_q == -1:
raise ValueError("can't find ending quote '%s' in '%s'" % (q, string))
# If quote is preceeded by even number of backslashes,
# then it is the ending quote, otherwise the quote
# character is escaped by backslash, so we should
# continue our search.
is_escaped = False
i = next_q - 1
while i >= 0 and rest[i] == '\\':
i -= 1
is_escaped = not is_escaped
quoted += rest[0:next_q + 1]
rest = rest[next_q + 1:]
if not is_escaped:
return (quoted, rest.lstrip())

0 comments on commit 57adfc2

Please sign in to comment.