libis-format 1.3.7.2 → 1.3.8.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/lib/libis/format/converter/email_converter.rb +17 -2
- data/lib/libis/format/tool/eml_to_pdf.rb +168 -0
- data/lib/libis/format/tool/mail_to_pdf.rb +447 -0
- data/lib/libis/format/tool/msg_to_pdf.rb +105 -221
- data/lib/libis/format/version.rb +1 -1
- data/libis-format.gemspec +6 -2
- metadata +39 -12
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ec695a5009a2c131983af21f4c773037731907280eb229982eeacc8f7c72bf6b
|
|
4
|
+
data.tar.gz: 48139c18c6b66ddc9b25941dcbecee30294c3d7124d889dd74fae34b2a176c74
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b9f232072ceed43e950d51869478c18e03cb79e157749a34e7580330a9e379f3008bd8f963461ea81261bdaf95b65d779b1d3c0efd1da86ef2d1fc729316cb43
|
|
7
|
+
data.tar.gz: cb40a76524a3854692b81baace242bf96a22c92bcde21192dffdaf6bd5afc187c7b4c7190866e0f0b9c17462d4392acec8b12e07fc6acaaf76192b171c37cb05
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
require_relative 'base'
|
|
4
4
|
|
|
5
5
|
require 'libis/format/tool/msg_to_pdf'
|
|
6
|
+
require 'libis/format/tool/eml_to_pdf'
|
|
6
7
|
require 'libis/format/type_database'
|
|
7
8
|
require 'rexml/document'
|
|
8
9
|
|
|
@@ -10,7 +11,6 @@ module Libis
|
|
|
10
11
|
module Format
|
|
11
12
|
module Converter
|
|
12
13
|
class EmailConverter < Libis::Format::Converter::Base
|
|
13
|
-
|
|
14
14
|
def self.input_types
|
|
15
15
|
%i[MSG EML]
|
|
16
16
|
end
|
|
@@ -28,7 +28,22 @@ module Libis
|
|
|
28
28
|
def convert(source, target, format, opts = {})
|
|
29
29
|
super
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
tool = tool_for_format(opts, source)
|
|
32
|
+
tool.run(source, target)
|
|
33
|
+
rescue StandardError => e
|
|
34
|
+
{ command: { status: -1 }, errors: [{ error: e.message, error_class: e.class, error_trace: e.backtrace }] }
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
def tool_for_format(opts, source)
|
|
40
|
+
if opts[:source_format] == :MSG || File.extname(source).casecmp('.msg').zero?
|
|
41
|
+
Format::Tool::MsgToPdf
|
|
42
|
+
elsif opts[:source_format] == :EML || File.extname(source).casecmp('.eml').zero?
|
|
43
|
+
Format::Tool::EmlToPdf
|
|
44
|
+
else
|
|
45
|
+
raise "Unsupported file extension #{File.extname(source)}"
|
|
46
|
+
end
|
|
32
47
|
end
|
|
33
48
|
end
|
|
34
49
|
end
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# rubocop:disable Style/Documentation, Metrics/*
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require_relative 'mail_to_pdf'
|
|
5
|
+
|
|
6
|
+
require 'mail'
|
|
7
|
+
require 'word_wrap'
|
|
8
|
+
|
|
9
|
+
module Libis
|
|
10
|
+
module Format
|
|
11
|
+
module Tool
|
|
12
|
+
class EmlToPdf < MailToPdf
|
|
13
|
+
protected
|
|
14
|
+
|
|
15
|
+
def open_email(source)
|
|
16
|
+
eml = File.read(source)
|
|
17
|
+
begin
|
|
18
|
+
msg = Mail.new(eml)
|
|
19
|
+
rescue StandardError => e
|
|
20
|
+
eml.force_encoding('ASCII-8BIT')
|
|
21
|
+
msg = Mail.new(eml)
|
|
22
|
+
@warnings << "Failed to parse message with default encoding, forced ASCII-8BIT: #{e.message}"
|
|
23
|
+
end
|
|
24
|
+
msg
|
|
25
|
+
rescue StandardError => e
|
|
26
|
+
raise "Failed to open message: #{e.message}"
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def close_email(_msg)
|
|
30
|
+
true
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def get_body_html(msg)
|
|
34
|
+
# Get the encoding
|
|
35
|
+
|
|
36
|
+
if !msg.multipart?
|
|
37
|
+
HTML_WRAPPER_TEMPLATE % WordWrap.ww(msg.decoded, 120, false)
|
|
38
|
+
|
|
39
|
+
elsif msg.html_part
|
|
40
|
+
body = msg.html_part.body.decoded
|
|
41
|
+
|
|
42
|
+
# if the encoding is not UTF-8, then the HTML may contain metadata that specifies the encoding
|
|
43
|
+
# the browser will use that metadata to render the HTML correctly, so we should not force it to UTF-8
|
|
44
|
+
encoding = body.scan(/<\?xml\s[^?>]*encoding="([^"]*)"[^?>]*\?>/).flatten.first
|
|
45
|
+
encoding ||= body.scan(/<meta\s+[^>]*charset=["']?([^"'>\s]+)["']?[^>]*>/).flatten.first
|
|
46
|
+
encoding ||= msg.html_part.charset
|
|
47
|
+
encoding ||= msg.content_type_parameters['charset'] || msg.charset || 'UTF-8'
|
|
48
|
+
|
|
49
|
+
body.force_encoding(encoding) unless encoding.casecmp(body.encoding.name).zero?
|
|
50
|
+
|
|
51
|
+
body
|
|
52
|
+
elsif msg.text_part
|
|
53
|
+
HTML_WRAPPER_TEMPLATE % msg.text_part.decoded
|
|
54
|
+
|
|
55
|
+
else
|
|
56
|
+
HTML_WRAPPER_TEMPLATE % ''
|
|
57
|
+
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def get_subject(msg)
|
|
62
|
+
msg.subject || 'No Subject'
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def get_headers(msg)
|
|
66
|
+
headers = {}
|
|
67
|
+
html = ''
|
|
68
|
+
|
|
69
|
+
field_list = msg.header_fields
|
|
70
|
+
|
|
71
|
+
%w[From To Cc Subject Date].each do |key|
|
|
72
|
+
value = find_hdr(field_list, key)
|
|
73
|
+
next unless value
|
|
74
|
+
|
|
75
|
+
if value.is_a? Time
|
|
76
|
+
begin
|
|
77
|
+
headers[key.downcase.to_sym] = value.iso8601
|
|
78
|
+
html += hdr_html(key, value.rfc2822)
|
|
79
|
+
rescue StandardError => e
|
|
80
|
+
logger.warn "Failed to parse date header '#{value}': #{e.message}"
|
|
81
|
+
end
|
|
82
|
+
else
|
|
83
|
+
headers[key.downcase.to_sym] = value
|
|
84
|
+
html += hdr_html(key, value)
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
[headers, html]
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def get_inline_attachment_data(attachments, cid)
|
|
92
|
+
attachments.each do |attachment|
|
|
93
|
+
next unless attachment.has_content_id?
|
|
94
|
+
next unless attachment.cid == cid
|
|
95
|
+
|
|
96
|
+
begin
|
|
97
|
+
attachment.data.rewind
|
|
98
|
+
rescue NoMethodError
|
|
99
|
+
# do nothing, attachment.data is not a stream
|
|
100
|
+
end
|
|
101
|
+
return {
|
|
102
|
+
mime_type: attachment.mime_type,
|
|
103
|
+
base64: Base64.strict_encode64(attachment.read)
|
|
104
|
+
}
|
|
105
|
+
end
|
|
106
|
+
nil
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def get_file_attachments(attachments, _used_files)
|
|
110
|
+
attachments.select do |attachment|
|
|
111
|
+
!attachment.inline? &&
|
|
112
|
+
!attachment.has_content_id? &&
|
|
113
|
+
attachment.mime_type != 'message/rfc822'
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def get_mail_attachments(attachments)
|
|
118
|
+
attachments.select do |attachment|
|
|
119
|
+
!attachment.inline? &&
|
|
120
|
+
!attachment.has_content_id? &&
|
|
121
|
+
attachment.mime_type == 'message/rfc822'
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def get_attachment_info(attachment)
|
|
126
|
+
if attachment.mime_type == 'message/rfc822'
|
|
127
|
+
sub_msg = Mail.new(attachment.body.to_s)
|
|
128
|
+
subject = sub_msg.subject.to_s.strip
|
|
129
|
+
|
|
130
|
+
{
|
|
131
|
+
embedded_msg: sub_msg,
|
|
132
|
+
filename: subject
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
elsif attachment.filename
|
|
136
|
+
|
|
137
|
+
{
|
|
138
|
+
data: attachment.decoded,
|
|
139
|
+
filename: attachment.filename
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
else
|
|
143
|
+
{
|
|
144
|
+
filename: attachment.content_id.to_s
|
|
145
|
+
}
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
private
|
|
150
|
+
|
|
151
|
+
def find_hdr(list, key)
|
|
152
|
+
hdr = list.find { |x| x.name.to_s =~ /^#{key}$/i }
|
|
153
|
+
return nil unless hdr
|
|
154
|
+
|
|
155
|
+
field = hdr.field
|
|
156
|
+
return hdr.value unless field
|
|
157
|
+
|
|
158
|
+
return field.decoded unless field.is_a? Mail::CommonDateField
|
|
159
|
+
|
|
160
|
+
return field.date_time.to_time.localtime if field.respond_to?(:date_time)
|
|
161
|
+
|
|
162
|
+
DateTime.parse(field.decoded).to_time.localtime
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
# rubocop:enable Style/Documentation, Metrics/*
|
|
@@ -0,0 +1,447 @@
|
|
|
1
|
+
# rubocop:disable Style/Documentation, Metrics/*
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require 'base64'
|
|
5
|
+
require 'cgi'
|
|
6
|
+
require 'pdfkit'
|
|
7
|
+
require 'time'
|
|
8
|
+
require 'fileutils'
|
|
9
|
+
require 'pathname'
|
|
10
|
+
require 'libis/format/config'
|
|
11
|
+
|
|
12
|
+
module Libis
|
|
13
|
+
module Format
|
|
14
|
+
module Tool
|
|
15
|
+
HEADER_STYLE = <<~HTML
|
|
16
|
+
<style>
|
|
17
|
+
.header-table {
|
|
18
|
+
margin: 0 0 10px 0;
|
|
19
|
+
padding: 0;
|
|
20
|
+
font-family: Arial, Helvetica, sans-serif;
|
|
21
|
+
}
|
|
22
|
+
.header-table table {
|
|
23
|
+
width: 100%;
|
|
24
|
+
}
|
|
25
|
+
.header-name {
|
|
26
|
+
padding-right: 5px;
|
|
27
|
+
color: #9E9E9E;
|
|
28
|
+
text-align: right;
|
|
29
|
+
vertical-align: top;
|
|
30
|
+
font-size: 12px;
|
|
31
|
+
}
|
|
32
|
+
.header-value {
|
|
33
|
+
font-size: 12px;
|
|
34
|
+
width: 99%;
|
|
35
|
+
}
|
|
36
|
+
.header_fields {
|
|
37
|
+
background: white;
|
|
38
|
+
margin: 0;
|
|
39
|
+
border: 1px solid #DDD;
|
|
40
|
+
border-radius: 3px;
|
|
41
|
+
padding: 8px;
|
|
42
|
+
box-sizing: border-box;
|
|
43
|
+
}
|
|
44
|
+
</style>
|
|
45
|
+
HTML
|
|
46
|
+
|
|
47
|
+
HEADER_TABLE_TEMPLATE = <<~HTML
|
|
48
|
+
<div class="header-table">
|
|
49
|
+
<table class="header_fields">
|
|
50
|
+
<tbody>
|
|
51
|
+
%s
|
|
52
|
+
</tbody>
|
|
53
|
+
</table>
|
|
54
|
+
</div>
|
|
55
|
+
HTML
|
|
56
|
+
|
|
57
|
+
HEADER_FIELD_TEMPLATE = <<~HTML
|
|
58
|
+
<tr>
|
|
59
|
+
<td class="header-name">%s</td>
|
|
60
|
+
<td class="header-value">%s</td>
|
|
61
|
+
</tr>
|
|
62
|
+
HTML
|
|
63
|
+
|
|
64
|
+
HTML_WRAPPER_TEMPLATE = <<~HTML
|
|
65
|
+
<!DOCTYPE html>
|
|
66
|
+
<html>
|
|
67
|
+
<head>
|
|
68
|
+
<style>
|
|
69
|
+
body {
|
|
70
|
+
font-size: 12px;
|
|
71
|
+
}
|
|
72
|
+
</style>
|
|
73
|
+
<title>title</title>
|
|
74
|
+
</head>
|
|
75
|
+
<body>
|
|
76
|
+
<pre>
|
|
77
|
+
%s
|
|
78
|
+
</pre>
|
|
79
|
+
</body>
|
|
80
|
+
</html>
|
|
81
|
+
HTML
|
|
82
|
+
|
|
83
|
+
HTML_BODY_TEMPLATE = <<~HTML
|
|
84
|
+
<!DOCTYPE html>
|
|
85
|
+
<html>
|
|
86
|
+
<head>
|
|
87
|
+
<style>
|
|
88
|
+
body {
|
|
89
|
+
font-size: 12px;
|
|
90
|
+
}
|
|
91
|
+
</style>
|
|
92
|
+
<title>title</title>
|
|
93
|
+
</head>
|
|
94
|
+
<body>
|
|
95
|
+
%s
|
|
96
|
+
</body>
|
|
97
|
+
</html>
|
|
98
|
+
HTML
|
|
99
|
+
|
|
100
|
+
ATTACHMENT_STYLE = <<~HTML
|
|
101
|
+
<style>
|
|
102
|
+
.attachment-list {
|
|
103
|
+
border: 1px solid #DDD;
|
|
104
|
+
margin: 0 0 10px 0;
|
|
105
|
+
padding: 0;
|
|
106
|
+
font-family: Arial, Helvetica, sans-serif;
|
|
107
|
+
}
|
|
108
|
+
.attachment-list ul {
|
|
109
|
+
list-style: disclosure-closed;
|
|
110
|
+
}
|
|
111
|
+
.attachment-list li {
|
|
112
|
+
font-size: 12px;
|
|
113
|
+
padding-left: 1em;
|
|
114
|
+
}
|
|
115
|
+
</style>
|
|
116
|
+
HTML
|
|
117
|
+
|
|
118
|
+
ATTACHMENT_LIST_TEMPLATE = <<~HTML
|
|
119
|
+
<div class="attachment-list">
|
|
120
|
+
<ul>
|
|
121
|
+
%s
|
|
122
|
+
</ul>
|
|
123
|
+
</div>
|
|
124
|
+
HTML
|
|
125
|
+
|
|
126
|
+
HTML_DOCTYPE_TEMPLATE = '<!DOCTYPE html>%s'
|
|
127
|
+
ATTACHMENT_ITEM_TEMPLATE = '<li>%s</li>'
|
|
128
|
+
|
|
129
|
+
IMG_CID_PLAIN_REGEX = /\[cid:(.*?)\]/im
|
|
130
|
+
IMG_CID_HTML_REGEX = /cid:([^"]*)/im
|
|
131
|
+
|
|
132
|
+
class MailToPdf
|
|
133
|
+
include ::Libis::Tools::Logger
|
|
134
|
+
|
|
135
|
+
def self.installed?
|
|
136
|
+
File.exist?(Libis::Format::Config[:wkhtmltopdf])
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def self.run(source, target, **options)
|
|
140
|
+
new.run source, target, **options
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def run(source, target, **options)
|
|
144
|
+
# Preliminary checks
|
|
145
|
+
@warnings = []
|
|
146
|
+
|
|
147
|
+
# PDF creation options
|
|
148
|
+
@pdf_options = {
|
|
149
|
+
page_size: 'A4',
|
|
150
|
+
margin_top: '10mm',
|
|
151
|
+
margin_bottom: '10mm',
|
|
152
|
+
margin_left: '10mm',
|
|
153
|
+
margin_right: '10mm',
|
|
154
|
+
# image_quality: 100,
|
|
155
|
+
# viewport_size: '2480x3508',
|
|
156
|
+
dpi: 300
|
|
157
|
+
}.merge options
|
|
158
|
+
|
|
159
|
+
# Check if source file exists
|
|
160
|
+
raise "File #{source} does not exist" unless File.exist?(source)
|
|
161
|
+
|
|
162
|
+
# Open the email
|
|
163
|
+
email = open_email(source)
|
|
164
|
+
|
|
165
|
+
# Convert the email message to PDF
|
|
166
|
+
result = email_to_pdf(email, target, root_msg: true)
|
|
167
|
+
|
|
168
|
+
# Close email message
|
|
169
|
+
close_email(email)
|
|
170
|
+
|
|
171
|
+
result
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
protected
|
|
175
|
+
|
|
176
|
+
def email_to_pdf(msg, target, root_msg: false)
|
|
177
|
+
# Make sure the target directory exists
|
|
178
|
+
outdir = File.dirname(target)
|
|
179
|
+
FileUtils.mkdir_p(outdir)
|
|
180
|
+
|
|
181
|
+
# Process the message body
|
|
182
|
+
# ------------------------
|
|
183
|
+
body = get_body(msg)
|
|
184
|
+
|
|
185
|
+
# Process headers
|
|
186
|
+
# ---------------
|
|
187
|
+
headers, headers_html = get_headers(msg)
|
|
188
|
+
|
|
189
|
+
# Add header section to the HTML body
|
|
190
|
+
body = add_headers_to_body(body, headers_html)
|
|
191
|
+
|
|
192
|
+
# Embed inline images
|
|
193
|
+
# -------------------
|
|
194
|
+
attachments = msg.attachments
|
|
195
|
+
used_files = embed_inline_attachments(body, attachments)
|
|
196
|
+
|
|
197
|
+
# Save other attachments
|
|
198
|
+
# ----------------------
|
|
199
|
+
attachments_dir = "#{target}.attachments"
|
|
200
|
+
|
|
201
|
+
files = save_attachments(attachments, attachments_dir, used_files)
|
|
202
|
+
|
|
203
|
+
# Add attachment section to the HTML body
|
|
204
|
+
body = add_attachments_to_body(body, files, attachments_dir)
|
|
205
|
+
|
|
206
|
+
# Save the HTML body as a .html file next to the PDF for debugging purposes
|
|
207
|
+
File.open("#{target}.html", 'wb') { |f| f.write(body) }
|
|
208
|
+
|
|
209
|
+
# Create PDF
|
|
210
|
+
# ----------
|
|
211
|
+
write_target_file(body, get_subject(msg), target)
|
|
212
|
+
|
|
213
|
+
files = [target] + files if File.exist?(target)
|
|
214
|
+
|
|
215
|
+
if root_msg
|
|
216
|
+
p = Pathname(File.dirname(files.first))
|
|
217
|
+
files.drop(1).each do |f|
|
|
218
|
+
(headers[:attachments] ||= []) << Pathname.new(f).relative_path_from(p).to_s
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
{
|
|
223
|
+
command: { status: 0 },
|
|
224
|
+
files:,
|
|
225
|
+
headers:,
|
|
226
|
+
warnings: @warnings
|
|
227
|
+
}
|
|
228
|
+
rescue StandardError => e
|
|
229
|
+
raise unless root_msg
|
|
230
|
+
|
|
231
|
+
close_email(msg) if msg
|
|
232
|
+
{
|
|
233
|
+
command: { status: -1 },
|
|
234
|
+
files: [],
|
|
235
|
+
headers: {},
|
|
236
|
+
errors: [
|
|
237
|
+
{
|
|
238
|
+
error: e.message,
|
|
239
|
+
error_class: e.class.name,
|
|
240
|
+
error_trace: e.backtrace
|
|
241
|
+
}
|
|
242
|
+
],
|
|
243
|
+
warnings: @warnings
|
|
244
|
+
}
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
def get_body(msg)
|
|
248
|
+
body = get_body_html(msg)
|
|
249
|
+
|
|
250
|
+
body = HTML_BODY_TEMPLATE % body unless /<body[^>]*>/i.match?(body)
|
|
251
|
+
body = HTML_DOCTYPE_TEMPLATE % body unless /<!DOCTYPE html/i.match?(body)
|
|
252
|
+
body.sub!(%r{<title>title</title>}, "<title>#{get_subject(msg)}</title>")
|
|
253
|
+
|
|
254
|
+
body
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
def add_headers_to_body(body, headers_html)
|
|
258
|
+
encoding = body.encoding
|
|
259
|
+
return body if headers_html.empty?
|
|
260
|
+
|
|
261
|
+
b = body.downcase
|
|
262
|
+
|
|
263
|
+
# Insert header block styles
|
|
264
|
+
if b.include?('</head>')
|
|
265
|
+
# if head exists, append the style block
|
|
266
|
+
body.gsub!(%r{</head>}i, "#{HEADER_STYLE}</head>")
|
|
267
|
+
elsif b.include?('<head/>')
|
|
268
|
+
# empty head, replace with the style block
|
|
269
|
+
body.gsub!(%r{<head/>}i, "<head>#{HEADER_STYLE}</head>")
|
|
270
|
+
else
|
|
271
|
+
# otherwise insert a head section before the body tag
|
|
272
|
+
body.gsub!(/<body/i, "<head>#{HEADER_STYLE}</head><body")
|
|
273
|
+
end
|
|
274
|
+
# Add the headers html table as first element in the body section
|
|
275
|
+
body.gsub!(/<body[^>]*>/i) { |m| "#{m}#{HEADER_TABLE_TEMPLATE % headers_html.encode(encoding)}" }
|
|
276
|
+
body
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
def hdr_html(key, value)
|
|
280
|
+
if key.is_a?(String) && value.is_a?(String) && !value.empty?
|
|
281
|
+
return format(HEADER_FIELD_TEMPLATE, key,
|
|
282
|
+
CGI.escapeHTML(value))
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
''
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
def embed_inline_attachments(body, attachments)
|
|
289
|
+
used_files = []
|
|
290
|
+
|
|
291
|
+
# First process plaintext cid entries
|
|
292
|
+
body.gsub!(IMG_CID_PLAIN_REGEX) do |_match|
|
|
293
|
+
data = get_inline_attachment_data(attachments, ::Regexp.last_match(1))
|
|
294
|
+
if data
|
|
295
|
+
used_files << ::Regexp.last_match(1)
|
|
296
|
+
"<img src=\"data:#{data[:mime_type]};base64,#{data[:base64]}\"/>"
|
|
297
|
+
else
|
|
298
|
+
'<img src=""/>'
|
|
299
|
+
end
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
# Then process HTML img tags with CID entries
|
|
303
|
+
body.gsub!(IMG_CID_HTML_REGEX) do |_match|
|
|
304
|
+
data = get_inline_attachment_data(attachments, ::Regexp.last_match(1))
|
|
305
|
+
if data
|
|
306
|
+
used_files << ::Regexp.last_match(1)
|
|
307
|
+
"data:#{data[:mime_type]};base64,#{data[:base64]}"
|
|
308
|
+
else
|
|
309
|
+
''
|
|
310
|
+
end
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
used_files
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
def save_attachments(attachments, outdir, used_files)
|
|
317
|
+
files = []
|
|
318
|
+
|
|
319
|
+
digits = ((attachments.count + 1) / 10) + 1
|
|
320
|
+
i = 1
|
|
321
|
+
|
|
322
|
+
get_attachments(attachments, used_files).each do |attachment|
|
|
323
|
+
prefix = "#{format('%0*d', digits, i)}-"
|
|
324
|
+
|
|
325
|
+
info = get_attachment_info(attachment)
|
|
326
|
+
|
|
327
|
+
if info[:embedded_msg]
|
|
328
|
+
sub_msg = info[:embedded_msg]
|
|
329
|
+
file = File.join(outdir, "#{prefix}#{info[:filename].tr('/', '_')}.msg.pdf")
|
|
330
|
+
|
|
331
|
+
result = email_to_pdf(sub_msg, file, root_msg: false)
|
|
332
|
+
|
|
333
|
+
if (e = result[:error])
|
|
334
|
+
raise e
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
files += result[:files]
|
|
338
|
+
elsif info[:data]
|
|
339
|
+
file = File.join(outdir, "#{prefix}#{info[:filename].tr('/', '_')}")
|
|
340
|
+
FileUtils.mkdir_p(File.dirname(file))
|
|
341
|
+
File.open(file, 'wb') { |f| f.write(info[:data]) }
|
|
342
|
+
files << file
|
|
343
|
+
else
|
|
344
|
+
@warnings << "Attachment #{info[:filename]} cannot be extracted"
|
|
345
|
+
next
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
i += 1
|
|
349
|
+
end
|
|
350
|
+
files
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
def add_attachments_to_body(body, files, attachments_dir)
|
|
354
|
+
return body if files.empty?
|
|
355
|
+
|
|
356
|
+
b = body.downcase
|
|
357
|
+
|
|
358
|
+
# Insert attachment block styles
|
|
359
|
+
if b.include?('</head>')
|
|
360
|
+
# if head exists, append the style block
|
|
361
|
+
body.gsub!(%r{</head>}i, "#{ATTACHMENT_STYLE}</head>")
|
|
362
|
+
elsif b.include?('<head/>')
|
|
363
|
+
# empty head, replace with the style block
|
|
364
|
+
body.gsub!(%r{<head/>}i, "<head>#{ATTACHMENT_STYLE}</head>")
|
|
365
|
+
else
|
|
366
|
+
# otherwise insert a head section before the body tag
|
|
367
|
+
body.gsub!(/<body/i, "<head>#{ATTACHMENT_STYLE}</head><body")
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
# Filter files to only include those that are in the attachments directory
|
|
371
|
+
# and map them to relative paths from the attachments directory
|
|
372
|
+
items = files.filter_map do |f|
|
|
373
|
+
Pathname.new(f).relative_path_from(Pathname.new(attachments_dir)).to_s if File.dirname(f) == attachments_dir
|
|
374
|
+
end
|
|
375
|
+
|
|
376
|
+
# Create the attachment item HTML
|
|
377
|
+
items = items.map do |f|
|
|
378
|
+
format(ATTACHMENT_ITEM_TEMPLATE, f, File.basename(f))
|
|
379
|
+
end.join("\n")
|
|
380
|
+
|
|
381
|
+
# Create the attachment list HTML
|
|
382
|
+
attachments_html = ATTACHMENT_LIST_TEMPLATE % items
|
|
383
|
+
|
|
384
|
+
# make sure the attachments_html is encoded in the same encoding as the body
|
|
385
|
+
attachments_html = attachments_html.encode(body.encoding)
|
|
386
|
+
|
|
387
|
+
# Add the attachments html list after the headers
|
|
388
|
+
# if there are no headers, then add it at the beginning of the body section
|
|
389
|
+
body.sub!(%r{<div class="header-table">.*?</div>}im) { |m| "#{m}#{attachments_html}" } ||
|
|
390
|
+
body.gsub!(/<body[^>]*>/i) { |m| "#{m}#{attachments_html}" }
|
|
391
|
+
|
|
392
|
+
body
|
|
393
|
+
end
|
|
394
|
+
|
|
395
|
+
def get_attachments(attachments, used_files)
|
|
396
|
+
get_file_attachments(attachments, used_files) + get_mail_attachments(attachments)
|
|
397
|
+
end
|
|
398
|
+
|
|
399
|
+
def write_target_file(body, title, target)
|
|
400
|
+
kit = PDFKit.new(body, title: title || 'message', **@pdf_options)
|
|
401
|
+
pdf = kit.to_pdf
|
|
402
|
+
File.open(target, 'wb') { |f| f.write(pdf) }
|
|
403
|
+
end
|
|
404
|
+
|
|
405
|
+
# ---------------------------------------
|
|
406
|
+
# Methods to be implemented by subclasses
|
|
407
|
+
# ---------------------------------------
|
|
408
|
+
def open_email(_source)
|
|
409
|
+
raise NotImplementedError, 'Subclasses must implement the open_email method'
|
|
410
|
+
end
|
|
411
|
+
|
|
412
|
+
def close_email(_email)
|
|
413
|
+
raise NotImplementedError, 'Subclasses must implement the close_email method'
|
|
414
|
+
end
|
|
415
|
+
|
|
416
|
+
def get_body_html(_msg)
|
|
417
|
+
raise NotImplementedError, 'Subclasses must implement the get_body_html method'
|
|
418
|
+
end
|
|
419
|
+
|
|
420
|
+
def get_subject(_msg)
|
|
421
|
+
raise NotImplementedError, 'Subclasses must implement the get_subject method'
|
|
422
|
+
end
|
|
423
|
+
|
|
424
|
+
def get_headers(_msg)
|
|
425
|
+
raise NotImplementedError, 'Subclasses must implement the get_headers method'
|
|
426
|
+
end
|
|
427
|
+
|
|
428
|
+
def get_inline_attachment_data(_attachments, _cid)
|
|
429
|
+
raise NotImplementedError, 'Subclasses must implement the get_inline_attachment_data method'
|
|
430
|
+
end
|
|
431
|
+
|
|
432
|
+
def get_file_attachments(_attachments, _used_files)
|
|
433
|
+
raise NotImplementedError, 'Subclasses must implement the get_file_attachments method'
|
|
434
|
+
end
|
|
435
|
+
|
|
436
|
+
def get_mail_attachments(_attachments)
|
|
437
|
+
raise NotImplementedError, 'Subclasses must implement the get_mail_attachments method'
|
|
438
|
+
end
|
|
439
|
+
|
|
440
|
+
def get_attachment_info(_attachment)
|
|
441
|
+
raise NotImplementedError, 'Subclasses must implement the get_attachment_info method'
|
|
442
|
+
end
|
|
443
|
+
end
|
|
444
|
+
end
|
|
445
|
+
end
|
|
446
|
+
end
|
|
447
|
+
# rubocop:enable Style/Documentation, Metrics/*
|
|
@@ -1,270 +1,154 @@
|
|
|
1
|
+
# rubocop:disable Style/Documentation, Metrics/*
|
|
1
2
|
# frozen_string_literal: true
|
|
2
3
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
require '
|
|
6
|
-
require '
|
|
7
|
-
require 'time'
|
|
8
|
-
require 'fileutils'
|
|
9
|
-
require 'pathname'
|
|
10
|
-
require 'libis/format/config'
|
|
4
|
+
require_relative 'mail_to_pdf'
|
|
5
|
+
|
|
6
|
+
require 'msg_extractor'
|
|
7
|
+
require 'word_wrap'
|
|
11
8
|
|
|
12
9
|
module Libis
|
|
13
10
|
module Format
|
|
14
11
|
module Tool
|
|
15
|
-
class MsgToPdf
|
|
16
|
-
|
|
12
|
+
class MsgToPdf < MailToPdf
|
|
13
|
+
protected
|
|
17
14
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
HEADER_FIELD_TEMPLATE = '<tr><td class="header-name">%s</td><td class="header-value">%s</td></tr>'
|
|
21
|
-
HTML_WRAPPER_TEMPLATE = '<!DOCTYPE html><html><head><style>body {font-size: 0.5cm;}</style><title>title</title></head><body>%s</body></html>' # rubocop:disable Layout/LineLength
|
|
15
|
+
def open_email(source)
|
|
16
|
+
msg = nil
|
|
22
17
|
|
|
23
|
-
|
|
24
|
-
|
|
18
|
+
# Open the message
|
|
19
|
+
msg = MsgExtractor.open(source)
|
|
25
20
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
21
|
+
unless msg.is_a?(MsgExtractor::Message)
|
|
22
|
+
raise "File #{File.basename(source)} is not an Outlook message but a #{msg.class.name}"
|
|
23
|
+
end
|
|
29
24
|
|
|
30
|
-
|
|
31
|
-
|
|
25
|
+
msg
|
|
26
|
+
rescue StandardError => e
|
|
27
|
+
raise "Failed to open message: #{e.message}"
|
|
32
28
|
end
|
|
33
29
|
|
|
34
|
-
def
|
|
35
|
-
|
|
36
|
-
# ------------------
|
|
37
|
-
|
|
38
|
-
@warnings = []
|
|
39
|
-
|
|
40
|
-
# Check if source file exists
|
|
41
|
-
raise "File #{source} does not exist" unless File.exist?(source)
|
|
42
|
-
|
|
43
|
-
# Retrieving the message
|
|
44
|
-
# ----------------------
|
|
45
|
-
|
|
46
|
-
# Open the message
|
|
47
|
-
msg = Mapi::Msg.open(source)
|
|
48
|
-
|
|
49
|
-
target_format = options.delete(:to_html) ? :HTML : :PDF
|
|
50
|
-
result = msg_to_pdf(msg, target, target_format, options)
|
|
51
|
-
msg.close
|
|
52
|
-
result
|
|
30
|
+
def close_email(_msg)
|
|
31
|
+
true
|
|
53
32
|
end
|
|
54
33
|
|
|
55
|
-
def
|
|
56
|
-
# Make sure the target directory exists
|
|
57
|
-
outdir = File.dirname(target)
|
|
58
|
-
FileUtils.mkdir_p(outdir)
|
|
59
|
-
|
|
34
|
+
def get_body_html(msg)
|
|
60
35
|
# Get the body of the message in HTML
|
|
61
|
-
body = msg.
|
|
36
|
+
body = msg.html_body
|
|
62
37
|
|
|
63
38
|
# Embed plain body in HTML as a fallback
|
|
64
|
-
body ||= HTML_WRAPPER_TEMPLATE % msg.
|
|
65
|
-
|
|
66
|
-
#
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
39
|
+
body ||= HTML_WRAPPER_TEMPLATE % WordWrap.ww(msg.body, 120, false)
|
|
40
|
+
|
|
41
|
+
# Worst case, just create empty body
|
|
42
|
+
body ||= HTML_WRAPPER_TEMPLATE % ''
|
|
43
|
+
|
|
44
|
+
# # Check and fix the character encoding
|
|
45
|
+
# begin
|
|
46
|
+
# # Try to encode into UTF-8
|
|
47
|
+
# body.encode!('UTF-8', universal_newline: true)
|
|
48
|
+
# rescue Encoding::InvalidByteSequenceError, Encoding::UndefinedConversionError
|
|
49
|
+
# begin
|
|
50
|
+
# # If it fails, the text may be in Windows' Latin1 (ISO-8859-1)
|
|
51
|
+
# body.force_encoding('ISO-8859-1').encode!('UTF-8', universal_newline: true)
|
|
52
|
+
# rescue Encoding::InvalidByteSequenceError, Encoding::UndefinedConversionError => e
|
|
53
|
+
# # If that fails too, log a warning and replace the invalid/unknown with a ? character.
|
|
54
|
+
# @warnings << "#{e.class}: #{e.message}"
|
|
55
|
+
# body.encode!('UTF-8', universal_newline: true, invalid: :replace, undef: :replace)
|
|
56
|
+
# end
|
|
57
|
+
# end
|
|
58
|
+
|
|
59
|
+
body
|
|
60
|
+
end
|
|
80
61
|
|
|
81
|
-
|
|
82
|
-
|
|
62
|
+
def get_subject(msg)
|
|
63
|
+
msg.subject || ''
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def get_headers(msg)
|
|
83
67
|
headers = {}
|
|
84
|
-
|
|
68
|
+
html = ''
|
|
85
69
|
|
|
86
70
|
%w[From To Cc Subject Date].each do |key|
|
|
87
|
-
value = find_hdr(msg
|
|
88
|
-
|
|
89
|
-
headers[key.downcase.to_sym] = value
|
|
90
|
-
hdr_html += hdr_html(key, value)
|
|
91
|
-
end
|
|
92
|
-
end
|
|
93
|
-
|
|
94
|
-
[:date].each do |key|
|
|
95
|
-
next unless headers[key]
|
|
96
|
-
|
|
97
|
-
headers[key] = DateTime.parse(headers[key]).to_time.localtime.iso8601
|
|
98
|
-
end
|
|
71
|
+
value = find_hdr(msg, key)
|
|
72
|
+
next unless value
|
|
99
73
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
if body =~ %r{</head>}
|
|
104
|
-
# if head exists, append the style block
|
|
105
|
-
body.gsub!(%r{</head>}, "#{HEADER_STYLE}</head>")
|
|
106
|
-
elsif body =~ %r{<head/>}
|
|
107
|
-
# empty head, replace with the style block
|
|
108
|
-
body.gsub!(%r{<head/>}, "<head>#{HEADER_STYLE}</head>")
|
|
74
|
+
if key.casecmp('Date').zero? && value.is_a?(Time)
|
|
75
|
+
headers[key.downcase.to_sym] = value.iso8601
|
|
76
|
+
html += hdr_html(key, value.rfc2822)
|
|
109
77
|
else
|
|
110
|
-
|
|
111
|
-
|
|
78
|
+
headers[key.downcase.to_sym] = value
|
|
79
|
+
html += hdr_html(key, value)
|
|
112
80
|
end
|
|
113
|
-
# Add the headers html table as first element in the body section
|
|
114
|
-
body.gsub!(/<body[^>]*>/) { |m| "#{m}#{HEADER_TABLE_TEMPLATE % hdr_html}" }
|
|
115
81
|
end
|
|
116
82
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
attachments = msg.attachments
|
|
120
|
-
used_files = []
|
|
121
|
-
|
|
122
|
-
# First process plaintext cid entries
|
|
123
|
-
body.gsub!(IMG_CID_PLAIN_REGEX) do |_match|
|
|
124
|
-
data = get_attachment_data(attachments, ::Regexp.last_match(1))
|
|
125
|
-
if data
|
|
126
|
-
used_files << ::Regexp.last_match(1)
|
|
127
|
-
"<img src=\"data:#{data[:mime_type]};base64,#{data[:base64]}\"/>"
|
|
128
|
-
else
|
|
129
|
-
'<img src=""/>'
|
|
130
|
-
end
|
|
131
|
-
end
|
|
83
|
+
[headers, html]
|
|
84
|
+
end
|
|
132
85
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
if data
|
|
137
|
-
used_files << ::Regexp.last_match(1)
|
|
138
|
-
"data:#{data[:mime_type]};base64,#{data[:base64]}"
|
|
139
|
-
else
|
|
140
|
-
''
|
|
141
|
-
end
|
|
142
|
-
end
|
|
86
|
+
def get_inline_attachment_data(attachments, cid)
|
|
87
|
+
attachments.each do |attachment|
|
|
88
|
+
next unless attachment.content_id == cid
|
|
143
89
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
if target_format == :PDF
|
|
149
|
-
# PDF creation options
|
|
150
|
-
pdf_options = {
|
|
151
|
-
page_size: 'A4',
|
|
152
|
-
margin_top: '10mm',
|
|
153
|
-
margin_bottom: '10mm',
|
|
154
|
-
margin_left: '10mm',
|
|
155
|
-
margin_right: '10mm',
|
|
156
|
-
# image_quality: 100,
|
|
157
|
-
# viewport_size: '2480x3508',
|
|
158
|
-
dpi: 300
|
|
159
|
-
}.merge pdf_options
|
|
160
|
-
|
|
161
|
-
subject = find_hdr(msg.headers, 'Subject')
|
|
162
|
-
kit = PDFKit.new(body, title: (subject || 'message'), **pdf_options)
|
|
163
|
-
pdf = kit.to_pdf
|
|
164
|
-
File.open(target, 'wb') { |f| f.write(pdf) }
|
|
165
|
-
else
|
|
166
|
-
File.open(target, 'wb') { |f| f.write(body) }
|
|
167
|
-
end
|
|
168
|
-
files << target if File.exist?(target)
|
|
169
|
-
|
|
170
|
-
# Save attachments
|
|
171
|
-
# ----------------
|
|
172
|
-
outdir = File.join(outdir, "#{File.basename(target)}.attachments")
|
|
173
|
-
digits = ((attachments.count + 1) / 10) + 1
|
|
174
|
-
i = 1
|
|
175
|
-
attachments.delete_if { |a| a.properties.attachment_hidden }.each do |a|
|
|
176
|
-
prefix = "#{format('%0*d', digits, i)}-"
|
|
177
|
-
if (sub_msg = a.instance_variable_get(:@embedded_msg))
|
|
178
|
-
subject = a.properties[:display_name] || sub_msg.subject || ''
|
|
179
|
-
file = File.join(outdir, "#{prefix}#{subject.gsub('/', '_')}.msg.#{target_format.to_s.downcase}")
|
|
180
|
-
result = msg_to_pdf(sub_msg, file, target_format, pdf_options, root_msg: false)
|
|
181
|
-
if (e = result[:error])
|
|
182
|
-
raise e
|
|
183
|
-
end
|
|
184
|
-
|
|
185
|
-
files += result[:files]
|
|
186
|
-
elsif a.filename
|
|
187
|
-
next if used_files.include?(a.filename)
|
|
188
|
-
|
|
189
|
-
file = File.join(outdir, "#{prefix}#{a.filename.gsub('/', '_')}")
|
|
190
|
-
FileUtils.mkdir_p(File.dirname(file))
|
|
191
|
-
File.open(file, 'wb') { |f| a.save(f) }
|
|
192
|
-
files << file
|
|
193
|
-
else
|
|
194
|
-
@warnings << "Attachment #{a.properties[:display_name]} cannot be extracted"
|
|
195
|
-
next
|
|
196
|
-
end
|
|
197
|
-
i += 1
|
|
90
|
+
return {
|
|
91
|
+
mime_type: attachment.mime_type,
|
|
92
|
+
base64: Base64.encode64(attachment.data).gsub(/[\r\n]/, '')
|
|
93
|
+
}
|
|
198
94
|
end
|
|
95
|
+
nil
|
|
96
|
+
end
|
|
199
97
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
(headers[:attachments] ||= []) << Pathname.new(f).relative_path_from(p).to_s
|
|
204
|
-
end
|
|
98
|
+
def get_file_attachments(attachments, _used_files)
|
|
99
|
+
attachments.select do |attachment|
|
|
100
|
+
!attachment.content_id && !attachment.embedded_message? && attachment.filename
|
|
205
101
|
end
|
|
102
|
+
end
|
|
206
103
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
files:,
|
|
210
|
-
headers:,
|
|
211
|
-
warnings: @warnings
|
|
212
|
-
}
|
|
213
|
-
rescue Exception => e
|
|
214
|
-
raise unless root_msg
|
|
215
|
-
|
|
216
|
-
msg.close
|
|
217
|
-
{
|
|
218
|
-
command: { status: -1 },
|
|
219
|
-
files: [],
|
|
220
|
-
headers: {},
|
|
221
|
-
errors: [
|
|
222
|
-
{
|
|
223
|
-
error: e.message,
|
|
224
|
-
error_class: e.class.name,
|
|
225
|
-
error_trace: e.backtrace
|
|
226
|
-
}
|
|
227
|
-
],
|
|
228
|
-
warnings: @warnings
|
|
229
|
-
}
|
|
104
|
+
def get_mail_attachments(attachments)
|
|
105
|
+
attachments.select(&:embedded_message?)
|
|
230
106
|
end
|
|
231
107
|
|
|
232
|
-
|
|
108
|
+
def get_attachment_info(attachment)
|
|
109
|
+
if attachment.embedded_message?
|
|
233
110
|
|
|
234
|
-
|
|
111
|
+
{
|
|
112
|
+
embedded_msg: attachment.data,
|
|
113
|
+
filename: attachment.message&.subject || 'email'
|
|
114
|
+
}
|
|
235
115
|
|
|
236
|
-
|
|
116
|
+
elsif attachment.filename
|
|
237
117
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
118
|
+
{
|
|
119
|
+
data: attachment.data,
|
|
120
|
+
filename: attachment.filename
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
else
|
|
124
|
+
{
|
|
125
|
+
filename: attachment.mime_type || 'unknown'
|
|
126
|
+
}
|
|
245
127
|
end
|
|
246
|
-
nil
|
|
247
128
|
end
|
|
248
129
|
|
|
249
|
-
|
|
250
|
-
return format(HEADER_FIELD_TEMPLATE, key, CGI.escapeHTML(value)) if key.is_a?(String) && value.is_a?(String) && !value.empty?
|
|
130
|
+
private
|
|
251
131
|
|
|
252
|
-
|
|
253
|
-
|
|
132
|
+
def find_hdr(msg, key)
|
|
133
|
+
value = if key.casecmp('From').zero?
|
|
134
|
+
msg.sender
|
|
135
|
+
else
|
|
136
|
+
msg.send(key.downcase.to_sym)
|
|
137
|
+
end
|
|
254
138
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
139
|
+
if value.is_a?(Array)
|
|
140
|
+
value.compact.empty? ? nil : value.compact.map(&:to_s).join(', ')
|
|
141
|
+
|
|
142
|
+
elsif value.is_a?(Time)
|
|
143
|
+
value.localtime
|
|
144
|
+
|
|
145
|
+
else
|
|
146
|
+
value.to_s
|
|
258
147
|
|
|
259
|
-
attachment.data.rewind
|
|
260
|
-
return {
|
|
261
|
-
mime_type: attachment.properties.attach_mime_tag,
|
|
262
|
-
base64: Base64.encode64(attachment.data.read).gsub(/[\r\n]/, '')
|
|
263
|
-
}
|
|
264
148
|
end
|
|
265
|
-
nil
|
|
266
149
|
end
|
|
267
150
|
end
|
|
268
151
|
end
|
|
269
152
|
end
|
|
270
153
|
end
|
|
154
|
+
# rubocop:enable Style/Documentation, Metrics/*
|
data/lib/libis/format/version.rb
CHANGED
data/libis-format.gemspec
CHANGED
|
@@ -16,7 +16,7 @@ Gem::Specification.new do |spec|
|
|
|
16
16
|
spec.homepage = ''
|
|
17
17
|
spec.license = 'MIT'
|
|
18
18
|
|
|
19
|
-
spec.platform = Gem::Platform::JAVA if defined?(RUBY_ENGINE) && RUBY_ENGINE == 'jruby'
|
|
19
|
+
# spec.platform = Gem::Platform::JAVA if defined?(RUBY_ENGINE) && RUBY_ENGINE == 'jruby'
|
|
20
20
|
spec.required_ruby_version = '>= 3.2'
|
|
21
21
|
|
|
22
22
|
spec.files = `git ls-files -z`.split("\x0").select do |f|
|
|
@@ -27,14 +27,18 @@ Gem::Specification.new do |spec|
|
|
|
27
27
|
|
|
28
28
|
spec.add_runtime_dependency 'chromaprint', '~> 0.0.2'
|
|
29
29
|
spec.add_runtime_dependency 'deep_dive', '~> 0.3'
|
|
30
|
-
spec.add_runtime_dependency 'libis-mapi', '~> 0.3'
|
|
30
|
+
# spec.add_runtime_dependency 'libis-mapi', '~> 0.3'
|
|
31
31
|
spec.add_runtime_dependency 'libis-tools', '~> 1.1'
|
|
32
|
+
spec.add_runtime_dependency 'mail', '~> 2.9'
|
|
32
33
|
spec.add_runtime_dependency 'mini_magick', '~> 5.0.1'
|
|
34
|
+
spec.add_runtime_dependency 'msg_extractor', '~> 0.1'
|
|
33
35
|
spec.add_runtime_dependency 'naturally', '~> 2.2'
|
|
34
36
|
spec.add_runtime_dependency 'new_rfc_2047', '~> 1.0'
|
|
35
37
|
spec.add_runtime_dependency 'os', '~> 1.1'
|
|
36
38
|
spec.add_runtime_dependency 'pdfinfo', '~> 1.4'
|
|
37
39
|
spec.add_runtime_dependency 'pdfkit', '~> 0.8'
|
|
40
|
+
# spec.add_runtime_dependency 'ruby-msg', '~> 1.5.3.1'
|
|
41
|
+
spec.add_runtime_dependency 'word_wrap', '~> 1.0'
|
|
38
42
|
|
|
39
43
|
spec.add_development_dependency 'awesome_print'
|
|
40
44
|
spec.add_development_dependency 'equivalent-xml'
|
metadata
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: libis-format
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.3.
|
|
4
|
+
version: 1.3.8.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Kris Dekeyser
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: bin
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
13
|
name: chromaprint
|
|
@@ -39,33 +38,33 @@ dependencies:
|
|
|
39
38
|
- !ruby/object:Gem::Version
|
|
40
39
|
version: '0.3'
|
|
41
40
|
- !ruby/object:Gem::Dependency
|
|
42
|
-
name: libis-
|
|
41
|
+
name: libis-tools
|
|
43
42
|
requirement: !ruby/object:Gem::Requirement
|
|
44
43
|
requirements:
|
|
45
44
|
- - "~>"
|
|
46
45
|
- !ruby/object:Gem::Version
|
|
47
|
-
version: '
|
|
46
|
+
version: '1.1'
|
|
48
47
|
type: :runtime
|
|
49
48
|
prerelease: false
|
|
50
49
|
version_requirements: !ruby/object:Gem::Requirement
|
|
51
50
|
requirements:
|
|
52
51
|
- - "~>"
|
|
53
52
|
- !ruby/object:Gem::Version
|
|
54
|
-
version: '
|
|
53
|
+
version: '1.1'
|
|
55
54
|
- !ruby/object:Gem::Dependency
|
|
56
|
-
name:
|
|
55
|
+
name: mail
|
|
57
56
|
requirement: !ruby/object:Gem::Requirement
|
|
58
57
|
requirements:
|
|
59
58
|
- - "~>"
|
|
60
59
|
- !ruby/object:Gem::Version
|
|
61
|
-
version: '
|
|
60
|
+
version: '2.9'
|
|
62
61
|
type: :runtime
|
|
63
62
|
prerelease: false
|
|
64
63
|
version_requirements: !ruby/object:Gem::Requirement
|
|
65
64
|
requirements:
|
|
66
65
|
- - "~>"
|
|
67
66
|
- !ruby/object:Gem::Version
|
|
68
|
-
version: '
|
|
67
|
+
version: '2.9'
|
|
69
68
|
- !ruby/object:Gem::Dependency
|
|
70
69
|
name: mini_magick
|
|
71
70
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -80,6 +79,20 @@ dependencies:
|
|
|
80
79
|
- - "~>"
|
|
81
80
|
- !ruby/object:Gem::Version
|
|
82
81
|
version: 5.0.1
|
|
82
|
+
- !ruby/object:Gem::Dependency
|
|
83
|
+
name: msg_extractor
|
|
84
|
+
requirement: !ruby/object:Gem::Requirement
|
|
85
|
+
requirements:
|
|
86
|
+
- - "~>"
|
|
87
|
+
- !ruby/object:Gem::Version
|
|
88
|
+
version: '0.1'
|
|
89
|
+
type: :runtime
|
|
90
|
+
prerelease: false
|
|
91
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
92
|
+
requirements:
|
|
93
|
+
- - "~>"
|
|
94
|
+
- !ruby/object:Gem::Version
|
|
95
|
+
version: '0.1'
|
|
83
96
|
- !ruby/object:Gem::Dependency
|
|
84
97
|
name: naturally
|
|
85
98
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -150,6 +163,20 @@ dependencies:
|
|
|
150
163
|
- - "~>"
|
|
151
164
|
- !ruby/object:Gem::Version
|
|
152
165
|
version: '0.8'
|
|
166
|
+
- !ruby/object:Gem::Dependency
|
|
167
|
+
name: word_wrap
|
|
168
|
+
requirement: !ruby/object:Gem::Requirement
|
|
169
|
+
requirements:
|
|
170
|
+
- - "~>"
|
|
171
|
+
- !ruby/object:Gem::Version
|
|
172
|
+
version: '1.0'
|
|
173
|
+
type: :runtime
|
|
174
|
+
prerelease: false
|
|
175
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
176
|
+
requirements:
|
|
177
|
+
- - "~>"
|
|
178
|
+
- !ruby/object:Gem::Version
|
|
179
|
+
version: '1.0'
|
|
153
180
|
- !ruby/object:Gem::Dependency
|
|
154
181
|
name: awesome_print
|
|
155
182
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -273,12 +300,14 @@ files:
|
|
|
273
300
|
- lib/libis/format/identifier.rb
|
|
274
301
|
- lib/libis/format/tool.rb
|
|
275
302
|
- lib/libis/format/tool/droid.rb
|
|
303
|
+
- lib/libis/format/tool/eml_to_pdf.rb
|
|
276
304
|
- lib/libis/format/tool/extension_identification.rb
|
|
277
305
|
- lib/libis/format/tool/ff_mpeg.rb
|
|
278
306
|
- lib/libis/format/tool/fido.rb
|
|
279
307
|
- lib/libis/format/tool/file_tool.rb
|
|
280
308
|
- lib/libis/format/tool/fop_pdf.rb
|
|
281
309
|
- lib/libis/format/tool/identification_tool.rb
|
|
310
|
+
- lib/libis/format/tool/mail_to_pdf.rb
|
|
282
311
|
- lib/libis/format/tool/msg_to_pdf.rb
|
|
283
312
|
- lib/libis/format/tool/office_to_pdf.rb
|
|
284
313
|
- lib/libis/format/tool/pdf_merge.rb
|
|
@@ -352,7 +381,6 @@ homepage: ''
|
|
|
352
381
|
licenses:
|
|
353
382
|
- MIT
|
|
354
383
|
metadata: {}
|
|
355
|
-
post_install_message:
|
|
356
384
|
rdoc_options: []
|
|
357
385
|
require_paths:
|
|
358
386
|
- lib
|
|
@@ -367,8 +395,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
367
395
|
- !ruby/object:Gem::Version
|
|
368
396
|
version: '0'
|
|
369
397
|
requirements: []
|
|
370
|
-
rubygems_version:
|
|
371
|
-
signing_key:
|
|
398
|
+
rubygems_version: 4.0.8
|
|
372
399
|
specification_version: 4
|
|
373
400
|
summary: LIBIS File format format services.
|
|
374
401
|
test_files: []
|