cloudimage 0.2.1 → 0.6.1

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: 278e88b372ae0e007da88f05ede4180b1db4203c5b36dca72acf56e1f510d2e6
4
- data.tar.gz: e4761b0f0ceb55bc6311e2966472a0b52dbc7694f37833f3dec6a6c8baf80a9c
3
+ metadata.gz: 4367d92586decc3e9c65338418bad7a9b6b741501de236f72d421a44713ec9e1
4
+ data.tar.gz: e5003ebd0e1b125007b395dfbe2c04256f52cec0d452428fa82fb154ed75eb56
5
5
  SHA512:
6
- metadata.gz: 323332b25479d79a894406a05dda4f9e91d119dd1d4b43f8a4880a61aa9bd5a2fa4756ec9b33e803241bef2c616f4eefdfda0e32683234f1255d4815bf582110
7
- data.tar.gz: af73227c277fb3b7b63150135e730536a7c40018e90b39c7478a4fb6592981e389e883a5529fb5ce76e217a2e117c353b1a9f6dbddde4d590a2b347dc916ccb1
6
+ metadata.gz: 4d614cd97790eec3443da301aae5b12fa733018e08934b5a5bc41604179537a36573bb44997bcbd9ef6677e678874fc81a4a73a6e55e87727233a84af1f9892d
7
+ data.tar.gz: 9adeccc55a9d1a3b3368a84657ac9954243e1eefa1fc5e0fb253385bb57d08cc0e1d75f68805cdfde7352a0bcdcefea533a8606b11960402dbe7f55127543f26
@@ -1,4 +1,44 @@
1
- ## master
1
+ # Changelog
2
+
3
+ ## [v0.6.0](https://github.com/scaleflex/cloudimage-rb/tree/v0.6.0) (2020-08-16)
4
+
5
+ [Full Changelog](https://github.com/scaleflex/cloudimage-rb/compare/v0.5.0...v0.6.0)
6
+
7
+ **Implemented enhancements:**
8
+
9
+ - Add srcset generation [\#27](https://github.com/scaleflex/cloudimage-rb/pull/27) ([janklimo](https://github.com/janklimo))
10
+ - Add default alias [\#26](https://github.com/scaleflex/cloudimage-rb/pull/26) ([janklimo](https://github.com/janklimo))
11
+ - Configurable API version URL component [\#25](https://github.com/scaleflex/cloudimage-rb/pull/25) ([janklimo](https://github.com/janklimo))
12
+
13
+ ## [v0.5.0](https://github.com/scaleflex/cloudimage-rb/tree/v0.5.0) (2020-08-02)
14
+
15
+ [Full Changelog](https://github.com/scaleflex/cloudimage-rb/compare/v0.4.0...v0.5.0)
16
+
17
+ **Implemented enhancements:**
18
+
19
+ - Invalidation [\#24](https://github.com/scaleflex/cloudimage-rb/pull/24) ([janklimo](https://github.com/janklimo))
20
+ - Add support for custom CNAMEs [\#21](https://github.com/scaleflex/cloudimage-rb/pull/21) ([janklimo](https://github.com/janklimo))
21
+
22
+ ## [v0.4.0](https://github.com/scaleflex/cloudimage-rb/tree/v0.4.0) (2020-07-19)
23
+
24
+ [Full Changelog](https://github.com/scaleflex/cloudimage-rb/compare/v0.3.0...v0.4.0)
25
+
26
+ **Implemented enhancements:**
27
+
28
+ - Add support for aliases [\#20](https://github.com/scaleflex/cloudimage-rb/pull/20) ([janklimo](https://github.com/janklimo))
29
+
30
+ ## [v0.3.0](https://github.com/scaleflex/cloudimage-rb/tree/v0.3.0) (2020-07-09)
31
+
32
+ [Full Changelog](https://github.com/scaleflex/cloudimage-rb/compare/v0.2.1...v0.3.0)
33
+
34
+ **Implemented enhancements:**
35
+
36
+ - Introduce URL sealing [\#10](https://github.com/scaleflex/cloudimage-rb/pull/10) ([janklimo](https://github.com/janklimo))
37
+
38
+ **Merged pull requests:**
39
+
40
+ - Use changelog generation [\#16](https://github.com/scaleflex/cloudimage-rb/pull/16) ([janklimo](https://github.com/janklimo))
41
+ - Add test coverage with SimpleCov [\#9](https://github.com/scaleflex/cloudimage-rb/pull/9) ([janklimo](https://github.com/janklimo))
2
42
 
3
43
  ## 0.2.1 (2020-06-29)
4
44
 
@@ -23,3 +63,6 @@
23
63
 
24
64
  - Set up Github actions for CI.
25
65
  [#1](https://github.com/scaleflex/cloudimage-rb/pull/1) (@janklimo)
66
+
67
+
68
+ \* *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,31 @@ 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.
296
+ - [Spotlightify][spotlightify] - Turning the spotlight on the best Shopify stores.
161
297
 
162
- ## Code of Conduct
298
+ Using this gem in your app? Let us know in [this issue](https://github.com/scaleflex/cloudimage-rb/issues/8)
299
+ so that we can feature it.
163
300
 
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).
301
+ [aliases]: https://docs.cloudimage.io/go/cloudimage-documentation-v7/en/domains-urls/aliases
302
+ [code-of-conduct]: https://github.com/scaleflex/cloudimage-rb/blob/master/CODE_OF_CONDUCT.md
303
+ [docs]: https://docs.cloudimage.io/go/cloudimage-documentation-v7/en/introduction
304
+ [invalidation-docs]: https://docs.cloudimage.io/go/cloudimage-documentation-v7/en/caching-acceleration/invalidation-api
305
+ [responsive-images]: https://docs.cloudimage.io/go/cloudimage-documentation-v7/en/responsive-images
306
+ [robin-pro]: https://apps.shopify.com/robin-pro-image-gallery
307
+ [spotlightify]: https://www.spotlightify.com/
308
+ [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
@@ -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,75 @@ 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
+ # path and aliases can be frozen
67
+ aliases = config[:aliases].dup
68
+
69
+ aliases[default_alias] = ''
70
+
71
+ path.dup.tap do |input|
72
+ aliases.each do |source, target|
73
+ input.sub!(source, target)
74
+ end
75
+ end
50
76
  end
51
77
 
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)
78
+ def default_alias
79
+ config[:include_api_version] ? "#{site}#{api_version}/" : "#{site}/"
80
+ end
81
+
82
+ def request_uri
83
+ uri.request_uri.delete_prefix(api_version)
84
+ end
85
+
86
+ def build_uri
87
+ if config[:include_api_version]
88
+ Addressable::URI.parse(site + api_version + path)
89
+ else
90
+ Addressable::URI.parse(site + path)
91
+ end
55
92
  end
56
93
 
57
94
  def set_uri_params(**extra_params)
95
+ seal_params(*extra_params.delete(:seal_params))
58
96
  url_params = params.merge(**extra_params)
59
97
  return unless url_params.any?
60
98
 
61
99
  uri.query_values = url_params
62
100
  end
63
101
 
64
- def sign_url
65
- url = uri.to_s
66
-
67
- return url if config[:salt].nil?
102
+ def secure_url
103
+ return uri.to_s if config[:salt].nil?
68
104
 
69
- url + "#{uri.query_values ? '&' : '?'}ci_sign=#{signature}"
70
- end
105
+ security = Security.new(uri, **config)
71
106
 
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)]
107
+ if config[:sign_urls]
108
+ security.sign_url(request_uri)
109
+ else
110
+ security.seal_url(path, sealed_params)
111
+ end
76
112
  end
77
113
  end
78
114
  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.1
4
+ version: 0.6.1
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-29 00:00:00.000000000 Z
11
+ date: 2020-09-13 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.1'
5
- end