libis-format 1.3.7.1 → 1.3.8
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 +80 -196
- data/lib/libis/format/version.rb +1 -1
- data/libis-format.gemspec +4 -1
- metadata +33 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 926fc551e289a3866aa7e79ba4b835eae9ab1cae31febc247746c59a8e6bed4c
|
|
4
|
+
data.tar.gz: 22ab9cab0fe38e3232fbf67805ae91432ffda56b1864eaab707efa2a8d5bde86
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: bb57e0586eb783302b0b5cec06bdfec3a55f4901ed214555e602d7658a4f4908c142096145bbb0cd3573a7a56fb049a13cd9ceba85b1958efb0d70400f578a07
|
|
7
|
+
data.tar.gz: f30fa360afcc72db8c42713aa5b2d21a46616d8bdbe75566f29bb088cdfc3b9d11e83d1c77d22d9a9a10d00d5a383fcdddaa45386bfc187813a6aafa11cbb51b
|
|
@@ -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,68 +1,39 @@
|
|
|
1
|
+
# rubocop:disable Style/Documentation, Metrics/*
|
|
1
2
|
# frozen_string_literal: true
|
|
2
3
|
|
|
4
|
+
require_relative 'mail_to_pdf'
|
|
5
|
+
|
|
3
6
|
require 'mapi/msg'
|
|
4
7
|
require 'rfc_2047'
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
require 'time'
|
|
8
|
-
require 'fileutils'
|
|
9
|
-
require 'pathname'
|
|
10
|
-
require 'libis/format/config'
|
|
8
|
+
|
|
9
|
+
Mapi::Log.level = Logger::Severity::INFO
|
|
11
10
|
|
|
12
11
|
module Libis
|
|
13
12
|
module Format
|
|
14
13
|
module Tool
|
|
15
|
-
class MsgToPdf
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
HEADER_STYLE = '<style>.header-table {margin: 0 0 20 0;padding: 0;font-family: Arial, Helvetica, sans-serif;}.header-name {padding-right: 5px;color: #9E9E9E;text-align: right;vertical-align: top;font-size: 12px;}.header-value {font-size: 12px;}#header_fields {#background: white;#margin: 0;#border: 1px solid #DDD;#border-radius: 3px;#padding: 8px;#width: 100%%;#box-sizing: border-box;#}</style><script type="text/javascript">function timer() {try {parent.postMessage(Math.max(document.body.offsetHeight, document.body.scrollHeight), \'*\');} catch (r) {}setTimeout(timer, 10);};timer();</script>' # rubocop:disable Layout/LineLength
|
|
19
|
-
HEADER_TABLE_TEMPLATE = '<div class="header-table"><table id="header_fields"><tbody>%s</tbody></table></div>'
|
|
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
|
|
22
|
-
|
|
23
|
-
IMG_CID_PLAIN_REGEX = /\[cid:(.*?)\]/m
|
|
24
|
-
IMG_CID_HTML_REGEX = /cid:([^"]*)/m
|
|
25
|
-
|
|
26
|
-
def self.installed?
|
|
27
|
-
File.exist?(Libis::Format::Config[:wkhtmltopdf])
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
def self.run(source, target, **options)
|
|
31
|
-
new.run source, target, **options
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
def run(source, target, **options)
|
|
35
|
-
# Preliminary checks
|
|
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
|
-
# ----------------------
|
|
14
|
+
class MsgToPdf < MailToPdf
|
|
15
|
+
protected
|
|
45
16
|
|
|
17
|
+
def open_email(source)
|
|
46
18
|
# Open the message
|
|
47
|
-
|
|
19
|
+
Mapi::Msg.open(source)
|
|
20
|
+
end
|
|
48
21
|
|
|
49
|
-
|
|
50
|
-
result = msg_to_pdf(msg, target, target_format, options)
|
|
22
|
+
def close_email(msg)
|
|
51
23
|
msg.close
|
|
52
|
-
|
|
24
|
+
true
|
|
53
25
|
end
|
|
54
26
|
|
|
55
|
-
def
|
|
56
|
-
# Make sure the target directory exists
|
|
57
|
-
outdir = File.dirname(target)
|
|
58
|
-
FileUtils.mkdir_p(outdir)
|
|
59
|
-
|
|
27
|
+
def get_body_html(msg)
|
|
60
28
|
# Get the body of the message in HTML
|
|
61
29
|
body = msg.properties.body_html
|
|
62
30
|
|
|
63
31
|
# Embed plain body in HTML as a fallback
|
|
64
32
|
body ||= HTML_WRAPPER_TEMPLATE % msg.properties.body
|
|
65
33
|
|
|
34
|
+
# Worst case, just create empty body
|
|
35
|
+
body ||= HTML_WRAPPER_TEMPLATE % ''
|
|
36
|
+
|
|
66
37
|
# Check and fix the character encoding
|
|
67
38
|
begin
|
|
68
39
|
# Try to encode into UTF-8
|
|
@@ -78,161 +49,92 @@ module Libis
|
|
|
78
49
|
end
|
|
79
50
|
end
|
|
80
51
|
|
|
81
|
-
|
|
82
|
-
|
|
52
|
+
body
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def get_subject(msg)
|
|
56
|
+
find_hdr(msg.headers, 'Subject') || 'No Subject'
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def get_headers(msg)
|
|
83
60
|
headers = {}
|
|
84
|
-
|
|
61
|
+
html = ''
|
|
85
62
|
|
|
86
63
|
%w[From To Cc Subject Date].each do |key|
|
|
87
64
|
value = find_hdr(msg.headers, key)
|
|
88
|
-
|
|
65
|
+
next unless value
|
|
66
|
+
|
|
67
|
+
if key.casecmp('Date').zero?
|
|
68
|
+
begin
|
|
69
|
+
value = DateTime.parse(value).to_time.localtime
|
|
70
|
+
headers[key.downcase.to_sym] = value.iso8601
|
|
71
|
+
html += hdr_html(key, value.rfc2822)
|
|
72
|
+
rescue StandardError => e
|
|
73
|
+
logger.warn "Failed to parse date header '#{value}': #{e.message}"
|
|
74
|
+
end
|
|
75
|
+
else
|
|
89
76
|
headers[key.downcase.to_sym] = value
|
|
90
|
-
|
|
77
|
+
html += hdr_html(key, value)
|
|
91
78
|
end
|
|
92
79
|
end
|
|
93
80
|
|
|
94
|
-
[
|
|
95
|
-
|
|
81
|
+
[headers, html]
|
|
82
|
+
end
|
|
96
83
|
|
|
97
|
-
|
|
98
|
-
|
|
84
|
+
def get_inline_attachment_data(attachments, cid)
|
|
85
|
+
attachments.each do |attachment|
|
|
86
|
+
next unless attachment.properties.attach_content_id == cid
|
|
99
87
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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>")
|
|
109
|
-
else
|
|
110
|
-
# otherwise insert a head section before the body tag
|
|
111
|
-
body.gsub!(/<body/, "<head>#{HEADER_STYLE}</head><body")
|
|
112
|
-
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}" }
|
|
88
|
+
attachment.data.rewind
|
|
89
|
+
return {
|
|
90
|
+
mime_type: attachment.properties.attach_mime_tag,
|
|
91
|
+
base64: Base64.encode64(attachment.data.read).gsub(/[\r\n]/, '')
|
|
92
|
+
}
|
|
115
93
|
end
|
|
94
|
+
nil
|
|
95
|
+
end
|
|
116
96
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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
|
|
97
|
+
def get_file_attachments(attachments, used_files)
|
|
98
|
+
attachments.select do |attachment|
|
|
99
|
+
!attachment.properties.attachment_hidden &&
|
|
100
|
+
attachment.filename &&
|
|
101
|
+
!attachment.filename.empty? &&
|
|
102
|
+
!used_files.include?(attachment.filename)
|
|
131
103
|
end
|
|
104
|
+
end
|
|
132
105
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
used_files << ::Regexp.last_match(1)
|
|
138
|
-
"data:#{data[:mime_type]};base64,#{data[:base64]}"
|
|
139
|
-
else
|
|
140
|
-
''
|
|
141
|
-
end
|
|
106
|
+
def get_mail_attachments(attachments)
|
|
107
|
+
attachments.select do |attachment|
|
|
108
|
+
!attachment.properties.attachment_hidden &&
|
|
109
|
+
attachment.instance_variable_get(:@embedded_msg)
|
|
142
110
|
end
|
|
111
|
+
end
|
|
143
112
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
files = []
|
|
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)
|
|
113
|
+
def get_attachment_info(attachment)
|
|
114
|
+
if (sub_msg = attachment.instance_variable_get(:@embedded_msg))
|
|
169
115
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
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}.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
|
|
116
|
+
{
|
|
117
|
+
embedded_msg: sub_msg,
|
|
118
|
+
filename: attachment.properties[:display_name] || sub_msg.subject || ''
|
|
119
|
+
}
|
|
184
120
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
121
|
+
elsif attachment.filename
|
|
122
|
+
io = StringIO.new
|
|
123
|
+
attachment.save(io)
|
|
124
|
+
io.rewind
|
|
188
125
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
else
|
|
194
|
-
@warnings << "Attachment #{a.properties[:display_name]} cannot be extracted"
|
|
195
|
-
next
|
|
196
|
-
end
|
|
197
|
-
i += 1
|
|
198
|
-
end
|
|
126
|
+
{
|
|
127
|
+
data: io.string,
|
|
128
|
+
filename: attachment.filename
|
|
129
|
+
}
|
|
199
130
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
end
|
|
131
|
+
else
|
|
132
|
+
{
|
|
133
|
+
filename: attachment.properties[:display_name] || 'unknown'
|
|
134
|
+
}
|
|
205
135
|
end
|
|
206
|
-
|
|
207
|
-
{
|
|
208
|
-
command: { status: 0 },
|
|
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
|
-
}
|
|
230
136
|
end
|
|
231
137
|
|
|
232
|
-
protected
|
|
233
|
-
|
|
234
|
-
def eml_to_html; end
|
|
235
|
-
|
|
236
138
|
private
|
|
237
139
|
|
|
238
140
|
def find_hdr(list, key)
|
|
@@ -245,26 +147,8 @@ module Libis
|
|
|
245
147
|
end
|
|
246
148
|
nil
|
|
247
149
|
end
|
|
248
|
-
|
|
249
|
-
def hdr_html(key, value)
|
|
250
|
-
return format(HEADER_FIELD_TEMPLATE, key, CGI.escapeHTML(value)) if key.is_a?(String) && value.is_a?(String) && !value.empty?
|
|
251
|
-
|
|
252
|
-
''
|
|
253
|
-
end
|
|
254
|
-
|
|
255
|
-
def get_attachment_data(attachments, cid)
|
|
256
|
-
attachments.each do |attachment|
|
|
257
|
-
next unless attachment.properties.attach_content_id == cid
|
|
258
|
-
|
|
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
|
-
end
|
|
265
|
-
nil
|
|
266
|
-
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|
|
|
@@ -29,12 +29,15 @@ Gem::Specification.new do |spec|
|
|
|
29
29
|
spec.add_runtime_dependency 'deep_dive', '~> 0.3'
|
|
30
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'
|
|
33
34
|
spec.add_runtime_dependency 'naturally', '~> 2.2'
|
|
34
35
|
spec.add_runtime_dependency 'new_rfc_2047', '~> 1.0'
|
|
35
36
|
spec.add_runtime_dependency 'os', '~> 1.1'
|
|
36
37
|
spec.add_runtime_dependency 'pdfinfo', '~> 1.4'
|
|
37
38
|
spec.add_runtime_dependency 'pdfkit', '~> 0.8'
|
|
39
|
+
# spec.add_runtime_dependency 'ruby-msg', '~> 1.5.3.1'
|
|
40
|
+
spec.add_runtime_dependency 'word_wrap', '~> 1.0'
|
|
38
41
|
|
|
39
42
|
spec.add_development_dependency 'awesome_print'
|
|
40
43
|
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
|
|
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
|
|
@@ -66,6 +65,20 @@ dependencies:
|
|
|
66
65
|
- - "~>"
|
|
67
66
|
- !ruby/object:Gem::Version
|
|
68
67
|
version: '1.1'
|
|
68
|
+
- !ruby/object:Gem::Dependency
|
|
69
|
+
name: mail
|
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
|
71
|
+
requirements:
|
|
72
|
+
- - "~>"
|
|
73
|
+
- !ruby/object:Gem::Version
|
|
74
|
+
version: '2.9'
|
|
75
|
+
type: :runtime
|
|
76
|
+
prerelease: false
|
|
77
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - "~>"
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: '2.9'
|
|
69
82
|
- !ruby/object:Gem::Dependency
|
|
70
83
|
name: mini_magick
|
|
71
84
|
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: []
|