-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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 registry #5765
Add MIME registry #5765
Conversation
bbea07c
to
c8ad4eb
Compare
src/mime.cr
Outdated
# | ||
# ``` | ||
# require "mime" | ||
# MIME[".html"] # => "text/html" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is MIME.[]
implemented?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, no. I was unsure about that. Should we have MIME.[]
and/or MIME.fetch
? Or a different name?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Personally, I'm finding Klass.[]
aliased to Klass.fetch
as a quite nice syntactic sugar, but of course there's the Crystal stand on aliasing...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No aliases please. Let's just name these methods normally and leave aside the unnecessary syntax sugar.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I figure it doesn't make much sense here anyway.
633ba09
to
a4b5703
Compare
I've added Anything else? |
src/mime.cr
Outdated
".htm" => "text/html; charset=utf-8", | ||
".html" => "text/html; charset=utf-8", | ||
".jpg" => "image/jpeg", | ||
".jepg" => "image/jpeg", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Small typo 🙂
a4b5703
to
d9da99a
Compare
src/crystal/system/unix/mime.cr
Outdated
] | ||
|
||
{% if flag?(:freebsd) %} | ||
MIME_SOURCES << "/usr/local/etc/mime.types" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
macro indent.
mediatype = parse_media_type(type) || raise Error.new "invalid media type: #{type}" | ||
|
||
@@types[extension] = type | ||
@@types_lower[extension.downcase] = type |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But you always downcase on line 99.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, it shouldn't be downcased before.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why bother keeping two hashes, and not just downcase
-d one?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Sija Resolution should be case-sensitive by default and only fall back to case-insensitive if there is no match.
src/mime.cr
Outdated
@@types_lower[extension.downcase] = type | ||
|
||
if mediatype | ||
array = @@extensions.fetch(mediatype) do |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@@extensions[mediatype] ||= [] of String
?
d9da99a
to
da3e5ac
Compare
Merge 🕦? |
spec/std/mime_spec.cr
Outdated
MIME.extensions("text/custom-type").should eq [".Custom-Type", ".custom-type2"] | ||
|
||
MIME.register(".custom-type", "text/custom-type-lower") | ||
MIME.fetch(".custom-type").should eq "text/custom-type-lower" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what happens when you fetch .Custom-Type
here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The same as before. I can add a spec.
src/crystal/system/win32/mime.cr
Outdated
module Crystal::System::MIME | ||
# Load MIME types from operating system source. | ||
def self.load | ||
# stub |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Replace this with a comment saying that windows supplies no mime types.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well it does, You'll just need to be able to access the registry to get them. But I will clarify that and add a TODO.
src/mime.cr
Outdated
# | ||
# A case sensitive search is tried first, if this yields not result, it it | ||
# matched case-insensitive. Returns *default* if *extension* is not registered. | ||
def self.fetch(extension : String, default) : String |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps rename fetch
to from_extension
? Or make extension
required? MIME.fetch(extension: ".foo")
. Having a MIME.from_filename("index.html")
or a MIME.fetch(filename: "index.html")
could be really nice (it just calls extname for you).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Comment could be improved too: if this yields not result, it it matched case-insensitive
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The problem with a required named argument is that the default
argument would be weird. And I don't think named argument should be used to distinguish whether the method receives an extension or filename argument.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
from_filename
and from_extension
sounds good.
Although I really like fetch
because it's short ;)
daa809c
to
49c1543
Compare
src/mime.cr
Outdated
# | ||
# *extension* must start with a dot (`.`) and must not contain any null bytes. | ||
def self.register(extension : String, type : String) : Nil | ||
raise ArgumentError.new("extension does not start with a dot: #{extension.inspect}") unless extension.starts_with?('.') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please use Sentence case for exception messages.
ditto all below.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just fix the specs and we're good to go.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe a few things could be done better. Especially the MIME <-> Crystal::System::MIME relationship.
Last but not leat: I'm really sick of seeing return nil
. I have goosebumps every single time I see these, and I really want to burn them with a f****** flamethrower.
src/mime.cr
Outdated
private TSPECIAL_CHARACTERS = {'(', ')', '<', '>', '@', ',', ';', ':', '\\', '"', '/', '[', ']', '?', '='} | ||
|
||
# :nodoc: | ||
def self.parse_media_type(type : String) : String? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Must be private. It's only public for testing purposes, but internal implementation detail musn't be tested directly. Test MIME.register
with various cases instead.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd want to avoid speccing this through MIME.register
for it's side effects. It's easier to just spec the method directly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Easier != what should be done.
I don't deny the tests were helpful to design the feature, but either #parse_media_type
as a value by itself, and thus should be public & tested, or it's an implementation detail and associated tests can be dropped.
MIME_SOURCES.each do |path| | ||
next unless ::File.exists?(path) | ||
::File.open(path) do |file| | ||
::MIME.load_mime_database file |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That should work the other way around: Crystal::System::MIME
should provide a method that returns (or yields) a mime.types
file that MIME
would try to parse.
That would avoid the circular dependency that MIME
depends on Crystal::System::MIME
which itself depends on MIME
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't like the circular dependency either. But the fact that Crystal::System::MIME.load
utilizes MIME.load_mime_database
is already a platform specific implementation.
It could certainly be reversed if load
yields if it needs to load a file. On windows it just wouldn't yield, but load the database differently. But that's baking platform-specifics into the abstraction API.
That's how I chose to implement it and I think it should stay this way. But I won't hurt about changing if requested.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's similar with Crystal::System::Time.load_localtime
btw., the Unix implementation calls the non-platform-specific implementation Time::Location.read_zoneinfo
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think what makes me wary is that something from Crystal::System is initializing an external namespace; and ::MIME should initialize itself using Crystal::System. I wish we could find a better way.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could pull the platform-dependent initialization implementations out of Crystal::System
into the main namespace. But that doesn't change anything, really.
end | ||
|
||
# :nodoc: | ||
def self.load_mime_database(io : IO) : Nil |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Either :
- make it public —so we can load whatever
mime.types
file we want, which would be quite useful (especially on Windows); - make it private —and have Crystal::System::MIME merely tell where the
mime.types
files are located; - move it to
Crystal::System::MIME
that will return/yield mime types thatMIME
can register (compatible with Windows' registry, no circular dependency).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, it should be public.
src/mime.cr
Outdated
array << extension | ||
end | ||
|
||
nil |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Redundant noise: the method's signature is already Nil.
@ysbaddaden wow, you just become my personal holy warrior of code style nit-picking 😲 |
@ysbaddaden Are your concerns satisfied? |
However, browsers prompt for download since they're not coming down with a relevant mimetype (they have application/octet-stream). See: crystal-lang/crystal#5765
Can this be merged? |
@asterite I think it should be more flexible than that, for example for loading the MIME database from a network resource. That's what |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
initialize_types
can be just init
if init
is guarded with a return if @@initalized
. Since it's wrong to call it twice, I think that is a good thing to do.
But is not mandatory.
Also I would've called reset_initialized
just reset
. Again, not mandatory. 👍on my side.
It's not actually wrong. But I'll rename |
@Sija: Named arguments for what? |
@straight-shoota |
I think a squash + rebase to 2 commits and we're done |
src/mime.cr
Outdated
".html" => "text/html; charset=utf-8", | ||
".jpg" => "image/jpeg", | ||
".jpeg" => "image/jpeg", | ||
".js" => "application/x-javascript", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Adding ; charset=utf-8
IMO would be a good failsafe (same as in .css
).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The charset
parameter is only relevant for text/*
types.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
application/javascript
is the official MIME type for JavaScript as per RFC 4329, but application/x-javascript
is still considered a de-factor standard.
This list was originally taken from the Go mime package but they've switched to application/javascript
since this PR was created and MDN suggest using that as well. So I guess it's fine to change this.
The RFC also specifies a charset
parameter for application/javascript
, so then it would be okay to use charset=utf8
as well.
adb6608
to
e4a128f
Compare
Changed Rebased & squashed. |
🎉 |
This adds a
MIME
type which works as registry for mime types and allows to look up the mime type registered for each file name extension.By default, a limited set of mime types is always registered, including those that have previously been used in
HTTP::StaticFileHandler
. Additionally, there is an implementation inCrystal::System::MIME
that tries do load a mime database from the operating system. On unix systems the following paths are looked up:For windows this is not yet implemented because we're missing access to the registry.
Applications (or shards) can also register custom mime types and extensions.
Closes #4632