Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move HTTP::Multipart to MIME::Multipart #7085

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
@@ -1,10 +1,10 @@
require "spec"
require "http"
require "mime/multipart"

describe HTTP::Multipart::Builder do
describe MIME::Multipart::Builder do
it "generates valid multipart messages" do
io = IO::Memory.new
builder = HTTP::Multipart::Builder.new(io, "fixed-boundary")
builder = MIME::Multipart::Builder.new(io, "fixed-boundary")

headers = HTTP::Headers{"X-Foo" => "bar"}
builder.body_part headers, "body part 1"
Expand All @@ -30,7 +30,7 @@ describe HTTP::Multipart::Builder do

it "generates valid multipart messages with preamble and epilogue" do
io = IO::Memory.new
builder = HTTP::Multipart::Builder.new(io, "fixed-boundary")
builder = MIME::Multipart::Builder.new(io, "fixed-boundary")

builder.preamble "Here is a preamble to explain why multipart/mixed "
builder.preamble "exists and why your mail client should support it"
Expand Down Expand Up @@ -64,15 +64,15 @@ describe HTTP::Multipart::Builder do

describe "#content_type" do
it "calculates the content type" do
builder = HTTP::Multipart::Builder.new(IO::Memory.new, "a delimiter string with a quote in \"")
builder = MIME::Multipart::Builder.new(IO::Memory.new, "a delimiter string with a quote in \"")
builder.content_type("alternative").should eq(%q(multipart/alternative; boundary="a delimiter string with a quote in \""))
end
end

describe ".preamble" do
it "accepts different data types" do
io = IO::Memory.new
builder = HTTP::Multipart::Builder.new(io, "boundary")
builder = MIME::Multipart::Builder.new(io, "boundary")

builder.preamble "string\r\n"
builder.preamble "slice\r\n".to_slice
Expand Down Expand Up @@ -103,10 +103,10 @@ describe HTTP::Multipart::Builder do
end

it "raises when called after starting the body" do
builder = HTTP::Multipart::Builder.new(IO::Memory.new)
builder = MIME::Multipart::Builder.new(IO::Memory.new)

builder.body_part HTTP::Headers.new, "test"
expect_raises(HTTP::Multipart::Error, "Cannot generate preamble: body already started") do
expect_raises(MIME::Multipart::Error, "Cannot generate preamble: body already started") do
builder.preamble "test"
end
end
Expand All @@ -115,7 +115,7 @@ describe HTTP::Multipart::Builder do
describe ".body_part" do
it "accepts different data types" do
io = IO::Memory.new
builder = HTTP::Multipart::Builder.new(io, "boundary")
builder = MIME::Multipart::Builder.new(io, "boundary")

headers = HTTP::Headers{"X-Foo" => "Bar"}

Expand Down Expand Up @@ -160,21 +160,21 @@ describe HTTP::Multipart::Builder do
end

it "raises when called after finishing" do
builder = HTTP::Multipart::Builder.new(IO::Memory.new)
builder = MIME::Multipart::Builder.new(IO::Memory.new)

builder.body_part HTTP::Headers.new, "test"
builder.finish
expect_raises(HTTP::Multipart::Error, "Cannot generate body part: already finished") do
expect_raises(MIME::Multipart::Error, "Cannot generate body part: already finished") do
builder.body_part HTTP::Headers.new, "test"
end
end

it "raises when called after epilogue" do
builder = HTTP::Multipart::Builder.new(IO::Memory.new)
builder = MIME::Multipart::Builder.new(IO::Memory.new)

builder.body_part HTTP::Headers.new, "test"
builder.epilogue "test"
expect_raises(HTTP::Multipart::Error, "Cannot generate body part: after epilogue") do
expect_raises(MIME::Multipart::Error, "Cannot generate body part: after epilogue") do
builder.body_part HTTP::Headers.new, "test"
end
end
Expand All @@ -183,7 +183,7 @@ describe HTTP::Multipart::Builder do
describe ".epilogue" do
it "accepts different data types" do
io = IO::Memory.new
builder = HTTP::Multipart::Builder.new(io, "boundary")
builder = MIME::Multipart::Builder.new(io, "boundary")

builder.body_part(HTTP::Headers.new)

Expand Down Expand Up @@ -215,53 +215,53 @@ describe HTTP::Multipart::Builder do
end

it "raises when called after finishing" do
builder = HTTP::Multipart::Builder.new(IO::Memory.new)
builder = MIME::Multipart::Builder.new(IO::Memory.new)

builder.body_part HTTP::Headers.new, "test"
builder.finish

expect_raises(HTTP::Multipart::Error, "Cannot generate epilogue: already finished") do
expect_raises(MIME::Multipart::Error, "Cannot generate epilogue: already finished") do
builder.epilogue "test"
end
end

it "raises when called with no body parts" do
builder = HTTP::Multipart::Builder.new(IO::Memory.new)
builder = MIME::Multipart::Builder.new(IO::Memory.new)

expect_raises(HTTP::Multipart::Error, "Cannot generate epilogue: no body parts") do
expect_raises(MIME::Multipart::Error, "Cannot generate epilogue: no body parts") do
builder.epilogue "test"
end

builder.preamble "test"

expect_raises(HTTP::Multipart::Error, "Cannot generate epilogue: no body parts") do
expect_raises(MIME::Multipart::Error, "Cannot generate epilogue: no body parts") do
builder.epilogue "test"
end
end
end

describe ".finish" do
it "raises if no body exists" do
builder = HTTP::Multipart::Builder.new(IO::Memory.new)
builder = MIME::Multipart::Builder.new(IO::Memory.new)

expect_raises(HTTP::Multipart::Error, "Cannot finish multipart: no body parts") do
expect_raises(MIME::Multipart::Error, "Cannot finish multipart: no body parts") do
builder.finish
end

builder.preamble "test"

expect_raises(HTTP::Multipart::Error, "Cannot finish multipart: no body parts") do
expect_raises(MIME::Multipart::Error, "Cannot finish multipart: no body parts") do
builder.finish
end
end

it "raises if already finished" do
builder = HTTP::Multipart::Builder.new(IO::Memory.new)
builder = MIME::Multipart::Builder.new(IO::Memory.new)

builder.body_part HTTP::Headers.new, "test"
builder.finish

expect_raises(HTTP::Multipart::Error, "Cannot finish multipart: already finished") do
expect_raises(MIME::Multipart::Error, "Cannot finish multipart: already finished") do
builder.finish
end
end
Expand Down
@@ -1,9 +1,9 @@
require "spec"
require "http"
require "mime/multipart"

private def parse(delim, data, *, gsub = true)
data_io = IO::Memory.new(gsub ? data.gsub('\n', "\r\n") : data)
parser = HTTP::Multipart::Parser.new(data_io, delim)
parser = MIME::Multipart::Parser.new(data_io, delim)

parsed = [] of {headers: HTTP::Headers, body: String}
while parser.has_next?
Expand All @@ -12,7 +12,7 @@ private def parse(delim, data, *, gsub = true)
parsed
end

describe HTTP::Multipart::Parser do
describe MIME::Multipart::Parser do
it "parses basic multipart messages" do
data = parse "AaB03x", <<-MULTIPART
--AaB03x
Expand Down Expand Up @@ -60,19 +60,19 @@ describe HTTP::Multipart::Parser do
end

it "handles invalid multipart data" do
expect_raises(HTTP::Multipart::Error, "EOF reading delimiter") do
expect_raises(MIME::Multipart::Error, "EOF reading delimiter") do
parse "AaB03x", "--AaB03x", gsub: false
end

expect_raises(HTTP::Multipart::Error, "EOF reading delimiter") do
expect_raises(MIME::Multipart::Error, "EOF reading delimiter") do
parse "AaB03x", "--AaB03x\r\n\r\n--AaB03x", gsub: false
end

expect_raises(HTTP::Multipart::Error, "EOF reading delimiter padding") do
expect_raises(MIME::Multipart::Error, "EOF reading delimiter padding") do
parse "AaB03x", "--AaB03x ", gsub: false
end

expect_raises(HTTP::Multipart::Error, "padding contained non-whitespace character") do
expect_raises(MIME::Multipart::Error, "padding contained non-whitespace character") do
parse "AaB03x", "--AaB03x foo \r\n\r\n--AaB03x--", gsub: false
end
end
Expand All @@ -89,26 +89,26 @@ describe HTTP::Multipart::Parser do
Foo
--AaB03x--
MULTIPART
parser = HTTP::Multipart::Parser.new(IO::Memory.new(input.gsub('\n', "\r\n")), "AaB03x")
parser = MIME::Multipart::Parser.new(IO::Memory.new(input.gsub('\n', "\r\n")), "AaB03x")

parser.next { }
parser.has_next?.should eq(false)

expect_raises(HTTP::Multipart::Error, "Multipart parser already finished parsing") do
expect_raises(MIME::Multipart::Error, "Multipart parser already finished parsing") do
parser.next { }
end
end

it "raises calling #next after errored" do
parser = HTTP::Multipart::Parser.new(IO::Memory.new("--AaB03x--"), "AaB03x")
parser = MIME::Multipart::Parser.new(IO::Memory.new("--AaB03x--"), "AaB03x")

expect_raises(HTTP::Multipart::Error, "no parts") do
expect_raises(MIME::Multipart::Error, "no parts") do
parser.next { }
end

parser.has_next?.should eq(false)

expect_raises(HTTP::Multipart::Error, "Multipart parser is in an errored state") do
expect_raises(MIME::Multipart::Error, "Multipart parser is in an errored state") do
parser.next { }
end
end
Expand All @@ -126,7 +126,7 @@ describe HTTP::Multipart::Parser do
--b--
MULTIPART

parser = HTTP::Multipart::Parser.new(IO::Memory.new(input.gsub('\n', "\r\n")), "b")
parser = MIME::Multipart::Parser.new(IO::Memory.new(input.gsub('\n', "\r\n")), "b")

ios = [] of IO

Expand Down
@@ -1,11 +1,11 @@
require "spec"
require "http"
require "mime/multipart"

describe HTTP::Multipart do
describe MIME::Multipart do
describe ".parse" do
it "parses multipart messages" do
multipart = "--aA40\r\nContent-Type: text/plain\r\n\r\nabcd\r\n--aA40--"
HTTP::Multipart.parse(IO::Memory.new(multipart), "aA40") do |headers, io|
MIME::Multipart.parse(IO::Memory.new(multipart), "aA40") do |headers, io|
headers["Content-Type"].should eq("text/plain")
io.gets_to_end.should eq("abcd")
end
Expand All @@ -15,12 +15,12 @@ describe HTTP::Multipart do
describe ".parse_boundary" do
it "parses unquoted boundaries" do
content_type = "multipart/mixed; boundary=a_-47HDS"
HTTP::Multipart.parse_boundary(content_type).should eq("a_-47HDS")
MIME::Multipart.parse_boundary(content_type).should eq("a_-47HDS")
end

it "parses quoted boundaries" do
content_type = %{multipart/mixed; boundary="aA_-<>()"}
HTTP::Multipart.parse_boundary(content_type).should eq(%{aA_-<>()})
MIME::Multipart.parse_boundary(content_type).should eq(%{aA_-<>()})
end
end

Expand All @@ -30,7 +30,7 @@ describe HTTP::Multipart do
body = "--aA40\r\nContent-Type: text/plain\r\n\r\nbody\r\n--aA40--"
request = HTTP::Request.new("POST", "/", headers, body)

HTTP::Multipart.parse(request) do |headers, io|
MIME::Multipart.parse(request) do |headers, io|
headers["Content-Type"].should eq("text/plain")
io.gets_to_end.should eq("body")
end
Expand Down
7 changes: 4 additions & 3 deletions src/http/formdata.cr
@@ -1,4 +1,5 @@
require "./formdata/**"
require "mime/multipart"

# Contains utilities for parsing `multipart/form-data` messages, which are
# commonly used for encoding HTML form data.
Expand Down Expand Up @@ -113,7 +114,7 @@ module HTTP::FormData
body = request.body
raise Error.new "Cannot extract form-data from HTTP request: body is empty" unless body

boundary = request.headers["Content-Type"]?.try { |header| Multipart.parse_boundary(header) }
boundary = request.headers["Content-Type"]?.try { |header| MIME::Multipart.parse_boundary(header) }
raise Error.new "Cannot extract form-data from HTTP request: could not find boundary in Content-Type" unless boundary

parse(body, boundary) { |part| yield part }
Expand Down Expand Up @@ -178,7 +179,7 @@ module HTTP::FormData
# ```
#
# See: `FormData::Builder`
def self.build(io, boundary = Multipart.generate_boundary)
def self.build(io, boundary = MIME::Multipart.generate_boundary)
builder = Builder.new(io, boundary)
yield builder
builder.finish
Expand All @@ -202,7 +203,7 @@ module HTTP::FormData
# ```
#
# See: `FormData::Builder`
def self.build(response : HTTP::Server::Response, boundary = Multipart.generate_boundary)
def self.build(response : HTTP::Server::Response, boundary = MIME::Multipart.generate_boundary)
builder = Builder.new(response, boundary)
yield builder
builder.finish
Expand Down
2 changes: 1 addition & 1 deletion src/http/formdata/builder.cr
Expand Up @@ -15,7 +15,7 @@ module HTTP::FormData
class Builder
# Creates a new `FormData::Builder` which writes to *io*, using the
# multipart boundary *boundary*.
def initialize(@io : IO, @boundary = Multipart.generate_boundary)
def initialize(@io : IO, @boundary = MIME::Multipart.generate_boundary)
@state = :START
end

Expand Down
2 changes: 1 addition & 1 deletion src/http/formdata/parser.cr
Expand Up @@ -2,7 +2,7 @@ module HTTP::FormData
class Parser
# Creates a new parser which parses *io* with multipart boundary *boundary*.
def initialize(io, boundary)
@multipart = Multipart::Parser.new(io, boundary)
@multipart = MIME::Multipart::Parser.new(io, boundary)
end

# Parses the next form-data part and yields field name, io, `FileMetadata`,
Expand Down
12 changes: 6 additions & 6 deletions src/http/multipart.cr → src/mime/multipart.cr
Expand Up @@ -2,11 +2,11 @@ require "random/secure"
require "./multipart/*"
require "mime/media_type"

# The `HTTP::Multipart` module contains utilities for parsing MIME multipart
# The `MIME::Multipart` module contains utilities for parsing MIME multipart
# messages, which contain multiple body parts, each containing a header section
# and binary body. The `multipart/form-data` content-type has a separate set of
# utilities in the `HTTP::FormData` module.
module HTTP::Multipart
module MIME::Multipart
# Parses a MIME multipart message, yielding `HTTP::Headers` and an `IO` for
# each body part.
#
Expand All @@ -15,7 +15,7 @@ module HTTP::Multipart
#
# ```
# multipart = "--aA40\r\nContent-Type: text/plain\r\n\r\nbody\r\n--aA40--"
# HTTP::Multipart.parse(IO::Memory.new(multipart), "aA40") do |headers, io|
# MIME::Multipart.parse(IO::Memory.new(multipart), "aA40") do |headers, io|
# headers["Content-Type"] # => "text/plain"
# io.gets_to_end # => "body"
# end
Expand All @@ -33,7 +33,7 @@ module HTTP::Multipart
# `nil` is the boundary was not found.
#
# ```
# HTTP::Multipart.parse_boundary("multipart/mixed; boundary=\"abcde\"") # => "abcde"
# MIME::Multipart.parse_boundary("multipart/mixed; boundary=\"abcde\"") # => "abcde"
# ```
def self.parse_boundary(content_type)
type = MIME::MediaType.parse?(content_type)
Expand All @@ -58,7 +58,7 @@ module HTTP::Multipart
# body = "--aA40\r\nContent-Type: text/plain\r\n\r\nbody\r\n--aA40--"
# request = HTTP::Request.new("POST", "/", headers, body)
#
# HTTP::Multipart.parse(request) do |headers, io|
# MIME::Multipart.parse(request) do |headers, io|
# headers["Content-Type"] # => "text/plain"
# io.gets_to_end # => "body"
# end
Expand Down Expand Up @@ -93,7 +93,7 @@ module HTTP::Multipart
# Returns a unique string suitable for use as a multipart boundary.
#
# ```
# HTTP::Multipart.generate_boundary # => "---------------------------dQu6bXHYb4m5zrRC3xPTGwV"
# MIME::Multipart.generate_boundary # => "---------------------------dQu6bXHYb4m5zrRC3xPTGwV"
# ```
def self.generate_boundary
"--------------------------#{Random::Secure.urlsafe_base64(18)}"
Expand Down