image_size 3.4.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 +11 -19
- data/.github/workflows/rubocop.yml +2 -2
- data/.rubocop.yml +3 -0
- data/.rubocop_todo.yml +0 -6
- data/CHANGELOG.markdown +12 -3
- data/README.markdown +49 -2
- data/image_size.gemspec +14 -5
- data/lib/image_size/chunky_reader.rb +2 -2
- data/lib/image_size/isobmff.rb +2 -2
- 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_reader.rb +61 -10
- data/lib/image_size.rb +44 -22
- data/spec/image_size/seekable_io_reader_spec.rb +18 -23
- data/spec/image_size_spec.rb +227 -30
- data/spec/images/svg/768b.430x430.svg +11 -0
- data/spec/test_server.rb +29 -3
- metadata +7 -8
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'
|
|
@@ -19,31 +22,18 @@ jobs:
|
|
|
19
22
|
- '3.1'
|
|
20
23
|
- '3.2'
|
|
21
24
|
- '3.3'
|
|
22
|
-
-
|
|
25
|
+
- '3.4'
|
|
26
|
+
- '4.0'
|
|
23
27
|
- jruby-9.4
|
|
28
|
+
- jruby-10.1
|
|
24
29
|
fail-fast: false
|
|
25
30
|
steps:
|
|
26
|
-
- uses: actions/checkout@
|
|
31
|
+
- uses: actions/checkout@v6
|
|
27
32
|
- uses: ruby/setup-ruby@v1
|
|
28
33
|
with:
|
|
29
34
|
ruby-version: "${{ matrix.ruby }}"
|
|
30
35
|
bundler-cache: true
|
|
31
36
|
- run: bundle exec rspec --format documentation
|
|
32
|
-
legacy:
|
|
33
|
-
runs-on: ubuntu-latest
|
|
34
|
-
container: ${{ matrix.container }}
|
|
35
|
-
strategy:
|
|
36
|
-
matrix:
|
|
37
|
-
container:
|
|
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:
|
|
@@ -55,9 +45,11 @@ jobs:
|
|
|
55
45
|
- '3.1'
|
|
56
46
|
- '3.2'
|
|
57
47
|
- '3.3'
|
|
48
|
+
- '3.4'
|
|
49
|
+
- '4.0'
|
|
58
50
|
fail-fast: false
|
|
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
data/.rubocop_todo.yml
CHANGED
data/CHANGELOG.markdown
CHANGED
|
@@ -2,11 +2,20 @@
|
|
|
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
|
+
|
|
5
14
|
## v3.4.0 (2024-01-16)
|
|
6
15
|
|
|
7
|
-
* 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)
|
|
8
|
-
* Allow fetching from HTTP server by requiring image_size/uri [@toy](https://github.com/toy)
|
|
9
|
-
* Fix for ArgumentError when requiring only image_size/uri_reader (without image_size) [@toy](https://github.com/toy)
|
|
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)
|
|
10
19
|
* Require ruby 1.9.3 [@toy](https://github.com/toy)
|
|
11
20
|
|
|
12
21
|
## v3.3.0 (2023-05-30)
|
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
|
|
@@ -40,6 +40,7 @@ image_size.size.w #=> 436
|
|
|
40
40
|
image_size.size.h #=> 429
|
|
41
41
|
image_size.media_type #=> "image/jpeg"
|
|
42
42
|
image_size.media_types #=> ["image/jpeg"]
|
|
43
|
+
image_size.byte_size #=> 10938
|
|
43
44
|
```
|
|
44
45
|
|
|
45
46
|
Or using `IO` object:
|
|
@@ -92,6 +93,20 @@ File.open('spec/images/jpeg/436x429.jpeg', 'rb') do |fh|
|
|
|
92
93
|
end
|
|
93
94
|
```
|
|
94
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
|
+
|
|
95
110
|
### Experimental: fetch image meta from HTTP server
|
|
96
111
|
|
|
97
112
|
If server recognises Range header, only needed chunks will be fetched even for TIFF images, otherwise required amount
|
|
@@ -140,9 +155,41 @@ puts Benchmark.measure{ p ImageSize.url(url).size }
|
|
|
140
155
|
0.006247 0.001045 0.007292 ( 0.197631)
|
|
141
156
|
```
|
|
142
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
|
+
|
|
143
190
|
## Licence
|
|
144
191
|
|
|
145
192
|
This code is free to use under the terms of the [Ruby's licence](LICENSE.txt).
|
|
146
193
|
|
|
147
194
|
Original author: Keisuke Minami <keisuke@rccn.com>.\
|
|
148
|
-
Further development 2010-
|
|
195
|
+
Further development 2010-2026 Ivan Kuchin https://github.com/toy/image_size
|
data/image_size.gemspec
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
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}"
|
|
@@ -16,11 +16,20 @@ Gem::Specification.new do |s|
|
|
|
16
16
|
'changelog_uri' => "https://github.com/toy/#{s.name}/blob/master/CHANGELOG.markdown",
|
|
17
17
|
'documentation_uri' => "https://www.rubydoc.info/gems/#{s.name}/#{s.version}",
|
|
18
18
|
'source_code_uri' => "https://github.com/toy/#{s.name}",
|
|
19
|
-
}
|
|
19
|
+
} if s.respond_to?(:metadata=)
|
|
20
20
|
|
|
21
|
-
s.files
|
|
22
|
-
|
|
23
|
-
|
|
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?))
|
|
31
|
+
|
|
32
|
+
s.test_files = Dir['spec/**/{*,.gitattributes}'].reject(&File.method(:directory?))
|
|
24
33
|
s.require_paths = %w[lib]
|
|
25
34
|
|
|
26
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,9 +56,9 @@ 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 = {
|
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
|
|
@@ -17,17 +17,24 @@ class ImageSize
|
|
|
17
17
|
module HTTPChunkyReader # :nodoc:
|
|
18
18
|
include ChunkyReader
|
|
19
19
|
|
|
20
|
+
def chunk_start(i)
|
|
21
|
+
chunk_size * i
|
|
22
|
+
end
|
|
23
|
+
|
|
20
24
|
def chunk_range_header(i)
|
|
21
|
-
{ 'Range' => "bytes=#{
|
|
25
|
+
{ 'Range' => "bytes=#{chunk_start(i)}-#{chunk_start(i + 1) - 1}" }
|
|
22
26
|
end
|
|
23
27
|
end
|
|
24
28
|
|
|
25
29
|
class BodyReader # :nodoc:
|
|
26
30
|
include ChunkyReader
|
|
27
31
|
|
|
32
|
+
attr_reader :byte_size
|
|
33
|
+
|
|
28
34
|
def initialize(response)
|
|
29
35
|
@body = String.new
|
|
30
36
|
@body_reader = response.to_enum(:read_body)
|
|
37
|
+
@byte_size = response.content_length
|
|
31
38
|
end
|
|
32
39
|
|
|
33
40
|
def [](offset, length)
|
|
@@ -46,20 +53,32 @@ class ImageSize
|
|
|
46
53
|
class RangeReader # :nodoc:
|
|
47
54
|
include HTTPChunkyReader
|
|
48
55
|
|
|
49
|
-
|
|
56
|
+
attr_reader :byte_size
|
|
57
|
+
|
|
58
|
+
def initialize(http, request_uri, chunk0, byte_size)
|
|
50
59
|
@http = http
|
|
51
60
|
@request_uri = request_uri
|
|
52
61
|
@chunks = { 0 => chunk0 }
|
|
62
|
+
@byte_size = byte_size
|
|
63
|
+
@last_chunk = nil
|
|
53
64
|
end
|
|
54
65
|
|
|
55
66
|
def chunk(i)
|
|
67
|
+
return if @byte_size && chunk_start(i) >= @byte_size
|
|
68
|
+
return if @last_chunk && i > @last_chunk
|
|
69
|
+
|
|
56
70
|
unless @chunks.key?(i)
|
|
57
71
|
response = @http.get(@request_uri, chunk_range_header(i))
|
|
58
72
|
case response
|
|
59
73
|
when Net::HTTPPartialContent
|
|
60
|
-
|
|
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
|
|
61
80
|
else
|
|
62
|
-
|
|
81
|
+
fail "Unexpected response: #{response}"
|
|
63
82
|
end
|
|
64
83
|
end
|
|
65
84
|
|
|
@@ -70,9 +89,11 @@ class ImageSize
|
|
|
70
89
|
class << self
|
|
71
90
|
include HTTPChunkyReader
|
|
72
91
|
|
|
73
|
-
def open(uri
|
|
92
|
+
def open(uri)
|
|
74
93
|
http = nil
|
|
75
|
-
(max_redirects + 1).times do
|
|
94
|
+
(ImageSize.max_redirects + 1).times do
|
|
95
|
+
ImageSize.uri_checker.call(uri)
|
|
96
|
+
|
|
76
97
|
unless http && http.address == uri.host && http.port == uri.port
|
|
77
98
|
http.finish if http
|
|
78
99
|
|
|
@@ -92,17 +113,19 @@ class ImageSize
|
|
|
92
113
|
when Net::HTTPRedirection
|
|
93
114
|
uri += response['location']
|
|
94
115
|
when Net::HTTPPartialContent
|
|
95
|
-
|
|
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)
|
|
96
119
|
when Net::HTTPRequestedRangeNotSatisfiable
|
|
97
120
|
return yield StringReader.new('')
|
|
98
121
|
else
|
|
99
|
-
|
|
122
|
+
fail "Unexpected response: #{response}"
|
|
100
123
|
end
|
|
101
124
|
end
|
|
102
125
|
|
|
103
|
-
|
|
126
|
+
fail "Too many redirects: #{uri}"
|
|
104
127
|
ensure
|
|
105
|
-
http.finish if http.started?
|
|
128
|
+
http.finish if http && http.started?
|
|
106
129
|
end
|
|
107
130
|
end
|
|
108
131
|
end
|
|
@@ -124,4 +147,32 @@ class ImageSize
|
|
|
124
147
|
def self.url(url)
|
|
125
148
|
new(url.is_a?(URI) ? url : URI(url))
|
|
126
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
|
|
127
178
|
end
|
data/lib/image_size.rb
CHANGED
|
@@ -36,14 +36,30 @@ class ImageSize
|
|
|
36
36
|
new(Pathname.new(path))
|
|
37
37
|
end
|
|
38
38
|
|
|
39
|
-
# Used for svg
|
|
39
|
+
# Used for svg and emf
|
|
40
40
|
def self.dpi
|
|
41
|
-
@dpi || 72
|
|
41
|
+
@dpi || 72.0
|
|
42
42
|
end
|
|
43
43
|
|
|
44
|
-
# Used for svg
|
|
44
|
+
# Used for svg and emf
|
|
45
45
|
def self.dpi=(dpi)
|
|
46
|
-
|
|
46
|
+
fail ArgumentError, "dpi should be nil or positive, got #{dpi}" unless dpi.nil? || dpi > 0
|
|
47
|
+
|
|
48
|
+
@dpi = dpi ? dpi.to_f : nil
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Size of chunk to use by IO and URI readers
|
|
52
|
+
def self.chunk_size
|
|
53
|
+
@chunk_size || 4096
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Set size of chunk to use by IO and URI readers
|
|
57
|
+
def self.chunk_size=(chunk_size)
|
|
58
|
+
unless chunk_size.nil? || (chunk_size.is_a?(Integer) && chunk_size > 0)
|
|
59
|
+
fail ArgumentError, "chunk_size should be a positive Integer or nil, got #{chunk_size}"
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
@chunk_size = chunk_size
|
|
47
63
|
end
|
|
48
64
|
|
|
49
65
|
# Given image as any class responding to read and eof? or data as String, finds its format and dimensions
|
|
@@ -51,6 +67,7 @@ class ImageSize
|
|
|
51
67
|
Reader.open(data) do |ir|
|
|
52
68
|
@format = detect_format(ir)
|
|
53
69
|
@width, @height = send("size_of_#{@format}", ir) if @format
|
|
70
|
+
@byte_size = ir.byte_size
|
|
54
71
|
end
|
|
55
72
|
end
|
|
56
73
|
|
|
@@ -65,6 +82,8 @@ class ImageSize
|
|
|
65
82
|
attr_reader :height
|
|
66
83
|
alias_method :h, :height
|
|
67
84
|
|
|
85
|
+
attr_reader :byte_size
|
|
86
|
+
|
|
68
87
|
# get image width and height as an array which to_s method returns "#{width}x#{height}"
|
|
69
88
|
def size
|
|
70
89
|
Size.new([width, height]) if format
|
|
@@ -72,7 +91,7 @@ class ImageSize
|
|
|
72
91
|
|
|
73
92
|
# Media type (formerly known as a MIME type)
|
|
74
93
|
def media_type
|
|
75
|
-
|
|
94
|
+
media_types.first
|
|
76
95
|
end
|
|
77
96
|
|
|
78
97
|
# All media types:
|
|
@@ -153,13 +172,13 @@ private
|
|
|
153
172
|
end
|
|
154
173
|
|
|
155
174
|
def size_of_mng(ir)
|
|
156
|
-
|
|
175
|
+
fail FormatError, 'MHDR not in place for MNG' unless ir[12, 4] == 'MHDR'
|
|
157
176
|
|
|
158
177
|
ir.unpack(16, 8, 'NN')
|
|
159
178
|
end
|
|
160
179
|
|
|
161
180
|
def size_of_png(ir)
|
|
162
|
-
|
|
181
|
+
fail FormatError, 'IHDR not in place for PNG' unless ir[12, 4] == 'IHDR'
|
|
163
182
|
|
|
164
183
|
ir.unpack(16, 8, 'NN')
|
|
165
184
|
end
|
|
@@ -177,7 +196,7 @@ private
|
|
|
177
196
|
loop do
|
|
178
197
|
offset += 1 until [nil, section_marker].include? ir[offset, 1]
|
|
179
198
|
offset += 1 until section_marker != ir[offset + 1, 1]
|
|
180
|
-
|
|
199
|
+
fail FormatError, 'EOF in JPEG' unless ir[offset, 1]
|
|
181
200
|
|
|
182
201
|
code, length = ir.unpack(offset, 4, 'xCn')
|
|
183
202
|
offset += 4
|
|
@@ -206,8 +225,7 @@ private
|
|
|
206
225
|
def size_of_ppm(ir)
|
|
207
226
|
header = ir[0, 1024]
|
|
208
227
|
header.gsub!(/^\#[^\n\r]*/m, '')
|
|
209
|
-
header
|
|
210
|
-
[$2.to_i, $3.to_i]
|
|
228
|
+
header.match(/^(?:P[1-6])\s+?(\d+)\s+?(\d+)/m)[1..2].map(&:to_i)
|
|
211
229
|
end
|
|
212
230
|
alias_method :size_of_pbm, :size_of_ppm
|
|
213
231
|
alias_method :size_of_pgm, :size_of_ppm
|
|
@@ -223,35 +241,37 @@ private
|
|
|
223
241
|
chunk = ir[offset, 32]
|
|
224
242
|
case chunk
|
|
225
243
|
when /\AWIDTH (\d+)\n/
|
|
226
|
-
width =
|
|
244
|
+
width = Regexp.last_match[1].to_i
|
|
227
245
|
when /\AHEIGHT (\d+)\n/
|
|
228
|
-
height =
|
|
246
|
+
height = Regexp.last_match[1].to_i
|
|
229
247
|
when /\AENDHDR\n/
|
|
230
248
|
break
|
|
231
249
|
when /\A(?:DEPTH|MAXVAL) \d+\n/, /\ATUPLTYPE \S+\n/
|
|
232
250
|
# ignore
|
|
233
251
|
else
|
|
234
|
-
|
|
252
|
+
fail FormatError, "Unexpected data in PAM header: #{chunk.inspect}"
|
|
235
253
|
end
|
|
236
|
-
offset +=
|
|
254
|
+
offset += Regexp.last_match[0].length
|
|
237
255
|
end
|
|
238
256
|
end
|
|
239
257
|
[width, height]
|
|
240
258
|
end
|
|
241
259
|
|
|
242
260
|
def size_of_xbm(ir)
|
|
243
|
-
ir[0, 1024]
|
|
244
|
-
[$1.to_i, $2.to_i]
|
|
261
|
+
ir[0, 1024].match(/^\#define\s*\S*\s*(\d+)\s*\n\#define\s*\S*\s*(\d+)/mi)[1..2].map(&:to_i)
|
|
245
262
|
end
|
|
246
263
|
|
|
247
264
|
def size_of_xpm(ir)
|
|
248
265
|
length = 1024
|
|
249
|
-
|
|
250
|
-
|
|
266
|
+
loop do
|
|
267
|
+
data = ir[0, length]
|
|
268
|
+
m = data.match(/"\s*(\d+)\s+(\d+)(?:\s+\d+\s+\d+){1,2}\s*"/m)
|
|
269
|
+
return m[1..2].map(&:to_i) if m
|
|
270
|
+
|
|
271
|
+
fail FormatError, 'XPM size not found' if data.length != length
|
|
251
272
|
|
|
252
273
|
length += 1024
|
|
253
274
|
end
|
|
254
|
-
[$1.to_i, $2.to_i]
|
|
255
275
|
end
|
|
256
276
|
|
|
257
277
|
def size_of_psd(ir)
|
|
@@ -271,7 +291,7 @@ private
|
|
|
271
291
|
width = height = nil
|
|
272
292
|
until width && height
|
|
273
293
|
ifd = ir.fetch(offset, 12)
|
|
274
|
-
|
|
294
|
+
fail FormatError, 'Reached end of directory entries in TIFF' if offset > num_dirent
|
|
275
295
|
|
|
276
296
|
tag, type = ifd.unpack(endian2b * 2)
|
|
277
297
|
offset += 12
|
|
@@ -390,11 +410,11 @@ private
|
|
|
390
410
|
HEIF_WALKER.recurse(ir) do |box, _path|
|
|
391
411
|
case box.type
|
|
392
412
|
when 'hdlr'
|
|
393
|
-
|
|
413
|
+
fail FormatError, "hdlr box too small (#{box.data_size})" if box.data_size < 8
|
|
394
414
|
|
|
395
415
|
return nil unless ir[box.data_offset + 4, 4] == 'pict'
|
|
396
416
|
when 'pitm'
|
|
397
|
-
|
|
417
|
+
fail FormatError, 'second pitm box encountered' if pitm
|
|
398
418
|
|
|
399
419
|
pitm = box.version == 0 ? ir.unpack1(box.data_offset, 2, 'n') : ir.unpack1(box.data_offset, 4, 'N')
|
|
400
420
|
when 'ipma'
|
|
@@ -436,4 +456,6 @@ private
|
|
|
436
456
|
end
|
|
437
457
|
alias_method :size_of_avif, :size_of_heif
|
|
438
458
|
alias_method :size_of_heic, :size_of_heif
|
|
459
|
+
|
|
460
|
+
private_constant :SVG_R, :XML_R, :JPEG_CODE_CHECK, :JP2_WALKER, :EMF_UMAX, :EMF_SMAX, :HEIF_WALKER
|
|
439
461
|
end
|
|
@@ -6,45 +6,40 @@ require 'image_size/seekable_io_reader'
|
|
|
6
6
|
|
|
7
7
|
describe ImageSize::SeekableIOReader do
|
|
8
8
|
context :[] do
|
|
9
|
-
def
|
|
10
|
-
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
def io
|
|
14
|
-
File.open('GPL', 'rb').tap do |io|
|
|
15
|
-
ios << io
|
|
16
|
-
end
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
after do
|
|
20
|
-
ios.pop.close until ios.empty?
|
|
9
|
+
def new_io(&block)
|
|
10
|
+
File.open('GPL', 'rb', &block)
|
|
21
11
|
end
|
|
22
12
|
|
|
23
13
|
def new_reader
|
|
24
|
-
|
|
14
|
+
new_io do |io|
|
|
15
|
+
yield ImageSize::SeekableIOReader.new(io)
|
|
16
|
+
end
|
|
25
17
|
end
|
|
26
18
|
|
|
27
|
-
let(:content){
|
|
19
|
+
let(:content){ new_io(&:read) }
|
|
28
20
|
|
|
29
21
|
it 'reads as expected when pieces are read consecutively' do
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
22
|
+
new_reader do |reader|
|
|
23
|
+
0.step(content.length + 4096, 100) do |offset|
|
|
24
|
+
expect(reader[offset, 100]).to eq(content[offset, 100])
|
|
25
|
+
end
|
|
33
26
|
end
|
|
34
27
|
end
|
|
35
28
|
|
|
36
29
|
it 'reads as expected when pieces are read backwards' do
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
30
|
+
new_reader do |reader|
|
|
31
|
+
(content.length + 4096).step(0, -100) do |offset|
|
|
32
|
+
expect(reader[offset, 100]).to eq(content[offset, 100])
|
|
33
|
+
end
|
|
40
34
|
end
|
|
41
35
|
end
|
|
42
36
|
|
|
43
37
|
it 'reads as expected when pieces are read in random order' do
|
|
44
38
|
100.times do
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
39
|
+
new_reader do |reader|
|
|
40
|
+
0.step(content.length + 4096, 100).to_a.shuffle.each do |offset|
|
|
41
|
+
expect(reader[offset, 100]).to eq(content[offset, 100])
|
|
42
|
+
end
|
|
48
43
|
end
|
|
49
44
|
end
|
|
50
45
|
end
|
data/spec/image_size_spec.rb
CHANGED
|
@@ -14,6 +14,20 @@ RSpec.configure do |config|
|
|
|
14
14
|
config.order = :random
|
|
15
15
|
end
|
|
16
16
|
|
|
17
|
+
class RestrictedIO
|
|
18
|
+
def initialize(data)
|
|
19
|
+
@io = StringIO.new(data)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def read(length = nil)
|
|
23
|
+
@io.read(length)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def eof?
|
|
27
|
+
@io.eof?
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
17
31
|
describe ImageSize do
|
|
18
32
|
before :all do
|
|
19
33
|
@server = TestServer.new
|
|
@@ -67,7 +81,7 @@ describe ImageSize do
|
|
|
67
81
|
height = match[2].to_i
|
|
68
82
|
format = match[3].to_sym
|
|
69
83
|
end
|
|
70
|
-
size =
|
|
84
|
+
size = [width, height] if format
|
|
71
85
|
media_types = ImageSize::MEDIA_TYPES[format] || []
|
|
72
86
|
media_type = format && media_types.first.to_s
|
|
73
87
|
{
|
|
@@ -79,6 +93,7 @@ describe ImageSize do
|
|
|
79
93
|
size: size,
|
|
80
94
|
media_type: media_type,
|
|
81
95
|
media_types: media_types,
|
|
96
|
+
byte_size: file_size,
|
|
82
97
|
}
|
|
83
98
|
end
|
|
84
99
|
let(:file_data){ File.binread(path) }
|
|
@@ -88,7 +103,7 @@ describe ImageSize do
|
|
|
88
103
|
max_file_size = 16_384
|
|
89
104
|
|
|
90
105
|
if file_size > max_file_size
|
|
91
|
-
|
|
106
|
+
fail "reduce resulting gem size, #{path} is too big (#{file_size} > #{max_file_size})"
|
|
92
107
|
end
|
|
93
108
|
end
|
|
94
109
|
|
|
@@ -119,6 +134,8 @@ describe ImageSize do
|
|
|
119
134
|
end
|
|
120
135
|
|
|
121
136
|
context 'given as unseekable IO' do
|
|
137
|
+
let(:attributes){ super().merge(byte_size: nil) }
|
|
138
|
+
|
|
122
139
|
it 'gets format and dimensions' do
|
|
123
140
|
IO.popen(%W[cat #{path}].shelljoin, 'rb') do |io|
|
|
124
141
|
image_size = ImageSize.new(io)
|
|
@@ -128,6 +145,16 @@ describe ImageSize do
|
|
|
128
145
|
end
|
|
129
146
|
end
|
|
130
147
|
|
|
148
|
+
context 'given an object allowing only read and eof?' do
|
|
149
|
+
let(:attributes){ super().merge(byte_size: nil) }
|
|
150
|
+
|
|
151
|
+
it 'gets format and dimensions' do
|
|
152
|
+
io = RestrictedIO.new(file_data)
|
|
153
|
+
image_size = ImageSize.new(io)
|
|
154
|
+
expect(image_size).to have_attributes(attributes)
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
131
158
|
context 'given as StringIO' do
|
|
132
159
|
it 'gets format and dimensions' do
|
|
133
160
|
io = StringIO.new(file_data)
|
|
@@ -167,64 +194,74 @@ describe ImageSize do
|
|
|
167
194
|
end
|
|
168
195
|
|
|
169
196
|
context 'fetching from webserver' do
|
|
170
|
-
let(:
|
|
197
|
+
let(:url){ "#{@server.base_url}#{path}#{query}" }
|
|
198
|
+
|
|
199
|
+
before{ ImageSize.chunk_size = 64 }
|
|
200
|
+
after{ ImageSize.chunk_size = nil }
|
|
201
|
+
|
|
202
|
+
subject do
|
|
203
|
+
retry_on Timeout::Error do
|
|
204
|
+
ImageSize.url(url)
|
|
205
|
+
end
|
|
206
|
+
end
|
|
171
207
|
|
|
172
208
|
context 'supporting range' do
|
|
173
209
|
context 'without redirects' do
|
|
210
|
+
let(:query){ '' }
|
|
211
|
+
|
|
174
212
|
it 'gets format and dimensions' do
|
|
175
|
-
|
|
176
|
-
ImageSize.url(file_url)
|
|
177
|
-
end
|
|
178
|
-
expect(image_size).to have_attributes(attributes)
|
|
213
|
+
is_expected.to have_attributes(attributes)
|
|
179
214
|
end
|
|
180
215
|
end
|
|
181
216
|
|
|
182
217
|
context 'with redirects' do
|
|
218
|
+
let(:query){ '?redirect=5' }
|
|
219
|
+
|
|
183
220
|
it 'gets format and dimensions' do
|
|
184
|
-
|
|
185
|
-
ImageSize.url("#{file_url}?redirect=5")
|
|
186
|
-
end
|
|
187
|
-
expect(image_size).to have_attributes(attributes)
|
|
221
|
+
is_expected.to have_attributes(attributes)
|
|
188
222
|
end
|
|
189
223
|
end
|
|
190
224
|
|
|
191
225
|
context 'with too many redirects' do
|
|
226
|
+
let(:query){ '?redirect=6' }
|
|
227
|
+
|
|
228
|
+
it 'raises error' do
|
|
229
|
+
expect{ subject }.to raise_error(/Too many redirects/)
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
context 'with unknown file size' do
|
|
234
|
+
let(:query){ '?unknown_file_size' }
|
|
235
|
+
let(:attributes){ super().merge(byte_size: file_size.zero? ? 0 : nil) }
|
|
236
|
+
|
|
192
237
|
it 'gets format and dimensions' do
|
|
193
|
-
|
|
194
|
-
retry_on Timeout::Error do
|
|
195
|
-
ImageSize.url("#{file_url}?redirect=6")
|
|
196
|
-
end
|
|
197
|
-
end.to raise_error(/Too many redirects/)
|
|
238
|
+
is_expected.to have_attributes(attributes)
|
|
198
239
|
end
|
|
199
240
|
end
|
|
200
241
|
end
|
|
201
242
|
|
|
202
243
|
context 'not supporting range' do
|
|
244
|
+
let(:query){ '?ignore_range' }
|
|
245
|
+
|
|
203
246
|
context 'without redirects' do
|
|
204
247
|
it 'gets format and dimensions' do
|
|
205
|
-
|
|
206
|
-
ImageSize.url("#{file_url}?ignore_range")
|
|
207
|
-
end
|
|
208
|
-
expect(image_size).to have_attributes(attributes)
|
|
248
|
+
is_expected.to have_attributes(attributes)
|
|
209
249
|
end
|
|
210
250
|
end
|
|
211
251
|
|
|
212
252
|
context 'with redirects' do
|
|
253
|
+
let(:query){ '?ignore_range&redirect=5' }
|
|
254
|
+
|
|
213
255
|
it 'gets format and dimensions' do
|
|
214
|
-
|
|
215
|
-
ImageSize.url("#{file_url}?ignore_range&redirect=5")
|
|
216
|
-
end
|
|
217
|
-
expect(image_size).to have_attributes(attributes)
|
|
256
|
+
is_expected.to have_attributes(attributes)
|
|
218
257
|
end
|
|
219
258
|
end
|
|
220
259
|
|
|
221
260
|
context 'with too many redirects' do
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
end
|
|
227
|
-
end.to raise_error(/Too many redirects/)
|
|
261
|
+
let(:query){ '?ignore_range&redirect=6' }
|
|
262
|
+
|
|
263
|
+
it 'raises error' do
|
|
264
|
+
expect{ subject }.to raise_error(/Too many redirects/)
|
|
228
265
|
end
|
|
229
266
|
end
|
|
230
267
|
end
|
|
@@ -248,4 +285,164 @@ describe ImageSize do
|
|
|
248
285
|
end.to raise_error(ImageSize::FormatError)
|
|
249
286
|
end
|
|
250
287
|
end
|
|
288
|
+
|
|
289
|
+
describe '.dpi' do
|
|
290
|
+
subject{ ImageSize.dpi }
|
|
291
|
+
|
|
292
|
+
after{ ImageSize.dpi = nil }
|
|
293
|
+
|
|
294
|
+
it 'is 72 by default' do
|
|
295
|
+
is_expected.to eql(72.0)
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
it 'is can be set and get converted to Float' do
|
|
299
|
+
ImageSize.dpi = 42
|
|
300
|
+
|
|
301
|
+
is_expected.to eql(42.0)
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
it 'is can not be set to 0' do
|
|
305
|
+
expect{ ImageSize.dpi = 0 }.to raise_error(ArgumentError)
|
|
306
|
+
|
|
307
|
+
is_expected.to eql(72.0)
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
it 'is can not be set to a negative value' do
|
|
311
|
+
expect{ ImageSize.dpi = -42 }.to raise_error(ArgumentError)
|
|
312
|
+
|
|
313
|
+
is_expected.to eql(72.0)
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
it 'can be reset' do
|
|
317
|
+
ImageSize.dpi = 42
|
|
318
|
+
ImageSize.dpi = nil
|
|
319
|
+
|
|
320
|
+
is_expected.to eql(72.0)
|
|
321
|
+
end
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
describe '.chunk_size' do
|
|
325
|
+
subject{ ImageSize.chunk_size }
|
|
326
|
+
|
|
327
|
+
after{ ImageSize.chunk_size = nil }
|
|
328
|
+
|
|
329
|
+
it 'is 4096 by default' do
|
|
330
|
+
is_expected.to eql(4096)
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
it 'is can be set to an Integer' do
|
|
334
|
+
ImageSize.chunk_size = 256
|
|
335
|
+
|
|
336
|
+
is_expected.to eql(256)
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
it 'is can not be set to 0' do
|
|
340
|
+
expect{ ImageSize.chunk_size = 0 }.to raise_error(ArgumentError)
|
|
341
|
+
|
|
342
|
+
is_expected.to eql(4096)
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
it 'is can not be set to a negative value' do
|
|
346
|
+
expect{ ImageSize.chunk_size = -1 }.to raise_error(ArgumentError)
|
|
347
|
+
|
|
348
|
+
is_expected.to eql(4096)
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
it 'is can not be set to a Float' do
|
|
352
|
+
expect{ ImageSize.chunk_size = 3.5 }.to raise_error(ArgumentError)
|
|
353
|
+
|
|
354
|
+
is_expected.to eql(4096)
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
it 'can be reset' do
|
|
358
|
+
ImageSize.chunk_size = 256
|
|
359
|
+
ImageSize.chunk_size = nil
|
|
360
|
+
|
|
361
|
+
is_expected.to eql(4096)
|
|
362
|
+
end
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
describe '.max_redirects' do
|
|
366
|
+
subject{ ImageSize.max_redirects }
|
|
367
|
+
|
|
368
|
+
after{ ImageSize.max_redirects = nil }
|
|
369
|
+
|
|
370
|
+
it 'is 4096 by default' do
|
|
371
|
+
is_expected.to eql(5)
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
it 'is can be set to an Integer' do
|
|
375
|
+
ImageSize.max_redirects = 3
|
|
376
|
+
|
|
377
|
+
is_expected.to eql(3)
|
|
378
|
+
end
|
|
379
|
+
|
|
380
|
+
it 'is can be set to 0' do
|
|
381
|
+
ImageSize.max_redirects = 0
|
|
382
|
+
|
|
383
|
+
is_expected.to eql(0)
|
|
384
|
+
end
|
|
385
|
+
|
|
386
|
+
it 'is can not be set to a negative value' do
|
|
387
|
+
expect{ ImageSize.max_redirects = -1 }.to raise_error(ArgumentError)
|
|
388
|
+
|
|
389
|
+
is_expected.to eql(5)
|
|
390
|
+
end
|
|
391
|
+
|
|
392
|
+
it 'is can not be set to a Float' do
|
|
393
|
+
expect{ ImageSize.max_redirects = 3.5 }.to raise_error(ArgumentError)
|
|
394
|
+
|
|
395
|
+
is_expected.to eql(5)
|
|
396
|
+
end
|
|
397
|
+
|
|
398
|
+
it 'can be reset' do
|
|
399
|
+
ImageSize.max_redirects = 3
|
|
400
|
+
ImageSize.max_redirects = nil
|
|
401
|
+
|
|
402
|
+
is_expected.to eql(5)
|
|
403
|
+
end
|
|
404
|
+
end
|
|
405
|
+
|
|
406
|
+
describe '.uri_checker' do
|
|
407
|
+
before{ ImageSize.chunk_size = 64 }
|
|
408
|
+
after do
|
|
409
|
+
ImageSize.chunk_size = nil
|
|
410
|
+
ImageSize.uri_checker = nil
|
|
411
|
+
end
|
|
412
|
+
|
|
413
|
+
it 'can be reset to the default checker' do
|
|
414
|
+
ImageSize.uri_checker = proc{ |uri| fail 'forbidden' if uri.port == @server.base_url.port }
|
|
415
|
+
ImageSize.uri_checker = nil
|
|
416
|
+
|
|
417
|
+
expect do
|
|
418
|
+
retry_on Timeout::Error do
|
|
419
|
+
ImageSize.url("#{@server.base_url}spec/images/empty")
|
|
420
|
+
end
|
|
421
|
+
end.not_to raise_error
|
|
422
|
+
end
|
|
423
|
+
|
|
424
|
+
it 'requires an object responding to call' do
|
|
425
|
+
expect{ ImageSize.uri_checker = Object.new }.to raise_error(ArgumentError)
|
|
426
|
+
end
|
|
427
|
+
|
|
428
|
+
it 'is checked before initial request' do
|
|
429
|
+
ImageSize.uri_checker = proc{ |uri| fail 'forbidden' if uri.port == @server.base_url.port }
|
|
430
|
+
|
|
431
|
+
expect do
|
|
432
|
+
retry_on Timeout::Error do
|
|
433
|
+
ImageSize.url("#{@server.base_url}spec/images/empty")
|
|
434
|
+
end
|
|
435
|
+
end.to raise_error('forbidden')
|
|
436
|
+
end
|
|
437
|
+
|
|
438
|
+
it 'is checked before redirect request' do
|
|
439
|
+
ImageSize.uri_checker = proc{ |uri| fail 'forbidden' if uri.port == @server.second_url.port }
|
|
440
|
+
|
|
441
|
+
expect do
|
|
442
|
+
retry_on Timeout::Error do
|
|
443
|
+
ImageSize.url("#{@server.base_url}spec/images/empty?redirect=1")
|
|
444
|
+
end
|
|
445
|
+
end.to raise_error('forbidden')
|
|
446
|
+
end
|
|
447
|
+
end
|
|
251
448
|
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="430" height="430">
|
|
2
|
+
<g font-family="'Arial Black'" text-anchor="middle">
|
|
3
|
+
<text x="266.322" y="79.322" fill="#0057b7" stroke-width=".817" font-size="108.96" transform="scale(.80705 1.2391)">RUSSIAN</text>
|
|
4
|
+
<text x="276.58" y="163.322" fill="#0057b7" stroke-width=".794" font-size="105.9" transform="scale(.78387 1.2757)">WARSHIP</text>
|
|
5
|
+
<text x="268.809" y="252.317" fill="gold" stroke-width=".803" font-size="107.02" transform="scale(.79274 1.2615)">GO FUCK</text>
|
|
6
|
+
<text x="303.286" y="306.546" fill="gold" stroke-width=".725" font-size="96.629" transform="scale(.71575 1.3971)">YOURSELF!</text>
|
|
7
|
+
</g>
|
|
8
|
+
</svg><!-- today is day 1529 of full-scale russian invasion of Ukraine -->
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
<!-- pad to 768 bytes -->
|
data/spec/test_server.rb
CHANGED
|
@@ -4,15 +4,37 @@ require 'webrick'
|
|
|
4
4
|
require 'stringio'
|
|
5
5
|
|
|
6
6
|
class TestServer
|
|
7
|
-
|
|
7
|
+
class FileHandler < WEBrick::HTTPServlet::FileHandler
|
|
8
|
+
def service(req, res)
|
|
9
|
+
super
|
|
10
|
+
rescue WEBrick::HTTPStatus::PartialContent
|
|
11
|
+
if req.query['unknown_file_size']
|
|
12
|
+
m = %r{\Abytes (\d+)-(\d+)/\d+\z}.match(res['content-range'])
|
|
13
|
+
raise "Unexpected content-range: #{res['content-range']}" unless m
|
|
14
|
+
|
|
15
|
+
res['content-range'] = "bytes #{m[1]}-#{m[2]}/*"
|
|
16
|
+
|
|
17
|
+
# need to manually get the chunk, as webrick internally relies on content-range header with total size
|
|
18
|
+
if res.body.is_a?(IO)
|
|
19
|
+
offset = m[1].to_i
|
|
20
|
+
size = m[2].to_i - offset + 1
|
|
21
|
+
|
|
22
|
+
res.body.seek(offset, IO::SEEK_SET)
|
|
23
|
+
res.body = res.body.read(size)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
raise
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
attr_reader :base_url, :second_url
|
|
8
32
|
|
|
9
33
|
def initialize(host = '127.0.0.1')
|
|
10
34
|
server_options = {
|
|
11
|
-
Logger: WEBrick::Log.new(StringIO.new),
|
|
12
35
|
AccessLog: [],
|
|
13
36
|
BindAddress: host,
|
|
14
37
|
Port: 0, # get the next available port
|
|
15
|
-
DocumentRoot: '.',
|
|
16
38
|
RequestCallback: proc do |req, res|
|
|
17
39
|
redirect = req.query['redirect'].to_i
|
|
18
40
|
if redirect > 0
|
|
@@ -30,7 +52,11 @@ class TestServer
|
|
|
30
52
|
end,
|
|
31
53
|
}
|
|
32
54
|
|
|
55
|
+
server_options[:Logger] = WEBrick::Log.new(StringIO.new) unless ENV['CI']
|
|
56
|
+
|
|
33
57
|
@server = WEBrick::HTTPServer.new(server_options)
|
|
58
|
+
@server.mount('/', FileHandler, '.')
|
|
59
|
+
|
|
34
60
|
@server.listen(host, 0) # listen on second port
|
|
35
61
|
|
|
36
62
|
@base_url = URI("http://#{host}:#{@server.listeners[0].addr[1]}/")
|
metadata
CHANGED
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: image_size
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.
|
|
4
|
+
version: 3.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Keisuke Minami
|
|
8
8
|
- Ivan Kuchin
|
|
9
|
-
autorequire:
|
|
10
9
|
bindir: bin
|
|
11
10
|
cert_chain: []
|
|
12
|
-
date:
|
|
11
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
13
12
|
dependencies:
|
|
14
13
|
- !ruby/object:Gem::Dependency
|
|
15
14
|
name: rspec
|
|
@@ -56,11 +55,11 @@ dependencies:
|
|
|
56
55
|
description: 'Measure following file dimensions: apng, avif, bmp, cur, emf, gif, heic,
|
|
57
56
|
heif, ico, j2c, jp2, jpeg, jpx, mng, pam, pbm, pcx, pgm, png, ppm, psd, svg, swf,
|
|
58
57
|
tiff, webp, xbm, xpm'
|
|
59
|
-
email:
|
|
60
58
|
executables: []
|
|
61
59
|
extensions: []
|
|
62
60
|
extra_rdoc_files: []
|
|
63
61
|
files:
|
|
62
|
+
- ".github/dependabot.yml"
|
|
64
63
|
- ".github/workflows/check.yml"
|
|
65
64
|
- ".github/workflows/rubocop.yml"
|
|
66
65
|
- ".gitignore"
|
|
@@ -120,6 +119,7 @@ files:
|
|
|
120
119
|
- spec/images/pnm/ascii.22x25.ppm
|
|
121
120
|
- spec/images/psd/16x20.psd
|
|
122
121
|
- spec/images/svg/72x100.svg
|
|
122
|
+
- spec/images/svg/768b.430x430.svg
|
|
123
123
|
- spec/images/svg/crlf.72x100.svg
|
|
124
124
|
- spec/images/svg/long.72x100.svg
|
|
125
125
|
- spec/images/svg/long.crlf.72x100.svg
|
|
@@ -140,9 +140,8 @@ licenses:
|
|
|
140
140
|
metadata:
|
|
141
141
|
bug_tracker_uri: https://github.com/toy/image_size/issues
|
|
142
142
|
changelog_uri: https://github.com/toy/image_size/blob/master/CHANGELOG.markdown
|
|
143
|
-
documentation_uri: https://www.rubydoc.info/gems/image_size/3.
|
|
143
|
+
documentation_uri: https://www.rubydoc.info/gems/image_size/3.5.0
|
|
144
144
|
source_code_uri: https://github.com/toy/image_size
|
|
145
|
-
post_install_message:
|
|
146
145
|
rdoc_options: []
|
|
147
146
|
require_paths:
|
|
148
147
|
- lib
|
|
@@ -157,8 +156,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
157
156
|
- !ruby/object:Gem::Version
|
|
158
157
|
version: '0'
|
|
159
158
|
requirements: []
|
|
160
|
-
rubygems_version:
|
|
161
|
-
signing_key:
|
|
159
|
+
rubygems_version: 4.0.3
|
|
162
160
|
specification_version: 4
|
|
163
161
|
summary: Measure image size/dimensions using pure Ruby
|
|
164
162
|
test_files:
|
|
@@ -199,6 +197,7 @@ test_files:
|
|
|
199
197
|
- spec/images/pnm/ascii.22x25.ppm
|
|
200
198
|
- spec/images/psd/16x20.psd
|
|
201
199
|
- spec/images/svg/72x100.svg
|
|
200
|
+
- spec/images/svg/768b.430x430.svg
|
|
202
201
|
- spec/images/svg/crlf.72x100.svg
|
|
203
202
|
- spec/images/svg/long.72x100.svg
|
|
204
203
|
- spec/images/svg/long.crlf.72x100.svg
|