Class: RMail::Parser::MultipartReader

Inherits:
PushbackReader show all
Defined in:
lib/rmail/parser/multipart.rb

Overview

A simple interface to facilitate parsing a multipart message.

The typical RubyMail user will have no use for this class. Although it is an example of how to use a PushbackReader, the typical RubyMail user will never use a PushbackReader either. ;-)

Instance Attribute Summary

Attributes inherited from PushbackReader

#chunk_size

Instance Method Summary collapse

Methods inherited from PushbackReader

#eof, maybe_contains_re, #pushback, #read, #standard_read_chunk

Constructor Details

#initialize(input, boundary) ⇒ MultipartReader

Creates a MIME multipart parser.

input is an object supporting a read method that takes one argument, a suggested number of bytes to read, and returns either a string of bytes read or nil if there are no more bytes to read.

boundary is the string boundary that separates the parts, without the “–” prefix.

This class is a suitable input source when parsing recursive multiparts.


54
55
56
57
58
59
60
61
62
63
64
65
66
# File 'lib/rmail/parser/multipart.rb', line 54

def initialize(input, boundary)
  super(input)
  escaped = Regexp.escape(boundary)
  @delimiter_re = /(?:\G|\n)--#{escaped}(--)?\s*?(\n|\z)/
  @might_be_delimiter_re = might_be_delimiter_re(boundary)
  @caryover = nil
  @chunks = []
  @eof = false
  @delimiter = nil
  @delimiter_is_last = false
  @in_epilogue = false
  @in_preamble = true
end

Instance Method Details

#delimiterObject

Call this to retrieve the delimiter string that terminated the part just read. This is cleared by #next_part.


204
205
206
# File 'lib/rmail/parser/multipart.rb', line 204

def delimiter
  @delimiter
end

#epilogue?Boolean

Call this to determine if #read is currently returning strings from the epilogue portion of a mime multipart.

Returns:

  • (Boolean)

198
199
200
# File 'lib/rmail/parser/multipart.rb', line 198

def epilogue?
  @in_epilogue
end

#next_partObject

Start reading the next part. Returns true if there is a next part to read, or false if we have reached the end of the file.


177
178
179
180
181
182
183
184
185
186
187
188
# File 'lib/rmail/parser/multipart.rb', line 177

def next_part
  if @eof
    false
  else
    if @delimiter
      @delimiter = nil
      @in_preamble = false
      @in_epilogue = @delimiter_is_last
    end
    true
  end
end

#preamble?Boolean

Call this to determine if #read is currently returning strings from the preamble portion of a mime multipart.

Returns:

  • (Boolean)

192
193
194
# File 'lib/rmail/parser/multipart.rb', line 192

def preamble?
  @in_preamble
end

#read_chunk(chunk_size) ⇒ Object

Returns the next chunk of data from the input stream as a string. The chunk_size is passed down to the read method of this object's input stream to suggest a size to it, but don't depend on the returned data being of any particular size.

If this method returns nil, you must call next_part to begin reading the next MIME part in the data stream.


75
76
77
78
79
80
81
82
83
84
85
# File 'lib/rmail/parser/multipart.rb', line 75

def read_chunk(chunk_size)
  chunk = read_chunk_low(chunk_size)
  if chunk
    if @in_epilogue
      while more = read_chunk_low(chunk_size)
        chunk << more
      end
    end
  end
  chunk
end

#read_chunk_low(chunk_size) ⇒ Object


87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
# File 'lib/rmail/parser/multipart.rb', line 87

def read_chunk_low(chunk_size)

  if @pushback
    return standard_read_chunk(chunk_size)
  end

  input_gave_nil = false
  loop {
    return nil if @eof || @delimiter

    unless @chunks.empty?
      chunk, @delimiter, @delimiter_is_last = @chunks.shift
      return chunk
    end

    chunk = standard_read_chunk(chunk_size)

    if chunk.nil?
      input_gave_nil = true
    end
    if @caryover
      if chunk
        @caryover << chunk
      end
      chunk = @caryover
      @caryover = nil
    end

    if chunk.nil?
      @eof = true
      return nil
    elsif @in_epilogue
      return chunk
    end

    start = 0
    found_last_delimiter = false

    while !found_last_delimiter and
        (start < chunk.length) and
        (found = chunk.index(@delimiter_re, start))

      if $~[2] == '' and !input_gave_nil
        break
      end

      delimiter = $~[0]

      # check if delimiter had the trailing --
      if $~.begin(1)
        found_last_delimiter = true
      end

      temp = if found == start
               nil
             else
               chunk[start, found - start]
             end

      @chunks << [ temp, delimiter, found_last_delimiter ]

      start = $~.end(0)
    end

    chunk = chunk[start..-1] if start > 0

    # If something that looks like a delimiter exists at the end
    # of this chunk, refrain from returning it.
    unless found_last_delimiter or input_gave_nil
      start = chunk.rindex(/\n/) || 0
      if chunk.index(@might_be_delimiter_re, start)
        @caryover = chunk[start..-1]
        chunk[start..-1] = ''
        chunk = nil if chunk.length == 0
      end
    end

    unless chunk.nil?
      @chunks << [ chunk, nil, false ]
    end
    chunk, @delimiter, @delimiter_is_last = @chunks.shift

    if chunk
      return chunk
    end
  }
end