web_package 0.0.0 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA256:
3
- metadata.gz: 21d491102ea52a0f47bd981ddf9d04e0be400e805b75340a58df13fc5a2304d0
4
- data.tar.gz: 9763deee5944d7dc29d5f47ab490d1ea447d14e7d204f7fa3e68a69eb5ff92a3
2
+ SHA1:
3
+ metadata.gz: 3480112115fbe00503b06c0f2348ce71540476a2
4
+ data.tar.gz: 302a745b6299ed41ce24477ea0e97e2028f36f1c
5
5
  SHA512:
6
- metadata.gz: 3b8864f0082ff7e8ccfcff2066c9cace5c877b8ef4e77f80ef9b6fc1be5f441a03436437f565eb430f169e2f673f42f2667dc19c47e3ae92d91d95a54b64ced4
7
- data.tar.gz: aab6bf17a00c12d82ba3e509e75765e9d92344c11409c876e1aa7800fa3b400b25cc9f209f2678a21a5f6d7e01404d931448ca26d6e8a9930d76d88437e6a590
6
+ metadata.gz: f246d62cead117fbd4e8f8da7ccba8efaa29ee22baeebf1d7aadcbf73fe26131d58640876810ee1c6676591538c07a23f8d2894daba08348b01ef9c10293efcc
7
+ data.tar.gz: 1e5f467edfb293b83b166bc1ce03d2bda57db2af45fdfd9d98ddcecff65aafb21edd879e38ef43999a5a9b81b0e8ae3959fbbc32eb8bfa7086cec5b0380b164f
data/.rubocop.yml CHANGED
@@ -1,19 +1,14 @@
1
- Metrics/LineLength:
2
- Max: 100
3
- IgnoreCopDirectives: true
4
-
5
- Naming/UncommunicativeMethodParamName:
6
- AllowedNames:
7
- - s
1
+ Layout/EmptyLineAfterGuardClause:
2
+ Exclude:
3
+ - lib/web_package/signed_http_exchange.rb
8
4
 
9
- Style/CharacterLiteral:
10
- Enabled: false
5
+ Layout/SpaceInsideRangeLiteral:
6
+ Exclude:
7
+ - lib/web_package/cbor.rb
11
8
 
12
- Metrics/MethodLength:
9
+ Layout/ExtraSpacing:
13
10
  Exclude:
14
11
  - lib/web_package/cbor.rb
15
- - lib/web_package/mice.rb
16
- - lib/web_package/signed_http_exchange.rb
17
12
 
18
13
  Metrics/AbcSize:
19
14
  Max: 20
@@ -22,21 +17,29 @@ Metrics/AbcSize:
22
17
  - lib/web_package/mice.rb
23
18
  - lib/web_package/signed_http_exchange.rb
24
19
 
25
- Layout/EmptyLineAfterGuardClause:
20
+ Metrics/ClassLength:
26
21
  Exclude:
27
22
  - lib/web_package/signed_http_exchange.rb
28
23
 
29
- Metrics/ClassLength:
24
+ Metrics/LineLength:
25
+ Max: 100
26
+ IgnoreCopDirectives: true
27
+
28
+ Metrics/MethodLength:
30
29
  Exclude:
30
+ - lib/web_package/cbor.rb
31
+ - lib/web_package/mice.rb
31
32
  - lib/web_package/signed_http_exchange.rb
32
33
 
33
- Style/RedundantReturn:
34
+ Naming/UncommunicativeMethodParamName:
35
+ AllowedNames:
36
+ - s
37
+
38
+ Style/CharacterLiteral:
34
39
  Enabled: false
35
40
 
36
- Layout/SpaceInsideRangeLiteral:
37
- Exclude:
38
- - lib/web_package/cbor.rb
41
+ Style/FrozenStringLiteralComment:
42
+ Enabled: false
39
43
 
40
- Layout/ExtraSpacing:
41
- Exclude:
42
- - lib/web_package/cbor.rb
44
+ Style/RedundantReturn:
45
+ Enabled: false
data/README.md CHANGED
@@ -1,2 +1,111 @@
1
1
  # web_package
2
2
  Ruby implementation of Signed HTTP Exchange format, allowing a browser to trust that a HTTP request-response pair was generated by the origin it claims.
3
+
4
+
5
+ ## Ever thought of saving the Internet on a flash?
6
+
7
+ Easily-peasily.
8
+
9
+ Let's sign a pair of request/response and serve the bundle as `application/signed-exchange` - Chromium browsers understand what it means and unpack them smoothly.
10
+
11
+ For that we need a certificate with a special "CanSignHttpExchanges" extension, but for the purpose of this guide we will use just a self-signed one. Please refer [here](https://github.com/WICG/webpackage/tree/master/go/signedexchange#creating-our-first-signed-exchange) to create such.
12
+
13
+ Also we need an `https` cdn serving static certificate in `application/cert-chain+cbor` format. We can use `gen-certurl` tool from [here](https://github.com/WICG/webpackage/tree/master/go/signedexchange#creating-our-first-signed-exchange) to convert PEM certificate into this format, so we could than serve it from a cdn.
14
+
15
+ ### Required environment variables
16
+ Having done the above-said we are now ready to assign required env vars:
17
+ ```bash
18
+ export SXG_CERT_URL='https://my.cdn.com/cert.cbor' \
19
+ SXG_CERT_PATH='/local/path/to/cert.pem' \
20
+ SXG_PRIV_PATH='/local/path/to/priv.key'
21
+ ```
22
+ Please note, that the variables are fetched during class initialization. And failing to provide valid paths will result in an exception.
23
+
24
+ ### Use it as a middleware
25
+
26
+ `WebPackage::Middleware` can handle `.sxg`-format requests by wrapping the respective HTML contents into signed exchange response. For example the route `https://my.app.com/abc.sxg` will respond with signed contents for `https://my.app.com/abc`.
27
+
28
+ If you already have a Rack-based application (like Rails or Sinatra), than it is easy incorporate an SXG proxy into its middleware stack.
29
+
30
+ #### Rails
31
+ Add the gem to your `Gemfile`:
32
+ ```ruby
33
+ gem 'web_package'
34
+ ```
35
+ And then add the middleware:
36
+ ```ruby
37
+ # config/application.rb
38
+ config.middleware.insert 0, 'WebPackage::Middleware'
39
+ ```
40
+
41
+ That is it. Now all successful `.sxg` requests will be wrapped into signed exchanges.
42
+
43
+ #### Pure Rack app
44
+ Imagine we have a simple web app:
45
+ ```ruby
46
+ # config.ru
47
+ run ->(env) { [200, {}, ['<h1>Hello world!</h1>']] }
48
+ ```
49
+ Add the gem and the middleware:
50
+ ```ruby
51
+ # Gemfile
52
+ gem 'web_package'
53
+
54
+ # config.ru
55
+ use WebPackage::Middleware
56
+ ```
57
+
58
+ We are done. Start your app by running a command `rackup config.ru`. Now all supplimentary `.sxg` routes will be available just out of the box.<br>
59
+ As expected, visiting `http://localhost:9292/hello` will produce:
60
+ ```html
61
+ <h1>Hello world!</h1>
62
+ ```
63
+ What's more, visiting `http://localhost:9292/hello.sxg` will spit signed http exchange, containing original `<h1>Hello world!</h1>` HTML:
64
+ ```text
65
+ sxg1-b3\x00\x00\x1Chttps://localhost:9292/hello\x00\x019\x00\x00?label;cert-sha256=*+DoXYlCX+bFRyW65R3bFA2ICIz8Tyu54MLFUFo5tziA=*;cert-url=\"https://my.cdn.com/cert.cbor\";date=1557657274;expires=1558262074;integrity=\"digest/mi-sha256-03\";sig=*MEUCIAKKz+KSuhlzywfU12h3SkEq5ZuYYMxDZIgEDGYMd9sAAiEAj66Il48eb0CXFAnuZhnS+j6dqZVLJ6IwUVGWShhQu9g=*;validity-url=\"https://localhost/hello\"?FdigestX9mi-sha256-03=4QeUScOpSoJl7KJ47F11rSDHUTHZhDVwLiSLOWMcvqg=G:statusC200Pcontent-encodingLmi-sha256-03Vx-content-type-optionsGnosniff\x00\x00\x00\x00\x00\x00@\x00<h1>Hello world!</h1>
66
+ ```
67
+
68
+ ### Use it as it is
69
+ ```ruby
70
+ require 'web_package'
71
+
72
+ # this is the request/response pair
73
+ request_url = 'https://my.app.com/abc'
74
+ response = [200, {}, ['<h1>Hello world!</h1>']]
75
+
76
+ exchange = WebPackage::SignedHttpExchange.new(request_url, response)
77
+
78
+ exchange.headers
79
+ # => {"Content-Type"=>"application/signed-exchange;v=b3", "Cache-Control"=>"no-transform", "X-Content-Type-Options"=>"nosniff"}
80
+
81
+ exchange.body
82
+ # => "sxg1-b3\x00\x00\x16https://my.app.com/abc\x00\x018\x00\x00\x8Clabel;cert-sha256=*+DoXYlCX+bFRyW65R3bFA2ICIz8Tyu54MLFUFo5tziA=*;cert-url=\"https://my.cdn.com/cert.cbor\";date=1557648268;expires=1558253068;integrity=\"digest/mi-sha256-03\";sig=*MEYCIQDSH2F6E/naM/ul1iIMZMBd9VHnrbsxp+dKhYcxy9u1ewIhAIRIuHcTVPLS73q2ETLLGwY5Y7nR52bDG251uBBHxsBZ*;validity-url=\"https://my.app.com/abc\"\xA4FdigestX9mi-sha256-03=4QeUScOpSoJl7KJ47F11rSDHUTHZhDVwLiSLOWMcvqg=G:statusC200Pcontent-encodingLmi-sha256-03Vx-content-type-optionsGnosniff\x00\x00\x00\x00\x00\x00@\x00<h1>Hello world!</h1>"
83
+ ```
84
+
85
+ The body can be stored on disk and served from any other server. That is, visiting e.g. `https://other.cdn.com/foo/bar.sxg` will result in <b>"Hello&nbsp;world!"</b> HTML with `https://my.app.com/abc` in a browser's address bar - with no requests sent to `https://my.app.com/abc` (until the page expired).
86
+
87
+ Successive reloads will force browser to factually send requests to `https://my.app.com/abc`.
88
+
89
+ Note also, that SXG is only supported by the anchor tag (`<a>`) and `link rel=prefetch`, so actually typing `https://other.cdn.com/foo/bar.sxg` into browser's address bar and hitting enter will just download an SXG file.
90
+
91
+ This all could be helpful to preload content or serve it from closer location. For details please refer to hands-on description of [Signed Http Exchanges](https://developers.google.com/web/updates/2018/11/signed-exchanges).
92
+
93
+ ### Self-signed certificates in Chrome
94
+ Chrome will not proceed with a self-signed certificate - at least as long as its cbor representation is generated with dummy data for OCSP. To accomodate this, please launch the browser with the following flags:
95
+ ```bash
96
+ chrome --user-data-dir=/tmp/udd\
97
+ --ignore-certificate-errors-spki-list=`openssl x509 -noout -pubkey -in cert.pem | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | base64`
98
+ ```
99
+
100
+ Note, that the browser might spit a warning `You are using unsupported command-line flag: --ignore-certificate-errors-spki-list` - just ignore it - the browser does support this flag (tested in versions 73 and 74).
101
+
102
+ ## Contributing
103
+ - Fork it
104
+ - Create your feature branch (git checkout -b my-new-feature)
105
+ - Commit your changes (git commit -am 'Add some feature')
106
+ - Push to the branch (git push origin my-new-feature)
107
+ - Create new Pull Request
108
+
109
+ ## License
110
+
111
+ Web package is released under the [MIT License](../master/LICENSE).
@@ -24,7 +24,9 @@ module WebPackage
24
24
  def generate_bytes(input)
25
25
  case input
26
26
  when Hash
27
- input = input.transform_keys { |key| bin(key) }
27
+ input = input.dup.tap do |hsh|
28
+ hsh.keys.each { |key| hsh[bin(key)] = hsh.delete(key) }
29
+ end
28
30
 
29
31
  bytes = hsh_size(input)
30
32
  bytes[0] |= major_type(5)
@@ -1,3 +1,6 @@
1
+ require 'digest/sha2'
2
+ require 'base64'
3
+
1
4
  module WebPackage
2
5
  # Helper methods used in the library.
3
6
  module Helpers
@@ -0,0 +1,24 @@
1
+ module WebPackage
2
+ # This class is a convenience to represent an original response, later to be signed and packed.
3
+ class InnerResponse
4
+ attr_reader :status, :headers, :body, :payload
5
+
6
+ def initialize(status, headers, body)
7
+ @status = status
8
+ @headers = headers
9
+ @body = body
10
+ @payload = unrack_body
11
+ end
12
+
13
+ private
14
+
15
+ def unrack_body
16
+ payload = nil
17
+
18
+ # Rack's body yields strings
19
+ @body.each { |str| payload ? (payload << str) : (payload = str) }
20
+
21
+ payload
22
+ end
23
+ end
24
+ end
@@ -10,7 +10,10 @@ module WebPackage
10
10
 
11
11
  def initialize(headers, body)
12
12
  @body = body.dup
13
- @headers = headers.transform_keys { |key| key.to_s.downcase } # only lowercase keys allowed
13
+ @headers = headers.dup.tap do |hsh|
14
+ # only lowercase keys allowed
15
+ hsh.keys.each { |key| hsh[key.to_s.downcase] = hsh.delete(key) }
16
+ end
14
17
 
15
18
  @encoded = false
16
19
  end
@@ -0,0 +1,48 @@
1
+ module WebPackage
2
+ SXG_EXT = '.sxg'.freeze
3
+ SXG_FLAG = 'web_package.sxg'.freeze
4
+
5
+ # A Rack-compatible middleware.
6
+ class Middleware
7
+ def initialize(app)
8
+ @app = app
9
+ end
10
+
11
+ def call(env)
12
+ env[SXG_FLAG] = true if sxg_delete!(env['PATH_INFO'])
13
+
14
+ response = @app.call(env)
15
+ return response unless response[0] == 200 && env[SXG_FLAG]
16
+
17
+ # the original body must be closed first
18
+ response[2].close if response[2].respond_to? :close
19
+
20
+ # substituting the original response with SXG
21
+ SignedHttpExchange.new(url(env), response).to_rack_response
22
+ end
23
+
24
+ private
25
+
26
+ def sxg_delete!(path)
27
+ return unless path.is_a?(String) && (i = path.rindex(SXG_EXT))
28
+
29
+ # check that extension is either the last char or followed by a slash
30
+ ch = path[i + SXG_EXT.size]
31
+ return if ch && ch != ?/
32
+
33
+ path.slice! i, SXG_EXT.size
34
+ end
35
+
36
+ def url(env)
37
+ URI("https://#{env['HTTP_HOST'] || env['SERVER_NAME']}").tap do |u|
38
+ path = env['PATH_INFO']
39
+ port = env['SERVER_PORT']
40
+ query = env['QUERY_STRING']
41
+
42
+ u.path = path
43
+ u.port = port if !u.port && port != '80'
44
+ u.query = query if query && !query.empty?
45
+ end.to_s
46
+ end
47
+ end
48
+ end
@@ -5,8 +5,6 @@ module WebPackage
5
5
  # SXG format allows a browser to trust that a single HTTP request/response pair was
6
6
  # generated by the origin it claims.
7
7
  #
8
- # Accetps two arguments: response url and response object (with body, headers and status).
9
- # Provides two public methods to build HTTP response in SXG format: headers and body.
10
8
  # Current implementation is lazy, meaning that signing is performed upon the
11
9
  # invocation of the `body` method.
12
10
  class SignedHttpExchange
@@ -17,25 +15,25 @@ module WebPackage
17
15
  'Cache-Control' => 'no-transform',
18
16
  'X-Content-Type-Options' => 'nosniff'
19
17
  }.freeze
20
- CERT_URL = ENV['SXG_CERT_URL']
21
- CERT_PATH = ENV['SXG_CERT_PATH']
22
- PRIV_PATH = ENV['SXG_PRIV_PATH']
18
+ CERT_URL = ENV.fetch 'SXG_CERT_URL'
19
+ CERT_PATH = ENV.fetch 'SXG_CERT_PATH'
20
+ PRIV_PATH = ENV.fetch 'SXG_PRIV_PATH'
23
21
  INTEGRITY = 'digest/mi-sha256-03'.freeze
24
22
 
25
23
  # Mock request-response pair just in case:
26
24
  MOCK_URL = 'https://example.com/wow-fake-path'.freeze
27
- Response = Struct.new :body, :headers, :status
28
- MOCK_RESP = Response['<h1>Hello!</h1>', { 'Content-Type' => 'text/html; charset=utf-8' }, 200]
25
+ MOCK_RESP = [200, { 'Content-Type' => 'text/html; charset=utf-8' }, ['<h1>Hello!</h1>']].freeze
29
26
 
30
- attr_reader :url, :response, :mice
31
-
32
- # accepts two args representing a request-response pair to be packed into SXG format
27
+ # Accepts two args representing a request-response pair:
28
+ # url - request url (string)
29
+ # response - an array, equivalent to Rack's one: [status_code, headers, body]
33
30
  def initialize(url = MOCK_URL, response = MOCK_RESP)
34
31
  @uri = build_uri_from url
35
- @response = response
32
+ @url = @uri.to_s
33
+ @inner = InnerResponse.new(*response)
36
34
 
37
35
  @cbor = CBOR.new
38
- @mice = MICE.new(@response.headers, @response.body).tap(&:encode!)
36
+ @mice = MICE.new(@inner.headers, @inner.payload).tap(&:encode!)
39
37
  @signer = Signer.new CERT_PATH, PRIV_PATH
40
38
  end
41
39
 
@@ -57,11 +55,11 @@ module WebPackage
57
55
  @body << "sxg1-b3\x00"
58
56
 
59
57
  # 2. 2 bytes storing a big-endian integer "fallbackUrlLength".
60
- @body << [fallback_url.bytesize].pack('S>')
58
+ @body << [@url.bytesize].pack('S>')
61
59
 
62
60
  # 3. "fallbackUrlLength" bytes holding a "fallbackUrl", which MUST be
63
61
  # an absolute URL with a scheme of "https".
64
- @body << fallback_url
62
+ @body << @url
65
63
 
66
64
  # 4. 3 bytes storing a big-endian integer "sigLength". If this is
67
65
  # larger than 16384 (16*1024), parsing MUST fail.
@@ -100,6 +98,10 @@ module WebPackage
100
98
  @body << @mice.body
101
99
  end
102
100
 
101
+ def to_rack_response
102
+ [200, headers, [body]]
103
+ end
104
+
103
105
  private
104
106
 
105
107
  def message
@@ -152,8 +154,8 @@ module WebPackage
152
154
 
153
155
  # 8. The 8-byte big-endian encoding of the length in bytes of
154
156
  # "requestUrl", followed by the bytes of "requestUrl".
155
- @message << [url.bytesize].pack('Q>')
156
- @message << url
157
+ @message << [@url.bytesize].pack('Q>')
158
+ @message << @url
157
159
 
158
160
  # 9. The 8-byte big-endian encoding of the length in bytes of
159
161
  # "responseHeaders", followed by the bytes of
@@ -164,12 +166,12 @@ module WebPackage
164
166
 
165
167
  def encoded_mice_headers
166
168
  @encoded_mice_headers ||=
167
- @cbor.generate @mice.headers.merge(':status' => bin(response.status))
169
+ @cbor.generate @mice.headers.merge(':status' => bin(@inner.status))
168
170
  end
169
171
 
170
172
  # returns a string representing serialized label + params
171
173
  def structured_header_for(label, params)
172
- if params['cert-url'].blank?
174
+ if params[:'cert-url'].to_s.empty?
173
175
  raise '[SignedHttpExchange] No certificate url provided - please use `SXG_CERT_URL` '\
174
176
  'env var. Endpoint should respond with `application/cert-chain+cbor` content type.'
175
177
  end
@@ -207,23 +209,18 @@ module WebPackage
207
209
 
208
210
  def build_uri_from(url)
209
211
  u = url.is_a?(URI) ? url : URI(url)
210
- raise "[SignedHttpExchange] Unsupported URI scheme: #{u.scheme}" unless u.is_a? URI::HTTPS
211
- raise '[SignedHttpExchange] Request host is required' if u.host.blank?
212
+ raise '[SignedHttpExchange] Request host is required' if u.host.nil?
212
213
 
213
214
  u
214
215
  end
215
216
 
216
- def fallback_url
217
- @fallback_url ||= @uri.to_s
218
- end
219
-
220
217
  def validity_url
221
218
  @validity_url ||= begin
222
219
  path = @uri.path
223
220
  fi = path.index(?.)
224
221
  no_format_path = fi ? path[0...fi] : path # path without format, i.e. default :html
225
222
 
226
- URI::HTTPS.build(host: @uri.host, path: no_format_path).to_s
223
+ URI::HTTPS.build(host: @uri.host, path: no_format_path, query: @uri.query).to_s
227
224
  end
228
225
  end
229
226
 
@@ -1,3 +1,5 @@
1
+ require 'openssl'
2
+
1
3
  module WebPackage
2
4
  # Performs signing of a message with ECDSA.
3
5
  class Signer
@@ -5,11 +7,11 @@ module WebPackage
5
7
  attr_reader :signed_at, :expires_at, :cert, :integrity, :cert_url
6
8
 
7
9
  def initialize(path_to_cert, path_to_key)
8
- @alg = OpenSSL::PKey::EC.new(File.read(path_to_key))
9
- @cert = OpenSSL::X509::Certificate.new(File.read(path_to_cert))
10
+ @alg = OpenSSL::PKey::EC.new(File.read(path_to_key))
11
+ @cert = OpenSSL::X509::Certificate.new(File.read(path_to_cert))
10
12
 
11
- @signed_at = Time.zone.now
12
- @expires_at = @signed_at + 7.days
13
+ @signed_at = Time.now
14
+ @expires_at = @signed_at + 60 * 60 * 24 * 7
13
15
  end
14
16
 
15
17
  def sign(message)
@@ -1,3 +1,3 @@
1
1
  module WebPackage
2
- VERSION = '0.0.0'.freeze
2
+ VERSION = '0.1.0'.freeze
3
3
  end
data/lib/web_package.rb CHANGED
@@ -1,8 +1,11 @@
1
- require 'web_package/errors/body_format_error'
2
- require 'web_package/errors/certificate_url_format_error'
1
+ require 'uri'
2
+
3
+ require 'web_package/errors/body_encoding_error'
3
4
  require 'web_package/version'
4
5
  require 'web_package/helpers'
5
6
  require 'web_package/mice'
6
7
  require 'web_package/cbor'
7
- require 'web_package/signed_http_exchange'
8
+ require 'web_package/inner_response'
8
9
  require 'web_package/signer'
10
+ require 'web_package/signed_http_exchange'
11
+ require 'web_package/middleware'
data/web_package.gemspec CHANGED
@@ -16,7 +16,8 @@ Gem::Specification.new do |s|
16
16
  s.files = `git ls-files`.split("\n")
17
17
  s.require_paths = ['lib']
18
18
 
19
- s.required_ruby_version = '>=2.0.0'
19
+ s.required_ruby_version = '>=2.2.0'
20
20
 
21
+ s.add_development_dependency 'byebug'
21
22
  s.add_development_dependency 'rubocop', '~> 0.67'
22
23
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: web_package
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.0
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Oleg Afanasyev
@@ -9,8 +9,22 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2019-04-30 00:00:00.000000000 Z
12
+ date: 2019-05-12 00:00:00.000000000 Z
13
13
  dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: byebug
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - ">="
19
+ - !ruby/object:Gem::Version
20
+ version: '0'
21
+ type: :development
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ version: '0'
14
28
  - !ruby/object:Gem::Dependency
15
29
  name: rubocop
16
30
  requirement: !ruby/object:Gem::Requirement
@@ -41,7 +55,9 @@ files:
41
55
  - lib/web_package/cbor.rb
42
56
  - lib/web_package/errors/body_encoding_error.rb
43
57
  - lib/web_package/helpers.rb
58
+ - lib/web_package/inner_response.rb
44
59
  - lib/web_package/mice.rb
60
+ - lib/web_package/middleware.rb
45
61
  - lib/web_package/signed_http_exchange.rb
46
62
  - lib/web_package/signer.rb
47
63
  - lib/web_package/version.rb
@@ -58,7 +74,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
58
74
  requirements:
59
75
  - - ">="
60
76
  - !ruby/object:Gem::Version
61
- version: 2.0.0
77
+ version: 2.2.0
62
78
  required_rubygems_version: !ruby/object:Gem::Requirement
63
79
  requirements:
64
80
  - - ">="
@@ -66,7 +82,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
66
82
  version: '0'
67
83
  requirements: []
68
84
  rubyforge_project:
69
- rubygems_version: 2.7.6
85
+ rubygems_version: 2.6.13
70
86
  signing_key:
71
87
  specification_version: 4
72
88
  summary: Packaging Websites with Ruby.