content_disposition
content_disposition.cr
Creating a properly encoded and escaped standards-compliant HTTP
Content-Disposition
header for potential filenames with special characters is
surprisingly confusing.
This library does that and only that, in a single 50-line file with no dependencies.
Crystal port of https://github.com/shrinerb/content_disposition
Content-Disposition header
Before we proceed with the usage guide, first a bit of explanation what is the
Content-Disposition
header. The Content-Disposition
response header
specifies the behaviour of the web browser when opening a URL.
The inline
disposition will display the content "inline", which means that
known MIME types from the Content-Type
response header are displayed inside
the browser, while unknown MIME types will be immediately downloaded.
Content-Disposition: inline
The attachment
disposition will tell the browser to always download the
content, regardless of the MIME type.
Content-Disposition: attachment
When the content is downloaded, by default the filename will be last URL
segment. This can be changed via the filename
parameter:
Content-Disposition: attachment; filename="image.jpg"
To support old browsers, the filename
should be the ASCII version of the
filename, while the filename*
parameter can be used for the full filename
with any potential UTF-8 characters. Special characters from the filename need
to be URL-encoded in both parameters.
Installation
-
Add the dependency to your
shard.yml
:dependencies: content_disposition: github: jetrockets/content_disposition
-
Run
shards install
Usage
require "content_disposition"
ContentDisposition.format(disposition: "inline", filename: "racecar.jpg")
# => "inline; filename=\"racecar.jpg\"; filename*=UTF-8''racecar.jpg"
A proper content-disposition value for non-ascii filenames has a pure-ascii
as well as an ascii component. By default the filename will be turned into ascii
by replacing any non-ascii chars with '?'
(which is then properly
percent-escaped to %3F
in output).
ContentDisposition.format(disposition: "attachment", filename: "råcëçâr.jpg")
# => "attachment; filename=\"r%3Fc%3F%3F%3Fr.jpg\"; filename*=UTF-8''r%C3%A5c%C3%AB%C3%A7%C3%A2r.jpg"
But you can pass in your own proc to do it however you want.
ContentDisposition.format(
disposition: "attachment",
filename: "råcëçâr.jpg",
to_ascii: ->(filename : String) { String.new(filename.encode("US-ASCII", invalid: :skip)) }
)
# => "attachment; filename=\"racecar.jpg\"; filename*=UTF-8''r%C3%A5c%C3%AB%C3%A7%C3%A2r.jpg"
You can also configure .to_ascii
globally for any invocation:
ContentDisposition.to_ascii = ->(filename : String) { String.new(filename.encode("US-ASCII", invalid: :skip)) }
The .format
method is aliased to .call
, so you can do:
ContentDisposition.(disposition: "inline", filename: "råcëçâr.jpg")
# => "inline; filename=\"r%3Fc%3F%3F%3Fr.jpg\"; filename*=UTF-8''r%C3%A5c%C3%AB%C3%A7%C3%A2r.jpg"
There are also .attachment
and .inline
shorthands:
ContentDisposition.attachment("racecar.jpg")
# => "attachment; filename=\"racecar.jpg\"; filename*=UTF-8''racecar.jpg"
ContentDisposition.inline("racecar.jpg")
# => "inline; filename=\"racecar.jpg\"; filename*=UTF-8''racecar.jpg"
You can also create a ContentDisposition
instance to build your own
Content-Disposition
header.
content_disposition = ContentDisposition.new(
disposition: "attachment",
filename: "råcëçâr.jpg",
)
content_disposition.disposition
# => "attachment"
content_disposition.filename
# => "råcëçâr.jpg"
content_disposition.ascii_filename
# => "filename=\"r%3Fc%3F%3F%3Fr.jpg\""
content_disposition.utf8_filename
# => "filename*=UTF-8''r%C3%A5c%C3%AB%C3%A7%C3%A2r.jpg"
content_disposition.to_s
# => "attachment; filename=\"r%3Fc%3F%3F%3Fr.jpg\"; filename*=UTF-8''r%C3%A5c%C3%AB%C3%A7%C3%A2r.jpg"
Contributing
- Fork it (https://github.com/jetrockets/content_disposition/fork)
- Create your feature branch (
git checkout -b my-new-feature
) - Commit your changes (
git commit -am 'Add some feature'
) - Push to the branch (
git push origin my-new-feature
) - Create a new Pull Request
Contributors
- Igor Alexandrov - creator and maintainer