mail_dude 0.1.2 → 0.1.3
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/CHANGELOG.md +5 -0
- data/lib/mail_dude/attachment_locator.rb +33 -15
- data/lib/mail_dude/attachment_scrubber.rb +36 -0
- data/lib/mail_dude/message_broadcast.rb +1 -1
- data/lib/mail_dude/message_presenter.rb +4 -4
- data/lib/mail_dude/message_serializer.rb +7 -3
- data/lib/mail_dude/stores/base.rb +7 -1
- data/lib/mail_dude/version.rb +1 -1
- data/lib/mail_dude.rb +1 -0
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 38404e9ab732d38243310741c269a4ec27e02a3076c479c891fe7bd532474d97
|
|
4
|
+
data.tar.gz: c66340b339905d47989e60b49d86073aab27785eae7a9da526fd910914db81ff
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3d28003d59309eccded62419a0d28573facd4a027045c9df28eefa7491206f5815bc9906f60cb2631a8ada54921bbf59c9b3c418fa28a89462eca055946ee2ef
|
|
7
|
+
data.tar.gz: 7d8011434a33827a7f795f3bf865b7995dfec41d999aab8ef9cfd8d99e2f8b9d3ecf725c75f74deddbb4702fb9f8353666e66565254ca4e23c21f8e1a89b9be2
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,26 @@
|
|
|
2
2
|
|
|
3
3
|
module MailDude
|
|
4
4
|
class AttachmentLocator
|
|
5
|
+
class << self
|
|
6
|
+
def attachment_part?(part)
|
|
7
|
+
!part.multipart? && (part.attachment? || part.filename.present? || inline_renderable_part?(part))
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def content_disposition(part)
|
|
11
|
+
part.content_disposition.to_s.split(';').first.to_s.downcase
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def inline_renderable_part?(part)
|
|
15
|
+
inline_part?(part) || part.content_id.present?
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def inline_part?(part)
|
|
21
|
+
content_disposition(part) == 'inline' && part.content_id.present?
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
5
25
|
Attachment = Struct.new(:id, :part, :metadata, keyword_init: true) do
|
|
6
26
|
def content_type
|
|
7
27
|
metadata['content_type']
|
|
@@ -35,6 +55,14 @@ module MailDude
|
|
|
35
55
|
end
|
|
36
56
|
end
|
|
37
57
|
|
|
58
|
+
def attachments_count
|
|
59
|
+
attachment_parts.length
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def attachments_present?
|
|
63
|
+
attachments_count.positive?
|
|
64
|
+
end
|
|
65
|
+
|
|
38
66
|
def find(attachment_id)
|
|
39
67
|
raise AttachmentNotFoundError, 'Attachment not found' unless attachment_id.to_s.match?(/\Aa\d+\z/)
|
|
40
68
|
|
|
@@ -44,6 +72,8 @@ module MailDude
|
|
|
44
72
|
|
|
45
73
|
def find_inline_by_cid(content_id)
|
|
46
74
|
normalized = normalize_content_id(content_id)
|
|
75
|
+
return nil if normalized.blank?
|
|
76
|
+
|
|
47
77
|
attachments.find { |attachment| attachment.metadata['content_id'] == normalized }
|
|
48
78
|
end
|
|
49
79
|
|
|
@@ -64,19 +94,7 @@ module MailDude
|
|
|
64
94
|
def attachment_parts
|
|
65
95
|
return [] unless mail
|
|
66
96
|
|
|
67
|
-
mail.all_parts.select { |part| attachment_part?(part) }
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
def attachment_part?(part)
|
|
71
|
-
!part.multipart? && (part.attachment? || part.filename.present? || inline_part?(part))
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
def content_disposition(part)
|
|
75
|
-
part.content_disposition.to_s.split(';').first.to_s.downcase
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
def inline_part?(part)
|
|
79
|
-
content_disposition(part) == 'inline' && part.content_id.present?
|
|
97
|
+
mail.all_parts.select { |part| self.class.attachment_part?(part) }
|
|
80
98
|
end
|
|
81
99
|
|
|
82
100
|
def mail
|
|
@@ -96,8 +114,8 @@ module MailDude
|
|
|
96
114
|
'filename' => sanitize_filename(part.filename, fallback: "attachment-#{id}"),
|
|
97
115
|
'content_type' => part.mime_type.presence || 'application/octet-stream',
|
|
98
116
|
'content_id' => normalize_content_id(part.content_id),
|
|
99
|
-
'disposition' => content_disposition(part).presence || 'attachment',
|
|
100
|
-
'inline' =>
|
|
117
|
+
'disposition' => self.class.content_disposition(part).presence || 'attachment',
|
|
118
|
+
'inline' => self.class.inline_renderable_part?(part),
|
|
101
119
|
'size_bytes' => decoded_size(part)
|
|
102
120
|
}
|
|
103
121
|
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MailDude
|
|
4
|
+
class AttachmentScrubber
|
|
5
|
+
RAW_SOURCE_OMITTED = "MailDude omitted raw message source because attachment capture is disabled.\n"
|
|
6
|
+
|
|
7
|
+
def initialize(mail)
|
|
8
|
+
@mail = mail
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def raw_source
|
|
12
|
+
source = mail&.to_s
|
|
13
|
+
return RAW_SOURCE_OMITTED if source.blank?
|
|
14
|
+
|
|
15
|
+
sanitized = Mail.read_from_string(source)
|
|
16
|
+
remove_attachments!(sanitized)
|
|
17
|
+
sanitized.to_s
|
|
18
|
+
rescue StandardError
|
|
19
|
+
RAW_SOURCE_OMITTED
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
attr_reader :mail
|
|
25
|
+
|
|
26
|
+
def remove_attachments!(message)
|
|
27
|
+
return remove_single_part_attachment!(message) unless message.multipart?
|
|
28
|
+
|
|
29
|
+
message.parts.recursive_delete_if { |part| AttachmentLocator.attachment_part?(part) }
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def remove_single_part_attachment!(message)
|
|
33
|
+
message.body = '' if AttachmentLocator.attachment_part?(message)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -41,7 +41,7 @@ module MailDude
|
|
|
41
41
|
sender: presenter.sender_summary,
|
|
42
42
|
recipients: presenter.recipient_summary,
|
|
43
43
|
captured_at: presenter.captured_at_label,
|
|
44
|
-
attachments_count: presenter.
|
|
44
|
+
attachments_count: presenter.attachment_count,
|
|
45
45
|
attachment_count_label: presenter.attachment_count_label,
|
|
46
46
|
mailer_label: presenter.mailer_label
|
|
47
47
|
}
|
|
@@ -80,12 +80,12 @@ module MailDude
|
|
|
80
80
|
raw_source.split(/\r?\n\r?\n/, 2).first.to_s
|
|
81
81
|
end
|
|
82
82
|
|
|
83
|
-
def has_attachments?
|
|
84
|
-
|
|
85
|
-
|
|
83
|
+
def has_attachments? = metadata_value('has_attachments') == true || attachments.any?
|
|
84
|
+
|
|
85
|
+
def attachment_count = metadata_value('attachments_count').presence&.to_i || attachments.length
|
|
86
86
|
|
|
87
87
|
def attachment_count_label
|
|
88
|
-
count =
|
|
88
|
+
count = attachment_count
|
|
89
89
|
"#{count} #{'attachment'.pluralize(count)}"
|
|
90
90
|
end
|
|
91
91
|
|
|
@@ -35,8 +35,8 @@ module MailDude
|
|
|
35
35
|
'mailer_action' => internal_header(INTERNAL_ACTION_HEADER),
|
|
36
36
|
'has_html' => part_present?('text/html'),
|
|
37
37
|
'has_text' => part_present?('text/plain'),
|
|
38
|
-
'has_attachments' =>
|
|
39
|
-
'attachments_count' =>
|
|
38
|
+
'has_attachments' => attachment_locator.attachments_present?,
|
|
39
|
+
'attachments_count' => attachment_locator.attachments_count,
|
|
40
40
|
'attachments' => attachments,
|
|
41
41
|
'size_bytes' => raw_source.bytesize
|
|
42
42
|
}
|
|
@@ -55,7 +55,11 @@ module MailDude
|
|
|
55
55
|
end
|
|
56
56
|
|
|
57
57
|
def attachments
|
|
58
|
-
@attachments ||=
|
|
58
|
+
@attachments ||= attachment_locator.attachments.map(&:metadata)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def attachment_locator
|
|
62
|
+
@attachment_locator ||= AttachmentLocator.new(mail)
|
|
59
63
|
end
|
|
60
64
|
|
|
61
65
|
def content_type
|
|
@@ -35,11 +35,17 @@ module MailDude
|
|
|
35
35
|
private
|
|
36
36
|
|
|
37
37
|
def build_record(mail, id: generate_id, captured_at: Time.now.utc)
|
|
38
|
-
raw_source = mail
|
|
38
|
+
raw_source = raw_source_for(mail)
|
|
39
39
|
metadata = MessageSerializer.new(mail, id: id, captured_at: captured_at, raw_source: raw_source).metadata
|
|
40
40
|
MessageRecord.new(id: id, metadata: metadata, raw_source: raw_source)
|
|
41
41
|
end
|
|
42
42
|
|
|
43
|
+
def raw_source_for(mail)
|
|
44
|
+
return mail.to_s if MailDude.configuration.capture_attachments
|
|
45
|
+
|
|
46
|
+
AttachmentScrubber.new(mail).raw_source
|
|
47
|
+
end
|
|
48
|
+
|
|
43
49
|
def generate_id
|
|
44
50
|
time = Time.now.utc
|
|
45
51
|
"#{time.strftime('%Y%m%dT%H%M%S')}#{format('%06d', time.usec)}Z-#{SecureRandom.hex(8)}"
|
data/lib/mail_dude/version.rb
CHANGED
data/lib/mail_dude.rb
CHANGED
|
@@ -17,6 +17,7 @@ require_relative 'mail_dude/pagination'
|
|
|
17
17
|
require_relative 'mail_dude/message_serializer'
|
|
18
18
|
require_relative 'mail_dude/message_presenter'
|
|
19
19
|
require_relative 'mail_dude/attachment_locator'
|
|
20
|
+
require_relative 'mail_dude/attachment_scrubber'
|
|
20
21
|
require_relative 'mail_dude/html_body_renderer'
|
|
21
22
|
require_relative 'mail_dude/message_broadcast'
|
|
22
23
|
require_relative 'mail_dude/stores/base'
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: mail_dude
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- MailDude contributors
|
|
@@ -184,6 +184,7 @@ files:
|
|
|
184
184
|
- lib/generators/mail_dude/templates/initializer.tt
|
|
185
185
|
- lib/mail_dude.rb
|
|
186
186
|
- lib/mail_dude/attachment_locator.rb
|
|
187
|
+
- lib/mail_dude/attachment_scrubber.rb
|
|
187
188
|
- lib/mail_dude/configuration.rb
|
|
188
189
|
- lib/mail_dude/dashboard.rb
|
|
189
190
|
- lib/mail_dude/delivery_method.rb
|