content_disposition 0.1.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
+
[](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__)
|