extract-msg 0.47.0__tar.gz → 0.48.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. {extract_msg-0.47.0 → extract_msg-0.48.1}/CHANGELOG.md +16 -0
  2. {extract_msg-0.47.0 → extract_msg-0.48.1}/PKG-INFO +5 -4
  3. {extract_msg-0.47.0 → extract_msg-0.48.1}/README.rst +4 -3
  4. {extract_msg-0.47.0 → extract_msg-0.48.1}/extract_msg/__init__.py +2 -2
  5. {extract_msg-0.47.0 → extract_msg-0.48.1}/extract_msg/__main__.py +4 -3
  6. {extract_msg-0.47.0 → extract_msg-0.48.1}/extract_msg/attachments/attachment_base.py +56 -23
  7. {extract_msg-0.47.0 → extract_msg-0.48.1}/extract_msg/encoding/utils.py +1 -1
  8. {extract_msg-0.47.0 → extract_msg-0.48.1}/extract_msg/enums.py +6 -1
  9. {extract_msg-0.47.0 → extract_msg-0.48.1}/extract_msg/msg_classes/message_signed_base.py +30 -9
  10. {extract_msg-0.47.0 → extract_msg-0.48.1}/extract_msg/msg_classes/msg.py +2 -2
  11. {extract_msg-0.47.0 → extract_msg-0.48.1}/extract_msg/ole_writer.py +69 -25
  12. {extract_msg-0.47.0 → extract_msg-0.48.1}/extract_msg/properties/named.py +41 -6
  13. {extract_msg-0.47.0 → extract_msg-0.48.1}/extract_msg/properties/properties_store.py +1 -1
  14. {extract_msg-0.47.0 → extract_msg-0.48.1}/extract_msg/structures/cfoas.py +4 -4
  15. {extract_msg-0.47.0 → extract_msg-0.48.1}/extract_msg/structures/entry_id.py +1 -1
  16. {extract_msg-0.47.0 → extract_msg-0.48.1}/extract_msg/structures/ole_pres.py +8 -8
  17. {extract_msg-0.47.0 → extract_msg-0.48.1}/extract_msg/structures/ole_stream_struct.py +2 -2
  18. {extract_msg-0.47.0 → extract_msg-0.48.1}/extract_msg/structures/recurrence_pattern.py +2 -2
  19. {extract_msg-0.47.0 → extract_msg-0.48.1}/extract_msg/structures/report_tag.py +3 -3
  20. {extract_msg-0.47.0 → extract_msg-0.48.1}/extract_msg/utils.py +24 -13
  21. {extract_msg-0.47.0 → extract_msg-0.48.1}/extract_msg.egg-info/PKG-INFO +5 -4
  22. {extract_msg-0.47.0 → extract_msg-0.48.1}/LICENSE.txt +0 -0
  23. {extract_msg-0.47.0 → extract_msg-0.48.1}/MANIFEST.in +0 -0
  24. {extract_msg-0.47.0 → extract_msg-0.48.1}/extract_msg/_rtf/__init__.py +0 -0
  25. {extract_msg-0.47.0 → extract_msg-0.48.1}/extract_msg/_rtf/create_doc.py +0 -0
  26. {extract_msg-0.47.0 → extract_msg-0.48.1}/extract_msg/_rtf/inject_rtf.py +0 -0
  27. {extract_msg-0.47.0 → extract_msg-0.48.1}/extract_msg/_rtf/token.py +0 -0
  28. {extract_msg-0.47.0 → extract_msg-0.48.1}/extract_msg/_rtf/tokenize_rtf.py +0 -0
  29. {extract_msg-0.47.0 → extract_msg-0.48.1}/extract_msg/attachments/__init__.py +0 -0
  30. {extract_msg-0.47.0 → extract_msg-0.48.1}/extract_msg/attachments/attachment.py +0 -0
  31. {extract_msg-0.47.0 → extract_msg-0.48.1}/extract_msg/attachments/broken_att.py +0 -0
  32. {extract_msg-0.47.0 → extract_msg-0.48.1}/extract_msg/attachments/custom_att.py +0 -0
  33. {extract_msg-0.47.0 → extract_msg-0.48.1}/extract_msg/attachments/custom_att_handler/__init__.py +0 -0
  34. {extract_msg-0.47.0 → extract_msg-0.48.1}/extract_msg/attachments/custom_att_handler/custom_handler.py +0 -0
  35. {extract_msg-0.47.0 → extract_msg-0.48.1}/extract_msg/attachments/custom_att_handler/lnk_obj_att.py +0 -0
  36. {extract_msg-0.47.0 → extract_msg-0.48.1}/extract_msg/attachments/custom_att_handler/outlook_image_dib.py +0 -0
  37. {extract_msg-0.47.0 → extract_msg-0.48.1}/extract_msg/attachments/emb_msg_att.py +0 -0
  38. {extract_msg-0.47.0 → extract_msg-0.48.1}/extract_msg/attachments/signed_att.py +0 -0
  39. {extract_msg-0.47.0 → extract_msg-0.48.1}/extract_msg/attachments/unsupported_att.py +0 -0
  40. {extract_msg-0.47.0 → extract_msg-0.48.1}/extract_msg/attachments/web_att.py +0 -0
  41. {extract_msg-0.47.0 → extract_msg-0.48.1}/extract_msg/constants/__init__.py +0 -0
  42. {extract_msg-0.47.0 → extract_msg-0.48.1}/extract_msg/constants/ps.py +0 -0
  43. {extract_msg-0.47.0 → extract_msg-0.48.1}/extract_msg/constants/re.py +0 -0
  44. {extract_msg-0.47.0 → extract_msg-0.48.1}/extract_msg/constants/st.py +0 -0
  45. {extract_msg-0.47.0 → extract_msg-0.48.1}/extract_msg/data/logging-config/logging-nt.json +0 -0
  46. {extract_msg-0.47.0 → extract_msg-0.48.1}/extract_msg/data/logging-config/logging-posix.json +0 -0
  47. {extract_msg-0.47.0 → extract_msg-0.48.1}/extract_msg/encoding/__init__.py +0 -0
  48. {extract_msg-0.47.0 → extract_msg-0.48.1}/extract_msg/encoding/_dt/__init__.py +0 -0
  49. {extract_msg-0.47.0 → extract_msg-0.48.1}/extract_msg/encoding/_dt/_mac_ce.py +0 -0
  50. {extract_msg-0.47.0 → extract_msg-0.48.1}/extract_msg/encoding/_dt/_mac_cyrillic.py +0 -0
  51. {extract_msg-0.47.0 → extract_msg-0.48.1}/extract_msg/encoding/_dt/_mac_greek.py +0 -0
  52. {extract_msg-0.47.0 → extract_msg-0.48.1}/extract_msg/encoding/_dt/_mac_iceland.py +0 -0
  53. {extract_msg-0.47.0 → extract_msg-0.48.1}/extract_msg/encoding/_dt/_mac_turkish.py +0 -0
  54. {extract_msg-0.47.0 → extract_msg-0.48.1}/extract_msg/encoding/_dt/_win874_dec.py +0 -0
  55. {extract_msg-0.47.0 → extract_msg-0.48.1}/extract_msg/encoding/_dt/_win950_dec.py +0 -0
  56. {extract_msg-0.47.0 → extract_msg-0.48.1}/extract_msg/exceptions.py +0 -0
  57. {extract_msg-0.47.0 → extract_msg-0.48.1}/extract_msg/msg_classes/__init__.py +0 -0
  58. {extract_msg-0.47.0 → extract_msg-0.48.1}/extract_msg/msg_classes/appointment.py +0 -0
  59. {extract_msg-0.47.0 → extract_msg-0.48.1}/extract_msg/msg_classes/calendar.py +0 -0
  60. {extract_msg-0.47.0 → extract_msg-0.48.1}/extract_msg/msg_classes/calendar_base.py +0 -0
  61. {extract_msg-0.47.0 → extract_msg-0.48.1}/extract_msg/msg_classes/contact.py +0 -0
  62. {extract_msg-0.47.0 → extract_msg-0.48.1}/extract_msg/msg_classes/journal.py +0 -0
  63. {extract_msg-0.47.0 → extract_msg-0.48.1}/extract_msg/msg_classes/meeting_cancellation.py +0 -0
  64. {extract_msg-0.47.0 → extract_msg-0.48.1}/extract_msg/msg_classes/meeting_exception.py +0 -0
  65. {extract_msg-0.47.0 → extract_msg-0.48.1}/extract_msg/msg_classes/meeting_forward.py +0 -0
  66. {extract_msg-0.47.0 → extract_msg-0.48.1}/extract_msg/msg_classes/meeting_related.py +0 -0
  67. {extract_msg-0.47.0 → extract_msg-0.48.1}/extract_msg/msg_classes/meeting_request.py +0 -0
  68. {extract_msg-0.47.0 → extract_msg-0.48.1}/extract_msg/msg_classes/meeting_response.py +0 -0
  69. {extract_msg-0.47.0 → extract_msg-0.48.1}/extract_msg/msg_classes/message.py +0 -0
  70. {extract_msg-0.47.0 → extract_msg-0.48.1}/extract_msg/msg_classes/message_base.py +0 -0
  71. {extract_msg-0.47.0 → extract_msg-0.48.1}/extract_msg/msg_classes/message_signed.py +0 -0
  72. {extract_msg-0.47.0 → extract_msg-0.48.1}/extract_msg/msg_classes/post.py +0 -0
  73. {extract_msg-0.47.0 → extract_msg-0.48.1}/extract_msg/msg_classes/sticky_note.py +0 -0
  74. {extract_msg-0.47.0 → extract_msg-0.48.1}/extract_msg/msg_classes/task.py +0 -0
  75. {extract_msg-0.47.0 → extract_msg-0.48.1}/extract_msg/msg_classes/task_request.py +0 -0
  76. {extract_msg-0.47.0 → extract_msg-0.48.1}/extract_msg/null_date.py +0 -0
  77. {extract_msg-0.47.0 → extract_msg-0.48.1}/extract_msg/open_msg.py +0 -0
  78. {extract_msg-0.47.0 → extract_msg-0.48.1}/extract_msg/properties/__init__.py +0 -0
  79. {extract_msg-0.47.0 → extract_msg-0.48.1}/extract_msg/properties/prop.py +0 -0
  80. {extract_msg-0.47.0 → extract_msg-0.48.1}/extract_msg/py.typed +0 -0
  81. {extract_msg-0.47.0 → extract_msg-0.48.1}/extract_msg/recipient.py +0 -0
  82. {extract_msg-0.47.0 → extract_msg-0.48.1}/extract_msg/structures/__init__.py +0 -0
  83. {extract_msg-0.47.0 → extract_msg-0.48.1}/extract_msg/structures/_helpers.py +0 -0
  84. {extract_msg-0.47.0 → extract_msg-0.48.1}/extract_msg/structures/business_card.py +0 -0
  85. {extract_msg-0.47.0 → extract_msg-0.48.1}/extract_msg/structures/contact_link_entry.py +0 -0
  86. {extract_msg-0.47.0 → extract_msg-0.48.1}/extract_msg/structures/dev_mode_a.py +0 -0
  87. {extract_msg-0.47.0 → extract_msg-0.48.1}/extract_msg/structures/dv_target_device.py +0 -0
  88. {extract_msg-0.47.0 → extract_msg-0.48.1}/extract_msg/structures/misc_id.py +0 -0
  89. {extract_msg-0.47.0 → extract_msg-0.48.1}/extract_msg/structures/mon_stream.py +0 -0
  90. {extract_msg-0.47.0 → extract_msg-0.48.1}/extract_msg/structures/odt.py +0 -0
  91. {extract_msg-0.47.0 → extract_msg-0.48.1}/extract_msg/structures/system_time.py +0 -0
  92. {extract_msg-0.47.0 → extract_msg-0.48.1}/extract_msg/structures/time_zone_definition.py +0 -0
  93. {extract_msg-0.47.0 → extract_msg-0.48.1}/extract_msg/structures/time_zone_struct.py +0 -0
  94. {extract_msg-0.47.0 → extract_msg-0.48.1}/extract_msg/structures/toc_entry.py +0 -0
  95. {extract_msg-0.47.0 → extract_msg-0.48.1}/extract_msg/structures/tz_rule.py +0 -0
  96. {extract_msg-0.47.0 → extract_msg-0.48.1}/extract_msg.egg-info/SOURCES.txt +0 -0
  97. {extract_msg-0.47.0 → extract_msg-0.48.1}/extract_msg.egg-info/dependency_links.txt +0 -0
  98. {extract_msg-0.47.0 → extract_msg-0.48.1}/extract_msg.egg-info/entry_points.txt +0 -0
  99. {extract_msg-0.47.0 → extract_msg-0.48.1}/extract_msg.egg-info/requires.txt +0 -0
  100. {extract_msg-0.47.0 → extract_msg-0.48.1}/extract_msg.egg-info/top_level.txt +0 -0
  101. {extract_msg-0.47.0 → extract_msg-0.48.1}/requirements.txt +0 -0
  102. {extract_msg-0.47.0 → extract_msg-0.48.1}/setup.cfg +0 -0
  103. {extract_msg-0.47.0 → extract_msg-0.48.1}/setup.py +0 -0
@@ -1,3 +1,19 @@
1
+ **v0.48.1**
2
+ * Added an option (`-s`, `--stdin`) to the command line to take an MSG file from stdin. This allows the user to pipe the MSG data from another program directly instead of having to write a middleman that uses the `extract-msg` library directly or having to write the file to the disk first.
3
+ * Changed main function to allow for manual argument list to be passed to it.
4
+ * Added attributes to `AttachmentBase` for creation and modification time. These can be accessed through `createdAt` or `creationTime` and `lastModificationTime` or `modifiedAt`.
5
+ * Changed `OleWriter` tests to output the name of the test file being done if an error occurs.
6
+ * Added tests for some command line stuff.
7
+
8
+ **v0.48.0**
9
+ * Adjusted error handling for named properties to handle critical streams being missing and to allow suppression of those errors.
10
+ * Adjusted error handling for named properties to allow silencing of errors caused by invalid references to the name stream. If `ErrorBehavior.NAMED_NAME_STREAM` is provided to the `MSGFile` instance, a warning will be logged and that entry will simply be dropped.
11
+ * Adjusted error handling for signed messages to better check for issues with the signed attachment. This should make errors from violating the standard much easier to understand. These errors can be ignored, but the attachment will *not* be parsed as a signed attachment.
12
+ * Minor docstring updates.
13
+ * Minor adjustments to `OleWriter` to prepare the code for being able to write version 4 files. Version 3 files are currently the only one's supported, but much of the code had hard-coded values that could be replaced with variables and small conditionals. This will have very little performance impact, and should not be noticeable.
14
+ * Improved comments on `OleWriter` to make private sections more understandable.
15
+ * Changed `MessageSignedBase._rawAttachments` to `MessageSignedBase.rawAttachments` to provide non-private access in a reliable way.
16
+
1
17
  **v0.47.0**
2
18
  * Changed the public API for `PropertiesStore` to improve the quality of its code. The properties type is now mandatory, and the intelligence field (and the related enum) has been removed.
3
19
  * Additionally, the `toBytes` and `__bytes__` methods will both generate based on the contents of this class, allowing for new properties to be created and for existing properties to be modified or removed if the class is set to writable on creation.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: extract_msg
3
- Version: 0.47.0
3
+ Version: 0.48.1
4
4
  Summary: Extracts emails and attachments saved in Microsoft Outlook's .msg files
5
5
  Home-page: https://github.com/TeamMsgExtractor/msg-extractor
6
6
  Download-URL: https://github.com/TeamMsgExtractor/msg-extractor/archives/master
@@ -79,7 +79,7 @@ refer to the usage information provided from the program's help dialog:
79
79
 
80
80
  usage: extract_msg [-h] [--use-content-id] [--json] [--file-logging] [-v] [--log LOG] [--config CONFIGPATH] [--out OUTPATH] [--use-filename] [--dump-stdout] [--html] [--pdf] [--wk-path WKPATH] [--wk-options [WKOPTIONS ...]]
81
81
  [--prepared-html] [--charset CHARSET] [--raw] [--rtf] [--allow-fallback] [--skip-body-not-found] [--zip ZIP] [--save-header] [--attachments-only] [--skip-hidden] [--no-folders] [--skip-embedded] [--extract-embedded]
82
- [--overwrite-existing] [--skip-not-implemented] [--out-name OUTNAME | --glob] [--ignore-rtfde] [--progress]
82
+ [--overwrite-existing] [--skip-not-implemented] [--out-name OUTNAME | --glob] [--ignore-rtfde] [--progress] [-s]
83
83
  msg [msg ...]
84
84
 
85
85
  extract_msg: Extracts emails and attachments saved in Microsoft Outlook's .msg files. https://github.com/TeamMsgExtractor/msg-extractor
@@ -125,6 +125,7 @@ refer to the usage information provided from the program's help dialog:
125
125
  --glob, --wildcard Interpret all paths as having wildcards. Incompatible with --out-name.
126
126
  --ignore-rtfde Ignores all errors thrown from RTFDE when trying to save. Useful for allowing fallback to continue when an exception happens.
127
127
  --progress Shows what file the program is currently working on during it's progress.
128
+ -s, --stdin Read file from stdin (only works with one file at a time).
128
129
 
129
130
  **To use this in your own script**, start by using:
130
131
 
@@ -277,8 +278,8 @@ your access to the newest major version of extract-msg.
277
278
  .. |License: GPL v3| image:: https://img.shields.io/badge/License-GPLv3-blue.svg
278
279
  :target: LICENSE.txt
279
280
 
280
- .. |PyPI3| image:: https://img.shields.io/badge/pypi-0.47.0-blue.svg
281
- :target: https://pypi.org/project/extract-msg/0.47.0/
281
+ .. |PyPI3| image:: https://img.shields.io/badge/pypi-0.48.1-blue.svg
282
+ :target: https://pypi.org/project/extract-msg/0.48.1/
282
283
 
283
284
  .. |PyPI2| image:: https://img.shields.io/badge/python-3.8+-brightgreen.svg
284
285
  :target: https://www.python.org/downloads/release/python-3810/
@@ -61,7 +61,7 @@ refer to the usage information provided from the program's help dialog:
61
61
 
62
62
  usage: extract_msg [-h] [--use-content-id] [--json] [--file-logging] [-v] [--log LOG] [--config CONFIGPATH] [--out OUTPATH] [--use-filename] [--dump-stdout] [--html] [--pdf] [--wk-path WKPATH] [--wk-options [WKOPTIONS ...]]
63
63
  [--prepared-html] [--charset CHARSET] [--raw] [--rtf] [--allow-fallback] [--skip-body-not-found] [--zip ZIP] [--save-header] [--attachments-only] [--skip-hidden] [--no-folders] [--skip-embedded] [--extract-embedded]
64
- [--overwrite-existing] [--skip-not-implemented] [--out-name OUTNAME | --glob] [--ignore-rtfde] [--progress]
64
+ [--overwrite-existing] [--skip-not-implemented] [--out-name OUTNAME | --glob] [--ignore-rtfde] [--progress] [-s]
65
65
  msg [msg ...]
66
66
 
67
67
  extract_msg: Extracts emails and attachments saved in Microsoft Outlook's .msg files. https://github.com/TeamMsgExtractor/msg-extractor
@@ -107,6 +107,7 @@ refer to the usage information provided from the program's help dialog:
107
107
  --glob, --wildcard Interpret all paths as having wildcards. Incompatible with --out-name.
108
108
  --ignore-rtfde Ignores all errors thrown from RTFDE when trying to save. Useful for allowing fallback to continue when an exception happens.
109
109
  --progress Shows what file the program is currently working on during it's progress.
110
+ -s, --stdin Read file from stdin (only works with one file at a time).
110
111
 
111
112
  **To use this in your own script**, start by using:
112
113
 
@@ -259,8 +260,8 @@ your access to the newest major version of extract-msg.
259
260
  .. |License: GPL v3| image:: https://img.shields.io/badge/License-GPLv3-blue.svg
260
261
  :target: LICENSE.txt
261
262
 
262
- .. |PyPI3| image:: https://img.shields.io/badge/pypi-0.47.0-blue.svg
263
- :target: https://pypi.org/project/extract-msg/0.47.0/
263
+ .. |PyPI3| image:: https://img.shields.io/badge/pypi-0.48.1-blue.svg
264
+ :target: https://pypi.org/project/extract-msg/0.48.1/
264
265
 
265
266
  .. |PyPI2| image:: https://img.shields.io/badge/python-3.8+-brightgreen.svg
266
267
  :target: https://www.python.org/downloads/release/python-3810/
@@ -27,8 +27,8 @@ https://github.com/TeamMsgExtractor/msg-extractor
27
27
  # along with this program. If not, see <http://www.gnu.org/licenses/>.
28
28
 
29
29
  __author__ = 'Destiny Peterson & Matthew Walker'
30
- __date__ = '2023-12-09'
31
- __version__ = '0.47.0'
30
+ __date__ = '2024-03-08'
31
+ __version__ = '0.48.1'
32
32
 
33
33
  __all__ = [
34
34
  # Modules:
@@ -10,12 +10,13 @@ import zipfile
10
10
 
11
11
  from extract_msg import __doc__, openMsg, utils
12
12
  from extract_msg.enums import ErrorBehavior
13
+ from typing import List
13
14
 
14
15
 
15
- def main() -> None:
16
+ def main(argv : List[str]) -> None:
16
17
  # Setup logging to stdout, indicate running from cli
17
18
  CLI_LOGGING = 'extract_msg_cli'
18
- args = utils.getCommandArgs(sys.argv[1:])
19
+ args = utils.getCommandArgs(argv[1:])
19
20
 
20
21
  # Determine where to save the files to.
21
22
  currentDir = os.getcwd() # Store this in case the path changes.
@@ -111,4 +112,4 @@ def main() -> None:
111
112
  _zip.close()
112
113
 
113
114
  if __name__ == '__main__':
114
- main()
115
+ main(sys.argv)
@@ -290,7 +290,7 @@ class AttachmentBase(abc.ABC):
290
290
 
291
291
  def getNamedProp(self, propertyName: str, guid: str, default: _T = None) -> Union[Any, _T]:
292
292
  """
293
- instance.namedProperties.get((propertyName, guid), default)
293
+ ``instance.namedProperties.get((propertyName, guid), default)``
294
294
 
295
295
  Can be overriden to create new behavior.
296
296
  """
@@ -400,7 +400,7 @@ class AttachmentBase(abc.ABC):
400
400
  the filename this function should receive should be "__substg1.0_001A".
401
401
 
402
402
  This should ALWAYS return a string if it was found, otherwise returns
403
- None.
403
+ ``None``.
404
404
 
405
405
  :raises ReferenceError: The associated ``MSGFile`` instance has been
406
406
  garbage collected.
@@ -415,10 +415,11 @@ class AttachmentBase(abc.ABC):
415
415
  class if it is found.
416
416
 
417
417
  :param overrideClass: Class/function to use to morph the data that was
418
- read. The data will be the first argument to the class's __init__
419
- function or the function itself, if that is what is provided. If
420
- the value is None, this function is not called. If you want it to
421
- be called regardless, you should handle the data directly.
418
+ read. The data will be the first argument to the class's
419
+ ``__init__`` method or the function itself, if that is what is
420
+ provided. If the value is ``None``, this function is not called. If
421
+ you want it to be called regardless, you should handle the data
422
+ directly.
422
423
  """
423
424
  value = self.getStream(streamID)
424
425
 
@@ -472,7 +473,7 @@ class AttachmentBase(abc.ABC):
472
473
  the first item specifies what the second value will be.
473
474
  """
474
475
 
475
- @functools.cached_property
476
+ @cached_property
476
477
  def attachmentEncoding(self) -> Optional[bytes]:
477
478
  """
478
479
  The encoding information about the attachment object.
@@ -482,7 +483,7 @@ class AttachmentBase(abc.ABC):
482
483
  """
483
484
  return self.getStream('__substg1.0_37020102')
484
485
 
485
- @functools.cached_property
486
+ @cached_property
486
487
  def additionalInformation(self) -> Optional[str]:
487
488
  """
488
489
  The additional information about the attachment.
@@ -494,7 +495,7 @@ class AttachmentBase(abc.ABC):
494
495
  """
495
496
  return self.getStringStream('__substg1.0_370F')
496
497
 
497
- @functools.cached_property
498
+ @cached_property
498
499
  def cid(self) -> Optional[str]:
499
500
  """
500
501
  Returns the Content ID of the attachment, if it exists.
@@ -525,8 +526,25 @@ class AttachmentBase(abc.ABC):
525
526
 
526
527
  @property
527
528
  def contentId(self) -> Optional[str]:
529
+ """
530
+ Alias of :attr:`cid`.
531
+ """
528
532
  return self.cid
529
533
 
534
+ @property
535
+ def createdAt(self) -> Optional[datetime.datetime]:
536
+ """
537
+ Alias of :attr:`creationTime`.
538
+ """
539
+ return self.creationTime
540
+
541
+ @cached_property
542
+ def creationTime(self) -> Optional[datetime.datetime]:
543
+ """
544
+ The time the attachment was created.
545
+ """
546
+ return self.getPropertyVal('30070040')
547
+
530
548
  @property
531
549
  @abc.abstractmethod
532
550
  def data(self) -> Optional[object]:
@@ -536,7 +554,7 @@ class AttachmentBase(abc.ABC):
536
554
  Returns ``None`` if there is no data to save.
537
555
  """
538
556
 
539
- @functools.cached_property
557
+ @cached_property
540
558
  def dataType(self) -> Optional[Type[object]]:
541
559
  """
542
560
  The class that the data type will use, if it can be retrieved.
@@ -559,14 +577,14 @@ class AttachmentBase(abc.ABC):
559
577
  """
560
578
  return self.__dir
561
579
 
562
- @functools.cached_property
580
+ @cached_property
563
581
  def displayName(self) -> Optional[str]:
564
582
  """
565
583
  Returns the display name of the folder.
566
584
  """
567
585
  return self.getStringStream('__substg1.0_3001')
568
586
 
569
- @functools.cached_property
587
+ @cached_property
570
588
  def exceptionReplaceTime(self) -> Optional[datetime.datetime]:
571
589
  """
572
590
  The original date and time at which the instance in the recurrence
@@ -576,48 +594,62 @@ class AttachmentBase(abc.ABC):
576
594
  """
577
595
  return self.getPropertyVal('7FF90040')
578
596
 
579
- @functools.cached_property
597
+ @cached_property
580
598
  def extension(self) -> Optional[str]:
581
599
  """
582
600
  The reported extension for the file.
583
601
  """
584
602
  return self.getStringStream('__substg1.0_3703')
585
603
 
586
- @functools.cached_property
604
+ @cached_property
587
605
  def hidden(self) -> bool:
588
606
  """
589
607
  Indicates whether an Attachment object is hidden from the end user.
590
608
  """
591
609
  return bool(self.getPropertyVal('7FFE000B'))
592
610
 
593
- @functools.cached_property
611
+ @cached_property
594
612
  def isAttachmentContactPhoto(self) -> bool:
595
613
  """
596
614
  Whether the attachment is a contact photo for a Contact object.
597
615
  """
598
616
  return bool(self.getPropertyVal('7FFF000B'))
599
617
 
600
- @functools.cached_property
618
+ @cached_property
619
+ def lastModificationTime(self) -> Optional[datetime.datetime]:
620
+ """
621
+ The last time the attachment was modified.
622
+ """
623
+ return self.getPropertyVal('30080040')
624
+
625
+ @cached_property
601
626
  def longFilename(self) -> Optional[str]:
602
627
  """
603
628
  Returns the long file name of the attachment, if it exists.
604
629
  """
605
630
  return self.getStringStream('__substg1.0_3707')
606
631
 
607
- @functools.cached_property
632
+ @cached_property
608
633
  def longPathname(self) -> Optional[str]:
609
634
  """
610
635
  The fully qualified path and file name with extension.
611
636
  """
612
637
  return self.getStringStream('__substg1.0_370D')
613
638
 
614
- @functools.cached_property
639
+ @cached_property
615
640
  def mimetype(self) -> Optional[str]:
616
641
  """
617
642
  The content-type mime header of the attachment, if specified.
618
643
  """
619
644
  return tryGetMimetype(self, self.getStringStream('__substg1.0_370E'))
620
645
 
646
+ @property
647
+ def modifiedAt(self) -> Optional[datetime.datetime]:
648
+ """
649
+ Alias of :attr:`lastModificationTime`.
650
+ """
651
+ return self.lastModificationTime
652
+
621
653
  @property
622
654
  def msg(self) -> MSGFile:
623
655
  """
@@ -630,7 +662,7 @@ class AttachmentBase(abc.ABC):
630
662
  raise ReferenceError('The MSGFile for this Attachment instance has been garbage collected.')
631
663
  return msg
632
664
 
633
- @functools.cached_property
665
+ @cached_property
634
666
  def name(self) -> Optional[str]:
635
667
  """
636
668
  The best name available for the file.
@@ -649,7 +681,7 @@ class AttachmentBase(abc.ABC):
649
681
  """
650
682
  return self.__namedProperties
651
683
 
652
- @functools.cached_property
684
+ @cached_property
653
685
  def payloadClass(self) -> Optional[str]:
654
686
  """
655
687
  The class name of an object that can display the contents of the
@@ -664,17 +696,18 @@ class AttachmentBase(abc.ABC):
664
696
  """
665
697
  return self.__props
666
698
 
667
- @functools.cached_property
699
+ @cached_property
668
700
  def renderingPosition(self) -> Optional[int]:
669
701
  """
670
702
  The offset, in rendered characters, to use when rendering the attachment
671
703
  within the main message text.
672
704
 
673
- A value of 0xFFFFFFFF indicates a hidden attachment that is not to be rendered.
705
+ A value of ``0xFFFFFFFF`` indicates a hidden attachment that is not to
706
+ be rendered.
674
707
  """
675
708
  return self.getPropertyVal('370B0003')
676
709
 
677
- @property
710
+ @cached_property
678
711
  def shortFilename(self) -> Optional[str]:
679
712
  """
680
713
  The short file name of the attachment, if it exists.
@@ -202,7 +202,7 @@ def variableByteDecode(codecName: str, data, errors: str, decodeTable: Dict[int,
202
202
  """
203
203
  Function for decoding variable-byte codecs that use one or two bytes per character.
204
204
 
205
- Checks if a character is less than 0x80, mapping it directly if so.
205
+ Checks if a character is less than ``0x80``, mapping it directly if so.
206
206
  Otherwise, it reads the next byte and combines the two before looking up the
207
207
  new value.
208
208
 
@@ -674,6 +674,9 @@ class ErrorBehavior(enum.IntFlag):
674
674
  * STANDARDS_VIOLATION: Silences StandardViolationError where acceptable.
675
675
  * OLE_DEFECT_INCORRECT: Silences defects of type DEFECT_INCORRECT that are
676
676
  enabled by default. This can lead to strange bugs.
677
+ * NAMED_NAME_STREAM: Silences errors caused by invalid indexes into the
678
+ name stream for named properties. Any properties with an invalid name will
679
+ simply be dropped.
677
680
  * SUPPRESS_ALL: Silences all of the above.
678
681
  """
679
682
  THROW = 0b000000
@@ -688,8 +691,10 @@ class ErrorBehavior(enum.IntFlag):
688
691
  # General.
689
692
  STANDARDS_VIOLATION = 0b010000
690
693
  OLE_DEFECT_INCORRECT = 0b100000
694
+ # Named Properties
695
+ NAMED_NAME_STREAM = 0b1000000
691
696
 
692
- SUPPRESS_ALL = 0b111111
697
+ SUPPRESS_ALL = 0b1111111
693
698
 
694
699
 
695
700
 
@@ -11,7 +11,7 @@ import re
11
11
  from typing import Generic, List, Optional, Type, TypeVar
12
12
 
13
13
  from ..attachments import AttachmentBase, SignedAttachment
14
- from ..enums import DeencapType, ErrorBehavior
14
+ from ..enums import AttachmentType, DeencapType, ErrorBehavior
15
15
  from ..exceptions import StandardViolationError
16
16
  from .message_base import MessageBase
17
17
  from ..utils import inputToBytes, inputToString, unwrapMultipart
@@ -47,21 +47,42 @@ class MessageSignedBase(MessageBase, Generic[_T]):
47
47
  :raises StandardViolationError: The standard for signed messages was
48
48
  blatantly violated.
49
49
  """
50
- atts = self._rawAttachments
50
+ # Set these to None to make error handling easier.
51
+ self._signedBody = None
52
+ self._signedHtmlBody = None
53
+
54
+ atts = self.rawAttachments
51
55
 
52
56
  if len(atts) != 1:
53
57
  if ErrorBehavior.STANDARDS_VIOLATION in self.errorBehavior:
54
58
  if len(atts) == 0:
55
- logger.error('Signed message has no attachments, a violation of the standard.')
56
- self._sAttachments = []
57
- self._signedBody = None
58
- self._signedHtmlBody = None
59
+ logger.error('Standards Violation: Signed message has no attachments.')
59
60
  return []
60
- # If there is at least one attachment, just try to use the
61
- # first.
61
+ # If there is at least one attachment, log an error about it
62
+ # and then try to use the first one.
63
+ logger.error('Standards Violation: Signed message has more than one attachment. Attempting to use first one.')
62
64
  else:
63
65
  raise StandardViolationError('Signed messages without exactly 1 (regular) attachment constitute a violation of the standard.')
64
66
 
67
+ # If we are here, validate that the found attachment is acceptable.
68
+ if atts[0].type is not AttachmentType.DATA:
69
+ # This is unacceptable and the attachment cannot be used.
70
+ if ErrorBehavior.STANDARDS_VIOLATION in self.errorBehavior:
71
+ logger.error('Standards Violation: Attachment on signed message is unacceptable (not binary).')
72
+ return []
73
+ raise StandardViolationError('Signed messages *must* have a binary attachment. Signed attachments cannot be parsed otherwise.')
74
+
75
+ # Check the mimetype *directly*. Detection of mimetype through data is
76
+ # unacceptable for this check.
77
+ #temp = atts[0].getStringStream('__substg1.0_370E')
78
+ #if temp == 'multipart/signed':
79
+ ## Do nothing, but this is how we detect that it is clear signed.
80
+ #pass
81
+ #else:
82
+ ## Do nothing, but this is how we detect that it is *not* clear
83
+ # signed.
84
+ #pass
85
+
65
86
  # We need to unwrap the multipart stream.
66
87
  unwrapped = unwrapMultipart(atts[0].data)
67
88
 
@@ -117,7 +138,7 @@ class MessageSignedBase(MessageBase, Generic[_T]):
117
138
  return htmlBody
118
139
 
119
140
  @functools.cached_property
120
- def _rawAttachments(self) -> List[AttachmentBase]:
141
+ def rawAttachments(self) -> List[AttachmentBase]:
121
142
  """
122
143
  A property to allow access to the non-signed attachments.
123
144
  """
@@ -83,8 +83,8 @@ class MSGFile:
83
83
  :param errorBehavior: Optional, the behavior to use in the event of
84
84
  certain types of errors. Uses the ``ErrorBehavior`` enum.
85
85
  :param overrideEncoding: Optional, an encoding to use instead of the one
86
- specified by the MSG file. If the value is "chardet" and you have
87
- the chardet module installed, an attempt will be made to
86
+ specified by the MSG file. If the value is ``"chardet"`` and you
87
+ have the ``chardet`` module installed, an attempt will be made to
88
88
  auto-detect the encoding based on some of the string properties. Do
89
89
  not report encoding errors caused by this.
90
90
  :param treePath: Internal variable used for giving representation of the