Class: MailSmtp::DataReader
- Inherits:
-
Object
- Object
- MailSmtp::DataReader
- Defined in:
- lib/mailsmtp/data_reader.rb
Overview
IO-like view of an SMTP DATA body. Yields dot-unstuffed bytes as the
underlying connection produces lines; ends at the terminating "." line.
SIZE and per-line limits set #failure and raise on #read / #readpartial
(never return a truncated body as success). #drain consumes through "."
without raising so the session can reply once, but still records SIZE/line
failures (and an absolute byte ceiling when SIZE is nil) so a no-op
receive cannot turn an oversize body into 250. Drain always stops once
that ceiling is hit — unbounded drain would let a fast sender burn the link.
readpartial fills up to maxlen (or the terminating "."), so a mid-DATA
disconnect raises ServiceUnavailable instead of returning a short chunk that
apps treat as end-of-message. EOFError is raised only after the ".".
Constant Summary collapse
- DRAIN_BYTE_CEILING =
Absolute drain ceiling when the server has no SIZE limit configured. Keep in sync with Server::DEFAULT_MAX_MESSAGE_SIZE (this file loads first).
10_485_760
Instance Attribute Summary collapse
-
#failure ⇒ Object
readonly
Returns the value of attribute failure.
Instance Method Summary collapse
- #binmode ⇒ Object
- #close ⇒ Object
- #closed? ⇒ Boolean
- #drain ⇒ Object
- #drain_ceiling_hit? ⇒ Boolean
- #eof? ⇒ Boolean
-
#initialize(max_message_size: nil, max_line_length: Server::MAX_TEXT_LINE_LENGTH, on_headers: nil, &read_line) ⇒ DataReader
constructor
A new instance of DataReader.
- #read(length = nil, outbuf = nil) ⇒ Object
- #readpartial(maxlen, outbuf = nil) ⇒ Object
Constructor Details
#initialize(max_message_size: nil, max_line_length: Server::MAX_TEXT_LINE_LENGTH, on_headers: nil, &read_line) ⇒ DataReader
Returns a new instance of DataReader.
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
# File 'lib/mailsmtp/data_reader.rb', line 23 def initialize(max_message_size: nil, max_line_length: Server::MAX_TEXT_LINE_LENGTH, on_headers: nil, &read_line) raise ArgumentError, "read_line block required" unless read_line @read_line = read_line @max_message_size = @max_line_length = max_line_length @on_headers = on_headers @buffer = +"".b @bytes_seen = 0 @eof = false @failure = nil @draining = false @headers_seen = false @drain_ceiling_hit = false end |
Instance Attribute Details
#failure ⇒ Object (readonly)
Returns the value of attribute failure.
21 22 23 |
# File 'lib/mailsmtp/data_reader.rb', line 21 def failure @failure end |
Instance Method Details
#binmode ⇒ Object
111 |
# File 'lib/mailsmtp/data_reader.rb', line 111 def binmode = self |
#close ⇒ Object
112 |
# File 'lib/mailsmtp/data_reader.rb', line 112 def close = nil |
#closed? ⇒ Boolean
113 |
# File 'lib/mailsmtp/data_reader.rb', line 113 def closed? = false |
#drain ⇒ Object
101 102 103 104 105 106 107 |
# File 'lib/mailsmtp/data_reader.rb', line 101 def drain @draining = true @buffer = +"" # Stop on SIZE, or on the absolute drain ceiling when SIZE is off / after # LineTooLong — never read forever waiting for ".". fill_until { @drain_ceiling_hit || @failure.is_a?(MessageTooLarge) } end |
#drain_ceiling_hit? ⇒ Boolean
109 |
# File 'lib/mailsmtp/data_reader.rb', line 109 def drain_ceiling_hit? = @drain_ceiling_hit |
#eof? ⇒ Boolean
97 98 99 |
# File 'lib/mailsmtp/data_reader.rb', line 97 def eof? @eof && @buffer.empty? && (@draining || @failure.nil?) end |
#read(length = nil, outbuf = nil) ⇒ Object
40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 |
# File 'lib/mailsmtp/data_reader.rb', line 40 def read(length = nil, outbuf = nil) raise @failure if @failure && !@draining # IO#read(0) returns an empty string without consuming input. if length == 0 chunk = +"" return outbuf ? outbuf.replace(chunk) : chunk end if length fill_until { @buffer.bytesize >= length || @eof || @failure } else fill_until { @eof || @failure } end raise @failure if @failure && !@draining if @buffer.empty? # IO#read(length) returns nil at EOF; read() with no length returns "". chunk = length ? nil : +"" elsif length chunk = @buffer.slice!(0, length) else chunk = @buffer @buffer = +"" end if outbuf outbuf.replace(chunk || +"") chunk else chunk end end |
#readpartial(maxlen, outbuf = nil) ⇒ Object
75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 |
# File 'lib/mailsmtp/data_reader.rb', line 75 def readpartial(maxlen, outbuf = nil) raise ArgumentError, "maxlen must be positive" unless maxlen.positive? raise @failure if @failure && !@draining # Fill up to maxlen (not "any bytes") so a peer drop is discovered on this # call — returning the first line alone lets short-read loops treat a # truncated body as complete while the engine later 451s / redelivers. fill_until { @buffer.bytesize >= maxlen || @eof || @failure } raise @failure if @failure && !@draining if @buffer.empty? # EOFError only after the terminating "."; a drop mid-DATA must not look # like a clean end-of-message (apps that rescue EOFError would accept a # truncated body and the engine would map that to LocalError / 451). raise EOFError, "end of data" if @eof raise ServiceUnavailable, "Connection lost during DATA" end chunk = @buffer.slice!(0, maxlen) outbuf ? outbuf.replace(chunk) : chunk end |