Skip to content

Commit bd0aaf6

Browse files
authored
Updated the hmac + mailbox libraries + associated tests (#6608)
* Updated hmac + test library * Updated mailbox library * Added mailbox library test (v3.13.10)
1 parent 37c47fc commit bd0aaf6

File tree

4 files changed

+2588
-13
lines changed

4 files changed

+2588
-13
lines changed

Lib/hmac.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ def __init__(self, key, msg=None, digestmod=''):
5353
raise TypeError("key: expected bytes or bytearray, but got %r" % type(key).__name__)
5454

5555
if not digestmod:
56-
raise TypeError("Missing required parameter 'digestmod'.")
56+
raise TypeError("Missing required argument 'digestmod'.")
5757

5858
if _hashopenssl and isinstance(digestmod, (str, _functype)):
5959
try:

Lib/mailbox.py

Lines changed: 84 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -395,6 +395,56 @@ def get_file(self, key):
395395
f = open(os.path.join(self._path, self._lookup(key)), 'rb')
396396
return _ProxyFile(f)
397397

398+
def get_info(self, key):
399+
"""Get the keyed message's "info" as a string."""
400+
subpath = self._lookup(key)
401+
if self.colon in subpath:
402+
return subpath.split(self.colon)[-1]
403+
return ''
404+
405+
def set_info(self, key, info: str):
406+
"""Set the keyed message's "info" string."""
407+
if not isinstance(info, str):
408+
raise TypeError(f'info must be a string: {type(info)}')
409+
old_subpath = self._lookup(key)
410+
new_subpath = old_subpath.split(self.colon)[0]
411+
if info:
412+
new_subpath += self.colon + info
413+
if new_subpath == old_subpath:
414+
return
415+
old_path = os.path.join(self._path, old_subpath)
416+
new_path = os.path.join(self._path, new_subpath)
417+
os.rename(old_path, new_path)
418+
self._toc[key] = new_subpath
419+
420+
def get_flags(self, key):
421+
"""Return as a string the standard flags that are set on the keyed message."""
422+
info = self.get_info(key)
423+
if info.startswith('2,'):
424+
return info[2:]
425+
return ''
426+
427+
def set_flags(self, key, flags: str):
428+
"""Set the given flags and unset all others on the keyed message."""
429+
if not isinstance(flags, str):
430+
raise TypeError(f'flags must be a string: {type(flags)}')
431+
# TODO: check if flags are valid standard flag characters?
432+
self.set_info(key, '2,' + ''.join(sorted(set(flags))))
433+
434+
def add_flag(self, key, flag: str):
435+
"""Set the given flag(s) without changing others on the keyed message."""
436+
if not isinstance(flag, str):
437+
raise TypeError(f'flag must be a string: {type(flag)}')
438+
# TODO: check that flag is a valid standard flag character?
439+
self.set_flags(key, ''.join(set(self.get_flags(key)) | set(flag)))
440+
441+
def remove_flag(self, key, flag: str):
442+
"""Unset the given string flag(s) without changing others on the keyed message."""
443+
if not isinstance(flag, str):
444+
raise TypeError(f'flag must be a string: {type(flag)}')
445+
if self.get_flags(key):
446+
self.set_flags(key, ''.join(set(self.get_flags(key)) - set(flag)))
447+
398448
def iterkeys(self):
399449
"""Return an iterator over keys."""
400450
self._refresh()
@@ -540,6 +590,8 @@ def _refresh(self):
540590
for subdir in self._toc_mtimes:
541591
path = self._paths[subdir]
542592
for entry in os.listdir(path):
593+
if entry.startswith('.'):
594+
continue
543595
p = os.path.join(path, entry)
544596
if os.path.isdir(p):
545597
continue
@@ -698,9 +750,13 @@ def flush(self):
698750
_sync_close(new_file)
699751
# self._file is about to get replaced, so no need to sync.
700752
self._file.close()
701-
# Make sure the new file's mode is the same as the old file's
702-
mode = os.stat(self._path).st_mode
703-
os.chmod(new_file.name, mode)
753+
# Make sure the new file's mode and owner are the same as the old file's
754+
info = os.stat(self._path)
755+
os.chmod(new_file.name, info.st_mode)
756+
try:
757+
os.chown(new_file.name, info.st_uid, info.st_gid)
758+
except (AttributeError, OSError):
759+
pass
704760
try:
705761
os.rename(new_file.name, self._path)
706762
except FileExistsError:
@@ -778,10 +834,11 @@ def get_message(self, key):
778834
"""Return a Message representation or raise a KeyError."""
779835
start, stop = self._lookup(key)
780836
self._file.seek(start)
781-
from_line = self._file.readline().replace(linesep, b'')
837+
from_line = self._file.readline().replace(linesep, b'').decode('ascii')
782838
string = self._file.read(stop - self._file.tell())
783839
msg = self._message_factory(string.replace(linesep, b'\n'))
784-
msg.set_from(from_line[5:].decode('ascii'))
840+
msg.set_unixfrom(from_line)
841+
msg.set_from(from_line[5:])
785842
return msg
786843

787844
def get_string(self, key, from_=False):
@@ -1089,10 +1146,24 @@ def __len__(self):
10891146
"""Return a count of messages in the mailbox."""
10901147
return len(list(self.iterkeys()))
10911148

1149+
def _open_mh_sequences_file(self, text):
1150+
mode = '' if text else 'b'
1151+
kwargs = {'encoding': 'ASCII'} if text else {}
1152+
path = os.path.join(self._path, '.mh_sequences')
1153+
while True:
1154+
try:
1155+
return open(path, 'r+' + mode, **kwargs)
1156+
except FileNotFoundError:
1157+
pass
1158+
try:
1159+
return open(path, 'x+' + mode, **kwargs)
1160+
except FileExistsError:
1161+
pass
1162+
10921163
def lock(self):
10931164
"""Lock the mailbox."""
10941165
if not self._locked:
1095-
self._file = open(os.path.join(self._path, '.mh_sequences'), 'rb+')
1166+
self._file = self._open_mh_sequences_file(text=False)
10961167
_lock_file(self._file)
10971168
self._locked = True
10981169

@@ -1146,7 +1217,11 @@ def remove_folder(self, folder):
11461217
def get_sequences(self):
11471218
"""Return a name-to-key-list dictionary to define each sequence."""
11481219
results = {}
1149-
with open(os.path.join(self._path, '.mh_sequences'), 'r', encoding='ASCII') as f:
1220+
try:
1221+
f = open(os.path.join(self._path, '.mh_sequences'), 'r', encoding='ASCII')
1222+
except FileNotFoundError:
1223+
return results
1224+
with f:
11501225
all_keys = set(self.keys())
11511226
for line in f:
11521227
try:
@@ -1169,7 +1244,7 @@ def get_sequences(self):
11691244

11701245
def set_sequences(self, sequences):
11711246
"""Set sequences using the given name-to-key-list dictionary."""
1172-
f = open(os.path.join(self._path, '.mh_sequences'), 'r+', encoding='ASCII')
1247+
f = self._open_mh_sequences_file(text=True)
11731248
try:
11741249
os.close(os.open(f.name, os.O_WRONLY | os.O_TRUNC))
11751250
for name, keys in sequences.items():
@@ -1956,10 +2031,7 @@ def readlines(self, sizehint=None):
19562031

19572032
def __iter__(self):
19582033
"""Iterate over lines."""
1959-
while True:
1960-
line = self.readline()
1961-
if not line:
1962-
return
2034+
while line := self.readline():
19632035
yield line
19642036

19652037
def tell(self):

Lib/test/test_hmac.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -505,6 +505,14 @@ def test_exercise_all_methods(self):
505505
self.fail("Exception raised during normal usage of HMAC class.")
506506

507507

508+
class UpdateTestCase(unittest.TestCase):
509+
@hashlib_helper.requires_hashdigest('sha256')
510+
def test_with_str_update(self):
511+
with self.assertRaises(TypeError):
512+
h = hmac.new(b"key", digestmod='sha256')
513+
h.update("invalid update")
514+
515+
508516
class CopyTestCase(unittest.TestCase):
509517

510518
@hashlib_helper.requires_hashdigest('sha256')

0 commit comments

Comments
 (0)