diff --git a/spec/std/mime/multipart_spec.cr b/spec/std/mime/multipart_spec.cr index b3fd52f2fac8..5aef9a98634c 100644 --- a/spec/std/mime/multipart_spec.cr +++ b/spec/std/mime/multipart_spec.cr @@ -35,5 +35,20 @@ describe MIME::Multipart do io.gets_to_end.should eq("body") end end + + it "parses multipart messages from HTTP client responses" do + headers = HTTP::Headers{"Content-Type" => "multipart/byteranges; boundary=aA40"} + body = "--aA40\r\nContent-Type: text/plain\r\n\r\nbody\r\n--aA40--" + response = HTTP::Client::Response.new( + status: :ok, + headers: headers, + body: body, + ) + + MIME::Multipart.parse(response) do |headers, io| + headers["Content-Type"].should eq("text/plain") + io.gets_to_end.should eq("body") + end + end end end diff --git a/src/mime/multipart.cr b/src/mime/multipart.cr index 89175b763ca9..943af6a15167 100644 --- a/src/mime/multipart.cr +++ b/src/mime/multipart.cr @@ -69,7 +69,9 @@ module MIME::Multipart # # See: `Multipart::Parser` def self.parse(request : HTTP::Request, &) - boundary = parse_boundary(request.headers["Content-Type"]) + if content_type = request.headers["Content-Type"]? + boundary = parse_boundary(content_type) + end return nil unless boundary body = request.body @@ -77,6 +79,47 @@ module MIME::Multipart parse(body, boundary) { |headers, io| yield headers, io } end + # Parses a MIME multipart message, yielding `HTTP::Headers` and an `IO` for + # each body part. + # + # Please note that the IO object yielded to the block is only valid while the + # block is executing. The IO is closed as soon as the supplied block returns. + # + # ``` + # require "http" + # require "mime/multipart" + # + # headers = HTTP::Headers{"Content-Type" => "multipart/byteranges; boundary=aA40"} + # body = "--aA40\r\nContent-Type: text/plain\r\n\r\nbody\r\n--aA40--" + # response = HTTP::Client::Response.new( + # status: :ok, + # headers: headers, + # body: body, + # ) + # + # MIME::Multipart.parse(response) do |headers, io| + # headers["Content-Type"] # => "text/plain" + # io.gets_to_end # => "body" + # end + # ``` + # + # See: `Multipart::Parser` + def self.parse(response : HTTP::Client::Response, &) + if content_type = response.headers["Content-Type"]? + boundary = parse_boundary(content_type) + end + return nil unless boundary + + if body = response.body.presence + body = IO::Memory.new(body) + else + body = response.body_io? + end + return nil unless body + + parse(body, boundary) { |headers, io| yield headers, io } + end + # Yields a `Multipart::Builder` to the given block, writing to *io* and # using *boundary*. `#finish` is automatically called on the builder. def self.build(io : IO, boundary : String = Multipart.generate_boundary, &)