cloudimage 0.2.0 → 0.6.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
2
  SHA256:
3
- metadata.gz: abb64d67ac6a4a0d7e87cd0758c94497b1c31a96f4dcfc9a4d10d2b66e4460b6
4
- data.tar.gz: f12f312eea0653ee858bcc0c124a842a574ee6e63b820832c93f275687d463e3
3
+ metadata.gz: c02d5fa30289aa70eb6b0d94e13f31130e5c1918ffb18d45197bb0b62cda26de
4
+ data.tar.gz: ce5da501f8200577f7c9003dba581b59f88628c99a03d54fa662ed0f336737fd
5
5
  SHA512:
6
- metadata.gz: ec3ede4d88539362085fe46726453aa9fca339bb875a2071c897d4797a093aef6caec2805e2927f363d0ea27f21ef319634513fc855e748b76366399840cb6aa
7
- data.tar.gz: d292ff52e7d4219bdbf3a0668c22f9c26da414bc187246eeb97caf550c80762a54462f5752da08312f94233caa87c5a09c156519e94093e1e3d0ee833cf4093b
6
+ metadata.gz: f698aefba14fee30249e29e431cf53d1e25440c97c08731ac4c050ba0bc58a95eeeac93143301fdd7cc31d7e96a4cdedac653aa832b61adf2f8c2a7681748d52
7
+ data.tar.gz: 3a58f037be0329dfbdfd4c1b0d0a71848caaf7d9c42fc1e9425d5f4c43015b81ee60cec29ef84cbf3318a6c195af851d82ab042d12166e7e98ae16bacff5bf2b
@@ -1,4 +1,38 @@
1
- ## master
1
+ # Changelog
2
+
3
+ ## [v0.5.0](https://github.com/scaleflex/cloudimage-rb/tree/v0.5.0) (2020-08-02)
4
+
5
+ [Full Changelog](https://github.com/scaleflex/cloudimage-rb/compare/v0.4.0...v0.5.0)
6
+
7
+ **Implemented enhancements:**
8
+
9
+ - Invalidation [\#24](https://github.com/scaleflex/cloudimage-rb/pull/24) ([janklimo](https://github.com/janklimo))
10
+ - Add support for custom CNAMEs [\#21](https://github.com/scaleflex/cloudimage-rb/pull/21) ([janklimo](https://github.com/janklimo))
11
+
12
+ ## [v0.4.0](https://github.com/scaleflex/cloudimage-rb/tree/v0.4.0) (2020-07-19)
13
+
14
+ [Full Changelog](https://github.com/scaleflex/cloudimage-rb/compare/v0.3.0...v0.4.0)
15
+
16
+ **Implemented enhancements:**
17
+
18
+ - Add support for aliases [\#20](https://github.com/scaleflex/cloudimage-rb/pull/20) ([janklimo](https://github.com/janklimo))
19
+
20
+ ## [v0.3.0](https://github.com/scaleflex/cloudimage-rb/tree/v0.3.0) (2020-07-09)
21
+
22
+ [Full Changelog](https://github.com/scaleflex/cloudimage-rb/compare/v0.2.1...v0.3.0)
23
+
24
+ **Implemented enhancements:**
25
+
26
+ - Introduce URL sealing [\#10](https://github.com/scaleflex/cloudimage-rb/pull/10) ([janklimo](https://github.com/janklimo))
27
+
28
+ **Merged pull requests:**
29
+
30
+ - Use changelog generation [\#16](https://github.com/scaleflex/cloudimage-rb/pull/16) ([janklimo](https://github.com/janklimo))
31
+ - Add test coverage with SimpleCov [\#9](https://github.com/scaleflex/cloudimage-rb/pull/9) ([janklimo](https://github.com/janklimo))
32
+
33
+ ## 0.2.1 (2020-06-29)
34
+
35
+ - Include `force_download` param.
2
36
 
3
37
  ## 0.2.0 (2020-06-28)
4
38
 
@@ -19,3 +53,6 @@
19
53
 
20
54
  - Set up Github actions for CI.
21
55
  [#1](https://github.com/scaleflex/cloudimage-rb/pull/1) (@janklimo)
56
+
57
+
58
+ \* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)*
data/README.md CHANGED
@@ -1,9 +1,8 @@
1
1
  # cloudimage
2
2
 
3
- ![](https://github.com/scaleflex/cloudimage-rb/workflows/Build/badge.svg)
3
+ [![Gem Version](https://badge.fury.io/rb/cloudimage.svg)](https://badge.fury.io/rb/cloudimage) ![Build status](https://github.com/scaleflex/cloudimage-rb/workflows/Build/badge.svg)
4
4
 
5
- `cloudimage` is the official Ruby API wrapper for
6
- [Cloudimage's API](https://docs.cloudimage.io/go/cloudimage-documentation-v7/en/introduction).
5
+ `cloudimage` is the official Ruby API wrapper for [Cloudimage's API][docs].
7
6
 
8
7
  Supports Ruby `2.4` and above, `JRuby`, and `TruffleRuby`.
9
8
 
@@ -12,14 +11,20 @@ Supports Ruby `2.4` and above, `JRuby`, and `TruffleRuby`.
12
11
  - [Usage](#usage)
13
12
  - [Hash of params](#hash-of-params)
14
13
  - [Chainable helpers](#chainable-helpers)
15
- - [Aliases](#aliases)
14
+ - [Method aliases](#method-aliases)
16
15
  - [Custom helpers](#custom-helpers)
16
+ - [URL aliases](#url-aliases)
17
+ - [`srcset` generation](#srcset-generation)
18
+ - [CNAME](#cname)
19
+ - [Optional API version](#optional-api-version)
17
20
  - [Security](#security)
21
+ - [URL signature](#url-signature)
22
+ - [URL sealing](#url-sealing)
23
+ - [Invalidation API](#invalidation-api)
18
24
  - [Development](#development)
19
- - [TODOs](#todos)
20
25
  - [Contributing](#contributing)
21
26
  - [License](#license)
22
- - [Code of Conduct](#code-of-conduct)
27
+ - [Showcase](#showcase)
23
28
 
24
29
  ## Installation
25
30
 
@@ -39,7 +44,7 @@ Or install it yourself as:
39
44
 
40
45
  ## Usage
41
46
 
42
- The only requirement to get started is your customer token. You can
47
+ The most common way to use Cloudimage is by means of your customer token. You can
43
48
  find it within your Admin interface:
44
49
 
45
50
  ![token](docs/token.png)
@@ -53,12 +58,16 @@ client = Cloudimage::Client.new(token: 'mysecrettoken')
53
58
 
54
59
  Cloudimage client accepts the following options:
55
60
 
56
- | Option | Required? | Additional info |
57
- | ------------------ | --------- | --------------------------------------------------- |
58
- | `token` | Yes | |
59
- | `salt` | No | See [Security](#security). |
60
- | `signature_length` | No | Integer value in the range `6..40`. Defaults to 18. |
61
- | `api_version` | No | Defaults to the current stable version. |
61
+ | Option | Type | Additional info |
62
+ | --------------------- | ------- | ------------------------------------------------------------------------------ |
63
+ | `token` | string | Required if `cname` is missing. |
64
+ | `cname` | string | Required if `token` is missing. See [CNAME](#cname). |
65
+ | `salt` | string | Optional. See [Security](#security). |
66
+ | `signature_length` | integer | Optional. Integer value in the range `6..40`. Defaults to 18. |
67
+ | `sign_urls` | boolean | Optional. Defaults to `true`. See [Security](#security). |
68
+ | `aliases` | hash | Optional. See [URL aliases](#url-aliases). |
69
+ | `api_key` | string | Optional. See [Invalidation API](#invalidation-api). |
70
+ | `include_api_version` | boolean | Optional. Defaults to true. See [Optional API version](#optional-api-version). |
62
71
 
63
72
  Calling `path` on the client object returns an instance of `Cloudimage::URI`.
64
73
  It accepts path to the image as a string and we we will use it to build
@@ -101,7 +110,7 @@ uri.heigth(200).to_url
101
110
  This is useful for catching typos and identifying deprecated methods in
102
111
  case Cloudimage's API changes.
103
112
 
104
- ### Aliases
113
+ ### Method aliases
105
114
 
106
115
  The gem comes with a handful of useful aliases. Consult
107
116
  [`Cloudimage::Params`](lib/cloudimage/params.rb) module for their full list.
@@ -119,8 +128,76 @@ need to accept arguments and will be translated into `param=1` in the final URL.
119
128
  For a list of custom helpers available to you, please consult
120
129
  [`Cloudimage::CustomHelpers`](lib/cloudimage/custom_helpers.rb) module.
121
130
 
131
+ ### URL aliases
132
+
133
+ Specify [aliases][aliases] to automatically replace parts of path with defined values.
134
+ Aliases is a hash which maps strings to be replaced with values to be used instead.
135
+
136
+ ```ruby
137
+ my_alias = 'https://store.s3-us-west-2.amazonaws.com/uploads'
138
+ client = Cloudimage::Client.new(token: 'token', aliases: { my_alias => '_uploads_' })
139
+ client.path('https://store.s3-us-west-2.amazonaws.com/uploads/image.jpg').to_url
140
+ # => "https://token.cloudimg.io/v7/_uploads_/image.jpg"
141
+ ```
142
+
143
+ [URL prefix][url-prefix] is just another form of URL alias. Simply make the target value an empty string:
144
+
145
+ ```ruby
146
+ prefix = 'https://store.s3-us-west-2.amazonaws.com/uploads/'
147
+ client = Cloudimage::Client.new(token: 'token', aliases: { prefix => '' })
148
+ client.path('https://store.s3-us-west-2.amazonaws.com/uploads/image.jpg').to_url
149
+ # => "https://token.cloudimg.io/v7/image.jpg"
150
+ ```
151
+
152
+ You don't need to specify an alias if the input to `path` is a URL that
153
+ already matches the base of the generated URL:
154
+
155
+ ```ruby
156
+ client = Cloudimage::Client.new(token: 'token')
157
+ client.path('https://token.cloudimg.io/v7/image.jpg').to_url(w: 200)
158
+ => "https://token.cloudimg.io/v7/image.jpg?w=200"
159
+ ```
160
+
161
+ ### `srcset` generation
162
+
163
+ Use the provided `to_srcset` method which also accepts any additional params to
164
+ be applied to the `srcset` URLs:
165
+
166
+ ```ruby
167
+ client = Cloudimage::Client.new(token: 'token')
168
+ client.path('/assets/image.jpg').to_srcset(blur: 5)
169
+ # => "https://token.cloudimg.io/v7/assets/image.jpg?blur=5&w=100 100w, https://token.cloudimg.io/v7/assets/image.jpg?blur=5&w=170 170w, https://token.cloudimg.io/v7/assets/image.jpg?blur=5&w=280 280w, https://token.cloudimg.io/v7/assets/image.jpg?blur=5&w=470 470w, https://token.cloudimg.io/v7/assets/image.jpg?blur=5&w=780 780w, https://token.cloudimg.io/v7/assets/image.jpg?blur=5&w=1300 1300w, https://token.cloudimg.io/v7/assets/image.jpg?blur=5&w=2170 2170w, https://token.cloudimg.io/v7/assets/image.jpg?blur=5&w=3620 3620w, https://token.cloudimg.io/v7/assets/image.jpg?blur=5&w=5760 5760w"
170
+ ```
171
+
172
+ A growth factor is applied to exponentially distribute widths between 100 and 5760 pixels.
173
+ See [`Cloudimage::Srcset`](/lib/cloudimage/srcset.rb) for implementation details.
174
+
175
+ ### CNAME
176
+
177
+ If you have a custom CNAME configured for your account, you can
178
+ use it to initialize the client:
179
+
180
+ ```ruby
181
+ client = Cloudimage::Client.new(cname: 'img.klimo.io')
182
+ client.path('/assets/image.jpg').to_url
183
+ # => 'https://img.klimo.io/v7/assets/image.jpg'
184
+ ```
185
+
186
+ ### Optional API version
187
+
188
+ If your account is configured to work without the API version component in the URL,
189
+ you can configure client not to include it in the generated URL:
190
+
191
+ ```ruby
192
+ client = Cloudimage::Client.new(cname: 'img.klimo.io', include_api_version: false)
193
+ client.path('/assets/image.jpg').to_url
194
+ # => "https://img.klimo.io/assets/image.jpg"
195
+ ```
196
+
122
197
  ### Security
123
198
 
199
+ #### URL signature
200
+
124
201
  If `salt` is defined, all URLs will be signed.
125
202
 
126
203
  You can control the length of the generated signature by specifying `signature_length`
@@ -133,6 +210,67 @@ uri.w(200).h(400).to_url
133
210
  # => "https://mysecrettoken.cloudimg.io/v7/assets/image.png?h=400&w=200&ci_sign=79cfbc458b"
134
211
  ```
135
212
 
213
+ #### URL sealing
214
+
215
+ Whereas URL signatures let you protect your URL from any kind of
216
+ tampering, URL sealing protects the params you specify while making
217
+ it possible to append additional params on the fly.
218
+
219
+ This is useful when working with Cloudimage's [responsive frontend libraries][responsive-images].
220
+ A common use case would be sealing your watermark but letting the
221
+ React client request the best possible width.
222
+
223
+ To seal your URLs, initialize client with `salt` and set
224
+ `sign_urls` to `false`. `signature_length` setting is applied
225
+ to control the length of the generated `ci_seal` value.
226
+
227
+ Use the `seal_params` helper to specify which params to seal
228
+ as a list of arguments. These could be symbols or strings.
229
+
230
+ ```ruby
231
+ client = Cloudimage::Client.new(token: 'demoseal', salt: 'test', sign_urls: false)
232
+
233
+ client
234
+ .path('/sample.li/birds.jpg')
235
+ .f('bright:10,contrast:20')
236
+ .w(300)
237
+ .h(400)
238
+ .seal_params(:w, :f)
239
+ .to_url
240
+ # => "https://demoseal.cloudimg.io/v7/sample.li/birds.jpg?ci_eqs=Zj1icmlnaHQlM0ExMCUyQ2NvbnRyYXN0JTNBMjAmdz0zMDA&ci_seal=67dd8cc44f6ba44ee5&h=400"
241
+
242
+ # Alternative approach:
243
+ client
244
+ .path('/sample.li/birds.jpg')
245
+ .to_url(f: 'bright:10,contrast:20', w: 300, h: 400, seal_params: [:w, :f])
246
+ # => "https://demoseal.cloudimg.io/v7/sample.li/birds.jpg?ci_eqs=Zj1icmlnaHQlM0ExMCUyQ2NvbnRyYXN0JTNBMjAmdz0zMDA&ci_seal=67dd8cc44f6ba44ee5&h=400"
247
+ ```
248
+
249
+ This approach protects `w` and `f` values from being edited but
250
+ makes it possible to freely modify the value of `h`.
251
+
252
+ ### Invalidation API
253
+
254
+ To access invalidation API you'll need to initialize client with
255
+ an API key.
256
+
257
+ The provided helper methods accept any number of strings:
258
+
259
+ ```ruby
260
+ client = Cloudimage::Client.new(token: 'token', api_key: 'key')
261
+
262
+ # Invalidate original
263
+ client.invalidate_original('/v7/image.jpg')
264
+
265
+ # Invalidate URLs
266
+ client.invalidate_urls('/v7/image.jpg?w=200', '/v7/image.jpg?h=300')
267
+
268
+ # Invalidate all
269
+ client.invalidate_all
270
+ ```
271
+
272
+ Consult the [invalidation API docs][invalidation-docs] for further details.
273
+
136
274
  ## Development
137
275
 
138
276
  After checking out the repo, run `bin/setup` to install dependencies.
@@ -140,27 +278,29 @@ Then, run `bundle exec rake` to run the tests. You can also run
140
278
  `bin/console` for an interactive prompt that will allow you to
141
279
  experiment.
142
280
 
143
- ### TODOs
144
-
145
- - URL sealing
146
- - Add support for custom CNAMEs
147
- - `srcset` generation
148
- - Purge cache API
149
-
150
281
  ## Contributing
151
282
 
152
283
  Bug reports and pull requests are welcome. This project is intended
153
284
  to be a safe, welcoming space for collaboration, and contributors
154
- are expected to adhere to the
155
- [code of conduct](https://github.com/scaleflex/cloudimage-rb/blob/master/CODE_OF_CONDUCT.md).
285
+ are expected to adhere to the [code of conduct][code-of-conduct].
156
286
 
157
287
  ## License
158
288
 
159
- The gem is available as open source under the terms of the
160
- [MIT License](https://opensource.org/licenses/MIT).
289
+ [MIT](https://opensource.org/licenses/MIT)
290
+
291
+ ## Showcase
292
+
293
+ Among others, `cloudimage` is used to power the following apps:
294
+
295
+ - [Robin PRO][robin-pro] - Fast, beautiful, mobile-friendly image galleries for Shopify stores.
161
296
 
162
- ## Code of Conduct
297
+ Using this gem in your app? Let us know in [this issue](https://github.com/scaleflex/cloudimage-rb/issues/8)
298
+ so that we can feature it.
163
299
 
164
- Everyone interacting with the project's codebase, issues, and pull
165
- requests is expected to follow the
166
- [code of conduct](https://github.com/scaleflex/cloudimage-rb/blob/master/CODE_OF_CONDUCT.md).
300
+ [aliases]: https://docs.cloudimage.io/go/cloudimage-documentation-v7/en/domains-urls/aliases
301
+ [code-of-conduct]: https://github.com/scaleflex/cloudimage-rb/blob/master/CODE_OF_CONDUCT.md
302
+ [docs]: https://docs.cloudimage.io/go/cloudimage-documentation-v7/en/introduction
303
+ [invalidation-docs]: https://docs.cloudimage.io/go/cloudimage-documentation-v7/en/caching-acceleration/invalidation-api
304
+ [responsive-images]: https://docs.cloudimage.io/go/cloudimage-documentation-v7/en/responsive-images
305
+ [robin-pro]: https://apps.shopify.com/robin-pro-image-gallery
306
+ [url-prefix]: https://docs.cloudimage.io/go/cloudimage-documentation-v7/en/domains-urls/origin-url-prefix
@@ -2,5 +2,4 @@
2
2
 
3
3
  require 'addressable/uri'
4
4
 
5
- require_relative 'cloudimage/version'
6
5
  require_relative 'cloudimage/client'
@@ -1,25 +1,22 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'uri'
4
+ require_relative 'invalidation'
4
5
 
5
6
  module Cloudimage
6
7
  class InvalidConfig < StandardError; end
7
8
 
8
9
  class Client
10
+ include Invalidation
11
+
9
12
  attr_reader :config
10
13
 
11
14
  API_VERSION = 'v7'
12
15
  DEFAULT_SIGNATURE_LENGTH = 18
13
16
 
14
17
  def initialize(**options)
15
- @config = {}
16
- @config[:token] = options[:token]
17
- @config[:salt] = options[:salt]
18
- @config[:signature_length] =
19
- options[:signature_length] || DEFAULT_SIGNATURE_LENGTH
20
- @config[:api_version] = API_VERSION
21
-
22
- ensure_valid_config
18
+ @config = set_config_defaults(options)
19
+ validate_config
23
20
  end
24
21
 
25
22
  def path(path)
@@ -28,18 +25,31 @@ module Cloudimage
28
25
 
29
26
  private
30
27
 
31
- def ensure_valid_config
32
- ensure_valid_token
33
- ensure_valid_signature_length
28
+ def set_config_defaults(options)
29
+ options.tap do |config|
30
+ config[:signature_length] =
31
+ options[:signature_length] || DEFAULT_SIGNATURE_LENGTH
32
+ config[:api_version] = API_VERSION
33
+ config[:include_api_version] =
34
+ options[:include_api_version].nil? ? true : false
35
+ config[:sign_urls] = options[:sign_urls].nil? ? true : false
36
+ config[:aliases] = options[:aliases] || {}
37
+ end
38
+ end
39
+
40
+ def validate_config
41
+ validate_site_config
42
+ validate_signature_length
34
43
  end
35
44
 
36
- def ensure_valid_token
37
- return unless config[:token].nil?
45
+ def validate_site_config
46
+ return unless config[:token].nil? && config[:cname].nil?
38
47
 
39
- raise InvalidConfig, 'Please specify your Cloudimage customer token.'
48
+ raise InvalidConfig,
49
+ 'Please specify your customer token or a custom CNAME.'
40
50
  end
41
51
 
42
- def ensure_valid_signature_length
52
+ def validate_signature_length
43
53
  return if config[:salt].nil?
44
54
  return if (6..40).cover? config[:signature_length]
45
55
 
@@ -5,5 +5,11 @@ module Cloudimage
5
5
  def positionable_crop(origin_x:, origin_y:, width:, height:)
6
6
  tl_px(origin_x, origin_y).br_px(origin_x + width, origin_y + height)
7
7
  end
8
+
9
+ def seal_params(*query_params)
10
+ # URI#query_values returns hash where keys are strings.
11
+ sealed_params.merge(query_params.map(&:to_s))
12
+ self
13
+ end
8
14
  end
9
15
  end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'net/http'
4
+ require 'json'
5
+
6
+ module Cloudimage
7
+ module Invalidation
8
+ ENDPOINT = ::URI.parse('https://api.cloudimage.com/invalidate')
9
+
10
+ %i[original urls all].each do |type|
11
+ define_method "invalidate_#{type}" do |*paths|
12
+ validate_api_key
13
+
14
+ body = {
15
+ scope: type,
16
+ }
17
+
18
+ body[:urls] = paths if paths.any?
19
+
20
+ send_request(body)
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ def validate_api_key
27
+ return if config[:api_key]
28
+
29
+ raise InvalidConfig, 'API key is required to perform cache invalidation.'
30
+ end
31
+
32
+ def headers
33
+ {
34
+ 'X-Client-Key': config[:api_key],
35
+ 'Content-Type': 'application/json',
36
+ }
37
+ end
38
+
39
+ def send_request(body)
40
+ http = Net::HTTP.new(ENDPOINT.host, ENDPOINT.port)
41
+ http.use_ssl = true
42
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
43
+
44
+ request = Net::HTTP::Post.new(ENDPOINT.path, headers)
45
+ request.body = body.to_json
46
+
47
+ http.request(request)
48
+ end
49
+ end
50
+ end
@@ -26,6 +26,7 @@ module Cloudimage
26
26
  contrast
27
27
  doc_page
28
28
  f
29
+ force_download
29
30
  force_format
30
31
  func
31
32
  gravity
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cloudimage
4
+ module Refinements
5
+ unless {}.respond_to?(:slice)
6
+ refine Hash do
7
+ def slice(*keys)
8
+ keys.each_with_object({}) do |k, acc|
9
+ acc[k] = self[k] if key?(k)
10
+ end
11
+ end
12
+ end
13
+ end
14
+
15
+ unless ''.respond_to?(:delete_prefix)
16
+ refine String do
17
+ def delete_prefix(prefix)
18
+ sub(/\A#{prefix}/, '')
19
+ end
20
+ end
21
+ end
22
+
23
+ unless Kernel.respond_to?(:yield_self)
24
+ refine Kernel do
25
+ def yield_self
26
+ unless block_given?
27
+ return ::Enumerator.new(1) do |yielder|
28
+ yielder.yield(self)
29
+ end
30
+ end
31
+
32
+ yield(self)
33
+ end
34
+ end
35
+ end
36
+
37
+ unless Kernel.respond_to?(:then)
38
+ refine Kernel do
39
+ def then
40
+ return yield_self unless block_given?
41
+
42
+ yield_self(&::Proc.new)
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'digest'
4
+ require 'base64'
5
+
6
+ require_relative 'refinements'
7
+
8
+ module Cloudimage
9
+ class Security
10
+ using Refinements
11
+
12
+ attr_reader :uri, :config
13
+
14
+ def initialize(uri, **config)
15
+ @uri = uri
16
+ @config = config
17
+ end
18
+
19
+ def sign_url(request_uri)
20
+ query = uri.query_values || {}
21
+ uri.query_values = query.merge(ci_sign: signature(request_uri))
22
+ end
23
+
24
+ def seal_url(path, sealed_params)
25
+ query = uri.query_values || {}
26
+ sealed_query = query.slice(*sealed_params)
27
+ query.keep_if { |k, _| !sealed_query.key?(k) }
28
+ eqs = eqs(sealed_query)
29
+ query[:ci_eqs] = eqs unless eqs.empty?
30
+ query[:ci_seal] = seal(path, eqs)
31
+ uri.query_values = query
32
+ end
33
+
34
+ private
35
+
36
+ def signature(request_uri)
37
+ digest = Digest::SHA1.hexdigest(config[:salt] + request_uri)
38
+ trim(digest, config[:signature_length])
39
+ end
40
+
41
+ def eqs(query_params)
42
+ uri = Addressable::URI.new
43
+ uri.query_values = query_params
44
+ Base64.urlsafe_encode64(uri.query, padding: false)
45
+ end
46
+
47
+ def seal(path, eqs)
48
+ digest = Digest::SHA1.hexdigest(path + eqs + config[:salt])
49
+ trim(digest, config[:signature_length])
50
+ end
51
+
52
+ def trim(signature, length)
53
+ signature[0, length]
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cloudimage
4
+ module Srcset
5
+ # 5760 is 3x 1920 (the largest common screen width):
6
+ # https://gs.statcounter.com/screen-resolution-stats
7
+ SRCSET_RANGE = (100..5_760).freeze
8
+ SRCSET_GROWTH_FACTOR = 1.67
9
+
10
+ def to_srcset(**extra_params)
11
+ srcset_widths
12
+ .map { |width| "#{to_url(**extra_params, w: width)} #{width}w" }
13
+ .join(', ')
14
+ end
15
+
16
+ private
17
+
18
+ def srcset_widths
19
+ current = SRCSET_RANGE.begin
20
+
21
+ [].tap do |widths|
22
+ loop do
23
+ widths << current
24
+ current = (current * SRCSET_GROWTH_FACTOR).round(-1)
25
+
26
+ break if current >= SRCSET_RANGE.end && widths << SRCSET_RANGE.end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -1,31 +1,35 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'digest'
3
+ require 'set'
4
4
 
5
5
  require_relative 'params'
6
6
  require_relative 'custom_helpers'
7
+ require_relative 'security'
8
+ require_relative 'srcset'
9
+ require_relative 'refinements'
7
10
 
8
11
  module Cloudimage
9
12
  class URI
13
+ using Refinements
14
+
10
15
  include Params
11
16
  include CustomHelpers
17
+ include Srcset
12
18
 
13
- attr_reader :uri, :params, :config
19
+ attr_reader :path, :uri, :params, :config, :sealed_params
14
20
 
15
21
  def initialize(path, **config)
16
22
  @config = config
17
23
  @params = {}
18
- @uri = build_uri_from(path)
24
+ @sealed_params = Set.new
25
+ @path = transform(path)
26
+ @uri = build_uri
19
27
  end
20
28
 
21
29
  PARAMS.each do |param|
22
30
  define_method param do |*args|
23
- @params[param] = if args.any?
24
- args.join(',')
25
- else
26
- # Flag params don't need to pass in arguments.
27
- @params[param] = 1
28
- end
31
+ # Flag params don't need to pass in arguments.
32
+ params[param] = args.any? ? args.join(',') : 1
29
33
  self
30
34
  end
31
35
  end
@@ -36,43 +40,72 @@ module Cloudimage
36
40
 
37
41
  def to_url(**extra_params)
38
42
  set_uri_params(**extra_params)
39
- sign_url
43
+ secure_url
44
+ uri.to_s
40
45
  end
41
46
 
42
47
  private
43
48
 
44
- def base_url
49
+ def site
50
+ return "https://#{config[:cname]}" if config[:cname]
51
+
45
52
  "https://#{config[:token]}.cloudimg.io"
46
53
  end
47
54
 
48
- def base_url_with_api_version
49
- "#{base_url}/#{config[:api_version]}"
55
+ def api_version
56
+ "/#{config[:api_version]}"
57
+ end
58
+
59
+ def transform(path)
60
+ path
61
+ .then { |input| input.start_with?('/') ? input : "/#{input}" }
62
+ .then(&method(:apply_aliases))
63
+ end
64
+
65
+ def apply_aliases(path)
66
+ config[:aliases][default_alias] = ''
67
+
68
+ path.dup.tap do |input|
69
+ config[:aliases].each do |source, target|
70
+ input.sub!(source, target)
71
+ end
72
+ end
73
+ end
74
+
75
+ def default_alias
76
+ config[:include_api_version] ? "#{site}#{api_version}/" : "#{site}/"
77
+ end
78
+
79
+ def request_uri
80
+ uri.request_uri.delete_prefix(api_version)
50
81
  end
51
82
 
52
- def build_uri_from(path)
53
- formatted_path = path.start_with?('/') ? path : "/#{path}"
54
- Addressable::URI.parse(base_url_with_api_version + formatted_path)
83
+ def build_uri
84
+ if config[:include_api_version]
85
+ Addressable::URI.parse(site + api_version + path)
86
+ else
87
+ Addressable::URI.parse(site + path)
88
+ end
55
89
  end
56
90
 
57
91
  def set_uri_params(**extra_params)
92
+ seal_params(*extra_params.delete(:seal_params))
58
93
  url_params = params.merge(**extra_params)
59
94
  return unless url_params.any?
60
95
 
61
96
  uri.query_values = url_params
62
97
  end
63
98
 
64
- def sign_url
65
- url = uri.to_s
66
-
67
- return url if config[:salt].nil?
99
+ def secure_url
100
+ return uri.to_s if config[:salt].nil?
68
101
 
69
- url + "#{uri.query_values ? '&' : '?'}ci_sign=#{signature}"
70
- end
102
+ security = Security.new(uri, **config)
71
103
 
72
- def signature
73
- path = uri.to_s.sub(base_url_with_api_version, '')
74
- digest = Digest::SHA1.hexdigest(config[:salt] + path)
75
- digest[0..(config[:signature_length] - 1)]
104
+ if config[:sign_urls]
105
+ security.sign_url(request_uri)
106
+ else
107
+ security.seal_url(path, sealed_params)
108
+ end
76
109
  end
77
110
  end
78
111
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cloudimage
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jan Klimo
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-06-28 00:00:00.000000000 Z
11
+ date: 2020-08-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: addressable
@@ -24,6 +24,34 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '2.7'
27
+ - !ruby/object:Gem::Dependency
28
+ name: github_changelog_generator
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 1.15.2
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 1.15.2
41
+ - !ruby/object:Gem::Dependency
42
+ name: pry
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '0.13'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '0.13'
27
55
  description: Fast and easy image resizing, transformation, and acceleration in the
28
56
  Cloud.
29
57
  email:
@@ -40,9 +68,12 @@ files:
40
68
  - lib/cloudimage.rb
41
69
  - lib/cloudimage/client.rb
42
70
  - lib/cloudimage/custom_helpers.rb
71
+ - lib/cloudimage/invalidation.rb
43
72
  - lib/cloudimage/params.rb
73
+ - lib/cloudimage/refinements.rb
74
+ - lib/cloudimage/security.rb
75
+ - lib/cloudimage/srcset.rb
44
76
  - lib/cloudimage/uri.rb
45
- - lib/cloudimage/version.rb
46
77
  homepage: https://github.com/scaleflex/cloudimage-rb
47
78
  licenses:
48
79
  - MIT
@@ -1,5 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Cloudimage
4
- VERSION = '0.2.0'
5
- end