mailbounce 0.1.1 → 0.1.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 475505e3807f1ffa431026bcce91ccf41f241a47eb48c85314ee0a2d3ceef68a
4
- data.tar.gz: b848522dea5184b318a6666f600130dbf2a8172bd347d18d1758df9d558d6720
3
+ metadata.gz: 4f99ac991751ae3b826fb20a5e9e9f13c0871ef50b8957abab82daddccad242a
4
+ data.tar.gz: 5568d4cfaf4644bf4c984116276e0142ec1d4842bf79c6fed565f5dd965c2484
5
5
  SHA512:
6
- metadata.gz: 64e4d94d50f45823c5c5dc26f824fc4c7818e8cc5c31e5d53e267a45f29e6ac98da199edc900e5e583ae8468d00e13369d299a694130102b1f4142611495c8b1
7
- data.tar.gz: 216ca4a564961a311306532736271f29c15cf900d64424d86e1578ae379ccc5fdac1470f94bcbdf3b5c1ab7cacc6ad6397cc99874fb52e4121abe70b29fe7f51
6
+ metadata.gz: 19e7c1b2748c550f40c283841e869de952724c29defc24404a2c7d0f251f515150e59383f304261c2b71ffb4d6e66d1f587952dd2e35ac86bc4c4edc9f11b6d5
7
+ data.tar.gz: febb828d483008e5abcd932ed760f86db3f8cde575f32ef47ae901a89d62b66a67c8349515aadababf0950bd85c9ca256f52c6dbfc57bb293d2f88677057f0ab
data/CHANGELOG.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.2
4
+
5
+ - Split a report into per-recipient blocks
6
+ - A report is now read only up to 1 MB, which `parse` takes as `max_size`
7
+
3
8
  ## 0.1.1
4
9
 
5
10
  Documentation
data/README.md CHANGED
@@ -110,6 +110,12 @@ Reports announce delays and successes by the same route and in the same shape, s
110
110
 
111
111
  A report naming several recipients speaks for a given address only when it names it; one covering a single recipient needn't name anyone. Nothing raises: a report with no machine-readable part, or bytes that aren't a message at all, reports nothing.
112
112
 
113
+ Bounce are cappted to 1 MB. Past that a report is refused like any other it can't read. Lower it where you'd rather hold less:
114
+
115
+ ```ruby
116
+ MailBounce.parse(raw_bounce, max_size: 64 * 1024)
117
+ ```
118
+
113
119
  ## Testing
114
120
 
115
121
  ```sh
@@ -1,4 +1,5 @@
1
1
  require "mail"
2
+ require "set"
2
3
 
3
4
  require "mailbounce/recipient"
4
5
 
@@ -13,6 +14,10 @@ module MailBounce
13
14
 
14
15
  FIELD_NAME = /\A(?<name>[A-Za-z][A-Za-z0-9\-]*)[ \t]*:/
15
16
 
17
+ # A bounce is a notice, not a mailbox — Postfix caps its own at 50 KB.
18
+ # Anything past this is not a report anyone meant to send.
19
+ MAX_SIZE = 1024 * 1024
20
+
16
21
  # Value stays on its line so an empty field cannot swallow the next.
17
22
  FIELDS = {
18
23
  action: /^Action[ \t]*:[ \t]*(?<value>.+)$/i,
@@ -23,12 +28,14 @@ module MailBounce
23
28
  remote_mta: /^Remote-MTA[ \t]*:[ \t]*(?<value>.+)$/i
24
29
  }.freeze
25
30
 
26
- def self.parse(raw)
27
- new(raw)
31
+ def self.parse(raw, max_size: MAX_SIZE)
32
+ new(raw, max_size: max_size)
28
33
  end
29
34
 
30
- def initialize(raw)
31
- @mail = Mail.new(raw.to_s)
35
+ def initialize(raw, max_size: MAX_SIZE)
36
+ report = raw.to_s
37
+
38
+ @mail = Mail.new(report) if report.bytesize <= max_size
32
39
  rescue StandardError
33
40
  @mail = nil
34
41
  end
@@ -83,22 +90,30 @@ module MailBounce
83
90
  end
84
91
 
85
92
  # A block ends when a field it already has reappears (order is not fixed).
93
+ # The names are kept rather than matched for again: a block grows, and
94
+ # reading all of it per line costs the square of its length.
86
95
  def per_recipient(chunk)
87
- chunk.lines.each_with_object([]) do |line, blocks|
88
- if blocks.last && continues?(blocks.last, line)
89
- blocks.last << line
90
- else
96
+ blocks, named = [], Set.new
97
+
98
+ chunk.lines.each do |line|
99
+ name = field_name(line)
100
+
101
+ if blocks.empty? || named.include?(name)
91
102
  blocks << +line
103
+ named = Set.new
104
+ else
105
+ blocks.last << line
92
106
  end
107
+
108
+ named << name if name
93
109
  end
110
+
111
+ blocks
94
112
  end
95
113
 
96
- def continues?(block, line)
97
- if name = line[FIELD_NAME, :name]
98
- !block.match?(/^#{Regexp.escape(name)}[ \t]*:/i)
99
- else
100
- true
101
- end
114
+ # Field names are case-insensitive; a continuation line has none.
115
+ def field_name(line)
116
+ line[FIELD_NAME, :name]&.downcase
102
117
  end
103
118
 
104
119
  def recipient_from(block)
@@ -1,3 +1,3 @@
1
1
  module MailBounce
2
- VERSION = "0.1.1"
2
+ VERSION = "0.1.2"
3
3
  end
data/lib/mailbounce.rb CHANGED
@@ -11,7 +11,7 @@ module MailBounce
11
11
  Rejection.new(response: response, sending_ip: sending_ip)
12
12
  end
13
13
 
14
- def self.parse(raw)
15
- Report.parse(raw)
14
+ def self.parse(raw, max_size: Report::MAX_SIZE)
15
+ Report.parse(raw, max_size: max_size)
16
16
  end
17
17
  end
data/mailbounce.gemspec CHANGED
@@ -9,7 +9,7 @@ Gem::Specification.new do |spec|
9
9
  spec.authors = [ "Simon Lev" ]
10
10
 
11
11
  spec.summary = "Email delivery failures for Ruby."
12
- spec.description = "Answers one question about a rejected message: whose fault was it?"
12
+ spec.description = "Answers one question about a rejected message: whose fault was it? Parses bounces, NDRs, and DSN reports to classify delivery failures."
13
13
 
14
14
  spec.homepage = "https://github.com/mailpiece/mailbounce"
15
15
  spec.license = "MIT"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mailbounce
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Simon Lev
@@ -23,7 +23,8 @@ dependencies:
23
23
  - - ">="
24
24
  - !ruby/object:Gem::Version
25
25
  version: '2.7'
26
- description: 'Answers one question about a rejected message: whose fault was it?'
26
+ description: 'Answers one question about a rejected message: whose fault was it? Parses
27
+ bounces, NDRs, and DSN reports to classify delivery failures.'
27
28
  executables: []
28
29
  extensions: []
29
30
  extra_rdoc_files: []