content_disposition 0.1.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +127 -30
- data/content_disposition.gemspec +5 -12
- data/lib/content_disposition.rb +42 -21
- data/lib/content_disposition/version.rb +1 -1
- metadata +6 -42
- data/.byebug_history +0 -2
- data/.gitignore +0 -11
- data/.rspec +0 -3
- data/.travis.yml +0 -7
- data/Gemfile +0 -6
- data/Gemfile.lock +0 -41
- data/Rakefile +0 -6
- data/bin/console +0 -14
- data/bin/setup +0 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ca5f57085a90a0ddbd221abdfe9fb9f4a7d3fcd69a40c85a9893b3c61d759de2
|
4
|
+
data.tar.gz: 8e4c9b54fb97512af8f4177f74af9846c1ddd230accf308cd3f1dd39a82b2637
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 57024e667667cb9844cd0511570302859fe5654dc964ad7db8207ed45ceea57d0346f6a7c217a5ed0fac81f1d72612e7fe1ad903600790562d02afebdd079f56
|
7
|
+
data.tar.gz: 63b0915f8ce3f9946fc712358ac7b470f77a737fe563973465cfb9179bb17b1ddbf2613fcc9755bf810dad0e766722aaf608a9a55c6973e173bac815391bf089
|
data/README.md
CHANGED
@@ -1,65 +1,162 @@
|
|
1
1
|
# ContentDisposition
|
2
2
|
|
3
|
-
|
4
|
-
Content-Disposition header for potential non-ascii filenames is surprisingly
|
5
|
-
confusing.
|
3
|
+
[![Gem Version](https://badge.fury.io/rb/content_disposition.svg)](https://badge.fury.io/rb/content_disposition)
|
6
4
|
|
7
|
-
|
5
|
+
Creating a properly encoded and escaped standards-compliant HTTP
|
6
|
+
`Content-Disposition` header for potential filenames with special characters is
|
7
|
+
surprisingly confusing.
|
8
|
+
|
9
|
+
This ruby gem does that and only that, in a single 50-line file with no dependencies.
|
8
10
|
It's code is shamelessly extracted and adapted from Rails'
|
9
|
-
`HTTP::ContentDisposition` class.
|
11
|
+
`ActionDispatch::HTTP::ContentDisposition` class.
|
10
12
|
|
11
|
-
|
13
|
+
## Content-Disposition header
|
12
14
|
|
13
|
-
|
14
|
-
|
15
|
-
|
15
|
+
Before we proceed with the usage guide, first a bit of explanation what is the
|
16
|
+
`Content-Disposition` header. The `Content-Disposition` response header
|
17
|
+
specifies the behaviour of the web browser when opening a URL.
|
16
18
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
then properly percent-escaped in output).
|
19
|
+
The `inline` disposition will display the content "inline", which means that
|
20
|
+
known MIME types from the `Content-Type` response header are displayed inside
|
21
|
+
the browser, while unknown MIME types will be immediately downloaded.
|
21
22
|
|
22
|
-
|
23
|
-
|
23
|
+
```http
|
24
|
+
Content-Disposition: inline
|
25
|
+
```
|
24
26
|
|
25
|
-
|
26
|
-
|
27
|
+
The `attachment` disposition will tell the browser to always download the
|
28
|
+
content, regardless of the MIME type.
|
27
29
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
)
|
32
|
-
# => "attachment; filename=\"racecar.jpg\"; filename*=UTF-8''r%C3%A5c%C3%AB%C3%A7%C3%A2r.jpg"
|
30
|
+
```http
|
31
|
+
Content-Disposition: attachment
|
32
|
+
```
|
33
33
|
|
34
|
-
|
34
|
+
When the content is downloaded, by default the filename will be last URL
|
35
|
+
segment. This can be changed via the `filename` parameter:
|
36
|
+
|
37
|
+
```http
|
38
|
+
Content-Disposition: attachment; filename="image.jpg"
|
39
|
+
```
|
40
|
+
|
41
|
+
To support old browsers, the `filename` should be the ASCII version of the
|
42
|
+
filename, while the `filename*` parameter can be used for the full filename
|
43
|
+
with any potential UTF-8 characters. Special characters from the filename need
|
44
|
+
to be URL-encoded in both parameters.
|
35
45
|
|
36
46
|
## Installation
|
37
47
|
|
38
48
|
Add this line to your application's Gemfile:
|
39
49
|
|
40
50
|
```ruby
|
41
|
-
gem
|
51
|
+
gem "content_disposition", "~> 1.0"
|
42
52
|
```
|
43
53
|
|
44
54
|
And then execute:
|
45
55
|
|
46
|
-
|
56
|
+
```
|
57
|
+
$ bundle
|
58
|
+
```
|
47
59
|
|
48
60
|
Or install it yourself as:
|
49
61
|
|
50
|
-
|
62
|
+
```
|
63
|
+
$ gem install content_disposition
|
64
|
+
```
|
65
|
+
|
66
|
+
## Usage
|
67
|
+
|
68
|
+
```ruby
|
69
|
+
require "content_disposition"
|
70
|
+
|
71
|
+
ContentDisposition.format(disposition: "inline", filename: "racecar.jpg")
|
72
|
+
# => "inline; filename=\"racecar.jpg\"; filename*=UTF-8''racecar.jpg"
|
73
|
+
```
|
74
|
+
|
75
|
+
A proper content-disposition value for non-ascii filenames has a pure-ascii
|
76
|
+
as well as an ascii component. By default the filename will be turned into ascii
|
77
|
+
by replacing any non-ascii chars with `'?'` (which is then properly
|
78
|
+
percent-escaped to `%3F` in output).
|
79
|
+
|
80
|
+
```ruby
|
81
|
+
ContentDisposition.format(disposition: "attachment", filename: "råcëçâr.jpg")
|
82
|
+
# => "attachment; filename=\"r%3Fc%3F%3F%3Fr.jpg\"; filename*=UTF-8''r%C3%A5c%C3%AB%C3%A7%C3%A2r.jpg"
|
83
|
+
```
|
84
|
+
|
85
|
+
But you can pass in your own proc to do it however you want. If you have a
|
86
|
+
dependency on the i18n gem, and want to do it just like Rails:
|
87
|
+
|
88
|
+
```ruby
|
89
|
+
ContentDisposition.format(
|
90
|
+
disposition: "attachment",
|
91
|
+
filename: "råcëçâr.jpg",
|
92
|
+
to_ascii: ->(filename) { I18n.transliterate(filename) }
|
93
|
+
)
|
94
|
+
# => "attachment; filename=\"racecar.jpg\"; filename*=UTF-8''r%C3%A5c%C3%AB%C3%A7%C3%A2r.jpg"
|
95
|
+
```
|
96
|
+
|
97
|
+
You can also configure `.to_ascii` globally for any invocation:
|
98
|
+
|
99
|
+
```ruby
|
100
|
+
ContentDisposition.to_ascii = ->(filename) { I18n.transliterate(filename) }
|
101
|
+
```
|
102
|
+
|
103
|
+
The `.format` method is aliased to `.call`, so you can do:
|
104
|
+
|
105
|
+
```ruby
|
106
|
+
ContentDisposition.(disposition: "inline", filename: "råcëçâr.jpg")
|
107
|
+
# => "inline; filename=\"r%3Fc%3F%3F%3Fr.jpg\"; filename*=UTF-8''r%C3%A5c%C3%AB%C3%A7%C3%A2r.jpg"
|
108
|
+
```
|
109
|
+
|
110
|
+
There are also `.attachment` and `.inline` shorthands:
|
51
111
|
|
112
|
+
```ruby
|
113
|
+
ContentDisposition.attachment("racecar.jpg")
|
114
|
+
# => "attachment; filename=\"racecar.jpg\"; filename*=UTF-8''racecar.jpg"
|
115
|
+
ContentDisposition.inline("racecar.jpg")
|
116
|
+
# => "inline; filename=\"racecar.jpg\"; filename*=UTF-8''racecar.jpg"
|
117
|
+
```
|
118
|
+
|
119
|
+
You can also create a `ContentDisposition` instance to build your own
|
120
|
+
`Content-Disposition` header.
|
121
|
+
|
122
|
+
```ruby
|
123
|
+
content_disposition = ContentDisposition.new(
|
124
|
+
disposition: "attachment",
|
125
|
+
filename: "råcëçâr.jpg",
|
126
|
+
)
|
127
|
+
|
128
|
+
content_disposition.disposition
|
129
|
+
# => "attachment"
|
130
|
+
content_disposition.filename
|
131
|
+
# => "råcëçâr.jpg"
|
132
|
+
|
133
|
+
content_disposition.ascii_filename
|
134
|
+
# => "filename=\"r%3Fc%3F%3F%3Fr.jpg\""
|
135
|
+
content_disposition.utf8_filename
|
136
|
+
# => "filename*=UTF-8''r%C3%A5c%C3%AB%C3%A7%C3%A2r.jpg"
|
137
|
+
|
138
|
+
content_disposition.to_s
|
139
|
+
# => "attachment; filename=\"r%3Fc%3F%3F%3Fr.jpg\"; filename*=UTF-8''r%C3%A5c%C3%AB%C3%A7%C3%A2r.jpg"
|
140
|
+
```
|
52
141
|
|
53
142
|
## Development
|
54
143
|
|
55
|
-
After checking out the repo, run `bin/setup` to install dependencies. Then, run
|
144
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run
|
145
|
+
`rake spec` to run the tests. You can also run `bin/console` for an interactive
|
146
|
+
prompt that will allow you to experiment.
|
56
147
|
|
57
|
-
To install this gem onto your local machine, run `bundle exec rake install`. To
|
148
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To
|
149
|
+
release a new version, update the version number in `version.rb`, and then run
|
150
|
+
`bundle exec rake release`, which will create a git tag for the version, push
|
151
|
+
git commits and tags, and push the `.gem` file to
|
152
|
+
[rubygems.org](https://rubygems.org).
|
58
153
|
|
59
154
|
## Contributing
|
60
155
|
|
61
|
-
Bug reports and pull requests are welcome on GitHub at
|
156
|
+
Bug reports and pull requests are welcome on GitHub at
|
157
|
+
https://github.com/shrinerb/content_disposition.
|
62
158
|
|
63
159
|
## License
|
64
160
|
|
65
|
-
The gem is available as open source under the terms of the [MIT
|
161
|
+
The gem is available as open source under the terms of the [MIT
|
162
|
+
License](https://opensource.org/licenses/MIT).
|
data/content_disposition.gemspec
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
|
2
1
|
lib = File.expand_path("../lib", __FILE__)
|
3
2
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
3
|
require "content_disposition/version"
|
@@ -9,22 +8,16 @@ Gem::Specification.new do |spec|
|
|
9
8
|
spec.authors = ["Jonathan Rochkind"]
|
10
9
|
spec.email = ["jrochkind@chemheritage.org"]
|
11
10
|
|
12
|
-
spec.
|
11
|
+
spec.required_ruby_version = ">= 2.3"
|
12
|
+
|
13
|
+
spec.summary = %q{Ruby gem to create HTTP Content-Disposition headers with proper escaping/encoding of filenames}
|
13
14
|
spec.homepage = "https://github.com/shrinerb/content_disposition"
|
14
15
|
spec.license = "MIT"
|
15
16
|
|
16
|
-
|
17
|
-
|
18
|
-
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
19
|
-
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
20
|
-
end
|
21
|
-
spec.bindir = "exe"
|
22
|
-
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
23
|
-
spec.require_paths = ["lib"]
|
17
|
+
spec.files = Dir["README.md", "LICENSE.txt", "lib/**/*.rb", "*.gemspec"]
|
18
|
+
spec.require_path = "lib"
|
24
19
|
|
25
20
|
spec.add_development_dependency "bundler", "~> 1.17"
|
26
21
|
spec.add_development_dependency "rake", "~> 10.0"
|
27
22
|
spec.add_development_dependency "rspec", "~> 3.0"
|
28
|
-
spec.add_development_dependency "i18n", "~> 1.0"
|
29
|
-
spec.add_development_dependency "byebug"
|
30
23
|
end
|
data/lib/content_disposition.rb
CHANGED
@@ -2,31 +2,41 @@
|
|
2
2
|
|
3
3
|
require "content_disposition/version"
|
4
4
|
|
5
|
-
class ContentDisposition
|
6
|
-
|
5
|
+
class ContentDisposition
|
6
|
+
ATTACHMENT = "attachment"
|
7
|
+
INLINE = "inline"
|
7
8
|
|
8
|
-
|
9
|
-
|
9
|
+
DEFAULT_TO_ASCII = ->(filename) do
|
10
|
+
filename.encode("US-ASCII", undef: :replace, replace: "?")
|
10
11
|
end
|
11
12
|
|
12
|
-
|
13
|
+
class << self
|
14
|
+
def attachment(filename = nil)
|
15
|
+
format(disposition: ATTACHMENT, filename: filename)
|
16
|
+
end
|
13
17
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
@to_ascii = to_ascii
|
18
|
-
end
|
18
|
+
def inline(filename = nil)
|
19
|
+
format(disposition: INLINE, filename: filename)
|
20
|
+
end
|
19
21
|
|
20
|
-
|
22
|
+
def format(**options)
|
23
|
+
new(**options).to_s
|
24
|
+
end
|
25
|
+
alias call format
|
21
26
|
|
22
|
-
|
23
|
-
'filename="' + percent_escape(to_ascii.call(filename), TRADITIONAL_ESCAPED_CHAR) + '"'
|
27
|
+
attr_accessor :to_ascii
|
24
28
|
end
|
25
29
|
|
26
|
-
|
30
|
+
attr_reader :disposition, :filename, :to_ascii
|
27
31
|
|
28
|
-
def
|
29
|
-
|
32
|
+
def initialize(disposition:, filename:, to_ascii: nil)
|
33
|
+
unless [ATTACHMENT, INLINE].include?(disposition.to_s)
|
34
|
+
fail ArgumentError, "unknown disposition: #{disposition.inspect}"
|
35
|
+
end
|
36
|
+
|
37
|
+
@disposition = disposition
|
38
|
+
@filename = filename
|
39
|
+
@to_ascii = to_ascii || self.class.to_ascii || DEFAULT_TO_ASCII
|
30
40
|
end
|
31
41
|
|
32
42
|
def to_s
|
@@ -37,12 +47,23 @@ class ContentDisposition # :nodoc:
|
|
37
47
|
end
|
38
48
|
end
|
39
49
|
|
50
|
+
TRADITIONAL_ESCAPED_CHAR = /[^ A-Za-z0-9!#$+.^_`|~-]/
|
51
|
+
|
52
|
+
def ascii_filename
|
53
|
+
'filename="' + percent_escape(to_ascii[filename], TRADITIONAL_ESCAPED_CHAR) + '"'
|
54
|
+
end
|
55
|
+
|
56
|
+
RFC_5987_ESCAPED_CHAR = /[^A-Za-z0-9!#$&+.^_`|~-]/
|
57
|
+
|
58
|
+
def utf8_filename
|
59
|
+
"filename*=UTF-8''" + percent_escape(filename, RFC_5987_ESCAPED_CHAR)
|
60
|
+
end
|
61
|
+
|
40
62
|
private
|
41
63
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
end
|
64
|
+
def percent_escape(string, pattern)
|
65
|
+
string.gsub(pattern) do |char|
|
66
|
+
char.bytes.map { |byte| "%%%02X" % byte }.join
|
46
67
|
end
|
68
|
+
end
|
47
69
|
end
|
48
|
-
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: content_disposition
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jonathan Rochkind
|
8
8
|
autorequire:
|
9
|
-
bindir:
|
9
|
+
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-12-
|
11
|
+
date: 2018-12-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -52,34 +52,6 @@ dependencies:
|
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '3.0'
|
55
|
-
- !ruby/object:Gem::Dependency
|
56
|
-
name: i18n
|
57
|
-
requirement: !ruby/object:Gem::Requirement
|
58
|
-
requirements:
|
59
|
-
- - "~>"
|
60
|
-
- !ruby/object:Gem::Version
|
61
|
-
version: '1.0'
|
62
|
-
type: :development
|
63
|
-
prerelease: false
|
64
|
-
version_requirements: !ruby/object:Gem::Requirement
|
65
|
-
requirements:
|
66
|
-
- - "~>"
|
67
|
-
- !ruby/object:Gem::Version
|
68
|
-
version: '1.0'
|
69
|
-
- !ruby/object:Gem::Dependency
|
70
|
-
name: byebug
|
71
|
-
requirement: !ruby/object:Gem::Requirement
|
72
|
-
requirements:
|
73
|
-
- - ">="
|
74
|
-
- !ruby/object:Gem::Version
|
75
|
-
version: '0'
|
76
|
-
type: :development
|
77
|
-
prerelease: false
|
78
|
-
version_requirements: !ruby/object:Gem::Requirement
|
79
|
-
requirements:
|
80
|
-
- - ">="
|
81
|
-
- !ruby/object:Gem::Version
|
82
|
-
version: '0'
|
83
55
|
description:
|
84
56
|
email:
|
85
57
|
- jrochkind@chemheritage.org
|
@@ -87,17 +59,8 @@ executables: []
|
|
87
59
|
extensions: []
|
88
60
|
extra_rdoc_files: []
|
89
61
|
files:
|
90
|
-
- ".byebug_history"
|
91
|
-
- ".gitignore"
|
92
|
-
- ".rspec"
|
93
|
-
- ".travis.yml"
|
94
|
-
- Gemfile
|
95
|
-
- Gemfile.lock
|
96
62
|
- LICENSE.txt
|
97
63
|
- README.md
|
98
|
-
- Rakefile
|
99
|
-
- bin/console
|
100
|
-
- bin/setup
|
101
64
|
- content_disposition.gemspec
|
102
65
|
- lib/content_disposition.rb
|
103
66
|
- lib/content_disposition/version.rb
|
@@ -113,7 +76,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
113
76
|
requirements:
|
114
77
|
- - ">="
|
115
78
|
- !ruby/object:Gem::Version
|
116
|
-
version: '
|
79
|
+
version: '2.3'
|
117
80
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
118
81
|
requirements:
|
119
82
|
- - ">="
|
@@ -124,5 +87,6 @@ rubyforge_project:
|
|
124
87
|
rubygems_version: 2.7.6
|
125
88
|
signing_key:
|
126
89
|
specification_version: 4
|
127
|
-
summary:
|
90
|
+
summary: Ruby gem to create HTTP Content-Disposition headers with proper escaping/encoding
|
91
|
+
of filenames
|
128
92
|
test_files: []
|
data/.byebug_history
DELETED
data/.gitignore
DELETED
data/.rspec
DELETED
data/.travis.yml
DELETED
data/Gemfile
DELETED
data/Gemfile.lock
DELETED
@@ -1,41 +0,0 @@
|
|
1
|
-
PATH
|
2
|
-
remote: .
|
3
|
-
specs:
|
4
|
-
content_disposition (0.1.0)
|
5
|
-
|
6
|
-
GEM
|
7
|
-
remote: https://rubygems.org/
|
8
|
-
specs:
|
9
|
-
byebug (10.0.2)
|
10
|
-
concurrent-ruby (1.1.3)
|
11
|
-
diff-lcs (1.3)
|
12
|
-
i18n (1.2.0)
|
13
|
-
concurrent-ruby (~> 1.0)
|
14
|
-
rake (10.5.0)
|
15
|
-
rspec (3.8.0)
|
16
|
-
rspec-core (~> 3.8.0)
|
17
|
-
rspec-expectations (~> 3.8.0)
|
18
|
-
rspec-mocks (~> 3.8.0)
|
19
|
-
rspec-core (3.8.0)
|
20
|
-
rspec-support (~> 3.8.0)
|
21
|
-
rspec-expectations (3.8.2)
|
22
|
-
diff-lcs (>= 1.2.0, < 2.0)
|
23
|
-
rspec-support (~> 3.8.0)
|
24
|
-
rspec-mocks (3.8.0)
|
25
|
-
diff-lcs (>= 1.2.0, < 2.0)
|
26
|
-
rspec-support (~> 3.8.0)
|
27
|
-
rspec-support (3.8.0)
|
28
|
-
|
29
|
-
PLATFORMS
|
30
|
-
ruby
|
31
|
-
|
32
|
-
DEPENDENCIES
|
33
|
-
bundler (~> 1.17)
|
34
|
-
byebug
|
35
|
-
content_disposition!
|
36
|
-
i18n (~> 1.0)
|
37
|
-
rake (~> 10.0)
|
38
|
-
rspec (~> 3.0)
|
39
|
-
|
40
|
-
BUNDLED WITH
|
41
|
-
1.17.1
|
data/Rakefile
DELETED
data/bin/console
DELETED
@@ -1,14 +0,0 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
|
3
|
-
require "bundler/setup"
|
4
|
-
require "content_disposition"
|
5
|
-
|
6
|
-
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
-
# with your gem easier. You can also use a different console, if you like.
|
8
|
-
|
9
|
-
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
-
# require "pry"
|
11
|
-
# Pry.start
|
12
|
-
|
13
|
-
require "irb"
|
14
|
-
IRB.start(__FILE__)
|