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

Add MIME::MediaType #7077

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Add MIME::MediaType
  • Loading branch information
straight-shoota committed Dec 20, 2018
commit 975255b3655e41ba248e94a54f521425de632dea
291 changes: 291 additions & 0 deletions spec/std/mime/media_type_spec.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,291 @@
require "spec"
require "mime/media_type"

private def parse(string)
type = MIME::MediaType.parse(string)

{type.media_type, type.@params}
end

private def assert_format(string, format = string, file = __FILE__, line = __LINE__)
MIME::MediaType.parse(string).to_s.should eq(format), file, line
end

describe MIME::MediaType do
describe ".new" do
it "create new instance" do
media_type = MIME::MediaType.new("foo/bar", {"param" => "value"})
media_type.media_type.should eq "foo/bar"
media_type["param"].should eq "value"
end

it "raises for invalid parameter name" do
expect_raises(MIME::Error, %q(Invalid parameter name "ß")) do
MIME::MediaType.new("foo/bar", {"param" => "value", "ß" => "invalid"})
end
end
end

describe ".parse" do
it "parses media type" do
MIME::MediaType.parse("text/html; charset=utf-8").media_type.should eq "text/html"
end

it "parses params" do
parse("text/html; charset=utf-8").should eq({"text/html", {"charset" => "utf-8"}})

parse("text/html; charset=us-ascii").should eq({"text/html", {"charset" => "us-ascii"}})

parse("text/html; foo = bar; bar= foo ;").should eq({"text/html", {"foo" => "bar", "bar" => "foo", "charset" => "utf-8"}})

parse(%(form-data; name="foo")).should eq({"form-data", {"name" => "foo"}})
parse(%(form-data; name ="foo")).should eq({"form-data", {"name" => "foo"}})
parse(%(form-data; name= "foo")).should eq({"form-data", {"name" => "foo"}})

parse(%( form-data ; name=foo)).should eq({"form-data", {"name" => "foo"}})

parse(%(FORM-DATA;name="foo")).should eq({"form-data", {"name" => "foo"}})

parse(%( FORM-DATA ; name="foo")).should eq({"form-data", {"name" => "foo"}})

expect_raises(MIME::Error, "Missing media type") { parse("") }
expect_raises(MIME::Error, "Missing media type") { parse(" ") }
expect_raises(MIME::Error, "Missing media type") { parse(";") }
expect_raises(MIME::Error, "Missing media type") { parse(" ;") }

expect_raises(MIME::Error, "Missing attribute name at 10") { parse("form-data;=foo") }
expect_raises(MIME::Error, "Missing attribute name at ") { parse("form-data; =foo") }
expect_raises(MIME::Error, "Missing attribute value") { parse("form-data;foo") }

parse(%(form-data; key=value; blah="value";name="foo" )).should eq({"form-data", {"key" => "value", "blah" => "value", "name" => "foo"}})

expect_raises(MIME::Error, "Duplicate key 'key' at 15") { parse(%(foo; key=val1; key=the-key-appears-again-which-is-bogus)) }

parse(%(FORM-DATA;NAMe="foo")).should eq({"form-data", {"name" => "foo"}})

# From RFC 2231:

parse(%(application/x-stuff; title*=us-ascii'en-us'This%20is%20%2A%2A%2Afun%2A%2A%2A)).should eq({"application/x-stuff", {"title" => "This is ***fun***"}})

parse(%(message/external-body; access-type=URL; URL*0="ftp://";URL*1="cs.utk.edu/pub/moore/bulk-mailer/bulk-mailer.tar")).should eq({
"message/external-body",
{"access-type" => "URL", "url" => "ftp://cs.utk.edu/pub/moore/bulk-mailer/bulk-mailer.tar"},
})

# Tests from http://greenbytes.de/tech/tc2231/

# attonly
parse(%(attachment)).should eq({"attachment", {} of String => String})
# attonlyucase
parse(%(ATTACHMENT)).should eq({"attachment", {} of String => String})
# attwithasciifilename
parse(%(attachment; filename="foo.html")).should eq({"attachment", {"filename" => "foo.html"}})
# attwithasciifilename25
parse(%(attachment; filename="0000000000111111111122222")).should eq({"attachment", {"filename" => "0000000000111111111122222"}})
# attwithasciifilename35
parse(%(attachment; filename="00000000001111111111222222222233333")).should eq({"attachment", {"filename" => "00000000001111111111222222222233333"}})
# attwithasciifnescapedchar
parse(%(attachment; filename="f\\oo.html")).should eq({"attachment", {"filename" => "f\\oo.html"}})
# attwithasciifnescapedquote
parse(%(attachment; filename="\\"quoting\\" tested.html")).should eq({"attachment", {"filename" => %("quoting" tested.html)}})
# attwithquotedsemicolon
parse(%(attachment; filename="Here's a semicolon;.html")).should eq({"attachment", {"filename" => "Here's a semicolon;.html"}})
# attwithfilenameandextparam
parse(%(attachment; foo="bar"; filename="foo.html")).should eq({"attachment", {"foo" => "bar", "filename" => "foo.html"}})
# attwithfilenameandextparamescaped
parse(%(attachment; foo="\\"\\\\";filename="foo.html")).should eq({"attachment", {"foo" => %("\\), "filename" => "foo.html"}})
# attwithasciifilenameucase
parse(%(attachment; FILENAME="foo.html")).should eq({"attachment", {"filename" => "foo.html"}})
# attwithasciifilenamenq
parse(%(attachment; filename=foo.html)).should eq({"attachment", {"filename" => "foo.html"}})
# attwithasciifilenamenqs
parse(%(attachment; filename=foo.html ;)).should eq({"attachment", {"filename" => "foo.html"}})
# attwithfntokensq
parse(%(attachment; filename='foo.html')).should eq({"attachment", {"filename" => "'foo.html'"}})
# attwithisofnplain
parse(%(attachment; filename="foo-ä.html")).should eq({"attachment", {"filename" => "foo-ä.html"}})
# attwithutf8fnplain
parse(%(attachment; filename="foo-ä.html")).should eq({"attachment", {"filename" => "foo-ä.html"}})
# attwithfnrawpctenca
parse(%(attachment; filename="foo-%41.html")).should eq({"attachment", {"filename" => "foo-%41.html"}})
# attwithfnusingpct
parse(%(attachment; filename="50%.html")).should eq({"attachment", {"filename" => "50%.html"}})
# attwithfnrawpctencaq
parse(%(attachment; filename="foo-%\\41.html")).should eq({"attachment", {"filename" => "foo-%\\41.html"}})
# attwithnamepct
parse(%(attachment; name="foo-%41.html")).should eq({"attachment", {"name" => "foo-%41.html"}})
# attwithfilenamepctandiso
parse(%(attachment; name="ä-%41.html")).should eq({"attachment", {"name" => "ä-%41.html"}})
# attwithfnrawpctenclong
parse(%(attachment; filename="foo-%c3%a4-%e2%82%ac.html")).should eq({"attachment", {"filename" => "foo-%c3%a4-%e2%82%ac.html"}})
# attwithasciifilenamews1
parse(%(attachment; filename ="foo.html")).should eq({"attachment", {"filename" => "foo.html"}})
# attmissingdisposition
expect_raises(MIME::Error, "Invalid character '=' at 8") { parse(%(filename=foo.html)) }
# attmissingdisposition2
expect_raises(MIME::Error, "Invalid character '=' at 1") { parse(%(x=y; filename=foo.html)) }
# attmissingdisposition3
expect_raises(MIME::Error, "Invalid character '\"' at 0") { parse(%("foo; filename=bar;baz"; filename=qux)) }
# attmissingdisposition4
expect_raises(MIME::Error, "Invalid character '=' at 8") { parse(%(filename=foo.html, filename=bar.html)) }
# emptydisposition
expect_raises(MIME::Error, "Missing media type") { parse(%(; filename=foo.html)) }
# doublecolon
expect_raises(MIME::Error, "Invalid character ':' at 0") { parse(%(: inline; attachment; filename=foo.html)) }
# attandinline
expect_raises(MIME::Error, "Invalid ';' at 18, expecting '='") { parse(%(inline; attachment; filename=foo.html)) }
# attandinline2
expect_raises(MIME::Error, "Invalid ';' at 18, expecting '='") { parse(%(attachment; inline; filename=foo.html)) }
# attbrokenquotedfn
expect_raises(MIME::Error, "Invalid character '.' at 31, expecting ';'") { parse(%(attachment; filename="foo.html".txt)) }
# attbrokenquotedfn2
expect_raises(MIME::Error, "Unclosed quote at 25") { parse(%(attachment; filename="bar)) }
# attbrokenquotedfn3
expect_raises(MIME::Error, "Unexpected '\"' at 24") { parse(%(attachment; filename=foo"bar;baz"qux)) }
# attmultinstances
expect_raises(MIME::Error, "Duplicate key 'filename' at 43") { parse(%(attachment; filename=foo.html, attachment; filename=bar.html)) }
# attmissingdelim
expect_raises(MIME::Error, "Unexpected '=' at 28") { parse(%(attachment; foo=foo filename=bar)) }
# attmissingdelim2
expect_raises(MIME::Error, "Unexpected '=' at 28") { parse(%(attachment; filename=bar foo=foo)) }
# attmissingdelim3
expect_raises(MIME::Error, "Invalid character '=' at 19") { parse(%(attachment filename=bar)) }
# attreversed
expect_raises(MIME::Error, "Invalid character '=' at 8") { parse(%(filename=foo.html; attachment)) }
# attconfusedparam
parse(%(attachment; xfilename=foo.html)).should eq({"attachment", {"xfilename" => "foo.html"}})
# attcdate
parse(%(attachment; creation-date="Wed, 12 Feb 1997 16:29:51 -0500")).should eq({"attachment", {"creation-date" => "Wed, 12 Feb 1997 16:29:51 -0500"}})
# attmdate
parse(%(attachment; modification-date="Wed, 12 Feb 1997 16:29:51 -0500")).should eq({"attachment", {"modification-date" => "Wed, 12 Feb 1997 16:29:51 -0500"}})
# dispext
parse("foobar").should eq({"foobar", {} of String => String})
# dispextbadfn
parse(%(attachment; example="filename=example.txt")).should eq({"attachment", {"example" => "filename=example.txt"}})
# # attwithfn2231utf8
parse(%(attachment; filename*=UTF-8''foo-%c3%a4-%e2%82%ac.html)).should eq({"attachment", {"filename" => "foo-ä-€.html"}})
# attwithfn2231noc
parse(%(attachment; filename*=''foo-%c3%a4-%e2%82%ac.html)).should eq({"attachment", {} of String => String})
# attwithfn2231utf8comp
parse(%(attachment; filename*=UTF-8''foo-a%cc%88.html)).should eq({"attachment", {"filename" => "foo-ä.html"}})
# attwithfn2231ws2
parse(%(attachment; filename*= UTF-8''foo-%c3%a4.html)).should eq({"attachment", {"filename" => "foo-ä.html"}})
# attwithfn2231ws3
parse(%(attachment; filename* =UTF-8''foo-%c3%a4.html)).should eq({"attachment", {"filename" => "foo-ä.html"}})
# attwithfn2231quot
parse(%(attachment; filename*="UTF-8''foo-%c3%a4.html")).should eq({"attachment", {"filename" => "foo-ä.html"}})
# attwithfn2231quot2
parse(%(attachment; filename*="foo%20bar.html")).should eq({"attachment", {} of String => String})
# attwithfn2231singleqmissing
parse(%(attachment; filename*=UTF-8'foo-%c3%a4.html)).should eq({"attachment", {} of String => String})
# attwithfn2231nbadpct1
parse(%(attachment; filename*=UTF-8''foo%)).should eq({"attachment", {} of String => String})
# attwithfn2231nbadpct2
parse(%(attachment; filename*=UTF-8''f%oo.html)).should eq({"attachment", {} of String => String})
# attwithfn2231dpct
parse(%(attachment; filename*=UTF-8''A-%2541.html)).should eq({"attachment", {"filename" => "A-%41.html"}})
# attfncont
parse(%(attachment; filename*0="foo."; filename*1="html")).should eq({"attachment", {"filename" => "foo.html"}})

expect_raises(MIME::Error, "Duplicate key 'foo *0' at 25") { parse(%(attachment; foo*0="foo"; foo *0="bar")) }
expect_raises(MIME::Error, "Duplicate key 'foo* 0' at 25") { parse(%(attachment; foo*0="foo"; foo* 0="bar")) }
expect_raises(MIME::Error, "Invalid key 'foo*foo' at 12") { parse(%(attachment; foo*foo=foo)) }

# attfncontenc
parse(%(attachment; filename*0*=UTF-8''foo-%c3%a4; filename*1=".html")).should eq({"attachment", {"filename" => "foo-ä.html"}})
# attfncontlz
parse(%(attachment; filename*0="foo"; filename*01="bar")).should eq({"attachment", {"filename" => "foo"}})
# attfncontnc
parse(%(attachment; filename*0="foo"; filename*2="bar")).should eq({"attachment", {"filename" => "foo"}})
# attfnconts1
parse(%(attachment; filename*1="foo."; filename*2="html")).should eq({"attachment", {} of String => String})
# attfncontord
parse(%(attachment; filename*1="bar"; filename*0="foo")).should eq({"attachment", {"filename" => "foobar"}})
# attfnboth
parse(%(attachment; filename="foo-ae.html"; filename*=UTF-8''foo-%c3%a4.html)).should eq({"attachment", {"filename" => "foo-ä.html"}})
# attfnboth2
parse(%(attachment; filename*=UTF-8''foo-%c3%a4.html; filename="foo-ae.html")).should eq({"attachment", {"filename" => "foo-ä.html"}})
# attfnboth3
parse(%(attachment; filename*0*=ISO-8859-15''euro-sign%3d%a4; filename*=ISO-8859-1''currency-sign%3d%a4)).should eq({"attachment", {"filename" => "currency-sign=¤"}})
# attnewandfn
parse(%(attachment; foobar=x; filename="foo.html")).should eq({"attachment", {"foobar" => "x", "filename" => "foo.html"}})

# Browsers also just send UTF-8 directly without RFC 2231,
# at least when the source page is served with UTF-8.
parse(%(form-data; firstname="Брэд"; lastname="Фицпатрик")).should eq({"form-data", {"firstname" => "Брэд", "lastname" => "Фицпатрик"}})

# Empty string used to be mishandled.
parse(%(foo; bar="")).should eq({"foo", {"bar" => ""}})

# Microsoft browsers in intranet mode do not think they need to escape \ in file name.
parse(%(form-data; name="file"; filename="C:\\dev\\go\\robots.txt")).should eq({"form-data", {"name" => "file", "filename" => "C:\\dev\\go\\robots.txt"}})

# Skip whitespace after =
parse(%(form-data; foo= foo)).should eq({"form-data", {"foo" => "foo"}})
parse(%(form-data; foo= " foo ")).should eq({"form-data", {"foo" => " foo "}})
end

it "sets default charset to utf-8 for text media types" do
type = MIME::MediaType.parse("text/html")
type["charset"]?.should eq "utf-8"

type = MIME::MediaType.parse("application/html")
type["charset"]?.should be_nil
end
end

it "#sub_type" do
MIME::MediaType.new("text/plain").sub_type.should eq "plain"
MIME::MediaType.new("foo").sub_type.should be_nil
end

it "#type" do
MIME::MediaType.new("text/plain").type.should eq "text"
MIME::MediaType.new("foo").type.should eq "foo"
end

it "#to_s" do
assert_format "foo/bar"
assert_format "text/plain; charset=utf-8"
assert_format "foo/bar; foo=bar; bar=foo;", "foo/bar; foo=bar; bar=foo"
assert_format "form-data; name=foo"
assert_format "attachment"
assert_format %(attachment; faa="\\"\\\\"; filename="foo.html?")
assert_format %(attachment; filename="foo-ä.html")
assert_format %(attachment; filename=foo-%41.html)
assert_format %(attachment; filename="\\"quoting\\" tested.html")
end

it "#fetch" do
MIME::MediaType.parse("x-application/example").fetch("foo", "baz").should eq "baz"
MIME::MediaType.parse("x-application/example; foo=bar").fetch("foo", "baz").should eq "bar"
MIME::MediaType.parse("x-application/example").fetch("foo") { |key| key }.should eq "foo"
MIME::MediaType.parse("x-application/example; foo=bar").fetch("foo") { |key| key }.should eq "bar"
end

it "#[]=" do
mime_type = MIME::MediaType.parse("x-application/example")
mime_type["foo"] = "bar"
mime_type["foo"].should eq "bar"

expect_raises(MIME::Error, "Invalid parameter name") do
mime_type["ä"] = "foo"
end
end

it "#each_parameter" do
mime_type = MIME::MediaType.parse("x-application/example; foo=bar; bar=baz")

arr = [] of String
mime_type.each_parameter do |key, value|
arr << "#{key}=#{value}"
end
arr.should eq ["foo=bar", "bar=baz"]

arr = [] of String
mime_type.each_parameter.each do |key, value|
arr << "#{key}=#{value}"
end
arr.should eq ["foo=bar", "bar=baz"]
end
end
Loading