image_size 3.3.0 → 3.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4d0b43a481f6a18e76f7cf8325240014d2a1e07a52cfb462be37444e020ff036
4
- data.tar.gz: 21edad5bbdd1a4e4b60953ee2769bd0648e8deb389f220f0dbd120f6a9dee09d
3
+ metadata.gz: 2a128281e6674bd684b8485151f3938a5d34d8fdf146e293e908e8cfb5c762d5
4
+ data.tar.gz: 0615d7750931da9cf45ec6003fd1f20dba7638e393a6d18bbec53901c078e864
5
5
  SHA512:
6
- metadata.gz: 78e9a671390fc80747273ccd6555e2faa915c4186261f473e86aa274f3e93e75c5e26f4674f3fea307d8a94ca21e352dfeb44a2046f20fb453d7e394f56c13b5
7
- data.tar.gz: 3dd7259403bb9aebc0d75834330f8db1b21d68bc619ec9204475e46e4c46b818ce28af2712d57a0c7ef3d7bb1a5922f3d91ba3a6b6779c7421e2fd2d9eaf4182
6
+ metadata.gz: d2d3cf28085954077a3016735c15ede7827dd1a72afd0617182d4278f0890f8ef41cbc8e84fe7d2661a270a8ce570b6e3cfeaa148c8be4d141c3a233cb712a59
7
+ data.tar.gz: a6dd7107a1c344a4963cf527d80e76c08e6b64cf22ae2edb6d2c7dad022f3452505a369fc9a4b4c665136e34ee2379a41b235e5d89881a32721c49ae14c6b6aa
@@ -0,0 +1,8 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: github-actions
4
+ directory: /
5
+ schedule:
6
+ interval: daily
7
+ cooldown:
8
+ default-days: 7
@@ -6,10 +6,13 @@ on:
6
6
  - cron: 45 4 * * 3
7
7
  jobs:
8
8
  check:
9
- runs-on: ubuntu-latest
9
+ runs-on: ubuntu-22.04
10
10
  strategy:
11
11
  matrix:
12
12
  ruby:
13
+ - '1.9.3'
14
+ - '2.0'
15
+ - '2.1'
13
16
  - '2.3'
14
17
  - '2.4'
15
18
  - '2.5'
@@ -18,32 +21,19 @@ jobs:
18
21
  - '3.0'
19
22
  - '3.1'
20
23
  - '3.2'
21
- - jruby-9.3
24
+ - '3.3'
25
+ - '3.4'
26
+ - '4.0'
22
27
  - jruby-9.4
28
+ - jruby-10.1
23
29
  fail-fast: false
24
30
  steps:
25
- - uses: actions/checkout@v3
31
+ - uses: actions/checkout@v6
26
32
  - uses: ruby/setup-ruby@v1
27
33
  with:
28
34
  ruby-version: "${{ matrix.ruby }}"
29
35
  bundler-cache: true
30
36
  - run: bundle exec rspec --format documentation
31
- legacy:
32
- runs-on: ubuntu-latest
33
- container: ${{ matrix.container }}
34
- strategy:
35
- matrix:
36
- container:
37
- - rspec/ci:1.8.7
38
- - rspec/ci:1.9.3
39
- - ruby:2.0
40
- - ruby:2.1
41
- - ruby:2.2
42
- fail-fast: false
43
- steps:
44
- - uses: actions/checkout@v3
45
- - run: bundle install
46
- - run: bundle exec rspec --format documentation
47
37
  windows:
48
38
  runs-on: windows-latest
49
39
  strategy:
@@ -54,10 +44,12 @@ jobs:
54
44
  - '3.0'
55
45
  - '3.1'
56
46
  - '3.2'
47
+ - '3.3'
48
+ - '3.4'
49
+ - '4.0'
57
50
  fail-fast: false
58
- continue-on-error: ${{ matrix.ruby == '3.2' }}
59
51
  steps:
60
- - uses: actions/checkout@v3
52
+ - uses: actions/checkout@v6
61
53
  - uses: ruby/setup-ruby@v1
62
54
  with:
63
55
  ruby-version: "${{ matrix.ruby }}"
@@ -8,9 +8,9 @@ jobs:
8
8
  rubocop:
9
9
  runs-on: ubuntu-latest
10
10
  steps:
11
- - uses: actions/checkout@v3
11
+ - uses: actions/checkout@v6
12
12
  - uses: ruby/setup-ruby@v1
13
13
  with:
14
- ruby-version: '3'
14
+ ruby-version: '4'
15
15
  bundler-cache: true
16
16
  - run: bundle exec rubocop
data/.rubocop.yml CHANGED
@@ -15,9 +15,6 @@ Layout/CaseIndentation:
15
15
  Layout/EndAlignment:
16
16
  EnforcedStyleAlignWith: variable
17
17
 
18
- Layout/FirstHashElementIndentation:
19
- EnforcedStyle: consistent
20
-
21
18
  Layout/LineLength:
22
19
  Max: 120
23
20
 
@@ -25,9 +22,6 @@ Layout/SpaceBeforeBlockBraces:
25
22
  EnforcedStyle: no_space
26
23
  EnforcedStyleForEmptyBraces: no_space
27
24
 
28
- Lint/PercentStringArray: # broken in rubocop 0.55.0
29
- Enabled: false
30
-
31
25
  Metrics/BlockLength:
32
26
  Exclude:
33
27
  - 'spec/**/*.rb'
@@ -41,45 +35,24 @@ Metrics/MethodLength:
41
35
  Naming/MethodParameterName:
42
36
  Enabled: false
43
37
 
44
- Style/AccessorGrouping:
45
- Enabled: false
46
-
47
38
  Style/Alias:
48
39
  EnforcedStyle: prefer_alias_method
49
40
 
50
- Style/EmptyCaseCondition:
51
- Enabled: false
52
-
53
- Style/Encoding:
54
- Enabled: false
55
-
56
- Style/FileRead:
41
+ Style/ArgumentsForwarding:
57
42
  Enabled: false
58
43
 
59
- Style/HashEachMethods:
60
- Enabled: true
61
-
62
- Style/HashSyntax:
63
- EnforcedStyle: hash_rockets
64
-
65
- Style/HashTransformKeys:
66
- Enabled: false
67
-
68
- Style/HashTransformValues:
69
- Enabled: false
70
-
71
- Style/IfUnlessModifier:
44
+ Style/EmptyCaseCondition:
72
45
  Enabled: false
73
46
 
74
47
  Style/NumericPredicate:
75
48
  Enabled: false
76
49
 
77
- Style/ParallelAssignment:
78
- Enabled: false
79
-
80
50
  Style/SafeNavigation:
81
51
  Enabled: false
82
52
 
53
+ Style/SignalException:
54
+ EnforcedStyle: semantic
55
+
83
56
  Style/SlicingWithRange:
84
57
  Enabled: false
85
58
 
data/.rubocop_todo.yml CHANGED
@@ -20,9 +20,3 @@ Metrics/CyclomaticComplexity:
20
20
  # Configuration parameters: IgnoredMethods.
21
21
  Metrics/PerceivedComplexity:
22
22
  Enabled: false
23
-
24
- # Offense count: 8
25
- # Cop supports --auto-correct.
26
- Style/PerlBackrefs:
27
- Exclude:
28
- - 'lib/image_size.rb'
data/CHANGELOG.markdown CHANGED
@@ -2,6 +2,22 @@
2
2
 
3
3
  ## unreleased
4
4
 
5
+ ## v3.5.0 (2026-05-03)
6
+
7
+ * Add `byte_size` method to expose the size of the image data in bytes [#27](https://github.com/toy/image_size/pull/27) [@dgodd](https://github.com/dgodd)
8
+ * Add minimal validation of `dpi` global configuration [@toy](https://github.com/toy)
9
+ * Make `chunk_size` globally configurable [@toy](https://github.com/toy)
10
+ * Make `max_redirects` globally configurable [@toy](https://github.com/toy)
11
+ * Prevent requesting chunks over http after end of file [#29](https://github.com/toy/image_size/issues/29) [@toy](https://github.com/toy)
12
+ * Add ability to restrict fetched URIs by setting `uri_checker` proc [@toy](https://github.com/toy)
13
+
14
+ ## v3.4.0 (2024-01-16)
15
+
16
+ * Provide access to media types using `media_type` and `media_types` methods [#22](https://github.com/toy/image_size/issues/22) [@toy](https://github.com/toy)
17
+ * Allow fetching from HTTP server by requiring `image_size/uri` [@toy](https://github.com/toy)
18
+ * Fix for `ArgumentError` when requiring only `image_size/uri_reader` (without image_size) [@toy](https://github.com/toy)
19
+ * Require ruby 1.9.3 [@toy](https://github.com/toy)
20
+
5
21
  ## v3.3.0 (2023-05-30)
6
22
 
7
23
  * Support `HEIF` (`HEIC` and `AVIF`) images [#19](https://github.com/toy/image_size/issues/19) [@toy](https://github.com/toy)
data/README.markdown CHANGED
@@ -1,5 +1,5 @@
1
1
  [![Gem Version](https://img.shields.io/gem/v/image_size?logo=rubygems)](https://rubygems.org/gems/image_size)
2
- [![Build Status](https://img.shields.io/github/actions/workflow/status/toy/image_size/check.yml?logo=github)](https://github.com/toy/image_size/actions/workflows/check.yml)
2
+ [![Check](https://img.shields.io/github/actions/workflow/status/toy/image_size/check.yml?label=check&logo=github)](https://github.com/toy/image_size/actions/workflows/check.yml)
3
3
  [![Rubocop](https://img.shields.io/github/actions/workflow/status/toy/image_size/rubocop.yml?label=rubocop&logo=rubocop)](https://github.com/toy/image_size/actions/workflows/rubocop.yml)
4
4
 
5
5
  # image_size
@@ -26,7 +26,7 @@ gem 'image_size', '~> 3.0'
26
26
  ```ruby
27
27
  image_size = ImageSize.path('spec/images/jpeg/436x429.jpeg')
28
28
 
29
- image_size.format #=> :jpec
29
+ image_size.format #=> :jpeg
30
30
  image_size.width #=> 436
31
31
  image_size.height #=> 429
32
32
  image_size.w #=> 436
@@ -38,6 +38,9 @@ image_size.size.width #=> 436
38
38
  image_size.size.height #=> 429
39
39
  image_size.size.w #=> 436
40
40
  image_size.size.h #=> 429
41
+ image_size.media_type #=> "image/jpeg"
42
+ image_size.media_types #=> ["image/jpeg"]
43
+ image_size.byte_size #=> 10938
41
44
  ```
42
45
 
43
46
  Or using `IO` object:
@@ -90,14 +93,27 @@ File.open('spec/images/jpeg/436x429.jpeg', 'rb') do |fh|
90
93
  end
91
94
  ```
92
95
 
96
+ ### Configuration
97
+
98
+ DPI used for converting `svg` and `emf` dimensions can be configured from default 72:
99
+
100
+ ```ruby
101
+ ImageSize.dpi = 150
102
+ ```
103
+
104
+ Chunk size used for reading files, IO and from remote can be configured from default 4096:
105
+
106
+ ```ruby
107
+ ImageSize.chunk_size = 256
108
+ ```
109
+
93
110
  ### Experimental: fetch image meta from HTTP server
94
111
 
95
112
  If server recognises Range header, only needed chunks will be fetched even for TIFF images, otherwise required amount
96
113
  of data will be fetched, in most cases first few kilobytes (TIFF images is an exception).
97
114
 
98
115
  ```ruby
99
- require 'image_size'
100
- require 'image_size/uri_reader'
116
+ require 'image_size/uri'
101
117
 
102
118
  url = 'http://upload.wikimedia.org/wikipedia/commons/b/b4/Mardin_1350660_1350692_33_images.jpg'
103
119
  p ImageSize.url(url).size
@@ -139,9 +155,41 @@ puts Benchmark.measure{ p ImageSize.url(url).size }
139
155
  0.006247 0.001045 0.007292 ( 0.197631)
140
156
  ```
141
157
 
158
+ #### Configuration
159
+
160
+ Maximum number of redirects can be configured from default 5:
161
+
162
+ ```ruby
163
+ ImageSize.max_redirects = 10
164
+ ```
165
+
166
+ Or redirects can be disabled:
167
+
168
+ ```ruby
169
+ ImageSize.max_redirects = 0
170
+ ```
171
+
172
+ A url checker can be added to reduce SSRF risk:
173
+
174
+ ```ruby
175
+ ImageSize.uri_checker = lambda do |uri|
176
+ raise 'host is not allowed' unless uri.host == 'upload.wikipedia.org'
177
+ end
178
+ ```
179
+
180
+ Alternatively `private_address_check` gem can be of help:
181
+
182
+ ```ruby
183
+ require "private_address_check/tcpsocket_ext"
184
+
185
+ PrivateAddressCheck.only_public_connections do
186
+ ImageSize.url(url).size
187
+ end
188
+ ```
189
+
142
190
  ## Licence
143
191
 
144
192
  This code is free to use under the terms of the [Ruby's licence](LICENSE.txt).
145
193
 
146
194
  Original author: Keisuke Minami <keisuke@rccn.com>.\
147
- Further development 2010-2023 Ivan Kuchin https://github.com/toy/image_size
195
+ Further development 2010-2026 Ivan Kuchin https://github.com/toy/image_size
data/image_size.gemspec CHANGED
@@ -2,23 +2,34 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = 'image_size'
5
- s.version = '3.3.0'
5
+ s.version = '3.5.0'
6
6
  s.summary = %q{Measure image size/dimensions using pure Ruby}
7
7
  s.description = %q{Measure following file dimensions: apng, avif, bmp, cur, emf, gif, heic, heif, ico, j2c, jp2, jpeg, jpx, mng, pam, pbm, pcx, pgm, png, ppm, psd, svg, swf, tiff, webp, xbm, xpm}
8
8
  s.homepage = "https://github.com/toy/#{s.name}"
9
9
  s.authors = ['Keisuke Minami', 'Ivan Kuchin']
10
10
  s.license = 'Ruby'
11
11
 
12
+ s.required_ruby_version = '>= 1.9.3'
13
+
12
14
  s.metadata = {
13
15
  'bug_tracker_uri' => "https://github.com/toy/#{s.name}/issues",
14
16
  'changelog_uri' => "https://github.com/toy/#{s.name}/blob/master/CHANGELOG.markdown",
15
17
  'documentation_uri' => "https://www.rubydoc.info/gems/#{s.name}/#{s.version}",
16
18
  'source_code_uri' => "https://github.com/toy/#{s.name}",
17
- }
19
+ } if s.respond_to?(:metadata=)
20
+
21
+ s.files = Dir[*%w[
22
+ .gitignore
23
+ .rubocop*.yml
24
+ Gemfile
25
+ LICENSE.txt
26
+ GPL
27
+ *.markdown
28
+ *.gemspec
29
+ {.github,lib,spec}/**/{*,.gitattributes}
30
+ ]].reject(&File.method(:directory?))
18
31
 
19
- s.files = `git ls-files`.split("\n")
20
- s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
21
- s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
32
+ s.test_files = Dir['spec/**/{*,.gitattributes}'].reject(&File.method(:directory?))
22
33
  s.require_paths = %w[lib]
23
34
 
24
35
  s.add_development_dependency 'rspec', '~> 3.0'
@@ -8,7 +8,7 @@ class ImageSize
8
8
 
9
9
  # Size of a chunk in which to read
10
10
  def chunk_size
11
- 4096
11
+ @chunk_size ||= ImageSize.chunk_size
12
12
  end
13
13
 
14
14
  # Including class should define method chunk that accepts the chunk number
@@ -18,7 +18,7 @@ class ImageSize
18
18
  # substring, behaves same as str[start, length] except start can't be
19
19
  # negative.
20
20
  def [](offset, length)
21
- raise ArgumentError, "expected offset not to be negative, got #{offset}" if offset < 0
21
+ fail ArgumentError, "expected offset not to be negative, got #{offset}" if offset < 0
22
22
  return if length < 0
23
23
 
24
24
  first = offset / chunk_size
@@ -56,17 +56,17 @@ class ImageSize
56
56
  when 1
57
57
  size = reader.unpack1(offset + 8, 8, 'Q>')
58
58
  relative_data_offset += 8
59
- raise FormatError, "Unexpected ISOBMFF xl-box size #{size}" if size < 16
59
+ fail FormatError, "Unexpected ISOBMFF xl-box size #{size}" if size < 16
60
60
  when 2..7
61
- raise FormatError, "Reserved ISOBMFF box size #{size}"
61
+ fail FormatError, "Reserved ISOBMFF box size #{size}"
62
62
  end
63
63
 
64
64
  attributes = {
65
- :type => type,
66
- :offset => offset,
67
- :size => size,
68
- :relative_data_offset => relative_data_offset,
69
- :index => index,
65
+ type: type,
66
+ offset: offset,
67
+ size: size,
68
+ relative_data_offset: relative_data_offset,
69
+ index: index,
70
70
  }
71
71
 
72
72
  if @full.include?(type)
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ImageSize
4
+ MEDIA_TYPES = {
5
+ apng: %w[image/apng image/vnd.mozilla.apng],
6
+ avif: %w[image/avif],
7
+ bmp: %w[image/bmp],
8
+ cur: %w[image/vnd.microsoft.icon],
9
+ emf: %w[image/emf],
10
+ gif: %w[image/gif],
11
+ heic: %w[image/heic image/heif],
12
+ ico: %w[image/x-icon image/vnd.microsoft.icon],
13
+ j2c: %w[image/j2c],
14
+ jp2: %w[image/jp2],
15
+ jpeg: %w[image/jpeg],
16
+ jpx: %w[image/jpx],
17
+ mng: %w[video/x-mng image/x-mng],
18
+ pam: %w[image/x-portable-arbitrarymap],
19
+ pbm: %w[image/x-portable-bitmap image/x-portable-anymap],
20
+ pcx: %w[image/x-pcx image/vnd.zbrush.pcx],
21
+ pgm: %w[image/x-portable-graymap image/x-portable-anymap],
22
+ png: %w[image/png],
23
+ ppm: %w[image/x-portable-pixmap image/x-portable-anymap],
24
+ psd: %w[image/vnd.adobe.photoshop],
25
+ svg: %w[image/svg+xml],
26
+ swf: %w[application/x-shockwave-flash application/vnd.adobe.flash.movie],
27
+ tiff: %w[image/tiff],
28
+ webp: %w[image/webp],
29
+ xbm: %w[image/x-xbitmap],
30
+ xpm: %w[image/x-xpixmap],
31
+ }.freeze
32
+ end
@@ -30,14 +30,15 @@ class ImageSize
30
30
  when input.is_a?(Pathname)
31
31
  input.open('rb'){ |f| yield for_io(f) }
32
32
  else
33
- raise ArgumentError, "expected data as String or an object responding to read and eof?, got #{input.class}"
33
+ fail ArgumentError, 'expected a String, a Pathname, a StringIO or an object responding to read and eof? ' \
34
+ "(IO), got #{input.class}"
34
35
  end
35
36
  end
36
37
 
37
38
  private
38
39
 
39
40
  def for_io(io)
40
- if io.respond_to?(:stat) && !io.stat.file?
41
+ if (io.respond_to?(:stat) && !io.stat.file?) || !io.respond_to?(:seek)
41
42
  StreamIOReader.new(io)
42
43
  else
43
44
  begin
@@ -54,7 +55,7 @@ class ImageSize
54
55
  chunk = self[offset, length]
55
56
 
56
57
  unless chunk && chunk.length == length
57
- raise FormatError, "Expected #{length} bytes at offset #{offset}, got #{chunk.inspect}"
58
+ fail FormatError, "Expected #{length} bytes at offset #{offset}, got #{chunk.inspect}"
58
59
  end
59
60
 
60
61
  chunk
@@ -12,6 +12,10 @@ class ImageSize
12
12
  @chunks = {}
13
13
  end
14
14
 
15
+ def byte_size
16
+ @io.size
17
+ end
18
+
15
19
  private
16
20
 
17
21
  def chunk(i)
@@ -11,6 +11,12 @@ class ImageSize
11
11
  @chunks = []
12
12
  end
13
13
 
14
+ def byte_size
15
+ return nil unless @io.respond_to?(:stat) && @io.stat.file?
16
+
17
+ @io.stat.size
18
+ end
19
+
14
20
  private
15
21
 
16
22
  def chunk(i)
@@ -17,5 +17,9 @@ class ImageSize
17
17
  def [](offset, length)
18
18
  @string[offset, length]
19
19
  end
20
+
21
+ def byte_size
22
+ @string.bytesize
23
+ end
20
24
  end
21
25
  end
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'image_size/uri_reader'
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'image_size'
3
4
  require 'image_size/reader'
4
5
  require 'image_size/chunky_reader'
5
6
 
@@ -16,17 +17,24 @@ class ImageSize
16
17
  module HTTPChunkyReader # :nodoc:
17
18
  include ChunkyReader
18
19
 
20
+ def chunk_start(i)
21
+ chunk_size * i
22
+ end
23
+
19
24
  def chunk_range_header(i)
20
- { 'Range' => "bytes=#{chunk_size * i}-#{(chunk_size * (i + 1)) - 1}" }
25
+ { 'Range' => "bytes=#{chunk_start(i)}-#{chunk_start(i + 1) - 1}" }
21
26
  end
22
27
  end
23
28
 
24
29
  class BodyReader # :nodoc:
25
30
  include ChunkyReader
26
31
 
32
+ attr_reader :byte_size
33
+
27
34
  def initialize(response)
28
35
  @body = String.new
29
36
  @body_reader = response.to_enum(:read_body)
37
+ @byte_size = response.content_length
30
38
  end
31
39
 
32
40
  def [](offset, length)
@@ -45,20 +53,32 @@ class ImageSize
45
53
  class RangeReader # :nodoc:
46
54
  include HTTPChunkyReader
47
55
 
48
- def initialize(http, request_uri, chunk0)
56
+ attr_reader :byte_size
57
+
58
+ def initialize(http, request_uri, chunk0, byte_size)
49
59
  @http = http
50
60
  @request_uri = request_uri
51
61
  @chunks = { 0 => chunk0 }
62
+ @byte_size = byte_size
63
+ @last_chunk = nil
52
64
  end
53
65
 
54
66
  def chunk(i)
67
+ return if @byte_size && chunk_start(i) >= @byte_size
68
+ return if @last_chunk && i > @last_chunk
69
+
55
70
  unless @chunks.key?(i)
56
71
  response = @http.get(@request_uri, chunk_range_header(i))
57
72
  case response
58
73
  when Net::HTTPPartialContent
59
- @chunks[i] = response.body
74
+ body = response.body
75
+ @chunks[i] = body
76
+ @last_chunk = i if body.length < chunk_size
77
+ when Net::HTTPRequestedRangeNotSatisfiable
78
+ @chunks[i] = nil
79
+ @last_chunk = i if !@last_chunk || @last_chunk > i
60
80
  else
61
- raise "Unexpected response: #{response}"
81
+ fail "Unexpected response: #{response}"
62
82
  end
63
83
  end
64
84
 
@@ -69,9 +89,11 @@ class ImageSize
69
89
  class << self
70
90
  include HTTPChunkyReader
71
91
 
72
- def open(uri, max_redirects = 5)
92
+ def open(uri)
73
93
  http = nil
74
- (max_redirects + 1).times do
94
+ (ImageSize.max_redirects + 1).times do
95
+ ImageSize.uri_checker.call(uri)
96
+
75
97
  unless http && http.address == uri.host && http.port == uri.port
76
98
  http.finish if http
77
99
 
@@ -91,17 +113,19 @@ class ImageSize
91
113
  when Net::HTTPRedirection
92
114
  uri += response['location']
93
115
  when Net::HTTPPartialContent
94
- return yield RangeReader.new(http, uri.request_uri, response.body)
116
+ m = response['content-range'].match(%r{\bbytes\s+\d+-\d+/(\d+)}i) if response['content-range']
117
+ byte_size = m[1].to_i if m
118
+ return yield RangeReader.new(http, uri.request_uri, response.body, byte_size)
95
119
  when Net::HTTPRequestedRangeNotSatisfiable
96
120
  return yield StringReader.new('')
97
121
  else
98
- raise "Unexpected response: #{response}"
122
+ fail "Unexpected response: #{response}"
99
123
  end
100
124
  end
101
125
 
102
- raise "Too many redirects: #{uri}"
126
+ fail "Too many redirects: #{uri}"
103
127
  ensure
104
- http.finish if http.started?
128
+ http.finish if http && http.started?
105
129
  end
106
130
  end
107
131
  end
@@ -123,4 +147,32 @@ class ImageSize
123
147
  def self.url(url)
124
148
  new(url.is_a?(URI) ? url : URI(url))
125
149
  end
150
+
151
+ # Maximum number of redirects
152
+ def self.max_redirects
153
+ @max_redirects || 5
154
+ end
155
+
156
+ # Set maximum number of redirects
157
+ def self.max_redirects=(max_redirects)
158
+ unless max_redirects.nil? || (max_redirects.is_a?(Integer) && max_redirects >= 0)
159
+ fail ArgumentError, "max_redirects should be 0, a positive Integer or nil, got #{max_redirects}"
160
+ end
161
+
162
+ @max_redirects = max_redirects
163
+ end
164
+
165
+ # Hook to call before making every request
166
+ def self.uri_checker
167
+ @uri_checker || proc{ |_uri| }
168
+ end
169
+
170
+ # Set hook to call before making every request
171
+ def self.uri_checker=(uri_checker)
172
+ unless uri_checker.nil? || uri_checker.respond_to?(:call)
173
+ fail ArgumentError, "uri_checker should respond to call or be nil, got #{uri_checker}"
174
+ end
175
+
176
+ @uri_checker = uri_checker
177
+ end
126
178
  end