ruby-thumbor 1.0.0 → 1.1.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.
- data/README.rdoc +19 -6
- data/lib/ruby-thumbor.rb +9 -142
- data/lib/thumbor/cascade.rb +70 -0
- data/lib/thumbor/crypto_url.rb +146 -0
- data/lib/thumbor/version.rb +3 -0
- data/spec/spec_helper.rb +15 -2
- data/spec/thumbor/cascade_spec.rb +392 -0
- data/spec/thumbor/crypto_url_spec.rb +371 -0
- data/spec/util/thumbor.rb +7 -0
- metadata +25 -49
- data/.gemtest +0 -0
- data/History.txt +0 -19
- data/Manifest.txt +0 -13
- data/PostInstall.txt +0 -3
- data/Rakefile +0 -21
- data/script/console +0 -10
- data/script/destroy +0 -14
- data/script/generate +0 -14
- data/spec/ruby-thumbor_spec.rb +0 -436
- data/spec/spec.opts +0 -1
- data/tasks/rspec.rake +0 -1
data/README.rdoc
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
= ruby-thumbor {<img src="https://secure.travis-ci.org/heynemann/ruby-thumbor.png?branch=master" alt="Build Status" />}[http://travis-ci.org/heynemann/ruby-thumbor]
|
1
|
+
= ruby-thumbor {<img src="https://secure.travis-ci.org/heynemann/ruby-thumbor.png?branch=master" alt="Build Status" />}[http://travis-ci.org/heynemann/ruby-thumbor] {<img src="https://gemnasium.com/heynemann/ruby-thumbor.png" alt="Dependency Status" />}[https://gemnasium.com/heynemann/ruby-thumbor]
|
2
2
|
|
3
3
|
* http://github.com/heynemann/ruby-thumbor
|
4
4
|
|
@@ -17,15 +17,32 @@ No dependencies required for regular usage.
|
|
17
17
|
|
18
18
|
* thumbor (http://github.com/globocom/thumbor) for running ruby-thumbor tests.
|
19
19
|
|
20
|
+
== INSTALL:
|
21
|
+
|
22
|
+
gem install ruby-thumbor
|
23
|
+
|
24
|
+
gem 'ruby-thumbor'
|
25
|
+
|
26
|
+
|
20
27
|
== USAGE:
|
21
28
|
|
22
|
-
|
29
|
+
require 'ruby-thumbor'
|
30
|
+
|
31
|
+
crypto = Thumbor::CryptoURL.new :key => 'my-security-key'
|
23
32
|
|
24
33
|
url = crypto.generate :width => 200, :height => 300, :image => 'my.server.com/path/to/image.jpg'
|
25
34
|
|
26
35
|
# url will contain something like:
|
27
36
|
# /2913921in321n3k2nj32hjhj3h22/my.server.com/path/to/image.jpg
|
28
37
|
|
38
|
+
or
|
39
|
+
|
40
|
+
require 'ruby-thumbor'
|
41
|
+
|
42
|
+
Thumbor.key = 'my-security-key'
|
43
|
+
image = Thumbor::Cascade.new('my.server.com/path/to/image.jpg')
|
44
|
+
image.width(300).height(200).watermark_filter('http://my-server.com/image.png', 30).generate
|
45
|
+
|
29
46
|
Available arguments to the generate method:
|
30
47
|
|
31
48
|
:meta => bool - flag that indicates that thumbor should return only meta-data on the operations it would
|
@@ -51,10 +68,6 @@ Available arguments to the generate method:
|
|
51
68
|
|
52
69
|
If you need more info on what each option does, check thumbor's documentation at https://github.com/globocom/thumbor/wiki.
|
53
70
|
|
54
|
-
== INSTALL:
|
55
|
-
|
56
|
-
* sudo gem install ruby-thumbor
|
57
|
-
|
58
71
|
== CONTRIBUTIONS:
|
59
72
|
|
60
73
|
* Hugo Lopes (hugobr) - Fixes in the usage readme part of the docs.
|
data/lib/ruby-thumbor.rb
CHANGED
@@ -1,146 +1,13 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
require 'openssl'
|
5
|
-
require 'base64'
|
6
|
-
require 'digest/md5'
|
7
|
-
require 'cgi'
|
1
|
+
require 'thumbor/crypto_url'
|
2
|
+
require 'thumbor/cascade'
|
8
3
|
|
9
4
|
module Thumbor
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
attr_accessor :key, :computed_key
|
14
|
-
|
15
|
-
def initialize(key)
|
16
|
-
@key = key
|
17
|
-
@computed_key = (key * 16)[0..15]
|
18
|
-
end
|
19
|
-
|
20
|
-
def pad(s)
|
21
|
-
s + ("{" * (16 - s.length % 16))
|
22
|
-
end
|
23
|
-
|
24
|
-
def calculate_width_and_height(url_parts, options)
|
25
|
-
width = options[:width]
|
26
|
-
height = options[:height]
|
27
|
-
|
28
|
-
if width and options[:flip]
|
29
|
-
width = width * -1
|
30
|
-
end
|
31
|
-
if height and options[:flop]
|
32
|
-
height = height * -1
|
33
|
-
end
|
34
|
-
|
35
|
-
if width or height
|
36
|
-
width = 0 if not width
|
37
|
-
height = 0 if not height
|
38
|
-
end
|
39
|
-
|
40
|
-
has_width = width
|
41
|
-
has_height = height
|
42
|
-
if options[:flip] and not has_width and not has_height
|
43
|
-
width = "-0"
|
44
|
-
height = '0' if not has_height and not options[:flop]
|
45
|
-
end
|
46
|
-
if options[:flop] and not has_width and not has_height
|
47
|
-
height = "-0"
|
48
|
-
width = '0' if not has_width and not options[:flip]
|
49
|
-
end
|
50
|
-
|
51
|
-
if width or height
|
52
|
-
width = width.to_s
|
53
|
-
height = height.to_s
|
54
|
-
url_parts.push(width << 'x' << height)
|
55
|
-
end
|
56
|
-
end
|
57
|
-
|
58
|
-
def url_for(options, include_hash = true)
|
59
|
-
if not options[:image]
|
60
|
-
raise 'image is a required argument.'
|
61
|
-
end
|
62
|
-
|
63
|
-
url_parts = Array.new
|
64
|
-
|
65
|
-
if options[:meta]
|
66
|
-
url_parts.push('meta')
|
67
|
-
end
|
68
|
-
|
69
|
-
crop = options[:crop]
|
70
|
-
if crop
|
71
|
-
crop_left = crop[0]
|
72
|
-
crop_top = crop[1]
|
73
|
-
crop_right = crop[2]
|
74
|
-
crop_bottom = crop[3]
|
75
|
-
|
76
|
-
if crop_left > 0 or crop_top > 0 or crop_bottom > 0 or crop_right > 0
|
77
|
-
url_parts.push(crop_left.to_s << 'x' << crop_top.to_s << ':' << crop_right.to_s << 'x' << crop_bottom.to_s)
|
78
|
-
end
|
79
|
-
end
|
80
|
-
|
81
|
-
if options[:fit_in]
|
82
|
-
url_parts.push('fit-in')
|
83
|
-
end
|
84
|
-
|
85
|
-
calculate_width_and_height(url_parts, options)
|
86
|
-
|
87
|
-
if options[:halign] and options[:halign] != :center
|
88
|
-
url_parts.push(options[:halign])
|
89
|
-
end
|
90
|
-
|
91
|
-
if options[:valign] and options[:valign] != :middle
|
92
|
-
url_parts.push(options[:valign])
|
93
|
-
end
|
94
|
-
|
95
|
-
if options[:smart]
|
96
|
-
url_parts.push('smart')
|
97
|
-
end
|
98
|
-
|
99
|
-
if options[:filters] && !options[:filters].empty?
|
100
|
-
filter_parts = []
|
101
|
-
options[:filters].each do |filter|
|
102
|
-
filter_parts.push(filter)
|
103
|
-
end
|
104
|
-
|
105
|
-
url_parts.push("filters:#{ filter_parts.join(':') }")
|
106
|
-
end
|
107
|
-
|
108
|
-
if include_hash
|
109
|
-
image_hash = Digest::MD5.hexdigest(options[:image])
|
110
|
-
url_parts.push(image_hash)
|
111
|
-
end
|
112
|
-
|
113
|
-
return url_parts.join('/')
|
114
|
-
end
|
115
|
-
|
116
|
-
def url_safe_base64(str)
|
117
|
-
Base64.encode64(str).gsub('+', '-').gsub('/', '_').gsub!(/[\n]/, '')
|
118
|
-
end
|
119
|
-
|
120
|
-
def generate_old(options)
|
121
|
-
url = pad(url_for(options))
|
122
|
-
cipher = OpenSSL::Cipher::Cipher.new('aes-128-ecb').encrypt
|
123
|
-
cipher.key = @computed_key
|
124
|
-
encrypted = cipher.update(url)
|
125
|
-
based = url_safe_base64(encrypted)
|
126
|
-
|
127
|
-
"/#{based}/#{options[:image]}"
|
128
|
-
end
|
129
|
-
|
130
|
-
def generate_new(options)
|
131
|
-
url_options = url_for(options, false)
|
132
|
-
url = "#{url_options}/#{options[:image]}"
|
133
|
-
|
134
|
-
signature = OpenSSL::HMAC.digest('sha1', @key, url)
|
135
|
-
signature = url_safe_base64(signature)
|
136
|
-
|
137
|
-
"/#{signature}/#{url}"
|
138
|
-
end
|
139
|
-
|
140
|
-
def generate(options)
|
141
|
-
return generate_old(options) if options[:old]
|
142
|
-
generate_new(options)
|
143
|
-
end
|
144
|
-
end
|
5
|
+
def self.key=(key)
|
6
|
+
@@key = key
|
7
|
+
end
|
145
8
|
|
9
|
+
def self.key
|
10
|
+
@@key
|
11
|
+
end
|
146
12
|
end
|
13
|
+
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
require 'openssl'
|
3
|
+
require 'base64'
|
4
|
+
require 'digest/md5'
|
5
|
+
require 'cgi'
|
6
|
+
|
7
|
+
module Thumbor
|
8
|
+
class Cascade
|
9
|
+
attr_accessor :image, :old_crypto, :options, :filters
|
10
|
+
|
11
|
+
@available_options = [:meta, :crop, :width, :height, :flip,:flop, :halign, :valign, :smart, :fit_in, :old, :trim]
|
12
|
+
|
13
|
+
extend Forwardable
|
14
|
+
|
15
|
+
def_delegators :@old_crypto, :computed_key
|
16
|
+
|
17
|
+
@available_options.each do |opt|
|
18
|
+
define_method(opt) do |*args|
|
19
|
+
args = [true] if args.empty?
|
20
|
+
@options[opt] = args
|
21
|
+
self
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def initialize(image=nil)
|
26
|
+
@image = image
|
27
|
+
@options = {}
|
28
|
+
@filters = []
|
29
|
+
@old_crypto = Thumbor::CryptoURL.new Thumbor.key
|
30
|
+
end
|
31
|
+
|
32
|
+
def url_for
|
33
|
+
@old_crypto.url_for prepare_options(@options).merge({:image => @image, :filters => @filters})
|
34
|
+
end
|
35
|
+
|
36
|
+
def generate
|
37
|
+
@old_crypto.generate prepare_options(@options).merge({:image => @image, :filters => @filters})
|
38
|
+
end
|
39
|
+
|
40
|
+
def method_missing(m, *args)
|
41
|
+
if /^(.+)_filter$/.match(m.to_s)
|
42
|
+
@filters << "#{$1}(#{escape_args(args).join(',')})"
|
43
|
+
self
|
44
|
+
else
|
45
|
+
super
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def escape_args(args)
|
52
|
+
args.map do |arg|
|
53
|
+
arg = CGI::escape(arg) if arg.is_a? String and arg.match(/^https?:\/\//)
|
54
|
+
arg
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def prepare_options(options)
|
59
|
+
options.reduce({}) do |final_options, item|
|
60
|
+
value = if item[1].length == 1
|
61
|
+
item[1].first
|
62
|
+
else
|
63
|
+
item[1]
|
64
|
+
end
|
65
|
+
final_options[item[0]] = value
|
66
|
+
final_options
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,146 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
require 'base64'
|
3
|
+
require 'digest/md5'
|
4
|
+
require 'cgi'
|
5
|
+
|
6
|
+
module Thumbor
|
7
|
+
class CryptoURL
|
8
|
+
attr_accessor :computed_key
|
9
|
+
|
10
|
+
def initialize(key)
|
11
|
+
Thumbor.key = key
|
12
|
+
@computed_key = (Thumbor.key * 16)[0..15]
|
13
|
+
end
|
14
|
+
|
15
|
+
def pad(s)
|
16
|
+
s + ("{" * (16 - s.length % 16))
|
17
|
+
end
|
18
|
+
|
19
|
+
def calculate_width_and_height(url_parts, options)
|
20
|
+
width = options[:width]
|
21
|
+
height = options[:height]
|
22
|
+
|
23
|
+
if width and options[:flip]
|
24
|
+
width = width * -1
|
25
|
+
end
|
26
|
+
if height and options[:flop]
|
27
|
+
height = height * -1
|
28
|
+
end
|
29
|
+
|
30
|
+
if width or height
|
31
|
+
width = 0 if not width
|
32
|
+
height = 0 if not height
|
33
|
+
end
|
34
|
+
|
35
|
+
has_width = width
|
36
|
+
has_height = height
|
37
|
+
if options[:flip] and not has_width and not has_height
|
38
|
+
width = "-0"
|
39
|
+
height = '0' if not has_height and not options[:flop]
|
40
|
+
end
|
41
|
+
if options[:flop] and not has_width and not has_height
|
42
|
+
height = "-0"
|
43
|
+
width = '0' if not has_width and not options[:flip]
|
44
|
+
end
|
45
|
+
|
46
|
+
if width or height
|
47
|
+
width = width.to_s
|
48
|
+
height = height.to_s
|
49
|
+
url_parts.push(width << 'x' << height)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def url_for(options, include_hash = true)
|
54
|
+
if not options[:image]
|
55
|
+
raise 'image is a required argument.'
|
56
|
+
end
|
57
|
+
|
58
|
+
url_parts = Array.new
|
59
|
+
|
60
|
+
if options[:meta]
|
61
|
+
url_parts.push('meta')
|
62
|
+
end
|
63
|
+
|
64
|
+
crop = options[:crop]
|
65
|
+
if crop
|
66
|
+
crop_left = crop[0]
|
67
|
+
crop_top = crop[1]
|
68
|
+
crop_right = crop[2]
|
69
|
+
crop_bottom = crop[3]
|
70
|
+
|
71
|
+
if crop_left > 0 or crop_top > 0 or crop_bottom > 0 or crop_right > 0
|
72
|
+
url_parts.push(crop_left.to_s << 'x' << crop_top.to_s << ':' << crop_right.to_s << 'x' << crop_bottom.to_s)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
if options[:fit_in]
|
77
|
+
url_parts.push('fit-in')
|
78
|
+
end
|
79
|
+
|
80
|
+
calculate_width_and_height(url_parts, options)
|
81
|
+
|
82
|
+
if options[:halign] and options[:halign] != :center
|
83
|
+
url_parts.push(options[:halign])
|
84
|
+
end
|
85
|
+
|
86
|
+
if options[:valign] and options[:valign] != :middle
|
87
|
+
url_parts.push(options[:valign])
|
88
|
+
end
|
89
|
+
|
90
|
+
if options[:smart]
|
91
|
+
url_parts.push('smart')
|
92
|
+
end
|
93
|
+
|
94
|
+
if options[:trim]
|
95
|
+
trim_options = ['trim']
|
96
|
+
trim_options << options[:trim] unless options[:trim] == true or options[:trim][0] == true
|
97
|
+
url_parts.push(trim_options.join(':'))
|
98
|
+
end
|
99
|
+
|
100
|
+
if options[:filters] && !options[:filters].empty?
|
101
|
+
filter_parts = []
|
102
|
+
options[:filters].each do |filter|
|
103
|
+
filter_parts.push(filter)
|
104
|
+
end
|
105
|
+
|
106
|
+
url_parts.push("filters:#{ filter_parts.join(':') }")
|
107
|
+
end
|
108
|
+
|
109
|
+
if include_hash
|
110
|
+
image_hash = Digest::MD5.hexdigest(options[:image])
|
111
|
+
url_parts.push(image_hash)
|
112
|
+
end
|
113
|
+
|
114
|
+
return url_parts.join('/')
|
115
|
+
end
|
116
|
+
|
117
|
+
def url_safe_base64(str)
|
118
|
+
Base64.encode64(str).gsub('+', '-').gsub('/', '_').gsub!(/[\n]/, '')
|
119
|
+
end
|
120
|
+
|
121
|
+
def generate_old(options)
|
122
|
+
url = pad(url_for(options))
|
123
|
+
cipher = OpenSSL::Cipher::Cipher.new('aes-128-ecb').encrypt
|
124
|
+
cipher.key = @computed_key
|
125
|
+
encrypted = cipher.update(url)
|
126
|
+
based = url_safe_base64(encrypted)
|
127
|
+
|
128
|
+
"/#{based}/#{options[:image]}"
|
129
|
+
end
|
130
|
+
|
131
|
+
def generate_new(options)
|
132
|
+
url_options = url_for(options, false)
|
133
|
+
url = "#{url_options}/#{options[:image]}"
|
134
|
+
|
135
|
+
signature = OpenSSL::HMAC.digest('sha1', Thumbor.key, url)
|
136
|
+
signature = url_safe_base64(signature)
|
137
|
+
|
138
|
+
"/#{signature}/#{url}"
|
139
|
+
end
|
140
|
+
|
141
|
+
def generate(options)
|
142
|
+
return generate_old(options) if options[:old]
|
143
|
+
generate_new(options)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,2 +1,15 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
2
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
3
|
+
|
4
|
+
if /^1\.9/ === RUBY_VERSION
|
5
|
+
require 'simplecov'
|
6
|
+
|
7
|
+
SimpleCov.start do
|
8
|
+
add_filter '/spec/'
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
RSpec.configure do |c|
|
13
|
+
c.filter_run :focus => true
|
14
|
+
c.run_all_when_everything_filtered = true
|
15
|
+
end
|