ruby-thumbor 1.0.0 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|