pec_ruby 0.1.0
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 +7 -0
- data/CHANGELOG.md +37 -0
- data/Gemfile +9 -0
- data/LICENSE +21 -0
- data/README.md +364 -0
- data/bin/pec_ruby +19 -0
- data/lib/pec_ruby/attachment.rb +61 -0
- data/lib/pec_ruby/cli.rb +215 -0
- data/lib/pec_ruby/client.rb +99 -0
- data/lib/pec_ruby/message.rb +184 -0
- data/lib/pec_ruby/version.rb +5 -0
- data/lib/pec_ruby.rb +21 -0
- metadata +118 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 81169a18f557a07493e4dc4dd5d1daee96638209bcd95cca4e79d3d8b1e6fe70
|
4
|
+
data.tar.gz: d84b84c4e2f75fcad73bbf8987dfe83ba9ff7d2997e5bda9a50117977b5096c4
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: b3eed5f566866056fa6fe9ac1bb5aa4d6698cd16ae6f56534ac8f63bdc307df5aa948f521765061d90963ba3284ec9d7583218fb9e5d2814a790fa18c97a8603
|
7
|
+
data.tar.gz: 44a1840dbb4cd1feb585cab2ca2e2ca81b403b5154dca1408553cb9001790877d445460b2a1df7c4286bb1f373197a4a508f2d3970c3ef9aa2c63e223f9567d6
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
# Changelog
|
2
|
+
|
3
|
+
All notable changes to this project will be documented in this file.
|
4
|
+
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
7
|
+
|
8
|
+
## [Unreleased]
|
9
|
+
|
10
|
+
## [0.1.0] - 2025-07-13
|
11
|
+
|
12
|
+
### Added
|
13
|
+
- Initial release of PecRuby gem
|
14
|
+
- IMAP client for connecting to Italian PEC servers
|
15
|
+
- Automatic extraction of postacert.eml attachments
|
16
|
+
- Message parsing and decoding functionality
|
17
|
+
- Attachment management with download capabilities
|
18
|
+
- Command-line interface (CLI) for interactive exploration
|
19
|
+
- Comprehensive error handling
|
20
|
+
- Support for flexible installation (with/without CLI dependencies)
|
21
|
+
- Complete API documentation
|
22
|
+
- Example usage files
|
23
|
+
|
24
|
+
### Features
|
25
|
+
- **PecRuby::Client**: Main client class for IMAP operations
|
26
|
+
- **PecRuby::Message**: Message representation with original content extraction
|
27
|
+
- **PecRuby::Attachment**: Attachment handling with save capabilities
|
28
|
+
- **PecRuby::CLI**: Interactive command-line interface
|
29
|
+
- **Error Classes**: Specific error types for better error handling
|
30
|
+
- **Flexible Dependencies**: Optional CLI dependencies for minimal installations
|
31
|
+
|
32
|
+
### Supported Providers
|
33
|
+
- Aruba PEC (imaps.pec.aruba.it)
|
34
|
+
- Generic IMAP-compliant PEC providers
|
35
|
+
|
36
|
+
[Unreleased]: https://github.com/egio12/pec_ruby/compare/v0.1.0...HEAD
|
37
|
+
[0.1.0]: https://github.com/egio12/pec_ruby/releases/tag/v0.1.0
|
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2025 EMG
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,364 @@
|
|
1
|
+
# PecRuby
|
2
|
+
|
3
|
+
A comprehensive Ruby gem for decoding and managing Italian PEC (Posta Elettronica Certificata) email messages.
|
4
|
+
|
5
|
+
## Features
|
6
|
+
|
7
|
+
- **IMAP Connection**: Connect to Italian PEC servers
|
8
|
+
- **Automatic Extraction**: Automatically extracts original messages from postacert.eml attachments
|
9
|
+
- **Attachment Management**: Download and manage attachments easily
|
10
|
+
- **CLI Included**: Command-line interface for exploring PEC messages
|
11
|
+
- **Programmatic API**: Methods for integrating PEC functionality into your Ruby applications
|
12
|
+
|
13
|
+
## Installation
|
14
|
+
|
15
|
+
### Library Only (without CLI)
|
16
|
+
|
17
|
+
To use only the programmatic API without the command-line interface:
|
18
|
+
|
19
|
+
```ruby
|
20
|
+
gem 'pec_ruby'
|
21
|
+
```
|
22
|
+
|
23
|
+
Or install directly:
|
24
|
+
|
25
|
+
```bash
|
26
|
+
gem install pec_ruby
|
27
|
+
```
|
28
|
+
|
29
|
+
### With CLI Included
|
30
|
+
|
31
|
+
To also use the command-line interface, install additional dependencies:
|
32
|
+
|
33
|
+
```bash
|
34
|
+
gem install pec_ruby tty-prompt awesome_print
|
35
|
+
```
|
36
|
+
|
37
|
+
Or in your Gemfile:
|
38
|
+
|
39
|
+
```ruby
|
40
|
+
gem 'pec_ruby'
|
41
|
+
gem 'tty-prompt', '~> 0.23'
|
42
|
+
gem 'awesome_print', '~> 1.9'
|
43
|
+
```
|
44
|
+
|
45
|
+
## CLI Usage
|
46
|
+
|
47
|
+
After complete installation (with CLI dependencies), you can use the CLI:
|
48
|
+
|
49
|
+
```bash
|
50
|
+
pec_ruby
|
51
|
+
```
|
52
|
+
|
53
|
+
**Note**: If you installed only the library without CLI dependencies, the `pec_ruby` executable will inform you how to install them.
|
54
|
+
|
55
|
+
The CLI allows you to:
|
56
|
+
- Connect to your PEC server
|
57
|
+
- Explore received messages
|
58
|
+
- View decoded original message contents
|
59
|
+
- Download attachments
|
60
|
+
|
61
|
+
## Programmatic Usage
|
62
|
+
|
63
|
+
### Basic Connection
|
64
|
+
|
65
|
+
```ruby
|
66
|
+
require 'pec_ruby'
|
67
|
+
|
68
|
+
# Connect to PEC server
|
69
|
+
client = PecRuby::Client.new(
|
70
|
+
host: 'imaps.pec.aruba.it',
|
71
|
+
username: 'your@domain.pec.it',
|
72
|
+
password: 'password'
|
73
|
+
)
|
74
|
+
|
75
|
+
client.connect
|
76
|
+
```
|
77
|
+
|
78
|
+
### Retrieving Messages
|
79
|
+
|
80
|
+
```ruby
|
81
|
+
# All messages (last 10)
|
82
|
+
messages = client.messages(limit: 10)
|
83
|
+
|
84
|
+
# Only PEC messages (with postacert.eml)
|
85
|
+
pec_messages = client.pec_messages(limit: 10)
|
86
|
+
|
87
|
+
# Specific message by UID
|
88
|
+
message = client.message(12345)
|
89
|
+
```
|
90
|
+
|
91
|
+
### Working with Messages
|
92
|
+
|
93
|
+
```ruby
|
94
|
+
message = client.pec_messages.first
|
95
|
+
|
96
|
+
# PEC container information
|
97
|
+
puts message.subject # PEC message subject
|
98
|
+
puts message.from # PEC sender
|
99
|
+
puts message.date # PEC message date
|
100
|
+
|
101
|
+
# Original message information
|
102
|
+
puts message.original_subject # Original subject
|
103
|
+
puts message.original_from # Original sender
|
104
|
+
puts message.original_body # Original message body
|
105
|
+
|
106
|
+
# Attachments
|
107
|
+
message.original_attachments.each do |attachment|
|
108
|
+
puts "#{attachment.filename} (#{attachment.size_kb} KB)"
|
109
|
+
|
110
|
+
# Save attachment
|
111
|
+
attachment.save_to("/path/to/file.pdf")
|
112
|
+
# or
|
113
|
+
attachment.save_to_dir("/downloads/")
|
114
|
+
end
|
115
|
+
```
|
116
|
+
|
117
|
+
## API Documentation
|
118
|
+
|
119
|
+
### PecRuby::Client
|
120
|
+
|
121
|
+
The main client class for connecting to PEC servers.
|
122
|
+
|
123
|
+
#### Constructor
|
124
|
+
|
125
|
+
```ruby
|
126
|
+
PecRuby::Client.new(host:, username:, password:, ssl: true)
|
127
|
+
```
|
128
|
+
|
129
|
+
**Parameters:**
|
130
|
+
- `host` (String): IMAP server hostname
|
131
|
+
- `username` (String): PEC email address
|
132
|
+
- `password` (String): Account password
|
133
|
+
- `ssl` (Boolean): Use SSL connection (default: true)
|
134
|
+
|
135
|
+
#### Instance Methods
|
136
|
+
|
137
|
+
##### `#connect`
|
138
|
+
Establishes connection to the PEC server.
|
139
|
+
|
140
|
+
```ruby
|
141
|
+
client.connect
|
142
|
+
# Returns: self
|
143
|
+
# Raises: PecRuby::ConnectionError, PecRuby::AuthenticationError
|
144
|
+
```
|
145
|
+
|
146
|
+
##### `#disconnect`
|
147
|
+
Safely disconnects from the PEC server.
|
148
|
+
|
149
|
+
```ruby
|
150
|
+
client.disconnect
|
151
|
+
# Returns: nil
|
152
|
+
```
|
153
|
+
|
154
|
+
##### `#connected?`
|
155
|
+
Checks if currently connected to the server.
|
156
|
+
|
157
|
+
```ruby
|
158
|
+
client.connected?
|
159
|
+
# Returns: Boolean
|
160
|
+
```
|
161
|
+
|
162
|
+
##### `#messages(limit: nil, reverse: true)`
|
163
|
+
Retrieves messages from the server.
|
164
|
+
|
165
|
+
```ruby
|
166
|
+
messages = client.messages(limit: 10, reverse: true)
|
167
|
+
# Returns: Array<PecRuby::Message>
|
168
|
+
```
|
169
|
+
|
170
|
+
**Parameters:**
|
171
|
+
- `limit` (Integer, optional): Maximum number of messages to retrieve
|
172
|
+
- `reverse` (Boolean): Return newest messages first (default: true)
|
173
|
+
|
174
|
+
##### `#pec_messages(limit: nil, reverse: true)`
|
175
|
+
Retrieves only messages containing postacert.eml.
|
176
|
+
|
177
|
+
```ruby
|
178
|
+
pec_messages = client.pec_messages(limit: 5)
|
179
|
+
# Returns: Array<PecRuby::Message>
|
180
|
+
```
|
181
|
+
|
182
|
+
##### `#message(uid)`
|
183
|
+
Retrieves a specific message by UID.
|
184
|
+
|
185
|
+
```ruby
|
186
|
+
message = client.message(12345)
|
187
|
+
# Returns: PecRuby::Message or nil
|
188
|
+
```
|
189
|
+
|
190
|
+
### PecRuby::Message
|
191
|
+
|
192
|
+
Represents a PEC message with access to both container and original message data.
|
193
|
+
|
194
|
+
#### Instance Methods
|
195
|
+
|
196
|
+
##### Basic PEC Container Information
|
197
|
+
|
198
|
+
```ruby
|
199
|
+
# PEC envelope information
|
200
|
+
message.uid # Integer: Message UID
|
201
|
+
message.subject # String: PEC subject (cleaned)
|
202
|
+
message.from # String: PEC sender
|
203
|
+
message.to # Array<String>: PEC recipients
|
204
|
+
message.date # Time: PEC message date
|
205
|
+
```
|
206
|
+
|
207
|
+
##### Original Message Access
|
208
|
+
|
209
|
+
```ruby
|
210
|
+
# Check if postacert.eml is available
|
211
|
+
message.has_postacert? # Boolean
|
212
|
+
|
213
|
+
# Original message information
|
214
|
+
message.original_subject # String: Original subject
|
215
|
+
message.original_from # String: Original sender
|
216
|
+
message.original_to # Array<String>: Original recipients
|
217
|
+
message.original_date # Time: Original message date
|
218
|
+
message.original_body # String: Original message body (decoded)
|
219
|
+
```
|
220
|
+
|
221
|
+
##### Attachments
|
222
|
+
|
223
|
+
```ruby
|
224
|
+
# Get original message attachments
|
225
|
+
message.original_attachments # Array<PecRuby::Attachment>
|
226
|
+
```
|
227
|
+
|
228
|
+
##### Summary Information
|
229
|
+
|
230
|
+
```ruby
|
231
|
+
# Get complete message summary
|
232
|
+
summary = message.summary
|
233
|
+
# Returns: Hash with all message information
|
234
|
+
```
|
235
|
+
|
236
|
+
### PecRuby::Attachment
|
237
|
+
|
238
|
+
Represents an attachment from the original message.
|
239
|
+
|
240
|
+
#### Instance Methods
|
241
|
+
|
242
|
+
```ruby
|
243
|
+
# Basic information
|
244
|
+
attachment.filename # String: Original filename
|
245
|
+
attachment.mime_type # String: MIME type
|
246
|
+
attachment.size # Integer: Size in bytes
|
247
|
+
attachment.size_kb # Float: Size in KB
|
248
|
+
attachment.size_mb # Float: Size in MB
|
249
|
+
|
250
|
+
# Content access
|
251
|
+
attachment.content # String: Raw binary content
|
252
|
+
|
253
|
+
# File operations
|
254
|
+
attachment.save_to(path) # Save to specific path
|
255
|
+
attachment.save_to_dir(directory) # Save to directory with original filename
|
256
|
+
|
257
|
+
# Summary
|
258
|
+
attachment.summary # Hash: Complete attachment information
|
259
|
+
attachment.to_s # String: Human-readable description
|
260
|
+
```
|
261
|
+
|
262
|
+
## Complete Example
|
263
|
+
|
264
|
+
```ruby
|
265
|
+
require 'pec_ruby'
|
266
|
+
|
267
|
+
begin
|
268
|
+
# Connect
|
269
|
+
client = PecRuby::Client.new(
|
270
|
+
host: 'imaps.pec.aruba.it',
|
271
|
+
username: 'example@pec.it',
|
272
|
+
password: 'password'
|
273
|
+
)
|
274
|
+
client.connect
|
275
|
+
|
276
|
+
# Get last 5 PEC messages
|
277
|
+
pec_messages = client.pec_messages(limit: 5)
|
278
|
+
|
279
|
+
pec_messages.each do |message|
|
280
|
+
puts "Subject: #{message.original_subject}"
|
281
|
+
puts "From: #{message.original_from}"
|
282
|
+
puts "Attachments: #{message.original_attachments.size}"
|
283
|
+
|
284
|
+
# Download attachments
|
285
|
+
message.original_attachments.each do |attachment|
|
286
|
+
attachment.save_to_dir('./downloads')
|
287
|
+
puts "Downloaded: #{attachment.filename}"
|
288
|
+
end
|
289
|
+
|
290
|
+
puts "─" * 40
|
291
|
+
end
|
292
|
+
|
293
|
+
ensure
|
294
|
+
client&.disconnect
|
295
|
+
end
|
296
|
+
```
|
297
|
+
|
298
|
+
## Error Handling
|
299
|
+
|
300
|
+
The gem defines several specific error classes:
|
301
|
+
|
302
|
+
```ruby
|
303
|
+
PecRuby::Error # Base error class
|
304
|
+
PecRuby::ConnectionError # Connection issues
|
305
|
+
PecRuby::AuthenticationError # Login failures
|
306
|
+
PecRuby::MessageNotFoundError # Message not found
|
307
|
+
PecRuby::PostacertNotFoundError # postacert.eml not found
|
308
|
+
```
|
309
|
+
|
310
|
+
Example with error handling:
|
311
|
+
|
312
|
+
```ruby
|
313
|
+
begin
|
314
|
+
client = PecRuby::Client.new(...)
|
315
|
+
client.connect
|
316
|
+
rescue PecRuby::AuthenticationError => e
|
317
|
+
puts "Login failed: #{e.message}"
|
318
|
+
rescue PecRuby::ConnectionError => e
|
319
|
+
puts "Connection error: #{e.message}"
|
320
|
+
rescue PecRuby::Error => e
|
321
|
+
puts "PEC error: #{e.message}"
|
322
|
+
end
|
323
|
+
```
|
324
|
+
|
325
|
+
## Supported PEC Providers
|
326
|
+
|
327
|
+
The gem has been tested with:
|
328
|
+
- Aruba PEC (`imaps.pec.aruba.it`) ✅ **Fully tested**
|
329
|
+
|
330
|
+
Other providers should work if they support standard IMAP, but have not been tested yet.
|
331
|
+
|
332
|
+
## Current Limitations
|
333
|
+
|
334
|
+
- **Message Threading**: The gem currently does not support message threading or conversation grouping. Each message is handled individually.
|
335
|
+
- **Provider Testing**: Only tested with Aruba PEC. Other providers may work but are not guaranteed.
|
336
|
+
- **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
|
+
|
338
|
+
## Development
|
339
|
+
|
340
|
+
After cloning the repository:
|
341
|
+
|
342
|
+
```bash
|
343
|
+
bundle install
|
344
|
+
bundle exec rspec # Run tests
|
345
|
+
bundle exec rubocop # Check code style
|
346
|
+
```
|
347
|
+
|
348
|
+
## Contributing
|
349
|
+
|
350
|
+
1. Fork the project
|
351
|
+
2. Create your feature branch (`git checkout -b feature/AmazingFeature`)
|
352
|
+
3. Commit your changes (`git commit -m 'Add some AmazingFeature'`)
|
353
|
+
4. Push to the branch (`git push origin feature/AmazingFeature`)
|
354
|
+
5. Open a Pull Request
|
355
|
+
|
356
|
+
## License
|
357
|
+
|
358
|
+
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/bin/pec_ruby
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require_relative '../lib/pec_ruby'
|
5
|
+
|
6
|
+
begin
|
7
|
+
if defined?(PecRuby::CLI)
|
8
|
+
PecRuby::CLI.new.run
|
9
|
+
else
|
10
|
+
puts "CLI non disponibile. Installa le dipendenze CLI:"
|
11
|
+
puts "gem install tty-prompt awesome_print"
|
12
|
+
exit 1
|
13
|
+
end
|
14
|
+
rescue LoadError => e
|
15
|
+
puts "Errore nel caricamento della CLI: #{e.message}"
|
16
|
+
puts "Installa le dipendenze CLI:"
|
17
|
+
puts "gem install tty-prompt awesome_print"
|
18
|
+
exit 1
|
19
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PecRuby
|
4
|
+
class Attachment
|
5
|
+
attr_reader :mail_attachment
|
6
|
+
|
7
|
+
def initialize(mail_attachment)
|
8
|
+
@mail_attachment = mail_attachment
|
9
|
+
end
|
10
|
+
|
11
|
+
def filename
|
12
|
+
@mail_attachment.filename || "unnamed_file"
|
13
|
+
end
|
14
|
+
|
15
|
+
def mime_type
|
16
|
+
@mail_attachment.mime_type || "application/octet-stream"
|
17
|
+
end
|
18
|
+
|
19
|
+
def size
|
20
|
+
content.bytesize
|
21
|
+
end
|
22
|
+
|
23
|
+
def size_kb
|
24
|
+
(size / 1024.0).round(1)
|
25
|
+
end
|
26
|
+
|
27
|
+
def size_mb
|
28
|
+
(size / 1024.0 / 1024.0).round(2)
|
29
|
+
end
|
30
|
+
|
31
|
+
def content
|
32
|
+
@mail_attachment.decoded
|
33
|
+
end
|
34
|
+
|
35
|
+
# Save attachment to file
|
36
|
+
def save_to(path)
|
37
|
+
File.binwrite(path, content)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Save attachment to directory with original filename
|
41
|
+
def save_to_dir(directory)
|
42
|
+
path = File.join(directory, filename)
|
43
|
+
save_to(path)
|
44
|
+
path
|
45
|
+
end
|
46
|
+
|
47
|
+
def summary
|
48
|
+
{
|
49
|
+
filename: filename,
|
50
|
+
mime_type: mime_type,
|
51
|
+
size: size,
|
52
|
+
size_kb: size_kb,
|
53
|
+
size_mb: size_mb
|
54
|
+
}
|
55
|
+
end
|
56
|
+
|
57
|
+
def to_s
|
58
|
+
"#{filename} (#{mime_type}, #{size_kb} KB)"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
data/lib/pec_ruby/cli.rb
ADDED
@@ -0,0 +1,215 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
begin
|
4
|
+
require 'tty-prompt'
|
5
|
+
require 'awesome_print'
|
6
|
+
rescue LoadError => e
|
7
|
+
raise LoadError, "CLI dependencies not available. Install with: gem install tty-prompt awesome_print"
|
8
|
+
end
|
9
|
+
|
10
|
+
module PecRuby
|
11
|
+
class CLI
|
12
|
+
def initialize
|
13
|
+
@client = nil
|
14
|
+
@prompt = TTY::Prompt.new
|
15
|
+
end
|
16
|
+
|
17
|
+
def run
|
18
|
+
puts banner
|
19
|
+
|
20
|
+
loop do
|
21
|
+
case main_menu
|
22
|
+
when :connect
|
23
|
+
connect_to_server
|
24
|
+
when :list_messages
|
25
|
+
list_and_select_messages if connected?
|
26
|
+
when :disconnect
|
27
|
+
disconnect_from_server
|
28
|
+
when :exit
|
29
|
+
disconnect_from_server if connected?
|
30
|
+
puts "\nArrivederci!"
|
31
|
+
break
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def banner
|
39
|
+
<<~BANNER
|
40
|
+
|
41
|
+
╔═══════════════════════════════════════════════════════════════╗
|
42
|
+
║ PEC Decoder CLI ║
|
43
|
+
║ Decodificatore PEC per Ruby ║
|
44
|
+
╚═══════════════════════════════════════════════════════════════╝
|
45
|
+
|
46
|
+
BANNER
|
47
|
+
end
|
48
|
+
|
49
|
+
def main_menu
|
50
|
+
choices = []
|
51
|
+
|
52
|
+
if connected?
|
53
|
+
choices << { name: "Lista e analizza messaggi PEC", value: :list_messages }
|
54
|
+
choices << { name: "Disconnetti dal server", value: :disconnect }
|
55
|
+
else
|
56
|
+
choices << { name: "Connetti al server PEC", value: :connect }
|
57
|
+
end
|
58
|
+
|
59
|
+
choices << { name: "Esci", value: :exit }
|
60
|
+
|
61
|
+
@prompt.select("Seleziona un'azione:", choices)
|
62
|
+
end
|
63
|
+
|
64
|
+
def connect_to_server
|
65
|
+
return if connected?
|
66
|
+
|
67
|
+
puts "\nCONNESSIONE AL SERVER PEC"
|
68
|
+
puts "─" * 40
|
69
|
+
|
70
|
+
host = @prompt.ask("Host IMAP:", default: "imaps.pec.aruba.it")
|
71
|
+
username = @prompt.ask("Username/Email:")
|
72
|
+
password = @prompt.mask("Password:")
|
73
|
+
|
74
|
+
print "Connessione in corso..."
|
75
|
+
|
76
|
+
begin
|
77
|
+
@client = Client.new(host: host, username: username, password: password)
|
78
|
+
@client.connect
|
79
|
+
puts " Connesso!"
|
80
|
+
puts "Connesso come: #{@client.username}"
|
81
|
+
rescue PecRuby::Error => e
|
82
|
+
puts " Errore!"
|
83
|
+
puts "ATTENZIONE: #{e.message}"
|
84
|
+
@client = nil
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def connected?
|
89
|
+
@client&.connected?
|
90
|
+
end
|
91
|
+
|
92
|
+
def disconnect_from_server
|
93
|
+
return unless connected?
|
94
|
+
|
95
|
+
@client.disconnect
|
96
|
+
@client = nil
|
97
|
+
puts "Disconnesso dal server"
|
98
|
+
end
|
99
|
+
|
100
|
+
def list_and_select_messages
|
101
|
+
puts "\nCaricamento messaggi..."
|
102
|
+
|
103
|
+
begin
|
104
|
+
messages = @client.pec_messages(limit: 20, reverse: true)
|
105
|
+
|
106
|
+
if messages.empty?
|
107
|
+
puts "Nessun messaggio PEC trovato"
|
108
|
+
return
|
109
|
+
end
|
110
|
+
|
111
|
+
choices = messages.map do |msg|
|
112
|
+
label = format_message_label(msg)
|
113
|
+
[label, msg]
|
114
|
+
end
|
115
|
+
|
116
|
+
puts "\n" + "─" * 120
|
117
|
+
puts "MESSAGGI PEC RICEVUTI (più recenti in alto)"
|
118
|
+
puts "─" * 120
|
119
|
+
|
120
|
+
selected_message = @prompt.select("Seleziona un messaggio:", choices.to_h, per_page: 15, cycle: true)
|
121
|
+
display_message(selected_message)
|
122
|
+
|
123
|
+
rescue PecRuby::Error => e
|
124
|
+
puts "Errore nel recupero messaggi: #{e.message}"
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def format_message_label(message)
|
129
|
+
subject = message.subject || "(nessun oggetto)"
|
130
|
+
from = message.from || "(mittente sconosciuto)"
|
131
|
+
date = message.date ? message.date.strftime("%d/%m %H:%M") : "N/A"
|
132
|
+
|
133
|
+
short_subject = subject.length > 60 ? "#{subject[0..56]}..." : subject
|
134
|
+
|
135
|
+
sprintf("%-60s | %-25s | %s",
|
136
|
+
short_subject,
|
137
|
+
from.to_s[0..24],
|
138
|
+
date)
|
139
|
+
end
|
140
|
+
|
141
|
+
def display_message(message)
|
142
|
+
puts "\n" + "="*80
|
143
|
+
puts "MESSAGGIO PEC DECODIFICATO"
|
144
|
+
puts "="*80
|
145
|
+
|
146
|
+
# Informazioni base PEC
|
147
|
+
puts "\nINFORMAZIONI CONTENITORE PEC"
|
148
|
+
puts "─"*50
|
149
|
+
puts sprintf("Oggetto PEC: %s", message.subject || "(nessun oggetto)")
|
150
|
+
puts sprintf("From PEC: %s", message.from || "(sconosciuto)")
|
151
|
+
puts sprintf("Data PEC: %s", message.date ? message.date.strftime("%d/%m/%Y %H:%M") : "(sconosciuta)")
|
152
|
+
|
153
|
+
# Informazioni messaggio originale
|
154
|
+
if message.has_postacert?
|
155
|
+
puts "\nMESSAGGIO ORIGINALE (da postacert.eml)"
|
156
|
+
puts "─"*50
|
157
|
+
puts sprintf("Oggetto: %s", message.original_subject || "(nessun oggetto)")
|
158
|
+
puts sprintf("Mittente: %s", message.original_from || "(sconosciuto)")
|
159
|
+
puts sprintf("Destinatari: %s", message.original_to.join(', '))
|
160
|
+
puts sprintf("Data: %s", message.original_date ? message.original_date.strftime("%d/%m/%Y %H:%M") : "(sconosciuta)")
|
161
|
+
|
162
|
+
# Corpo del messaggio
|
163
|
+
body = message.original_body
|
164
|
+
if body && !body.strip.empty?
|
165
|
+
puts "\nCORPO DEL MESSAGGIO"
|
166
|
+
puts "─"*50
|
167
|
+
puts body.strip
|
168
|
+
end
|
169
|
+
|
170
|
+
# Allegati
|
171
|
+
attachments = message.original_attachments
|
172
|
+
puts "\nALLEGATI"
|
173
|
+
puts "─"*50
|
174
|
+
if attachments.any?
|
175
|
+
attachments.each_with_index do |att, i|
|
176
|
+
puts sprintf("%d. %-30s | %s | %.1f KB",
|
177
|
+
i+1,
|
178
|
+
att.filename,
|
179
|
+
att.mime_type,
|
180
|
+
att.size_kb)
|
181
|
+
end
|
182
|
+
|
183
|
+
if @prompt.yes?("\nVuoi scaricare gli allegati?")
|
184
|
+
download_attachments(attachments)
|
185
|
+
end
|
186
|
+
else
|
187
|
+
puts " Nessun allegato presente"
|
188
|
+
end
|
189
|
+
else
|
190
|
+
puts "\nATTENZIONE: Questo messaggio non contiene postacert.eml"
|
191
|
+
end
|
192
|
+
|
193
|
+
puts "\n" + "="*80
|
194
|
+
@prompt.keypress("\nPremi un tasto per continuare...")
|
195
|
+
end
|
196
|
+
|
197
|
+
def download_attachments(attachments)
|
198
|
+
download_dir = @prompt.ask("Directory di download:", default: "./downloads")
|
199
|
+
|
200
|
+
begin
|
201
|
+
require 'fileutils'
|
202
|
+
FileUtils.mkdir_p(download_dir)
|
203
|
+
|
204
|
+
attachments.each do |attachment|
|
205
|
+
file_path = attachment.save_to_dir(download_dir)
|
206
|
+
puts "Salvato: #{file_path}"
|
207
|
+
end
|
208
|
+
|
209
|
+
puts "Tutti gli allegati sono stati salvati in #{download_dir}"
|
210
|
+
rescue => e
|
211
|
+
puts "Errore nel salvataggio: #{e.message}"
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'net/imap'
|
4
|
+
require 'mail'
|
5
|
+
|
6
|
+
module PecRuby
|
7
|
+
class Client
|
8
|
+
attr_reader :imap, :host, :username
|
9
|
+
|
10
|
+
def initialize(host:, username:, password:, ssl: true)
|
11
|
+
@host = host
|
12
|
+
@username = username
|
13
|
+
@password = password
|
14
|
+
@ssl = ssl
|
15
|
+
@imap = nil
|
16
|
+
end
|
17
|
+
|
18
|
+
def connect
|
19
|
+
@imap = Net::IMAP.new(@host, ssl: @ssl)
|
20
|
+
authenticate
|
21
|
+
select_inbox
|
22
|
+
self
|
23
|
+
rescue Net::IMAP::Error => e
|
24
|
+
raise ConnectionError, "Failed to connect to #{@host}: #{e.message}"
|
25
|
+
end
|
26
|
+
|
27
|
+
def disconnect
|
28
|
+
return unless @imap
|
29
|
+
|
30
|
+
begin
|
31
|
+
@imap.logout if @imap && !@imap.disconnected?
|
32
|
+
rescue => e
|
33
|
+
# Ignore logout errors if connection is already closed
|
34
|
+
end
|
35
|
+
|
36
|
+
begin
|
37
|
+
@imap.disconnect if @imap && !@imap.disconnected?
|
38
|
+
rescue => e
|
39
|
+
# Ignore disconnect errors if connection is already closed
|
40
|
+
end
|
41
|
+
|
42
|
+
@imap = nil
|
43
|
+
end
|
44
|
+
|
45
|
+
def connected?
|
46
|
+
@imap && !@imap.disconnected?
|
47
|
+
end
|
48
|
+
|
49
|
+
# Get all messages or a subset
|
50
|
+
def messages(limit: nil, reverse: true)
|
51
|
+
raise ConnectionError, "Not connected" unless connected?
|
52
|
+
|
53
|
+
sequence_numbers = @imap.search(['ALL'])
|
54
|
+
sequence_numbers = sequence_numbers.reverse if reverse
|
55
|
+
sequence_numbers = sequence_numbers.first(limit) if limit
|
56
|
+
|
57
|
+
fetch_messages(sequence_numbers)
|
58
|
+
end
|
59
|
+
|
60
|
+
# Get a specific message by UID
|
61
|
+
def message(uid)
|
62
|
+
raise ConnectionError, "Not connected" unless connected?
|
63
|
+
|
64
|
+
fetch_data = @imap.uid_fetch(uid, ["UID", "ENVELOPE", "BODYSTRUCTURE"])
|
65
|
+
return nil if fetch_data.nil? || fetch_data.empty?
|
66
|
+
|
67
|
+
Message.new(self, fetch_data.first)
|
68
|
+
end
|
69
|
+
|
70
|
+
# Get messages with postacert.eml only
|
71
|
+
def pec_messages(limit: nil, reverse: true)
|
72
|
+
messages(limit: limit, reverse: reverse).select(&:has_postacert?)
|
73
|
+
end
|
74
|
+
|
75
|
+
# Internal method to fetch message body parts
|
76
|
+
def fetch_body_part(uid, part_id)
|
77
|
+
@imap.uid_fetch(uid, "BODY[#{part_id}]")[0].attr["BODY[#{part_id}]"]
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
|
82
|
+
def authenticate
|
83
|
+
@imap.authenticate("PLAIN", @username, @password)
|
84
|
+
rescue Net::IMAP::Error => e
|
85
|
+
raise AuthenticationError, "Authentication failed: #{e.message}"
|
86
|
+
end
|
87
|
+
|
88
|
+
def select_inbox
|
89
|
+
@imap.select('INBOX')
|
90
|
+
end
|
91
|
+
|
92
|
+
def fetch_messages(sequence_numbers)
|
93
|
+
return [] if sequence_numbers.empty?
|
94
|
+
|
95
|
+
messages_data = @imap.fetch(sequence_numbers, ["UID", "ENVELOPE", "BODYSTRUCTURE"])
|
96
|
+
messages_data.map { |msg_data| Message.new(self, msg_data) }
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,184 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'mail'
|
4
|
+
|
5
|
+
module PecRuby
|
6
|
+
class Message
|
7
|
+
attr_reader :uid, :envelope, :bodystructure, :client
|
8
|
+
|
9
|
+
def initialize(client, fetch_data)
|
10
|
+
@client = client
|
11
|
+
@uid = fetch_data.attr["UID"]
|
12
|
+
@envelope = fetch_data.attr["ENVELOPE"]
|
13
|
+
@bodystructure = fetch_data.attr["BODYSTRUCTURE"]
|
14
|
+
@postacert_mail = nil
|
15
|
+
@postacert_extracted = false
|
16
|
+
end
|
17
|
+
|
18
|
+
# Basic envelope information
|
19
|
+
def subject
|
20
|
+
return nil unless @envelope.subject
|
21
|
+
|
22
|
+
decoded = Mail::Encodings.value_decode(@envelope.subject)
|
23
|
+
decoded.gsub!("POSTA CERTIFICATA:", "") if decoded.start_with?("POSTA CERTIFICATA:")
|
24
|
+
decoded.strip
|
25
|
+
end
|
26
|
+
|
27
|
+
def from
|
28
|
+
return nil unless @envelope.from&.first
|
29
|
+
|
30
|
+
from_addr = @envelope.from.first
|
31
|
+
extract_real_sender(from_addr)
|
32
|
+
end
|
33
|
+
|
34
|
+
def to
|
35
|
+
return [] unless @envelope.to
|
36
|
+
|
37
|
+
@envelope.to.map { |addr| "#{addr.mailbox}@#{addr.host}" }
|
38
|
+
end
|
39
|
+
|
40
|
+
def date
|
41
|
+
@envelope.date ? Time.parse(@envelope.date.to_s) : nil
|
42
|
+
end
|
43
|
+
|
44
|
+
# Check if message contains postacert.eml
|
45
|
+
def has_postacert?
|
46
|
+
!find_postacert_part_ids.empty?
|
47
|
+
end
|
48
|
+
|
49
|
+
# Extract and return the original message from postacert.eml
|
50
|
+
def postacert_message
|
51
|
+
return @postacert_mail if @postacert_extracted
|
52
|
+
|
53
|
+
@postacert_extracted = true
|
54
|
+
part_ids = find_postacert_part_ids
|
55
|
+
|
56
|
+
if part_ids.empty?
|
57
|
+
@postacert_mail = nil
|
58
|
+
return nil
|
59
|
+
end
|
60
|
+
|
61
|
+
begin
|
62
|
+
part_id = part_ids.first
|
63
|
+
raw_data = @client.fetch_body_part(@uid, part_id)
|
64
|
+
@postacert_mail = Mail.read_from_string(raw_data)
|
65
|
+
rescue => e
|
66
|
+
raise Error, "Failed to extract postacert.eml: #{e.message}"
|
67
|
+
end
|
68
|
+
|
69
|
+
@postacert_mail
|
70
|
+
end
|
71
|
+
|
72
|
+
# Get original message subject
|
73
|
+
def original_subject
|
74
|
+
postacert_message&.subject
|
75
|
+
end
|
76
|
+
|
77
|
+
# Get original message sender
|
78
|
+
def original_from
|
79
|
+
postacert_message&.from&.first
|
80
|
+
end
|
81
|
+
|
82
|
+
# Get original message recipients
|
83
|
+
def original_to
|
84
|
+
postacert_message&.to || []
|
85
|
+
end
|
86
|
+
|
87
|
+
# Get original message date
|
88
|
+
def original_date
|
89
|
+
postacert_message&.date
|
90
|
+
end
|
91
|
+
|
92
|
+
# Get original message body (text/plain preferred)
|
93
|
+
def original_body
|
94
|
+
mail = postacert_message
|
95
|
+
return nil unless mail
|
96
|
+
|
97
|
+
text_part = extract_text_part(mail, "text/plain")
|
98
|
+
html_part = extract_text_part(mail, "text/html")
|
99
|
+
selected_part = text_part || html_part
|
100
|
+
|
101
|
+
return nil unless selected_part
|
102
|
+
|
103
|
+
raw_body = selected_part.body.decoded
|
104
|
+
charset = selected_part.charset ||
|
105
|
+
selected_part.content_type_parameters&.[]("charset") ||
|
106
|
+
"UTF-8"
|
107
|
+
|
108
|
+
raw_body.force_encoding(charset).encode("UTF-8")
|
109
|
+
end
|
110
|
+
|
111
|
+
# Get original message attachments
|
112
|
+
def original_attachments
|
113
|
+
mail = postacert_message
|
114
|
+
return [] unless mail&.attachments
|
115
|
+
|
116
|
+
mail.attachments.map { |att| Attachment.new(att) }
|
117
|
+
end
|
118
|
+
|
119
|
+
# Summary information
|
120
|
+
def summary
|
121
|
+
{
|
122
|
+
uid: @uid,
|
123
|
+
subject: subject,
|
124
|
+
from: from,
|
125
|
+
to: to,
|
126
|
+
date: date,
|
127
|
+
has_postacert: has_postacert?,
|
128
|
+
original_subject: original_subject,
|
129
|
+
original_from: original_from,
|
130
|
+
original_to: original_to,
|
131
|
+
original_date: original_date,
|
132
|
+
attachments_count: original_attachments.size
|
133
|
+
}
|
134
|
+
end
|
135
|
+
|
136
|
+
private
|
137
|
+
|
138
|
+
def extract_real_sender(from_addr)
|
139
|
+
email = "#{from_addr.mailbox}@#{from_addr.host}"
|
140
|
+
|
141
|
+
# Handle "Per conto di:" in name field
|
142
|
+
if from_addr.name&.include?("Per conto di:")
|
143
|
+
email_match = from_addr.name.match(/([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})/)
|
144
|
+
return email_match[1] if email_match
|
145
|
+
elsif from_addr.name && !from_addr.name.include?("posta-certificata@")
|
146
|
+
return from_addr.name
|
147
|
+
end
|
148
|
+
|
149
|
+
email
|
150
|
+
end
|
151
|
+
|
152
|
+
def find_postacert_part_ids(bodystructure = @bodystructure, path = "")
|
153
|
+
results = []
|
154
|
+
|
155
|
+
if bodystructure.respond_to?(:parts) && bodystructure.parts
|
156
|
+
bodystructure.parts.each_with_index do |part, index|
|
157
|
+
part_path = path.empty? ? "#{index + 1}" : "#{path}.#{index + 1}"
|
158
|
+
results += find_postacert_part_ids(part, part_path)
|
159
|
+
end
|
160
|
+
elsif bodystructure.media_type == "MESSAGE" && bodystructure.subtype == "RFC822"
|
161
|
+
if bodystructure.param && bodystructure.param["NAME"]&.downcase&.include?("postacert.eml")
|
162
|
+
results << path
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
results
|
167
|
+
end
|
168
|
+
|
169
|
+
def extract_text_part(mail, preferred_type = "text/plain")
|
170
|
+
return mail unless mail.multipart?
|
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
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
data/lib/pec_ruby.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "pec_ruby/version"
|
4
|
+
require_relative "pec_ruby/client"
|
5
|
+
require_relative "pec_ruby/message"
|
6
|
+
require_relative "pec_ruby/attachment"
|
7
|
+
|
8
|
+
# CLI is optional - only load if dependencies are available
|
9
|
+
begin
|
10
|
+
require_relative "pec_ruby/cli"
|
11
|
+
rescue LoadError
|
12
|
+
# CLI dependencies not available - skip CLI functionality
|
13
|
+
end
|
14
|
+
|
15
|
+
module PecRuby
|
16
|
+
class Error < StandardError; end
|
17
|
+
class ConnectionError < Error; end
|
18
|
+
class AuthenticationError < Error; end
|
19
|
+
class MessageNotFoundError < Error; end
|
20
|
+
class PostacertNotFoundError < Error; end
|
21
|
+
end
|
metadata
ADDED
@@ -0,0 +1,118 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: pec_ruby
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- EMG
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2025-07-13 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: mail
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '2.7'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '2.7'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: net-imap
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0.3'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0.3'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rubocop
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '1.0'
|
69
|
+
description: A comprehensive Ruby library for handling Italian certified email (PEC)
|
70
|
+
messages. Includes methods for extracting postacert.eml contents, decoding attachments,
|
71
|
+
and a CLI for exploring PEC messages.
|
72
|
+
email:
|
73
|
+
- enricomaria.giordano@icloud.com
|
74
|
+
executables:
|
75
|
+
- pec_ruby
|
76
|
+
extensions: []
|
77
|
+
extra_rdoc_files: []
|
78
|
+
files:
|
79
|
+
- CHANGELOG.md
|
80
|
+
- Gemfile
|
81
|
+
- LICENSE
|
82
|
+
- README.md
|
83
|
+
- bin/pec_ruby
|
84
|
+
- lib/pec_ruby.rb
|
85
|
+
- lib/pec_ruby/attachment.rb
|
86
|
+
- lib/pec_ruby/cli.rb
|
87
|
+
- lib/pec_ruby/client.rb
|
88
|
+
- lib/pec_ruby/message.rb
|
89
|
+
- lib/pec_ruby/version.rb
|
90
|
+
homepage: https://github.com/egio12/pec_ruby
|
91
|
+
licenses:
|
92
|
+
- MIT
|
93
|
+
metadata:
|
94
|
+
allowed_push_host: https://rubygems.org
|
95
|
+
homepage_uri: https://github.com/egio12/pec_ruby
|
96
|
+
source_code_uri: https://github.com/egio12/pec_ruby
|
97
|
+
changelog_uri: https://github.com/egio12/pec_ruby/blob/main/CHANGELOG.md
|
98
|
+
post_install_message:
|
99
|
+
rdoc_options: []
|
100
|
+
require_paths:
|
101
|
+
- lib
|
102
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
103
|
+
requirements:
|
104
|
+
- - ">="
|
105
|
+
- !ruby/object:Gem::Version
|
106
|
+
version: 2.6.0
|
107
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
108
|
+
requirements:
|
109
|
+
- - ">="
|
110
|
+
- !ruby/object:Gem::Version
|
111
|
+
version: '0'
|
112
|
+
requirements: []
|
113
|
+
rubygems_version: 3.4.19
|
114
|
+
signing_key:
|
115
|
+
specification_version: 4
|
116
|
+
summary: Ruby gem for decoding and reading Italian PEC (Posta Elettronica Certificata)
|
117
|
+
emails
|
118
|
+
test_files: []
|