pec_ruby 0.1.0 → 0.2.1
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.
- checksums.yaml +4 -4
- data/.env.example +10 -0
- data/.rspec +3 -0
- data/.rspec_status +118 -0
- data/CHANGELOG.md +53 -2
- data/Gemfile.lock +84 -0
- data/README.md +227 -16
- data/Rakefile +12 -0
- data/lib/pec_ruby/attachment.rb +154 -0
- data/lib/pec_ruby/client.rb +2 -1
- data/lib/pec_ruby/message.rb +173 -24
- data/lib/pec_ruby/version.rb +1 -1
- metadata +9 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 88d9db11466fbbf9fff86f31304a5bb4192ee83768657010df58285591556f8d
|
4
|
+
data.tar.gz: 7c3d1dfdee2ec73bce2b8a47974aa89cb3e68ef5e173225ee1ddae6c48dd02c3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c549c540649866f806a5a48a877ee54c3707a39561fa751562cd5c6d155265b5ad50aeb0f76569356d6cccd44d078fa51a53d2087d98192e2ae9d9216f429e46
|
7
|
+
data.tar.gz: 90b7400f64ff7ff65721c120900b52abe3f2d7aba08ce8398ba9c49fac0e371b79a23bddd9939589a4f53f336041982902fb4cbe3bded882daaea7458274d133
|
data/.env.example
ADDED
data/.rspec
ADDED
data/.rspec_status
ADDED
@@ -0,0 +1,118 @@
|
|
1
|
+
example_id | status | run_time |
|
2
|
+
-------------------------------------------------- | ------ | --------------- |
|
3
|
+
./spec/pec_ruby/attachment_spec.rb[1:1:1] | passed | 0.00045 seconds |
|
4
|
+
./spec/pec_ruby/attachment_spec.rb[1:2:1:1] | passed | 0.00446 seconds |
|
5
|
+
./spec/pec_ruby/attachment_spec.rb[1:2:2:1] | passed | 0.00011 seconds |
|
6
|
+
./spec/pec_ruby/attachment_spec.rb[1:3:1:1] | passed | 0.00008 seconds |
|
7
|
+
./spec/pec_ruby/attachment_spec.rb[1:3:2:1] | passed | 0.00007 seconds |
|
8
|
+
./spec/pec_ruby/attachment_spec.rb[1:4:1] | passed | 0.00007 seconds |
|
9
|
+
./spec/pec_ruby/attachment_spec.rb[1:5:1] | passed | 0.00007 seconds |
|
10
|
+
./spec/pec_ruby/attachment_spec.rb[1:6:1] | passed | 0.00008 seconds |
|
11
|
+
./spec/pec_ruby/attachment_spec.rb[1:7:1] | passed | 0.00029 seconds |
|
12
|
+
./spec/pec_ruby/attachment_spec.rb[1:8:1] | passed | 0.00074 seconds |
|
13
|
+
./spec/pec_ruby/attachment_spec.rb[1:8:2] | passed | 0.00036 seconds |
|
14
|
+
./spec/pec_ruby/attachment_spec.rb[1:9:1] | passed | 0.00076 seconds |
|
15
|
+
./spec/pec_ruby/attachment_spec.rb[1:10:1] | passed | 0.00065 seconds |
|
16
|
+
./spec/pec_ruby/attachment_spec.rb[1:11:1] | passed | 0.00016 seconds |
|
17
|
+
./spec/pec_ruby/attachment_spec.rb[1:12:1:1] | passed | 0.00045 seconds |
|
18
|
+
./spec/pec_ruby/attachment_spec.rb[1:12:2:1] | passed | 0.00012 seconds |
|
19
|
+
./spec/pec_ruby/attachment_spec.rb[1:12:3:1] | passed | 0.0001 seconds |
|
20
|
+
./spec/pec_ruby/attachment_spec.rb[1:13:1:1] | passed | 0.00073 seconds |
|
21
|
+
./spec/pec_ruby/attachment_spec.rb[1:13:2:1] | passed | 0.00019 seconds |
|
22
|
+
./spec/pec_ruby/attachment_spec.rb[1:13:3:1] | passed | 0.0008 seconds |
|
23
|
+
./spec/pec_ruby/attachment_spec.rb[2:1:1] | passed | 0.00005 seconds |
|
24
|
+
./spec/pec_ruby/attachment_spec.rb[2:2:1] | passed | 0.00008 seconds |
|
25
|
+
./spec/pec_ruby/attachment_spec.rb[2:3:1] | passed | 0.00007 seconds |
|
26
|
+
./spec/pec_ruby/attachment_spec.rb[2:4:1] | passed | 0.00015 seconds |
|
27
|
+
./spec/pec_ruby/attachment_spec.rb[2:5:1] | passed | 0.00007 seconds |
|
28
|
+
./spec/pec_ruby/attachment_spec.rb[2:6:1:1] | passed | 0.00007 seconds |
|
29
|
+
./spec/pec_ruby/attachment_spec.rb[2:6:2:1] | passed | 0.00016 seconds |
|
30
|
+
./spec/pec_ruby/attachment_spec.rb[2:7:1] | passed | 0.0002 seconds |
|
31
|
+
./spec/pec_ruby/attachment_spec.rb[2:8:1:1] | passed | 0.00008 seconds |
|
32
|
+
./spec/pec_ruby/attachment_spec.rb[2:8:2:1] | passed | 0.00012 seconds |
|
33
|
+
./spec/pec_ruby/client_spec.rb[1:1:1] | passed | 0.00005 seconds |
|
34
|
+
./spec/pec_ruby/client_spec.rb[1:1:2] | passed | 0.00003 seconds |
|
35
|
+
./spec/pec_ruby/client_spec.rb[1:1:3] | passed | 0.00003 seconds |
|
36
|
+
./spec/pec_ruby/client_spec.rb[1:2:1:1] | passed | 0.00003 seconds |
|
37
|
+
./spec/pec_ruby/client_spec.rb[1:2:2:1] | passed | 0.00014 seconds |
|
38
|
+
./spec/pec_ruby/client_spec.rb[1:2:3:1] | passed | 0.0003 seconds |
|
39
|
+
./spec/pec_ruby/client_spec.rb[1:3:1] | passed | 0.00049 seconds |
|
40
|
+
./spec/pec_ruby/client_spec.rb[1:3:2] | passed | 0.00021 seconds |
|
41
|
+
./spec/pec_ruby/client_spec.rb[1:3:3] | passed | 0.00019 seconds |
|
42
|
+
./spec/pec_ruby/client_spec.rb[1:3:4] | passed | 0.00015 seconds |
|
43
|
+
./spec/pec_ruby/client_spec.rb[1:3:5:1] | passed | 0.00019 seconds |
|
44
|
+
./spec/pec_ruby/client_spec.rb[1:3:6:1] | passed | 0.00018 seconds |
|
45
|
+
./spec/pec_ruby/client_spec.rb[1:4:1:1] | passed | 0.00018 seconds |
|
46
|
+
./spec/pec_ruby/client_spec.rb[1:4:1:2] | passed | 0.00014 seconds |
|
47
|
+
./spec/pec_ruby/client_spec.rb[1:4:2:1] | passed | 0.00004 seconds |
|
48
|
+
./spec/pec_ruby/client_spec.rb[1:4:3:1] | passed | 0.00015 seconds |
|
49
|
+
./spec/pec_ruby/client_spec.rb[1:5:1:1] | passed | 0.00049 seconds |
|
50
|
+
./spec/pec_ruby/client_spec.rb[1:5:2:1] | passed | 0.00024 seconds |
|
51
|
+
./spec/pec_ruby/client_spec.rb[1:5:2:2] | passed | 0.00022 seconds |
|
52
|
+
./spec/pec_ruby/client_spec.rb[1:5:2:3] | passed | 0.00035 seconds |
|
53
|
+
./spec/pec_ruby/client_spec.rb[1:5:2:4] | passed | 0.00021 seconds |
|
54
|
+
./spec/pec_ruby/client_spec.rb[1:6:1:1] | passed | 0.00011 seconds |
|
55
|
+
./spec/pec_ruby/client_spec.rb[1:6:2:1] | passed | 0.00018 seconds |
|
56
|
+
./spec/pec_ruby/client_spec.rb[1:6:2:2] | passed | 0.00035 seconds |
|
57
|
+
./spec/pec_ruby/client_spec.rb[1:6:3:1] | passed | 0.00014 seconds |
|
58
|
+
./spec/pec_ruby/message_refactoring_spec.rb[1:1:1] | failed | 0.0025 seconds |
|
59
|
+
./spec/pec_ruby/message_refactoring_spec.rb[1:1:2] | failed | 0.00085 seconds |
|
60
|
+
./spec/pec_ruby/message_refactoring_spec.rb[1:1:3] | failed | 0.00088 seconds |
|
61
|
+
./spec/pec_ruby/message_refactoring_spec.rb[1:2:1] | failed | 0.00086 seconds |
|
62
|
+
./spec/pec_ruby/message_refactoring_spec.rb[1:3:1] | failed | 0.00089 seconds |
|
63
|
+
./spec/pec_ruby/message_refactoring_spec.rb[1:3:2] | failed | 0.00073 seconds |
|
64
|
+
./spec/pec_ruby/message_refactoring_spec.rb[1:4:1] | failed | 0.00081 seconds |
|
65
|
+
./spec/pec_ruby/message_refactoring_spec.rb[1:4:2] | failed | 0.00068 seconds |
|
66
|
+
./spec/pec_ruby/message_refactoring_spec.rb[1:5:1] | failed | 0.00058 seconds |
|
67
|
+
./spec/pec_ruby/message_spec.rb[1:1:1] | passed | 0.00024 seconds |
|
68
|
+
./spec/pec_ruby/message_spec.rb[1:2:1:1] | passed | 0.00046 seconds |
|
69
|
+
./spec/pec_ruby/message_spec.rb[1:2:2:1] | passed | 0.00021 seconds |
|
70
|
+
./spec/pec_ruby/message_spec.rb[1:2:2:2:1] | passed | 0.00029 seconds |
|
71
|
+
./spec/pec_ruby/message_spec.rb[1:3:1:1] | passed | 0.00012 seconds |
|
72
|
+
./spec/pec_ruby/message_spec.rb[1:3:2:1] | passed | 0.00013 seconds |
|
73
|
+
./spec/pec_ruby/message_spec.rb[1:3:2:2:1] | passed | 0.00013 seconds |
|
74
|
+
./spec/pec_ruby/message_spec.rb[1:4:1:1] | passed | 0.00019 seconds |
|
75
|
+
./spec/pec_ruby/message_spec.rb[1:4:2:1] | passed | 0.00013 seconds |
|
76
|
+
./spec/pec_ruby/message_spec.rb[1:5:1:1] | passed | 0.0001 seconds |
|
77
|
+
./spec/pec_ruby/message_spec.rb[1:5:2:1] | passed | 0.0014 seconds |
|
78
|
+
./spec/pec_ruby/message_spec.rb[1:6:1:1] | passed | 0.0002 seconds |
|
79
|
+
./spec/pec_ruby/message_spec.rb[1:6:2:1] | passed | 0.00012 seconds |
|
80
|
+
./spec/pec_ruby/message_spec.rb[1:7:1:1] | passed | 0.00013 seconds |
|
81
|
+
./spec/pec_ruby/message_spec.rb[1:7:1:2] | passed | 0.00015 seconds |
|
82
|
+
./spec/pec_ruby/message_spec.rb[1:7:2:1] | passed | 0.00029 seconds |
|
83
|
+
./spec/pec_ruby/message_spec.rb[1:7:2:2] | passed | 0.00132 seconds |
|
84
|
+
./spec/pec_ruby/message_spec.rb[1:7:3:1] | passed | 0.00024 seconds |
|
85
|
+
./spec/pec_ruby/message_spec.rb[1:8:1:1] | passed | 0.00012 seconds |
|
86
|
+
./spec/pec_ruby/message_spec.rb[1:8:2:1] | passed | 0.0001 seconds |
|
87
|
+
./spec/pec_ruby/message_spec.rb[1:9:1:1] | passed | 0.00013 seconds |
|
88
|
+
./spec/pec_ruby/message_spec.rb[1:9:2:1] | passed | 0.00018 seconds |
|
89
|
+
./spec/pec_ruby/message_spec.rb[1:10:1:1] | passed | 0.00015 seconds |
|
90
|
+
./spec/pec_ruby/message_spec.rb[1:10:2:1] | passed | 0.00298 seconds |
|
91
|
+
./spec/pec_ruby/message_spec.rb[1:10:3:1] | passed | 0.0018 seconds |
|
92
|
+
./spec/pec_ruby/message_spec.rb[1:11:1:1] | passed | 0.00038 seconds |
|
93
|
+
./spec/pec_ruby/message_spec.rb[1:11:2:1] | passed | 0.00018 seconds |
|
94
|
+
./spec/pec_ruby/message_spec.rb[1:12:1:1] | passed | 0.00042 seconds |
|
95
|
+
./spec/pec_ruby/message_spec.rb[1:12:2:1] | passed | 0.00016 seconds |
|
96
|
+
./spec/pec_ruby/message_spec.rb[1:13:1] | passed | 0.00023 seconds |
|
97
|
+
./spec/pec_ruby/message_spec.rb[1:14:1] | passed | 0.00026 seconds |
|
98
|
+
./spec/pec_ruby/message_spec.rb[1:15:1:1] | passed | 0.00011 seconds |
|
99
|
+
./spec/pec_ruby/message_spec.rb[1:15:2:1] | passed | 0.0001 seconds |
|
100
|
+
./spec/pec_ruby/message_spec.rb[1:16:1] | passed | 0.00017 seconds |
|
101
|
+
./spec/pec_ruby/message_spec.rb[1:17:1] | passed | 0.00023 seconds |
|
102
|
+
./spec/pec_ruby/message_spec.rb[1:18:1] | passed | 0.00619 seconds |
|
103
|
+
./spec/pec_ruby/nested_postacert_spec.rb[1:1:1] | failed | 0.00106 seconds |
|
104
|
+
./spec/pec_ruby/nested_postacert_spec.rb[1:1:2] | failed | 0.00146 seconds |
|
105
|
+
./spec/pec_ruby/nested_postacert_spec.rb[1:1:3] | failed | 0.00163 seconds |
|
106
|
+
./spec/pec_ruby/nested_postacert_spec.rb[1:1:4] | failed | 0.00081 seconds |
|
107
|
+
./spec/pec_ruby/nested_postacert_spec.rb[1:1:5] | failed | 0.00136 seconds |
|
108
|
+
./spec/pec_ruby/nested_postacert_spec.rb[1:1:6] | failed | 0.00155 seconds |
|
109
|
+
./spec/pec_ruby/nested_postacert_spec.rb[1:2:1] | failed | 0.00058 seconds |
|
110
|
+
./spec/pec_ruby/nested_postacert_spec.rb[1:2:2] | failed | 0.00052 seconds |
|
111
|
+
./spec/pec_ruby/nested_postacert_spec.rb[1:3:1] | failed | 0.00056 seconds |
|
112
|
+
./spec/pec_ruby/nested_postacert_spec.rb[1:3:2] | failed | 0.00062 seconds |
|
113
|
+
./spec/pec_ruby_spec.rb[1:1] | passed | 0.00006 seconds |
|
114
|
+
./spec/pec_ruby_spec.rb[1:2:1] | passed | 0.00005 seconds |
|
115
|
+
./spec/pec_ruby_spec.rb[1:2:2] | passed | 0.00003 seconds |
|
116
|
+
./spec/pec_ruby_spec.rb[1:2:3] | passed | 0.00004 seconds |
|
117
|
+
./spec/pec_ruby_spec.rb[1:2:4] | passed | 0.00005 seconds |
|
118
|
+
./spec/pec_ruby_spec.rb[1:2:5] | passed | 0.00019 seconds |
|
data/CHANGELOG.md
CHANGED
@@ -5,7 +5,56 @@ All notable changes to this project will be documented in this file.
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
7
7
|
|
8
|
-
## [
|
8
|
+
## [0.2.1] - 2025-07-13
|
9
|
+
|
10
|
+
### Fixed
|
11
|
+
- **Fixed forwarded postacert.eml detection**: Now correctly detects and processes postacert.eml files that are forwarded as attachments in nested MESSAGE/RFC822 structures
|
12
|
+
- Improved nested postacert part ID detection algorithm to handle complex IMAP message structures
|
13
|
+
|
14
|
+
### Added
|
15
|
+
- Comprehensive test suite for nested postacert detection scenarios
|
16
|
+
- Performance optimizations with memoization for attachment calculations
|
17
|
+
- Enhanced error handling for malformed IMAP structures
|
18
|
+
- Support for environment variable configuration (`PEC_HOST`, `PEC_USERNAME`, `PEC_PASSWORD`)
|
19
|
+
- Security improvements to prevent credential leaks in test files
|
20
|
+
|
21
|
+
### Changed
|
22
|
+
- **Code refactoring**: Unified duplicate postacert detection methods into single parameterized method
|
23
|
+
- **Performance**: Added memoization to `original_attachments`, `nested_postacerts`, and `nested_postacert_attachments` methods
|
24
|
+
- **Security**: Test files now use environment variables instead of hardcoded credentials
|
25
|
+
- Improved robustness of bodystructure parsing with defensive programming techniques
|
26
|
+
|
27
|
+
### Security
|
28
|
+
- Removed all hardcoded credentials from test files
|
29
|
+
- Added `.env.example` for secure configuration
|
30
|
+
- Enhanced `.gitignore` to prevent accidental credential commits
|
31
|
+
- Added security documentation in README
|
32
|
+
|
33
|
+
## [0.2.0] - 2025-07-13
|
34
|
+
|
35
|
+
### Changed
|
36
|
+
- **Breaking Change**: `original_body` now returns a hash with format information instead of plain text
|
37
|
+
- Hash contains: `content`, `content_type`, and `charset` keys
|
38
|
+
- Allows proper handling of HTML vs plain text content
|
39
|
+
- Preserves original formatting for correct display
|
40
|
+
|
41
|
+
### Added
|
42
|
+
- `original_body_text` method for getting plain text content only
|
43
|
+
- `original_body_html` method for getting HTML content only
|
44
|
+
- Enhanced body format detection and handling
|
45
|
+
- Better charset handling for international content
|
46
|
+
- **Nested postacert.eml support** for handling forwarded PECs
|
47
|
+
- `Attachment#postacert?` to detect postacert.eml attachments
|
48
|
+
- `Attachment#as_postacert_message` to parse nested PECs
|
49
|
+
- `Message#nested_postacerts` to get nested postacert attachments
|
50
|
+
- `Message#original_regular_attachments` to get non-postacert attachments
|
51
|
+
- `Message#has_nested_postacerts?` to check for nested PECs
|
52
|
+
- `Message#nested_postacert_messages` to get parsed nested messages
|
53
|
+
- `Message#all_postacert_messages` for hierarchical view of all messages
|
54
|
+
- `PecRuby::NestedPostacertMessage` class for nested postacert handling
|
55
|
+
- Full API compatibility with original message methods
|
56
|
+
- Support for multi-level nesting (postacert within postacert)
|
57
|
+
- Automatic detection and parsing of deeper nesting levels
|
9
58
|
|
10
59
|
## [0.1.0] - 2025-07-13
|
11
60
|
|
@@ -33,5 +82,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
33
82
|
- Aruba PEC (imaps.pec.aruba.it)
|
34
83
|
- Generic IMAP-compliant PEC providers
|
35
84
|
|
36
|
-
[Unreleased]: https://github.com/egio12/pec_ruby/compare/v0.1
|
85
|
+
[Unreleased]: https://github.com/egio12/pec_ruby/compare/v0.2.1...HEAD
|
86
|
+
[0.2.1]: https://github.com/egio12/pec_ruby/compare/v0.2.0...v0.2.1
|
87
|
+
[0.2.0]: https://github.com/egio12/pec_ruby/compare/v0.1.0...v0.2.0
|
37
88
|
[0.1.0]: https://github.com/egio12/pec_ruby/releases/tag/v0.1.0
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
pec_ruby (0.2.1)
|
5
|
+
mail (~> 2.7)
|
6
|
+
net-imap (~> 0.3)
|
7
|
+
|
8
|
+
GEM
|
9
|
+
remote: https://rubygems.org/
|
10
|
+
specs:
|
11
|
+
ast (2.4.3)
|
12
|
+
date (3.4.1)
|
13
|
+
diff-lcs (1.6.2)
|
14
|
+
json (2.12.2)
|
15
|
+
language_server-protocol (3.17.0.5)
|
16
|
+
lint_roller (1.1.0)
|
17
|
+
mail (2.8.1)
|
18
|
+
mini_mime (>= 0.1.1)
|
19
|
+
net-imap
|
20
|
+
net-pop
|
21
|
+
net-smtp
|
22
|
+
mini_mime (1.1.5)
|
23
|
+
net-imap (0.5.9)
|
24
|
+
date
|
25
|
+
net-protocol
|
26
|
+
net-pop (0.1.2)
|
27
|
+
net-protocol
|
28
|
+
net-protocol (0.2.2)
|
29
|
+
timeout
|
30
|
+
net-smtp (0.5.1)
|
31
|
+
net-protocol
|
32
|
+
parallel (1.27.0)
|
33
|
+
parser (3.3.8.0)
|
34
|
+
ast (~> 2.4.1)
|
35
|
+
racc
|
36
|
+
prism (1.4.0)
|
37
|
+
racc (1.8.1)
|
38
|
+
rainbow (3.1.1)
|
39
|
+
rake (13.3.0)
|
40
|
+
regexp_parser (2.10.0)
|
41
|
+
rspec (3.13.1)
|
42
|
+
rspec-core (~> 3.13.0)
|
43
|
+
rspec-expectations (~> 3.13.0)
|
44
|
+
rspec-mocks (~> 3.13.0)
|
45
|
+
rspec-core (3.13.5)
|
46
|
+
rspec-support (~> 3.13.0)
|
47
|
+
rspec-expectations (3.13.5)
|
48
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
49
|
+
rspec-support (~> 3.13.0)
|
50
|
+
rspec-mocks (3.13.5)
|
51
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
52
|
+
rspec-support (~> 3.13.0)
|
53
|
+
rspec-support (3.13.4)
|
54
|
+
rubocop (1.78.0)
|
55
|
+
json (~> 2.3)
|
56
|
+
language_server-protocol (~> 3.17.0.2)
|
57
|
+
lint_roller (~> 1.1.0)
|
58
|
+
parallel (~> 1.10)
|
59
|
+
parser (>= 3.3.0.2)
|
60
|
+
rainbow (>= 2.2.2, < 4.0)
|
61
|
+
regexp_parser (>= 2.9.3, < 3.0)
|
62
|
+
rubocop-ast (>= 1.45.1, < 2.0)
|
63
|
+
ruby-progressbar (~> 1.7)
|
64
|
+
unicode-display_width (>= 2.4.0, < 4.0)
|
65
|
+
rubocop-ast (1.45.1)
|
66
|
+
parser (>= 3.3.7.2)
|
67
|
+
prism (~> 1.4)
|
68
|
+
ruby-progressbar (1.13.0)
|
69
|
+
timeout (0.4.3)
|
70
|
+
unicode-display_width (3.1.4)
|
71
|
+
unicode-emoji (~> 4.0, >= 4.0.4)
|
72
|
+
unicode-emoji (4.0.4)
|
73
|
+
|
74
|
+
PLATFORMS
|
75
|
+
arm64-darwin-24
|
76
|
+
|
77
|
+
DEPENDENCIES
|
78
|
+
pec_ruby!
|
79
|
+
rake (~> 13.0)
|
80
|
+
rspec (~> 3.0)
|
81
|
+
rubocop (~> 1.21)
|
82
|
+
|
83
|
+
BUNDLED WITH
|
84
|
+
2.4.19
|
data/README.md
CHANGED
@@ -6,9 +6,12 @@ A comprehensive Ruby gem for decoding and managing Italian PEC (Posta Elettronic
|
|
6
6
|
|
7
7
|
- **IMAP Connection**: Connect to Italian PEC servers
|
8
8
|
- **Automatic Extraction**: Automatically extracts original messages from postacert.eml attachments
|
9
|
+
- **Nested PEC Support**: **NEW in v0.2.1** - Detects and processes forwarded PEC messages (nested postacert.eml files)
|
9
10
|
- **Attachment Management**: Download and manage attachments easily
|
11
|
+
- **Performance Optimized**: **NEW in v0.2.1** - Memoization for faster repeated access to attachments
|
10
12
|
- **CLI Included**: Command-line interface for exploring PEC messages
|
11
13
|
- **Programmatic API**: Methods for integrating PEC functionality into your Ruby applications
|
14
|
+
- **Comprehensive Testing**: Full test suite with both unit and integration tests
|
12
15
|
|
13
16
|
## Installation
|
14
17
|
|
@@ -57,6 +60,8 @@ The CLI allows you to:
|
|
57
60
|
- Explore received messages
|
58
61
|
- View decoded original message contents
|
59
62
|
- Download attachments
|
63
|
+
- **NEW in v0.2.1**: Detect and process forwarded PEC messages (nested postacert.eml files)
|
64
|
+
- **NEW in v0.2.1**: Enhanced performance with memoization for large attachments
|
60
65
|
|
61
66
|
## Programmatic Usage
|
62
67
|
|
@@ -101,16 +106,33 @@ puts message.date # PEC message date
|
|
101
106
|
# Original message information
|
102
107
|
puts message.original_subject # Original subject
|
103
108
|
puts message.original_from # Original sender
|
104
|
-
|
109
|
+
body_info = message.original_body # Original message body with format info
|
105
110
|
|
106
111
|
# Attachments
|
107
112
|
message.original_attachments.each do |attachment|
|
108
113
|
puts "#{attachment.filename} (#{attachment.size_kb} KB)"
|
109
114
|
|
110
|
-
#
|
111
|
-
attachment.
|
112
|
-
|
113
|
-
|
115
|
+
# Check if attachment is a nested postacert.eml (forwarded PEC)
|
116
|
+
if attachment.postacert?
|
117
|
+
puts " -> This is a nested postacert.eml!"
|
118
|
+
nested_msg = attachment.as_postacert_message
|
119
|
+
puts " -> Original subject: #{nested_msg.subject}"
|
120
|
+
puts " -> Original from: #{nested_msg.from}"
|
121
|
+
else
|
122
|
+
# Save regular attachment
|
123
|
+
attachment.save_to("/path/to/file.pdf")
|
124
|
+
# or
|
125
|
+
attachment.save_to_dir("/downloads/")
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
# Handle nested postacerts (forwarded PECs)
|
130
|
+
if message.has_nested_postacerts?
|
131
|
+
puts "This message contains #{message.nested_postacerts.size} forwarded PEC(s)"
|
132
|
+
|
133
|
+
message.nested_postacert_messages.each do |nested_msg|
|
134
|
+
puts "Nested PEC: #{nested_msg.subject} from #{nested_msg.from}"
|
135
|
+
end
|
114
136
|
end
|
115
137
|
```
|
116
138
|
|
@@ -132,6 +154,15 @@ PecRuby::Client.new(host:, username:, password:, ssl: true)
|
|
132
154
|
- `password` (String): Account password
|
133
155
|
- `ssl` (Boolean): Use SSL connection (default: true)
|
134
156
|
|
157
|
+
> **Security Note**: For production usage, consider using environment variables instead of hardcoding credentials:
|
158
|
+
> ```ruby
|
159
|
+
> client = PecRuby::Client.new(
|
160
|
+
> host: ENV['PEC_HOST'],
|
161
|
+
> username: ENV['PEC_USERNAME'],
|
162
|
+
> password: ENV['PEC_PASSWORD']
|
163
|
+
> )
|
164
|
+
> ```
|
165
|
+
|
135
166
|
#### Instance Methods
|
136
167
|
|
137
168
|
##### `#connect`
|
@@ -215,14 +246,52 @@ message.original_subject # String: Original subject
|
|
215
246
|
message.original_from # String: Original sender
|
216
247
|
message.original_to # Array<String>: Original recipients
|
217
248
|
message.original_date # Time: Original message date
|
218
|
-
message.original_body #
|
249
|
+
message.original_body # Hash: Original message body with format info
|
250
|
+
message.original_body_text # String: Plain text body only
|
251
|
+
message.original_body_html # String: HTML body only
|
252
|
+
```
|
253
|
+
|
254
|
+
##### Original Message Body
|
255
|
+
|
256
|
+
The `original_body` method returns a hash with format information, allowing you to handle different content types appropriately:
|
257
|
+
|
258
|
+
```ruby
|
259
|
+
body_info = message.original_body
|
260
|
+
if body_info
|
261
|
+
puts "Content type: #{body_info[:content_type]}"
|
262
|
+
puts "Charset: #{body_info[:charset]}"
|
263
|
+
|
264
|
+
case body_info[:content_type]
|
265
|
+
when 'text/html'
|
266
|
+
# Handle HTML content - preserve formatting for web display
|
267
|
+
html_content = body_info[:content]
|
268
|
+
# You can now render this in a web browser or HTML viewer
|
269
|
+
when 'text/plain'
|
270
|
+
# Handle plain text content
|
271
|
+
text_content = body_info[:content]
|
272
|
+
puts text_content
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
# Or use convenience methods for specific formats
|
277
|
+
text_only = message.original_body_text # Returns nil if no text/plain part
|
278
|
+
html_only = message.original_body_html # Returns nil if no text/html part
|
219
279
|
```
|
220
280
|
|
221
281
|
##### Attachments
|
222
282
|
|
223
283
|
```ruby
|
224
284
|
# Get original message attachments
|
225
|
-
message.original_attachments
|
285
|
+
message.original_attachments # Array<PecRuby::Attachment> - All attachments
|
286
|
+
message.original_regular_attachments # Array<PecRuby::Attachment> - Non-postacert attachments only
|
287
|
+
message.nested_postacerts # Array<PecRuby::Attachment> - Nested postacert.eml files only
|
288
|
+
|
289
|
+
# Check for nested postacerts (forwarded PECs)
|
290
|
+
message.has_nested_postacerts? # Boolean
|
291
|
+
message.nested_postacert_messages # Array<PecRuby::NestedPostacertMessage>
|
292
|
+
|
293
|
+
# Get all postacert messages in a flattened structure
|
294
|
+
message.all_postacert_messages # Array<Hash> - Hierarchical view of all messages
|
226
295
|
```
|
227
296
|
|
228
297
|
##### Summary Information
|
@@ -254,11 +323,42 @@ attachment.content # String: Raw binary content
|
|
254
323
|
attachment.save_to(path) # Save to specific path
|
255
324
|
attachment.save_to_dir(directory) # Save to directory with original filename
|
256
325
|
|
326
|
+
# Nested postacert detection and parsing
|
327
|
+
attachment.postacert? # Boolean: Check if this is a postacert.eml
|
328
|
+
attachment.as_postacert_message # PecRuby::NestedPostacertMessage: Parse as nested PEC
|
329
|
+
|
257
330
|
# Summary
|
258
331
|
attachment.summary # Hash: Complete attachment information
|
259
332
|
attachment.to_s # String: Human-readable description
|
260
333
|
```
|
261
334
|
|
335
|
+
### PecRuby::NestedPostacertMessage
|
336
|
+
|
337
|
+
Represents a nested postacert.eml file (forwarded PEC) found within attachments.
|
338
|
+
|
339
|
+
#### Instance Methods
|
340
|
+
|
341
|
+
```ruby
|
342
|
+
# Basic message information
|
343
|
+
nested_msg.subject # String: Subject of the nested message
|
344
|
+
nested_msg.from # String: Sender of the nested message
|
345
|
+
nested_msg.to # Array<String>: Recipients of the nested message
|
346
|
+
nested_msg.date # Time: Date of the nested message
|
347
|
+
|
348
|
+
# Body content (same API as original_body)
|
349
|
+
nested_msg.body # Hash: Body with content_type and charset info
|
350
|
+
nested_msg.body_text # String: Plain text body only
|
351
|
+
nested_msg.body_html # String: HTML body only
|
352
|
+
|
353
|
+
# Nested attachments
|
354
|
+
nested_msg.attachments # Array<PecRuby::Attachment>
|
355
|
+
nested_msg.nested_postacerts # Array<PecRuby::Attachment> - Even deeper nesting!
|
356
|
+
nested_msg.has_nested_postacerts? # Boolean: Check for deeper nesting
|
357
|
+
|
358
|
+
# Summary
|
359
|
+
nested_msg.summary # Hash: Complete nested message information
|
360
|
+
```
|
361
|
+
|
262
362
|
## Complete Example
|
263
363
|
|
264
364
|
```ruby
|
@@ -279,14 +379,56 @@ begin
|
|
279
379
|
pec_messages.each do |message|
|
280
380
|
puts "Subject: #{message.original_subject}"
|
281
381
|
puts "From: #{message.original_from}"
|
282
|
-
puts "
|
382
|
+
puts "Total attachments: #{message.original_attachments.size}"
|
383
|
+
puts "Regular attachments: #{message.original_regular_attachments.size}"
|
384
|
+
puts "Nested PECs: #{message.nested_postacerts.size}"
|
385
|
+
|
386
|
+
# Handle message body based on format
|
387
|
+
body_info = message.original_body
|
388
|
+
if body_info
|
389
|
+
puts "Body format: #{body_info[:content_type]}"
|
390
|
+
case body_info[:content_type]
|
391
|
+
when 'text/html'
|
392
|
+
puts "HTML content available for web display"
|
393
|
+
# Save HTML to file for viewing
|
394
|
+
File.write("./downloads/message_#{message.uid}.html", body_info[:content])
|
395
|
+
when 'text/plain'
|
396
|
+
puts "Text content:"
|
397
|
+
puts body_info[:content][0..100] + "..." # First 100 chars
|
398
|
+
end
|
399
|
+
end
|
283
400
|
|
284
|
-
# Download attachments
|
285
|
-
message.
|
401
|
+
# Download regular attachments
|
402
|
+
message.original_regular_attachments.each do |attachment|
|
286
403
|
attachment.save_to_dir('./downloads')
|
287
404
|
puts "Downloaded: #{attachment.filename}"
|
288
405
|
end
|
289
406
|
|
407
|
+
# Handle nested postacerts (forwarded PECs)
|
408
|
+
if message.has_nested_postacerts?
|
409
|
+
puts "Found #{message.nested_postacerts.size} forwarded PEC(s):"
|
410
|
+
|
411
|
+
message.nested_postacert_messages.each_with_index do |nested_msg, index|
|
412
|
+
puts " Nested PEC ##{index + 1}:"
|
413
|
+
puts " Subject: #{nested_msg.subject}"
|
414
|
+
puts " From: #{nested_msg.from}"
|
415
|
+
puts " Attachments: #{nested_msg.attachments.size}"
|
416
|
+
|
417
|
+
# Download nested PEC attachments
|
418
|
+
nested_msg.attachments.each do |nested_attachment|
|
419
|
+
unless nested_attachment.postacert? # Avoid infinite recursion
|
420
|
+
nested_attachment.save_to_dir('./downloads/nested')
|
421
|
+
puts " Downloaded nested: #{nested_attachment.filename}"
|
422
|
+
end
|
423
|
+
end
|
424
|
+
|
425
|
+
# Check for even deeper nesting
|
426
|
+
if nested_msg.has_nested_postacerts?
|
427
|
+
puts " -> This nested PEC contains #{nested_msg.nested_postacerts.size} more nested PEC(s)!"
|
428
|
+
end
|
429
|
+
end
|
430
|
+
end
|
431
|
+
|
290
432
|
puts "─" * 40
|
291
433
|
end
|
292
434
|
|
@@ -295,6 +437,41 @@ ensure
|
|
295
437
|
end
|
296
438
|
```
|
297
439
|
|
440
|
+
### Nested PEC Detection Example (NEW in v0.2.1)
|
441
|
+
|
442
|
+
Handle forwarded PEC messages that contain other PEC messages as attachments:
|
443
|
+
|
444
|
+
```ruby
|
445
|
+
# Find a message with forwarded PECs
|
446
|
+
message = client.pec_messages.find { |msg| msg.has_nested_postacerts? }
|
447
|
+
|
448
|
+
if message
|
449
|
+
puts "Found message with #{message.nested_postacerts.size} forwarded PEC(s):"
|
450
|
+
|
451
|
+
# Process each forwarded PEC
|
452
|
+
message.nested_postacert_messages.each_with_index do |nested_msg, index|
|
453
|
+
puts " Forwarded PEC ##{index + 1}:"
|
454
|
+
puts " Subject: #{nested_msg.subject}"
|
455
|
+
puts " From: #{nested_msg.from}"
|
456
|
+
puts " Date: #{nested_msg.date}"
|
457
|
+
puts " Attachments: #{nested_msg.attachments.size}"
|
458
|
+
|
459
|
+
# Download attachments from the forwarded PEC
|
460
|
+
nested_msg.attachments.each do |attachment|
|
461
|
+
unless attachment.postacert? # Avoid infinite recursion
|
462
|
+
attachment.save_to_dir('./downloads/forwarded')
|
463
|
+
puts " Downloaded: #{attachment.filename}"
|
464
|
+
end
|
465
|
+
end
|
466
|
+
|
467
|
+
# Check for even deeper nesting (PEC forwarded within forwarded PEC)
|
468
|
+
if nested_msg.has_nested_postacerts?
|
469
|
+
puts " -> Contains #{nested_msg.nested_postacerts.size} more forwarded PEC(s)!"
|
470
|
+
end
|
471
|
+
end
|
472
|
+
end
|
473
|
+
```
|
474
|
+
|
298
475
|
## Error Handling
|
299
476
|
|
300
477
|
The gem defines several specific error classes:
|
@@ -335,6 +512,46 @@ Other providers should work if they support standard IMAP, but have not been tes
|
|
335
512
|
- **Provider Testing**: Only tested with Aruba PEC. Other providers may work but are not guaranteed.
|
336
513
|
- **Legal Compliance**: This library has not been evaluated for compliance with Italian PEC regulations or legal requirements. The message parsing methods used may not preserve all legally required aspects of certified email messages. Users should consult with legal experts and review applicable regulations before using this library in legally sensitive contexts.
|
337
514
|
|
515
|
+
## Testing & Development Configuration
|
516
|
+
|
517
|
+
### Environment Variables
|
518
|
+
|
519
|
+
For security, use environment variables to configure your PEC credentials:
|
520
|
+
|
521
|
+
```bash
|
522
|
+
# Copy the example environment file
|
523
|
+
cp .env.example .env
|
524
|
+
|
525
|
+
# Edit .env with your actual credentials
|
526
|
+
export PEC_HOST=imaps.pec.aruba.it
|
527
|
+
export PEC_USERNAME=your@domain.pec.it
|
528
|
+
export PEC_PASSWORD=your_password
|
529
|
+
export PEC_TEST_UID=1234 # Optional: specific message UID for testing
|
530
|
+
```
|
531
|
+
|
532
|
+
### Running Tests
|
533
|
+
|
534
|
+
The gem includes comprehensive tests for all functionality:
|
535
|
+
|
536
|
+
```bash
|
537
|
+
bundle install
|
538
|
+
|
539
|
+
# Run all tests (will skip integration tests without PEC credentials)
|
540
|
+
bundle exec rspec
|
541
|
+
|
542
|
+
# Run with PEC credentials for full integration testing
|
543
|
+
PEC_HOST=imaps.pec.aruba.it PEC_USERNAME=your@domain.pec.it PEC_PASSWORD=your_password bundle exec rspec
|
544
|
+
|
545
|
+
# Run specific test suites
|
546
|
+
bundle exec rspec spec/pec_ruby/nested_postacert_spec.rb # Nested PEC detection tests
|
547
|
+
bundle exec rspec spec/pec_ruby/message_refactoring_spec.rb # Performance & refactoring tests
|
548
|
+
|
549
|
+
# Check code style
|
550
|
+
bundle exec rubocop
|
551
|
+
```
|
552
|
+
|
553
|
+
**Note**: Integration tests require real PEC credentials and will be skipped if environment variables are not set. Unit tests will always run.
|
554
|
+
|
338
555
|
## Development
|
339
556
|
|
340
557
|
After cloning the repository:
|
@@ -356,9 +573,3 @@ bundle exec rubocop # Check code style
|
|
356
573
|
## License
|
357
574
|
|
358
575
|
Distributed under the MIT License. See `LICENSE` for more information.
|
359
|
-
|
360
|
-
## Contact
|
361
|
-
|
362
|
-
Enrico Giordano - enricomaria.giordano@icloud.com
|
363
|
-
|
364
|
-
Project Link: [https://github.com/egio12/pec_ruby](https://github.com/egio12/pec_ruby)
|
data/Rakefile
ADDED
data/lib/pec_ruby/attachment.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'mail'
|
4
|
+
|
3
5
|
module PecRuby
|
4
6
|
class Attachment
|
5
7
|
attr_reader :mail_attachment
|
@@ -57,5 +59,157 @@ module PecRuby
|
|
57
59
|
def to_s
|
58
60
|
"#{filename} (#{mime_type}, #{size_kb} KB)"
|
59
61
|
end
|
62
|
+
|
63
|
+
# Check if this attachment is a postacert.eml file
|
64
|
+
def postacert?
|
65
|
+
filename&.downcase&.include?('postacert.eml') ||
|
66
|
+
(filename&.downcase&.end_with?('.eml') && mime_type&.include?('message'))
|
67
|
+
end
|
68
|
+
|
69
|
+
# Parse this attachment as a postacert.eml if it is one
|
70
|
+
# Returns a PecRuby::Message-like object for the nested postacert
|
71
|
+
def as_postacert_message
|
72
|
+
return nil unless postacert?
|
73
|
+
|
74
|
+
begin
|
75
|
+
# Parse the attachment content as an email message
|
76
|
+
nested_mail = Mail.read_from_string(content)
|
77
|
+
|
78
|
+
# Create a simplified message object for the nested postacert
|
79
|
+
NestedPostacertMessage.new(nested_mail)
|
80
|
+
rescue => e
|
81
|
+
raise PecRuby::Error, "Failed to parse nested postacert.eml: #{e.message}"
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# Simplified message class for nested postacert emails
|
87
|
+
class NestedPostacertMessage
|
88
|
+
attr_reader :mail
|
89
|
+
|
90
|
+
def initialize(mail)
|
91
|
+
@mail = mail
|
92
|
+
end
|
93
|
+
|
94
|
+
def subject
|
95
|
+
@mail.subject
|
96
|
+
end
|
97
|
+
|
98
|
+
def from
|
99
|
+
@mail.from&.first
|
100
|
+
end
|
101
|
+
|
102
|
+
def to
|
103
|
+
@mail.to || []
|
104
|
+
end
|
105
|
+
|
106
|
+
def date
|
107
|
+
@mail.date
|
108
|
+
end
|
109
|
+
|
110
|
+
def body
|
111
|
+
# Try to get text/plain first, then text/html
|
112
|
+
text_part = extract_text_part(@mail, "text/plain")
|
113
|
+
html_part = extract_text_part(@mail, "text/html")
|
114
|
+
selected_part = text_part || html_part
|
115
|
+
|
116
|
+
return nil unless selected_part
|
117
|
+
|
118
|
+
raw_body = selected_part.body.decoded
|
119
|
+
charset = selected_part.charset ||
|
120
|
+
selected_part.content_type_parameters&.[]("charset") ||
|
121
|
+
"UTF-8"
|
122
|
+
|
123
|
+
content = raw_body.dup.force_encoding(charset).encode("UTF-8")
|
124
|
+
|
125
|
+
{
|
126
|
+
content: content,
|
127
|
+
content_type: selected_part.mime_type,
|
128
|
+
charset: charset
|
129
|
+
}
|
130
|
+
end
|
131
|
+
|
132
|
+
def body_text
|
133
|
+
text_part = extract_text_part(@mail, "text/plain")
|
134
|
+
return nil unless text_part
|
135
|
+
|
136
|
+
raw_body = text_part.body.decoded
|
137
|
+
charset = text_part.charset ||
|
138
|
+
text_part.content_type_parameters&.[]("charset") ||
|
139
|
+
"UTF-8"
|
140
|
+
|
141
|
+
raw_body.dup.force_encoding(charset).encode("UTF-8")
|
142
|
+
end
|
143
|
+
|
144
|
+
def body_html
|
145
|
+
html_part = extract_text_part(@mail, "text/html")
|
146
|
+
return nil unless html_part
|
147
|
+
|
148
|
+
raw_body = html_part.body.decoded
|
149
|
+
charset = html_part.charset ||
|
150
|
+
html_part.content_type_parameters&.[]("charset") ||
|
151
|
+
"UTF-8"
|
152
|
+
|
153
|
+
raw_body.dup.force_encoding(charset).encode("UTF-8")
|
154
|
+
end
|
155
|
+
|
156
|
+
def attachments
|
157
|
+
return [] unless @mail&.attachments
|
158
|
+
|
159
|
+
@mail.attachments.map { |att| Attachment.new(att) }
|
160
|
+
end
|
161
|
+
|
162
|
+
def summary
|
163
|
+
{
|
164
|
+
subject: subject,
|
165
|
+
from: from,
|
166
|
+
to: to,
|
167
|
+
date: date,
|
168
|
+
attachments_count: attachments.size,
|
169
|
+
nested_postacerts_count: nested_postacerts.size
|
170
|
+
}
|
171
|
+
end
|
172
|
+
|
173
|
+
# Find any nested postacert.eml files in this message's attachments
|
174
|
+
def nested_postacerts
|
175
|
+
attachments.select(&:postacert?)
|
176
|
+
end
|
177
|
+
|
178
|
+
# Check if this nested message has any nested postacert.eml files
|
179
|
+
def has_nested_postacerts?
|
180
|
+
!nested_postacerts.empty?
|
181
|
+
end
|
182
|
+
|
183
|
+
private
|
184
|
+
|
185
|
+
def extract_text_part(mail, preferred_type = "text/plain")
|
186
|
+
return mail unless mail.multipart?
|
187
|
+
|
188
|
+
mail.parts.each do |part|
|
189
|
+
if part.multipart?
|
190
|
+
found = extract_text_part(part, preferred_type)
|
191
|
+
return found if found
|
192
|
+
elsif part.mime_type == preferred_type
|
193
|
+
return part
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
nil
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
# Simple wrapper for mail attachments that mimics Mail::Attachment interface
|
202
|
+
class SimpleMailAttachment
|
203
|
+
attr_reader :mail, :filename, :mime_type
|
204
|
+
|
205
|
+
def initialize(mail, filename, mime_type)
|
206
|
+
@mail = mail
|
207
|
+
@filename = filename
|
208
|
+
@mime_type = mime_type
|
209
|
+
end
|
210
|
+
|
211
|
+
def decoded
|
212
|
+
@mail.to_s
|
213
|
+
end
|
60
214
|
end
|
61
215
|
end
|
data/lib/pec_ruby/client.rb
CHANGED
data/lib/pec_ruby/message.rb
CHANGED
@@ -20,7 +20,7 @@ module PecRuby
|
|
20
20
|
return nil unless @envelope.subject
|
21
21
|
|
22
22
|
decoded = Mail::Encodings.value_decode(@envelope.subject)
|
23
|
-
decoded.gsub
|
23
|
+
decoded = decoded.gsub("POSTA CERTIFICATA:", "") if decoded.start_with?("POSTA CERTIFICATA:")
|
24
24
|
decoded.strip
|
25
25
|
end
|
26
26
|
|
@@ -89,13 +89,15 @@ module PecRuby
|
|
89
89
|
postacert_message&.date
|
90
90
|
end
|
91
91
|
|
92
|
-
# Get original message body
|
92
|
+
# Get original message body with format information
|
93
93
|
def original_body
|
94
94
|
mail = postacert_message
|
95
95
|
return nil unless mail
|
96
96
|
|
97
97
|
text_part = extract_text_part(mail, "text/plain")
|
98
98
|
html_part = extract_text_part(mail, "text/html")
|
99
|
+
|
100
|
+
# Prefer text/plain, but return HTML if that's all we have
|
99
101
|
selected_part = text_part || html_part
|
100
102
|
|
101
103
|
return nil unless selected_part
|
@@ -105,15 +107,127 @@ module PecRuby
|
|
105
107
|
selected_part.content_type_parameters&.[]("charset") ||
|
106
108
|
"UTF-8"
|
107
109
|
|
108
|
-
raw_body.force_encoding(charset).encode("UTF-8")
|
110
|
+
content = raw_body.dup.force_encoding(charset).encode("UTF-8")
|
111
|
+
|
112
|
+
{
|
113
|
+
content: content,
|
114
|
+
content_type: selected_part.mime_type,
|
115
|
+
charset: charset
|
116
|
+
}
|
109
117
|
end
|
110
118
|
|
111
|
-
# Get original message
|
112
|
-
def
|
119
|
+
# Get original message body as plain text only
|
120
|
+
def original_body_text
|
113
121
|
mail = postacert_message
|
114
|
-
return
|
122
|
+
return nil unless mail
|
115
123
|
|
116
|
-
|
124
|
+
text_part = extract_text_part(mail, "text/plain")
|
125
|
+
return nil unless text_part
|
126
|
+
|
127
|
+
raw_body = text_part.body.decoded
|
128
|
+
charset = text_part.charset ||
|
129
|
+
text_part.content_type_parameters&.[]("charset") ||
|
130
|
+
"UTF-8"
|
131
|
+
|
132
|
+
raw_body.dup.force_encoding(charset).encode("UTF-8")
|
133
|
+
end
|
134
|
+
|
135
|
+
# Get original message body as HTML only
|
136
|
+
def original_body_html
|
137
|
+
mail = postacert_message
|
138
|
+
return nil unless mail
|
139
|
+
|
140
|
+
html_part = extract_text_part(mail, "text/html")
|
141
|
+
return nil unless html_part
|
142
|
+
|
143
|
+
raw_body = html_part.body.decoded
|
144
|
+
charset = html_part.charset ||
|
145
|
+
html_part.content_type_parameters&.[]("charset") ||
|
146
|
+
"UTF-8"
|
147
|
+
|
148
|
+
raw_body.dup.force_encoding(charset).encode("UTF-8")
|
149
|
+
end
|
150
|
+
|
151
|
+
# Get original message attachments (with memoization)
|
152
|
+
def original_attachments
|
153
|
+
@original_attachments ||= begin
|
154
|
+
mail = postacert_message
|
155
|
+
attachments = []
|
156
|
+
|
157
|
+
# Add attachments from the original message
|
158
|
+
if mail&.attachments
|
159
|
+
attachments += mail.attachments.map { |att| Attachment.new(att) }
|
160
|
+
end
|
161
|
+
|
162
|
+
# Also check for postacert.eml attachments in the outer message structure
|
163
|
+
# This handles cases where postacert.eml files are forwarded as attachments
|
164
|
+
attachments += nested_postacert_attachments
|
165
|
+
|
166
|
+
attachments
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
# Get original message attachments that are NOT postacert.eml files
|
171
|
+
def original_regular_attachments
|
172
|
+
original_attachments.reject(&:postacert?)
|
173
|
+
end
|
174
|
+
|
175
|
+
# Get nested postacert.eml files from original message attachments
|
176
|
+
def nested_postacerts
|
177
|
+
@nested_postacerts ||= original_attachments.select(&:postacert?)
|
178
|
+
end
|
179
|
+
|
180
|
+
# Check if original message has nested postacert.eml files
|
181
|
+
def has_nested_postacerts?
|
182
|
+
!nested_postacerts.empty?
|
183
|
+
end
|
184
|
+
|
185
|
+
# Get all nested postacert messages parsed and ready to use
|
186
|
+
def nested_postacert_messages
|
187
|
+
nested_postacerts.map(&:as_postacert_message).compact
|
188
|
+
end
|
189
|
+
|
190
|
+
# Get a flattened view of all postacert messages (original + nested)
|
191
|
+
# Returns array with the original message first, followed by nested ones
|
192
|
+
def all_postacert_messages
|
193
|
+
messages = []
|
194
|
+
|
195
|
+
# Add the main postacert message (this message)
|
196
|
+
if has_postacert?
|
197
|
+
messages << {
|
198
|
+
level: 0,
|
199
|
+
message: self,
|
200
|
+
type: :main_postacert
|
201
|
+
}
|
202
|
+
end
|
203
|
+
|
204
|
+
# Add nested postacert messages
|
205
|
+
nested_postacert_messages.each_with_index do |nested_msg, index|
|
206
|
+
messages << {
|
207
|
+
level: 1,
|
208
|
+
message: nested_msg,
|
209
|
+
type: :nested_postacert,
|
210
|
+
index: index
|
211
|
+
}
|
212
|
+
|
213
|
+
# Check for deeper nesting (postacert within postacert within postacert)
|
214
|
+
if nested_msg.has_nested_postacerts?
|
215
|
+
nested_msg.nested_postacerts.each_with_index do |deep_nested, deep_index|
|
216
|
+
deep_nested_msg = deep_nested.as_postacert_message
|
217
|
+
if deep_nested_msg
|
218
|
+
messages << {
|
219
|
+
level: 2,
|
220
|
+
message: deep_nested_msg,
|
221
|
+
type: :deep_nested_postacert,
|
222
|
+
parent_index: index,
|
223
|
+
index: deep_index
|
224
|
+
}
|
225
|
+
end
|
226
|
+
end
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
messages
|
117
231
|
end
|
118
232
|
|
119
233
|
# Summary information
|
@@ -129,7 +243,11 @@ module PecRuby
|
|
129
243
|
original_from: original_from,
|
130
244
|
original_to: original_to,
|
131
245
|
original_date: original_date,
|
132
|
-
attachments_count: original_attachments.size
|
246
|
+
attachments_count: original_attachments.size,
|
247
|
+
regular_attachments_count: original_regular_attachments.size,
|
248
|
+
nested_postacerts_count: nested_postacerts.size,
|
249
|
+
has_nested_postacerts: has_nested_postacerts?,
|
250
|
+
total_postacert_messages: all_postacert_messages.size
|
133
251
|
}
|
134
252
|
end
|
135
253
|
|
@@ -149,36 +267,67 @@ module PecRuby
|
|
149
267
|
email
|
150
268
|
end
|
151
269
|
|
152
|
-
def
|
270
|
+
def nested_postacert_attachments
|
271
|
+
@nested_postacert_attachments ||= begin
|
272
|
+
attachments = []
|
273
|
+
|
274
|
+
# Find all postacert.eml parts (including nested)
|
275
|
+
all_postacert_part_ids = find_postacert_part_ids(@bodystructure, "", true)
|
276
|
+
main_postacert_part_id = find_postacert_part_ids.first
|
277
|
+
|
278
|
+
# Add any postacert.eml parts that are not the main one as attachments
|
279
|
+
nested_postacert_part_ids = all_postacert_part_ids - [main_postacert_part_id]
|
280
|
+
|
281
|
+
nested_postacert_part_ids.each do |part_id|
|
282
|
+
begin
|
283
|
+
raw_data = @client.fetch_body_part(@uid, part_id)
|
284
|
+
mail_data = Mail.read_from_string(raw_data)
|
285
|
+
|
286
|
+
# Create a simplified attachment wrapper
|
287
|
+
wrapper = SimpleMailAttachment.new(mail_data, "postacert.eml", "message/rfc822")
|
288
|
+
attachments << Attachment.new(wrapper)
|
289
|
+
rescue => e
|
290
|
+
puts "Warning: Failed to extract nested postacert.eml at #{part_id}: #{e.message}"
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
attachments
|
295
|
+
end
|
296
|
+
end
|
297
|
+
|
298
|
+
|
299
|
+
def find_postacert_part_ids(bodystructure = @bodystructure, path = "", include_nested = false)
|
153
300
|
results = []
|
301
|
+
|
302
|
+
return results unless bodystructure
|
154
303
|
|
155
304
|
if bodystructure.respond_to?(:parts) && bodystructure.parts
|
156
305
|
bodystructure.parts.each_with_index do |part, index|
|
157
306
|
part_path = path.empty? ? "#{index + 1}" : "#{path}.#{index + 1}"
|
158
|
-
results += find_postacert_part_ids(part, part_path)
|
307
|
+
results += find_postacert_part_ids(part, part_path, include_nested)
|
159
308
|
end
|
160
|
-
elsif bodystructure.media_type
|
161
|
-
|
309
|
+
elsif bodystructure.respond_to?(:media_type) && bodystructure.media_type == "MESSAGE" &&
|
310
|
+
bodystructure.respond_to?(:subtype) && bodystructure.subtype == "RFC822"
|
311
|
+
if bodystructure.respond_to?(:param) && bodystructure.param &&
|
312
|
+
bodystructure.param["NAME"]&.downcase&.include?("postacert.eml")
|
162
313
|
results << path
|
163
314
|
end
|
315
|
+
|
316
|
+
# Search inside MESSAGE/RFC822 bodies for nested postacert.eml files if requested
|
317
|
+
if include_nested && bodystructure.respond_to?(:body) && bodystructure.body&.respond_to?(:parts)
|
318
|
+
bodystructure.body.parts.each_with_index do |nested_part, nested_index|
|
319
|
+
nested_path = "#{path}.#{nested_index + 1}"
|
320
|
+
results += find_postacert_part_ids(nested_part, nested_path, include_nested)
|
321
|
+
end
|
322
|
+
end
|
164
323
|
end
|
165
324
|
|
166
325
|
results
|
167
326
|
end
|
168
327
|
|
328
|
+
# Delegate to the shared implementation in NestedPostacertMessage
|
169
329
|
def extract_text_part(mail, preferred_type = "text/plain")
|
170
|
-
|
171
|
-
|
172
|
-
mail.parts.each do |part|
|
173
|
-
if part.multipart?
|
174
|
-
found = extract_text_part(part, preferred_type)
|
175
|
-
return found if found
|
176
|
-
elsif part.mime_type == preferred_type
|
177
|
-
return part
|
178
|
-
end
|
179
|
-
end
|
180
|
-
|
181
|
-
nil
|
330
|
+
NestedPostacertMessage.new(mail).send(:extract_text_part, mail, preferred_type)
|
182
331
|
end
|
183
332
|
end
|
184
333
|
end
|
data/lib/pec_ruby/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pec_ruby
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1
|
4
|
+
version: 0.2.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- EMG
|
@@ -67,8 +67,9 @@ dependencies:
|
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '1.0'
|
69
69
|
description: A comprehensive Ruby library for handling Italian certified email (PEC)
|
70
|
-
messages.
|
71
|
-
and a CLI for exploring PEC messages.
|
70
|
+
messages. Supports nested PEC detection, extracting postacert.eml contents, attachment
|
71
|
+
management, and includes a CLI for exploring PEC messages. Features performance
|
72
|
+
optimizations and secure credential handling.
|
72
73
|
email:
|
73
74
|
- enricomaria.giordano@icloud.com
|
74
75
|
executables:
|
@@ -76,10 +77,15 @@ executables:
|
|
76
77
|
extensions: []
|
77
78
|
extra_rdoc_files: []
|
78
79
|
files:
|
80
|
+
- ".env.example"
|
81
|
+
- ".rspec"
|
82
|
+
- ".rspec_status"
|
79
83
|
- CHANGELOG.md
|
80
84
|
- Gemfile
|
85
|
+
- Gemfile.lock
|
81
86
|
- LICENSE
|
82
87
|
- README.md
|
88
|
+
- Rakefile
|
83
89
|
- bin/pec_ruby
|
84
90
|
- lib/pec_ruby.rb
|
85
91
|
- lib/pec_ruby/attachment.rb
|