1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 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 74 75 76 77 78 79 80 81 82 83 84 85 86 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
|
require 'multipart_parser/parser'
module MultipartParser
class NotMultipartError < StandardError; end;
# A more high level interface to MultipartParser.
class Reader
# Initializes a MultipartReader, that will
# read a request with the given boundary value.
def initialize(boundary)
@parser = Parser.new
@parser.init_with_boundary(boundary)
@header_field = ''
@header_value = ''
@part = nil
@ended = false
@on_error = nil
@on_part = nil
init_parser_callbacks
end
# Returns true if the parser has finished parsing
def ended?
@ended
end
# Sets to a code block to call
# when part headers have been parsed.
def on_part(&callback)
@on_part = callback
end
# Sets a code block to call when
# a parser error occurs.
def on_error(&callback)
@on_error = callback
end
# Write data from the given buffer (String)
# into the reader.
def write(buffer)
bytes_parsed = @parser.write(buffer)
if bytes_parsed != buffer.size
msg = "Parser error, #{bytes_parsed} of #{buffer.length} bytes parsed"
@on_error.call(msg) unless @on_error.nil?
end
end
# Extracts a boundary value from a Content-Type header.
# Note that it is the header value you provide here.
# Raises NotMultipartError if content_type is invalid.
def self.extract_boundary_value(content_type)
if content_type =~ /multipart/i
if match = (content_type =~ /boundary=(?:"([^"]+)"|([^;]+))/i)
$1 || $2
else
raise NotMultipartError.new("No multipart boundary")
end
else
raise NotMultipartError.new("Not a multipart content type!")
end
end
class Part
attr_accessor :filename, :headers, :name, :mime
def initialize
@headers = {}
@data_callback = nil
@end_callback = nil
end
# Calls the data callback with the given data
def emit_data(data)
@data_callback.call(data) unless @data_callback.nil?
end
# Calls the end callback
def emit_end
@end_callback.call unless @end_callback.nil?
end
# Sets a block to be called when part data
# is read. The block should take one parameter,
# namely the read data.
def on_data(&callback)
@data_callback = callback
end
# Sets a block to be called when all data
# for the part has been read.
def on_end(&callback)
@end_callback = callback
end
end
private
def init_parser_callbacks
@parser.on(:part_begin) do
@part = Part.new
@header_field = ''
@header_value = ''
end
@parser.on(:header_field) do |b, start, the_end|
@header_field << b[start...the_end]
end
@parser.on(:header_value) do |b, start, the_end|
@header_value << b[start...the_end]
end
@parser.on(:header_end) do
@header_field.downcase!
@part.headers[@header_field] = @header_value
if @header_field == 'content-disposition'
if @header_value =~ /name="([^"]+)"/i
@part.name = $1
end
if @header_value =~ /filename="([^;]+)"/i
match = $1
start = (match.rindex("\\") || -1)+1
@part.filename = match[start...(match.length)]
end
elsif @header_field == 'content-type'
@part.mime = @header_value
end
@header_field = ''
@header_value = ''
end
@parser.on(:headers_end) do
@on_part.call(@part) unless @on_part.nil?
end
@parser.on(:part_data) do |b, start, the_end|
@part.emit_data b[start...the_end]
end
@parser.on(:part_end) do
@part.emit_end
end
@parser.on(:end) do
@ended = true
end
end
end
end
|