class HTTP::Message::Body
Represents HTTP message body.
def build_query_multipart_str(query, boundary)
def build_query_multipart_str(query, boundary) parts = Parts.new query.each do |attr, value| headers = ["--#{boundary}"] if Message.file?(value) remember_pos(value) param_str = params_from_file(value).collect { |k, v| "#{k}=\"#{v}\"" }.join("; ") if value.respond_to?(:mime_type) content_type = value.mime_type elsif value.respond_to?(:content_type) content_type = value.content_type else path = value.respond_to?(:path) ? value.path : nil content_type = Message.mime_type(path) end headers << %{Content-Disposition: form-data; name="#{attr}"; #{param_str}} headers << %{Content-Type: #{content_type}} elsif attr.is_a?(Hash) h = attr value = h[:content] h.each do |h_key, h_val| headers << %{#{h_key}: #{h_val}} if h_key != :content end remember_pos(value) if Message.file?(value) else headers << %{Content-Disposition: form-data; name="#{attr}"} value = value.to_s end parts.add(headers.join(CRLF) + CRLF + CRLF) parts.add(value) parts.add(CRLF) end parts.add("--#{boundary}--" + CRLF + CRLF) # empty epilogue parts end
def content
def content @body end
def dump(header = '', dev = ''.dup)
String.
If no dev (the second argument) given, this method returns a dumped
reason. (header is dumped to dev, too)
Message header must be given as the first argument for performance
dev needs to respond to <<.
Dumps message body to given dev.
def dump(header = '', dev = ''.dup) if @body.is_a?(Parts) dev << header @body.parts.each do |part| if Message.file?(part) reset_pos(part) dump_file(part, dev, @body.sizes[part]) else dev << part end end elsif Message.file?(@body) dev << header reset_pos(@body) dump_file(@body, dev, @size) elsif @body dev << header + @body else dev << header end dev end
def dump_chunk(str)
def dump_chunk(str) dump_chunk_size(str.bytesize) + (str + CRLF) end
def dump_chunk_size(size)
def dump_chunk_size(size) sprintf("%x", size) + CRLF end
def dump_chunked(header = '', dev = ''.dup)
If no dev (the second argument) given, this method returns a dumped
reason. (header is dumped to dev, too)
Message header must be given as the first argument for performance
dev needs to respond to <<.
Dumps message body with chunked encoding to given dev.
def dump_chunked(header = '', dev = ''.dup) dev << header if @body.is_a?(Parts) @body.parts.each do |part| if Message.file?(part) reset_pos(part) dump_chunks(part, dev) else dev << dump_chunk(part) end end dev << (dump_last_chunk + CRLF) elsif @body reset_pos(@body) dump_chunks(@body, dev) dev << (dump_last_chunk + CRLF) end dev end
def dump_chunks(io, dev)
def dump_chunks(io, dev) buf = ''.dup while !io.read(@chunk_size, buf).nil? dev << dump_chunk(buf) end end
def dump_file(io, dev, sz)
def dump_file(io, dev, sz) buf = ''.dup rest = sz while rest > 0 n = io.read([rest, @chunk_size].min, buf) raise ArgumentError.new("Illegal size value: #size returns #{sz} but cannot read") if n.nil? dev << buf rest -= n.bytesize end end
def dump_last_chunk
def dump_last_chunk dump_chunk_size(0) end
def init_request(body = nil, boundary = nil)
def init_request(body = nil, boundary = nil) @boundary = boundary @positions = {} set_content(body, boundary) @chunk_size = DEFAULT_CHUNK_SIZE self end
def init_response(body = nil)
def init_response(body = nil) @body = body if @body.respond_to?(:bytesize) @size = @body.bytesize elsif @body.respond_to?(:size) @size = @body.size else @size = nil end self end
def initialize
Creates a Message::Body. Use init_request or init_response
def initialize @body = nil @size = nil @positions = nil @chunk_size = nil end
def params_from_file(value)
def params_from_file(value) params = {} original_filename = value.respond_to?(:original_filename) ? value.original_filename : nil path = value.respond_to?(:path) ? value.path : nil params['filename'] = original_filename || File.basename(path || '') # Creation time is not available from File::Stat if value.respond_to?(:mtime) params['modification-date'] = value.mtime.rfc822 end if value.respond_to?(:atime) params['read-date'] = value.atime.rfc822 end params end
def remember_pos(io)
def remember_pos(io) # IO may not support it (ex. IO.pipe) @positions[io] = io.pos if io.respond_to?(:pos) end
def reset_pos(io)
def reset_pos(io) io.pos = @positions[io] if @positions.key?(io) end
def set_content(body, boundary = nil)
def set_content(body, boundary = nil) if Message.file?(body) # uses Transfer-Encoding: chunked if body does not respond to :size. # bear in mind that server may not support it. at least ruby's CGI doesn't. @body = body remember_pos(@body) @size = body.respond_to?(:size) ? body.size - body.pos : nil elsif boundary and Message.multiparam_query?(body) @body = build_query_multipart_str(body, boundary) @size = @body.size else @body = Message.create_query_part_str(body) @size = @body.bytesize end end