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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4357dd0d7386d18a66f54fca68d8cda7e6838f5a25131662af28b72065f086ff
4
- data.tar.gz: 7953dfb28ad9282ca1ffd1f4d306d59144e8dfe7887f8dc5ce5632d88ce697ba
3
+ metadata.gz: ca5f57085a90a0ddbd221abdfe9fb9f4a7d3fcd69a40c85a9893b3c61d759de2
4
+ data.tar.gz: 8e4c9b54fb97512af8f4177f74af9846c1ddd230accf308cd3f1dd39a82b2637
5
5
  SHA512:
6
- metadata.gz: 8d748cb2c5b95251da8995412f0fee3117eb3866d8cc3bc1f46db717555f1fb36c42de49ebfd7164405070b0f0e6eedc38d19db57264361a5f2fb5ece6a4a390
7
- data.tar.gz: b8223f63ea093401c653779fa02b3adc3e742fe9b57fa95f4c3a8d0f7b72fae6a90afc9173fbf2958b5d5d5e8acbd9eaf583ac590137e56c6058e264ddb8a2b1
6
+ metadata.gz: 57024e667667cb9844cd0511570302859fe5654dc964ad7db8207ed45ceea57d0346f6a7c217a5ed0fac81f1d72612e7fe1ad903600790562d02afebdd079f56
7
+ data.tar.gz: 63b0915f8ce3f9946fc712358ac7b470f77a737fe563973465cfb9179bb17b1ddbf2613fcc9755bf810dad0e766722aaf608a9a55c6973e173bac815391bf089
data/README.md CHANGED
@@ -1,65 +1,162 @@
1
1
  # ContentDisposition
2
2
 
3
- Creating a properly encoded and escaped a standards-complaint HTTP
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
- This gem does that and only that, in a single 50-line file with no dependencies.
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
- require 'content_disposition'
13
+ ## Content-Disposition header
12
14
 
13
- headers["Content-Disposition"] = ContentDisposition.format(disposition: :attachment, filename: "racecar.jpg")
14
- ContentDisposition.format(disposition: "attachment", filename: "råcëçâr.jpg")
15
- ContentDisposition.new(disposition: :inline, filename: "автомобиль.jpg")
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
- A proper content-disposition value for non-ascii filenames has a pure-ascii
18
- as well as an ascii component. By default the filename will be turned into ascii,
19
- for the ascii component by replacing any non-ascii chars with `'?'` (which is
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
- But you can pass in your own proc to do it however you want. If you have a
23
- dependency on the i18n gem, and want to do it just like Rails:
23
+ ```http
24
+ Content-Disposition: inline
25
+ ```
24
26
 
25
- ContentDisposition.format(disposition: "attachment", filename: "råcëçâr.jpg")
26
- # => "attachment; filename=\"r%3Fc%3F%3F%3Fr.jpg\"; filename*=UTF-8''r%C3%A5c%C3%AB%C3%A7%C3%A2r.jpg"
27
+ The `attachment` disposition will tell the browser to always download the
28
+ content, regardless of the MIME type.
27
29
 
28
- ContentDisposition.format(disposition: "attachment",
29
- filename: "råcëçâr.jpg",
30
- to_ascii: ->(str) { I18n.transliterate(str) }
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
- That's it.
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 'content_disposition'
51
+ gem "content_disposition", "~> 1.0"
42
52
  ```
43
53
 
44
54
  And then execute:
45
55
 
46
- $ bundle
56
+ ```
57
+ $ bundle
58
+ ```
47
59
 
48
60
  Or install it yourself as:
49
61
 
50
- $ gem install content_disposition
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 `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
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 release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
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 https://github.com/shrinerb/content_disposition.
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 License](https://opensource.org/licenses/MIT).
161
+ The gem is available as open source under the terms of the [MIT
162
+ License](https://opensource.org/licenses/MIT).
@@ -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.summary = %q{Create content-disposition headers with proper escaping/encoding}
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
- # Specify which files should be added to the gem when it is released.
17
- # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
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
@@ -2,31 +2,41 @@
2
2
 
3
3
  require "content_disposition/version"
4
4
 
5
- class ContentDisposition # :nodoc:
6
- DEFAULT_TO_ASCII = ->(str) { str.encode("US-ASCII", undef: :replace, replace: "?") }
5
+ class ContentDisposition
6
+ ATTACHMENT = "attachment"
7
+ INLINE = "inline"
7
8
 
8
- def self.format(disposition:, filename:, to_ascii: DEFAULT_TO_ASCII)
9
- new(disposition: disposition, filename: filename, to_ascii: to_ascii).to_s
9
+ DEFAULT_TO_ASCII = ->(filename) do
10
+ filename.encode("US-ASCII", undef: :replace, replace: "?")
10
11
  end
11
12
 
12
- attr_reader :disposition, :filename, :to_ascii
13
+ class << self
14
+ def attachment(filename = nil)
15
+ format(disposition: ATTACHMENT, filename: filename)
16
+ end
13
17
 
14
- def initialize(disposition:, filename:, to_ascii: DEFAULT_TO_ASCII)
15
- @disposition = disposition
16
- @filename = filename
17
- @to_ascii = to_ascii
18
- end
18
+ def inline(filename = nil)
19
+ format(disposition: INLINE, filename: filename)
20
+ end
19
21
 
20
- TRADITIONAL_ESCAPED_CHAR = /[^ A-Za-z0-9!#$+.^_`|~-]/
22
+ def format(**options)
23
+ new(**options).to_s
24
+ end
25
+ alias call format
21
26
 
22
- def ascii_filename
23
- 'filename="' + percent_escape(to_ascii.call(filename), TRADITIONAL_ESCAPED_CHAR) + '"'
27
+ attr_accessor :to_ascii
24
28
  end
25
29
 
26
- RFC_5987_ESCAPED_CHAR = /[^A-Za-z0-9!#$&+.^_`|~-]/
30
+ attr_reader :disposition, :filename, :to_ascii
27
31
 
28
- def utf8_filename
29
- "filename*=UTF-8''" + percent_escape(filename, RFC_5987_ESCAPED_CHAR)
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
- def percent_escape(string, pattern)
43
- string.gsub(pattern) do |char|
44
- char.bytes.map { |byte| "%%%02X" % byte }.join
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
-
@@ -1,3 +1,3 @@
1
1
  class ContentDisposition
2
- VERSION = "0.1.0"
2
+ VERSION = "1.0.0"
3
3
  end
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: 0.1.0
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jonathan Rochkind
8
8
  autorequire:
9
- bindir: exe
9
+ bindir: bin
10
10
  cert_chain: []
11
- date: 2018-12-13 00:00:00.000000000 Z
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: '0'
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: Create content-disposition headers with proper escaping/encoding
90
+ summary: Ruby gem to create HTTP Content-Disposition headers with proper escaping/encoding
91
+ of filenames
128
92
  test_files: []
@@ -1,2 +0,0 @@
1
- q
2
- I18n.default_locale
data/.gitignore DELETED
@@ -1,11 +0,0 @@
1
- /.bundle/
2
- /.yardoc
3
- /_yardoc/
4
- /coverage/
5
- /doc/
6
- /pkg/
7
- /spec/reports/
8
- /tmp/
9
-
10
- # rspec failure tracking
11
- .rspec_status
data/.rspec DELETED
@@ -1,3 +0,0 @@
1
- --format documentation
2
- --color
3
- --require spec_helper
@@ -1,7 +0,0 @@
1
- ---
2
- sudo: false
3
- language: ruby
4
- cache: bundler
5
- rvm:
6
- - 2.5.3
7
- before_install: gem install bundler -v 1.17.1
data/Gemfile DELETED
@@ -1,6 +0,0 @@
1
- source "https://rubygems.org"
2
-
3
- git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
-
5
- # Specify your gem's dependencies in content_disposition.gemspec
6
- gemspec
@@ -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
@@ -1,6 +0,0 @@
1
- require "bundler/gem_tasks"
2
- require "rspec/core/rake_task"
3
-
4
- RSpec::Core::RakeTask.new(:spec)
5
-
6
- task :default => :spec
@@ -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__)
data/bin/setup DELETED
@@ -1,8 +0,0 @@
1
- #!/usr/bin/env bash
2
- set -euo pipefail
3
- IFS=$'\n\t'
4
- set -vx
5
-
6
- bundle install
7
-
8
- # Do any other automated setup that you need to do here