Skip to content

Commit 0a1e16b

Browse files
committed
Clearer and more consistent use of raw strings
1 parent 59e6987 commit 0a1e16b

File tree

1 file changed

+22
-21
lines changed

1 file changed

+22
-21
lines changed

emailproxy.py

Lines changed: 22 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
__author__ = 'Simon Robinson'
77
__copyright__ = 'Copyright (c) 2022 Simon Robinson'
88
__license__ = 'Apache 2.0'
9-
__version__ = '2023-03-22' # ISO 8601 (YYYY-MM-DD)
9+
__version__ = '2023-03-27' # ISO 8601 (YYYY-MM-DD)
1010

1111
import abc
1212
import argparse
@@ -148,11 +148,11 @@ class NSObject:
148148
LOG_FILE_MAX_SIZE = 32 * 1024 * 1024 # when using a log file, its maximum size in bytes before rollover (0 = no limit)
149149
LOG_FILE_MAX_BACKUPS = 10 # the number of log files to keep when LOG_FILE_MAX_SIZE is exceeded (0 = disable rollover)
150150

151-
IMAP_TAG_PATTERN = r"[!#$&',-\[\]-z|}~]+" # https://ietf.org/rfc/rfc9051.html#name-formal-syntax
152-
IMAP_AUTHENTICATION_REQUEST_MATCHER = re.compile(
153-
r'^(?P<tag>%s) (?P<command>(LOGIN|AUTHENTICATE)) (?P<flags>.*)$' % IMAP_TAG_PATTERN, flags=re.IGNORECASE)
151+
IMAP_TAG_PATTERN = r'[!#$&\',-\[\]-z|}~]+' # https://ietf.org/rfc/rfc9051.html#name-formal-syntax
152+
IMAP_AUTHENTICATION_REQUEST_MATCHER = re.compile('^(?P<tag>%s) (?P<command>(LOGIN|AUTHENTICATE)) '
153+
'(?P<flags>.*)$' % IMAP_TAG_PATTERN, flags=re.IGNORECASE)
154154
IMAP_LITERAL_MATCHER = re.compile(r'^{(?P<length>\d+)(?P<continuation>\+?)}$')
155-
IMAP_CAPABILITY_MATCHER = re.compile(r'^(?:\* |\* OK \[)CAPABILITY .*$', flags=re.IGNORECASE) # note: '* ' and '* OK ['
155+
IMAP_CAPABILITY_MATCHER = re.compile(r'^\* (?:OK \[)?CAPABILITY .*$', flags=re.IGNORECASE) # note: '* ' *and* '* OK ['
156156

157157
REQUEST_QUEUE = queue.Queue() # requests for authentication
158158
RESPONSE_QUEUE = queue.Queue() # responses from user
@@ -945,7 +945,7 @@ def encode_oauth2_string(input_string):
945945
def strip_quotes(text):
946946
"""Remove double quotes (i.e., " characters) around a string - used for IMAP LOGIN command"""
947947
if text.startswith('"') and text.endswith('"'):
948-
return text[1:-1].replace('\\"', '"') # also need to fix any escaped quotes within the string
948+
return text[1:-1].replace(r'\"', '"') # also need to fix any escaped quotes within the string
949949
return text
950950

951951
@staticmethod
@@ -1151,11 +1151,12 @@ def handle_read(self):
11511151
else:
11521152
# IMAP LOGIN command with inline username/password, POP PASS and IMAP/POP/SMTP AUTH(ENTICATE)
11531153
tag_pattern = IMAP_TAG_PATTERN.encode('utf-8')
1154-
log_data = re.sub(b'(%s) (LOGIN) (.*)\r\n' % tag_pattern, b'\\1 \\2 %s\r\n' % CENSOR_MESSAGE,
1155-
line, flags=re.IGNORECASE)
1156-
log_data = re.sub(b'(PASS) (.*)\r\n', b'\\1 %s\r\n' % CENSOR_MESSAGE, log_data, flags=re.IGNORECASE)
1157-
log_data = re.sub(b'(%s)?( ?)(AUTH)(ENTICATE)? (PLAIN|LOGIN) (.*)\r\n' % tag_pattern,
1158-
b'\\1\\2\\3\\4 \\5 %s\r\n' % CENSOR_MESSAGE, log_data, flags=re.IGNORECASE)
1154+
log_data = re.sub(b'(%s) (LOGIN) (.*)\r\n' % tag_pattern,
1155+
br'\1 \2 ' + CENSOR_MESSAGE + b'\r\n', line, flags=re.IGNORECASE)
1156+
log_data = re.sub(b'(PASS) (.*)\r\n',
1157+
br'\1 ' + CENSOR_MESSAGE + b'\r\n', log_data, flags=re.IGNORECASE)
1158+
log_data = re.sub(b'(%s)?( )?(AUTH)(ENTICATE)? (PLAIN|LOGIN) (.*)\r\n' % tag_pattern,
1159+
br'\1\2\3\4 \5 ' + CENSOR_MESSAGE + b'\r\n', log_data, flags=re.IGNORECASE)
11591160

11601161
Log.debug(self.info_string(), '-->', log_data)
11611162
try:
@@ -1641,16 +1642,16 @@ def process_data(self, byte_data):
16411642

16421643
# intercept pre-auth CAPABILITY response to advertise only AUTH=PLAIN (+SASL-IR) and re-enable LOGIN if required
16431644
if IMAP_CAPABILITY_MATCHER.match(str_response):
1644-
capability = r"[!#$&'+-\[^-z|}~]+" # https://ietf.org/rfc/rfc9051.html#name-formal-syntax
1645-
updated_response = re.sub(r'( AUTH=' + capability + r')+', ' AUTH=PLAIN', str_response, flags=re.IGNORECASE)
1646-
if not re.search(r' AUTH=PLAIN', updated_response, re.IGNORECASE):
1645+
capability = r'[!#$&\'+-\[^-z|}~]+' # https://ietf.org/rfc/rfc9051.html#name-formal-syntax
1646+
updated_response = re.sub('( AUTH=%s)+' % capability, ' AUTH=PLAIN', str_response, flags=re.IGNORECASE)
1647+
if not re.search(' AUTH=PLAIN', updated_response, re.IGNORECASE):
16471648
# cannot just replace e.g., one 'CAPABILITY ' match because IMAP4 must be first if present (RFC 1730)
1648-
updated_response = re.sub(r'(CAPABILITY)( IMAP' + capability + r')?', r'\g<1>\g<2> AUTH=PLAIN',
1649-
updated_response, count=1, flags=re.IGNORECASE)
1649+
updated_response = re.sub('(CAPABILITY)( IMAP%s)?' % capability, r'\1\2 AUTH=PLAIN', updated_response,
1650+
count=1, flags=re.IGNORECASE)
16501651
updated_response = updated_response.replace(' AUTH=PLAIN', '', updated_response.count(' AUTH=PLAIN') - 1)
1651-
if not re.search(r' SASL-IR', updated_response, re.IGNORECASE):
1652+
if not re.search(' SASL-IR', updated_response, re.IGNORECASE):
16521653
updated_response = updated_response.replace(' AUTH=PLAIN', ' AUTH=PLAIN SASL-IR')
1653-
updated_response = re.sub(r' LOGINDISABLED', '', updated_response, count=1, flags=re.IGNORECASE)
1654+
updated_response = re.sub(' LOGINDISABLED', '', updated_response, count=1, flags=re.IGNORECASE)
16541655
byte_data = (b'%s\r\n' % updated_response.encode('utf-8'))
16551656

16561657
super().process_data(byte_data)
@@ -1768,7 +1769,7 @@ def process_data(self, byte_data):
17681769
# intercept EHLO response AUTH capabilities and replace with what we can actually do - note that we assume
17691770
# an AUTH line will be included in the response; if there are any servers for which this is not the case, we
17701771
# could cache and re-stream as in POP. Formal syntax: https://tools.ietf.org/html/rfc4954#section-8
1771-
updated_response = re.sub(r'250([ -])AUTH( [!-*,-<>-~]+)+', '250\\1AUTH PLAIN LOGIN', str_data,
1772+
updated_response = re.sub('250([ -])AUTH( [!-*,-<>-~]+)+', r'250\1AUTH PLAIN LOGIN', str_data,
17721773
flags=re.IGNORECASE)
17731774
updated_response = b'%s\r\n' % updated_response.encode('utf-8')
17741775
if self.starttls_state is self.STARTTLS.COMPLETE:
@@ -2619,7 +2620,7 @@ def get_script_start_command(self, quote_args=True):
26192620
if self.args.external_auth:
26202621
script_command.append('--external-auth')
26212622

2622-
return ['"%s"' % arg.replace('"', '\\"') if quote_args and ' ' in arg else arg for arg in script_command]
2623+
return ['"%s"' % arg.replace('"', r'\"') if quote_args and ' ' in arg else arg for arg in script_command]
26232624

26242625
def linux_restart(self, icon):
26252626
# Linux restarting is separate because it is used for reloading the configuration file as well as start at login
@@ -2680,7 +2681,7 @@ def notify(self, title, text):
26802681
notification_centre.setDelegate_(self.macos_user_notification_centre_delegate)
26812682
notification_centre.deliverNotification_(user_notification)
26822683
except Exception:
2683-
for replacement in (('\\', '\\\\'), ('"', '\\"')): # osascript approach requires sanitisation
2684+
for replacement in (('\\', r'\\'), ('"', r'\"')): # osascript approach requires sanitisation
26842685
text = text.replace(*replacement)
26852686
title = title.replace(*replacement)
26862687
subprocess.call(['osascript', '-e', 'display notification "%s" with title "%s"' % (text, title)])

0 commit comments

Comments
 (0)