Add MIME registry#5765
Conversation
bbea07c to
c8ad4eb
Compare
| # | ||
| # ``` | ||
| # require "mime" | ||
| # MIME[".html"] # => "text/html" |
There was a problem hiding this comment.
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.
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.
No aliases please. Let's just name these methods normally and leave aside the unnecessary syntax sugar.
There was a problem hiding this comment.
Yeah, I figure it doesn't make much sense here anyway.
633ba09 to
a4b5703
Compare
|
I've added Anything else? |
| ".htm" => "text/html; charset=utf-8", | ||
| ".html" => "text/html; charset=utf-8", | ||
| ".jpg" => "image/jpeg", | ||
| ".jepg" => "image/jpeg", |
a4b5703 to
d9da99a
Compare
| ] | ||
|
|
||
| {% if flag?(:freebsd) %} | ||
| MIME_SOURCES << "/usr/local/etc/mime.types" |
| 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.
But you always downcase on line 99.
There was a problem hiding this comment.
Yeah, it shouldn't be downcased before.
There was a problem hiding this comment.
Why bother keeping two hashes, and not just downcase-d one?
There was a problem hiding this comment.
@Sija Resolution should be case-sensitive by default and only fall back to case-insensitive if there is no match.
| @@types_lower[extension.downcase] = type | ||
|
|
||
| if mediatype | ||
| array = @@extensions.fetch(mediatype) do |
There was a problem hiding this comment.
@@extensions[mediatype] ||= [] of String?
d9da99a to
da3e5ac
Compare
|
Merge 🕦? |
| 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.
what happens when you fetch .Custom-Type here?
There was a problem hiding this comment.
The same as before. I can add a spec.
| module Crystal::System::MIME | ||
| # Load MIME types from operating system source. | ||
| def self.load | ||
| # stub |
There was a problem hiding this comment.
Replace this with a comment saying that windows supplies no mime types.
There was a problem hiding this comment.
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.
| # | ||
| # 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.
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.
Comment could be improved too: if this yields not result, it it matched case-insensitive.
There was a problem hiding this comment.
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.
from_filename and from_extension sounds good.
Although I really like fetch because it's short ;)
daa809c to
49c1543
Compare
| # | ||
| # *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.
Please use Sentence case for exception messages.
ditto all below.
RX14
left a comment
There was a problem hiding this comment.
Just fix the specs and we're good to go.
ysbaddaden
left a comment
There was a problem hiding this comment.
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.
| private TSPECIAL_CHARACTERS = {'(', ')', '<', '>', '@', ',', ';', ':', '\\', '"', '/', '[', ']', '?', '='} | ||
|
|
||
| # :nodoc: | ||
| def self.parse_media_type(type : String) : String? |
There was a problem hiding this comment.
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.
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.
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.
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.
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.
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.
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.
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.
Either :
- make it public —so we can load whatever
mime.typesfile we want, which would be quite useful (especially on Windows); - make it private —and have Crystal::System::MIME merely tell where the
mime.typesfiles are located; - move it to
Crystal::System::MIMEthat will return/yield mime types thatMIMEcan register (compatible with Windows' registry, no circular dependency).
There was a problem hiding this comment.
Yes, it should be public.
| array << extension | ||
| end | ||
|
|
||
| nil |
There was a problem hiding this comment.
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 |
bcardiff
left a comment
There was a problem hiding this comment.
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 |
| ".html" => "text/html; charset=utf-8", | ||
| ".jpg" => "image/jpeg", | ||
| ".jpeg" => "image/jpeg", | ||
| ".js" => "application/x-javascript", |
There was a problem hiding this comment.
Adding ; charset=utf-8 IMO would be a good failsafe (same as in .css).
There was a problem hiding this comment.
The charset parameter is only relevant for text/* types.
There was a problem hiding this comment.
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
MIMEtype 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::MIMEthat 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