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 +4 -4
- data/.github/dependabot.yml +8 -0
- data/.github/workflows/check.yml +13 -21
- data/.github/workflows/rubocop.yml +2 -2
- data/.rubocop.yml +5 -32
- data/.rubocop_todo.yml +0 -6
- data/CHANGELOG.markdown +16 -0
- data/README.markdown +53 -5
- data/image_size.gemspec +16 -5
- data/lib/image_size/chunky_reader.rb +2 -2
- data/lib/image_size/isobmff.rb +7 -7
- data/lib/image_size/media_types.rb +32 -0
- data/lib/image_size/reader.rb +4 -3
- data/lib/image_size/seekable_io_reader.rb +4 -0
- data/lib/image_size/stream_io_reader.rb +6 -0
- data/lib/image_size/string_reader.rb +4 -0
- data/lib/image_size/uri.rb +3 -0
- data/lib/image_size/uri_reader.rb +62 -10
- data/lib/image_size.rb +64 -36
- data/spec/image_size/chunky_reader_spec.rb +1 -1
- data/spec/image_size/isobmff_spec.rb +44 -44
- data/spec/image_size/seekable_io_reader_spec.rb +18 -23
- data/spec/image_size_spec.rb +262 -29
- data/spec/images/svg/768b.430x430.svg +11 -0
- data/spec/test_server.rb +34 -7
- metadata +10 -9
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2a128281e6674bd684b8485151f3938a5d34d8fdf146e293e908e8cfb5c762d5
|
|
4
|
+
data.tar.gz: 0615d7750931da9cf45ec6003fd1f20dba7638e393a6d18bbec53901c078e864
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d2d3cf28085954077a3016735c15ede7827dd1a72afd0617182d4278f0890f8ef41cbc8e84fe7d2661a270a8ce570b6e3cfeaa148c8be4d141c3a233cb712a59
|
|
7
|
+
data.tar.gz: a6dd7107a1c344a4963cf527d80e76c08e6b64cf22ae2edb6d2c7dad022f3452505a369fc9a4b4c665136e34ee2379a41b235e5d89881a32721c49ae14c6b6aa
|
data/.github/workflows/check.yml
CHANGED
|
@@ -6,10 +6,13 @@ on:
|
|
|
6
6
|
- cron: 45 4 * * 3
|
|
7
7
|
jobs:
|
|
8
8
|
check:
|
|
9
|
-
runs-on: ubuntu-
|
|
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
|
-
-
|
|
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@
|
|
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@
|
|
52
|
+
- uses: actions/checkout@v6
|
|
61
53
|
- uses: ruby/setup-ruby@v1
|
|
62
54
|
with:
|
|
63
55
|
ruby-version: "${{ matrix.ruby }}"
|
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/
|
|
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/
|
|
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
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
|
[](https://rubygems.org/gems/image_size)
|
|
2
|
-
[](https://github.com/toy/image_size/actions/workflows/check.yml)
|
|
3
3
|
[](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 #=> :
|
|
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-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
data/lib/image_size/isobmff.rb
CHANGED
|
@@ -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
|
-
|
|
59
|
+
fail FormatError, "Unexpected ISOBMFF xl-box size #{size}" if size < 16
|
|
60
60
|
when 2..7
|
|
61
|
-
|
|
61
|
+
fail FormatError, "Reserved ISOBMFF box size #{size}"
|
|
62
62
|
end
|
|
63
63
|
|
|
64
64
|
attributes = {
|
|
65
|
-
:
|
|
66
|
-
:
|
|
67
|
-
:
|
|
68
|
-
:
|
|
69
|
-
:
|
|
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
|
data/lib/image_size/reader.rb
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
58
|
+
fail FormatError, "Expected #{length} bytes at offset #{offset}, got #{chunk.inspect}"
|
|
58
59
|
end
|
|
59
60
|
|
|
60
61
|
chunk
|
|
@@ -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=#{
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
122
|
+
fail "Unexpected response: #{response}"
|
|
99
123
|
end
|
|
100
124
|
end
|
|
101
125
|
|
|
102
|
-
|
|
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
|