web_package 0.0.0 → 0.1.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
- 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.