mail_dude 0.1.2 → 0.1.4
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 +10 -0
- data/app/views/mail_dude/messages/_tabs.html.erb +1 -2
- 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 +12 -4
- 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 +6 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8ba107ba98ca78fe89c3e34585b6d49fe14d4e89f88dbfd587d62b95e0a651d5
|
|
4
|
+
data.tar.gz: 7a0cc62678dad96f2ecb50eaa4a6a708a451f76563e08488aee8897a66289bfb
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c3ae2168b7602568b0c6ac480b8b1c2221d3bff427934c1029a103df9d808c6a8c0a8038704949fbe5acc00ad9571ec96f10cd0f6bbf070ba841cddf64bf57ec
|
|
7
|
+
data.tar.gz: a0d5e30232fb7d383597cf34f857880e6a06510f7e230c7f838c6062bce1a1f4ceeb1281a9a7d8050ddab87a9b354f76dfedf30e1be1be67b2f6d98b1768cfe6
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.4
|
|
4
|
+
|
|
5
|
+
- Allow sandboxed previews to load same-origin inline attachments.
|
|
6
|
+
- Serialize Action Mailer inline attachments from captured raw source.
|
|
7
|
+
|
|
8
|
+
## 0.1.3
|
|
9
|
+
|
|
10
|
+
- Fix CID image rendering for Content-ID attachments without inline disposition.
|
|
11
|
+
- Strip attachment payloads from stored raw source when attachment capture is disabled.
|
|
12
|
+
|
|
3
13
|
## 0.1.2
|
|
4
14
|
|
|
5
15
|
- Add official Ruby 3.1 support.
|
|
@@ -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
|
}
|
|
@@ -54,8 +54,16 @@ module MailDude
|
|
|
54
54
|
fallback.present? ? [fallback] : []
|
|
55
55
|
end
|
|
56
56
|
|
|
57
|
-
def attachments
|
|
58
|
-
|
|
57
|
+
def attachments = @attachments ||= attachment_locator.attachments.map(&:metadata)
|
|
58
|
+
|
|
59
|
+
def attachment_locator
|
|
60
|
+
@attachment_locator ||= AttachmentLocator.new(attachment_source)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def attachment_source
|
|
64
|
+
return mail unless MailDude.configuration.capture_attachments && raw_source.present?
|
|
65
|
+
|
|
66
|
+
MessageRecord.new(id: id, metadata: {}, raw_source: raw_source)
|
|
59
67
|
end
|
|
60
68
|
|
|
61
69
|
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,10 +1,11 @@
|
|
|
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.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- MailDude contributors
|
|
8
|
+
autorequire:
|
|
8
9
|
bindir: bin
|
|
9
10
|
cert_chain: []
|
|
10
11
|
date: 2026-06-23 00:00:00.000000000 Z
|
|
@@ -184,6 +185,7 @@ files:
|
|
|
184
185
|
- lib/generators/mail_dude/templates/initializer.tt
|
|
185
186
|
- lib/mail_dude.rb
|
|
186
187
|
- lib/mail_dude/attachment_locator.rb
|
|
188
|
+
- lib/mail_dude/attachment_scrubber.rb
|
|
187
189
|
- lib/mail_dude/configuration.rb
|
|
188
190
|
- lib/mail_dude/dashboard.rb
|
|
189
191
|
- lib/mail_dude/delivery_method.rb
|
|
@@ -207,6 +209,7 @@ licenses:
|
|
|
207
209
|
- MIT
|
|
208
210
|
metadata:
|
|
209
211
|
rubygems_mfa_required: 'true'
|
|
212
|
+
post_install_message:
|
|
210
213
|
rdoc_options: []
|
|
211
214
|
require_paths:
|
|
212
215
|
- lib
|
|
@@ -221,7 +224,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
221
224
|
- !ruby/object:Gem::Version
|
|
222
225
|
version: '0'
|
|
223
226
|
requirements: []
|
|
224
|
-
rubygems_version: 3.
|
|
227
|
+
rubygems_version: 3.3.7
|
|
228
|
+
signing_key:
|
|
225
229
|
specification_version: 4
|
|
226
230
|
summary: A Rails Action Mailer capture engine for development and QA.
|
|
227
231
|
test_files: []
|