waz-storage 1.1.2 → 1.1.3
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/Gemfile.lock +1 -1
- data/README.rdoc +13 -9
- data/lib/waz/storage/core_service.rb +122 -122
- data/waz-storage.gemspec +1 -1
- metadata +4 -4
data/Gemfile.lock
CHANGED
data/README.rdoc
CHANGED
@@ -1,11 +1,3 @@
|
|
1
|
-
= Why this fork ?
|
2
|
-
|
3
|
-
The main difference with the original library is the adding of the method "railsetag".
|
4
|
-
This method add to any blob object an "x_ms_meta_railsetag" header, its value is calculated as the rails etag way (Digest::MD5.hexdigest(file))
|
5
|
-
|
6
|
-
By doing so we can easily know if a locale copy of a file is up-to-date on Azure CDN.
|
7
|
-
This is very much used in the "waz-sync" gem which purpose is to sync file between local storage and Azure.
|
8
|
-
|
9
1
|
= Windows Azure Storage library — simple gem for accessing WAZ‘s Storage REST API
|
10
2
|
A simple implementation of Windows Azure Storage API for Ruby, inspired by the S3 gems and self experience of dealing with queues. The major
|
11
3
|
goal of the whole gem is to enable ruby developers [like me =)] to leverage Windows Azure Storage features and have another option
|
@@ -23,6 +15,18 @@ Full documentation for the gem is available at http://waz-storage.heroku.com
|
|
23
15
|
Well, this is a sum up of the whole experience of writing those gems and getting them to work together to simplify
|
24
16
|
end user experience. Although there're some breaking changes, it's pretty backward compatible with existing gems.
|
25
17
|
|
18
|
+
=== What's new on v1.1.3?
|
19
|
+
|
20
|
+
- Fixed an issue with URL enconding (thanks @survili)
|
21
|
+
|
22
|
+
=== What's new on v1.1.2?
|
23
|
+
|
24
|
+
The main difference with the original library is the adding of the method "railsetag".
|
25
|
+
This method add to any blob object an "x_ms_meta_railsetag" header, its value is calculated as the rails etag way (Digest::MD5.hexdigest(file))
|
26
|
+
|
27
|
+
By doing so we can easily know if a locale copy of a file is up-to-date on Azure CDN.
|
28
|
+
This is very much used in the "waz-sync" gem which purpose is to sync file between local storage and Azure.
|
29
|
+
|
26
30
|
=== What's new on the v1.1 version? [thanks to smarx]
|
27
31
|
- Upload from stream, plus XML/URI escaping in various places, and a few other minor fixes
|
28
32
|
- Add upload method to container
|
@@ -298,7 +302,7 @@ The things listed above do not represent any sort of priority, or the order they
|
|
298
302
|
|
299
303
|
Written by Johnny G. Halife (johnny.halife at me dot com)
|
300
304
|
|
301
|
-
contributed by: Ezequiel Morito (http://twitter.com/ezequielm), Juan Pablo Garcia (http://twitter.com/jpgd), Steve Marx (http://twitter.com/smarx)
|
305
|
+
contributed by: Ezequiel Morito (http://twitter.com/ezequielm), Juan Pablo Garcia (http://twitter.com/jpgd), Steve Marx (http://twitter.com/smarx), G. Montard (http://github.com/gmontard)
|
302
306
|
|
303
307
|
Released under the MIT License: http://www.opensource.org/licenses/mit-license.php
|
304
308
|
|
@@ -1,122 +1,122 @@
|
|
1
|
-
module WAZ
|
2
|
-
module Storage
|
3
|
-
# This module is imported by the specific services that use Shared Key authentication profile. On the current implementation
|
4
|
-
# this module is imported from WAZ::Queues::Service and WAZ::Blobs::Service.
|
5
|
-
module SharedKeyCoreService
|
6
|
-
attr_accessor :account_name, :access_key, :use_ssl, :base_url, :type_of_service, :use_devenv, :use_sas_auth_only, :sharedaccesssignature
|
7
|
-
|
8
|
-
# Creates an instance of the implementor service (internally used by the API).
|
9
|
-
def initialize(options = {})
|
10
|
-
# Flag to define the use of shared access signature only
|
11
|
-
self.use_sas_auth_only = options[:use_sas_auth_only] or false
|
12
|
-
self.sharedaccesssignature = options[:sharedaccesssignature]
|
13
|
-
|
14
|
-
self.account_name = options[:account_name]
|
15
|
-
self.access_key = options[:access_key]
|
16
|
-
self.type_of_service = options[:type_of_service]
|
17
|
-
self.use_ssl = options[:use_ssl] or false
|
18
|
-
self.use_devenv = !!options[:use_devenv]
|
19
|
-
self.base_url = "#{options[:type_of_service] or "blobs"}.#{options[:base_url] or "core.windows.net"}" unless self.use_devenv
|
20
|
-
self.base_url ||= (options[:base_url] or "core.windows.net")
|
21
|
-
end
|
22
|
-
|
23
|
-
# Generates a request based on Adam Wiggings' rest-client, including all the required headers
|
24
|
-
# for interacting with Windows Azure Storage API (except for Tables). This methods embeds the
|
25
|
-
# authorization key signature on the request based on the given access_key.
|
26
|
-
def generate_request(verb, url, headers = {}, payload = nil)
|
27
|
-
http_headers = {}
|
28
|
-
headers.each{ |k, v| http_headers[k.to_s.gsub(/_/, '-')] = v} unless headers.nil?
|
29
|
-
http_headers.merge!("x-ms-Date" => Time.new.httpdate)
|
30
|
-
http_headers.merge!("Content-Length" => (payload or "").size)
|
31
|
-
request = {:headers => http_headers, :method => verb.to_s.downcase.to_sym, :url => url, :payload => payload}
|
32
|
-
request[:headers].merge!("Authorization" => "SharedKey #{account_name}:#{generate_signature(request)}") unless self.use_sas_auth_only
|
33
|
-
return RestClient::Request.new(request)
|
34
|
-
end
|
35
|
-
|
36
|
-
# Generates the request uri based on the resource path, the protocol, the account name and the parameters passed
|
37
|
-
# on the options hash.
|
38
|
-
def generate_request_uri(path = nil, options = {})
|
39
|
-
protocol = use_ssl ? "https" : "http"
|
40
|
-
query_params = options.keys.sort{ |a, b| a.to_s <=> b.to_s}.map{ |k| "#{k.to_s.gsub(/_/, '')}=#{CGI.escape(options[k].to_s)}"}.join("&") unless options.nil? or options.empty?
|
41
|
-
uri = "#{protocol}://#{base_url}/#{path.start_with?(account_name) ? "" : account_name }#{((path or "").start_with?("/") or path.start_with?(account_name)) ? "" : "/"}#{(path or "")}" if !self.use_devenv.nil? and self.use_devenv
|
42
|
-
uri ||= "#{protocol}://#{account_name}.#{base_url}#{(path or "").start_with?("/") ? "" : "/"}#{(path or "")}"
|
43
|
-
if self.use_sas_auth_only
|
44
|
-
uri << "?#{self.sharedaccesssignature.gsub(/\?/,'')}"
|
45
|
-
else
|
46
|
-
uri << "?#{query_params}" if query_params
|
47
|
-
end
|
48
|
-
return uri
|
49
|
-
end
|
50
|
-
|
51
|
-
# Canonicalizes the request headers by following Microsoft's specification on how those headers have to be sorted
|
52
|
-
# and which of the given headers apply to be canonicalized.
|
53
|
-
def canonicalize_headers(headers)
|
54
|
-
cannonicalized_headers = headers.keys.select {|h| h.to_s.start_with? 'x-ms'}.map{ |h| "#{h.downcase.strip}:#{headers[h].strip}" }.sort{ |a, b| a <=> b }.join("\x0A")
|
55
|
-
return cannonicalized_headers
|
56
|
-
end
|
57
|
-
|
58
|
-
# Creates a canonical representation of the message by combining account_name/resource_path.
|
59
|
-
def canonicalize_message(url)
|
60
|
-
uri_component = url.gsub(/https?:\/\/[^\/]+\//i, '').gsub(/\?.*/i, '')
|
61
|
-
comp_component = url.scan(/comp=[^&]+/i).first()
|
62
|
-
uri_component << "?#{comp_component}" if comp_component
|
63
|
-
canonicalized_message = "/#{self.account_name}/#{uri_component}"
|
64
|
-
return canonicalized_message
|
65
|
-
end
|
66
|
-
|
67
|
-
# Generates the signature based on Micosoft specs for the REST API. It includes some special headers,
|
68
|
-
# the canonicalized header line and the canonical form of the message, all of the joined by \n character. Encoded with
|
69
|
-
# Base64 and encrypted with SHA256 using the access_key as the seed.
|
70
|
-
def generate_signature(options = {})
|
71
|
-
return generate_signature20090919(options) if options[:headers]["x-ms-version"] == "2011-08-18"
|
72
|
-
|
73
|
-
signature = options[:method].to_s.upcase + "\x0A" +
|
74
|
-
(options[:headers]["Content-MD5"] or "") + "\x0A" +
|
75
|
-
(options[:headers]["Content-Type"] or "") + "\x0A" +
|
76
|
-
(options[:headers]["Date"] or "")+ "\x0A"
|
77
|
-
|
78
|
-
signature += canonicalize_headers(options[:headers]) + "\x0A" unless self.type_of_service == 'table'
|
79
|
-
signature += canonicalize_message(options[:url])
|
80
|
-
signature = signature.toutf8 if(signature.respond_to? :toutf8)
|
81
|
-
Base64.encode64(HMAC::SHA256.new(Base64.decode64(self.access_key)).update(signature).digest)
|
82
|
-
end
|
83
|
-
|
84
|
-
def generate_signature20090919(options = {})
|
85
|
-
signature = options[:method].to_s.upcase + "\x0A" +
|
86
|
-
(options[:headers]["Content-Encoding"] or "") + "\x0A" +
|
87
|
-
(options[:headers]["Content-Language"] or "") + "\x0A" +
|
88
|
-
(options[:headers]["Content-Length"] or "").to_s + "\x0A" +
|
89
|
-
(options[:headers]["Content-MD5"] or "") + "\x0A" +
|
90
|
-
(options[:headers]["Content-Type"] or "") + "\x0A" +
|
91
|
-
(options[:headers]["Date"] or "")+ "\x0A" +
|
92
|
-
(options[:headers]["If-Modified-Since"] or "")+ "\x0A" +
|
93
|
-
(options[:headers]["If-Match"] or "")+ "\x0A" +
|
94
|
-
(options[:headers]["If-None-Match"] or "")+ "\x0A" +
|
95
|
-
(options[:headers]["If-Unmodified-Since"] or "")+ "\x0A" +
|
96
|
-
(options[:headers]["Range"] or "")+ "\x0A" +
|
97
|
-
canonicalize_headers(options[:headers]) + "\x0A" +
|
98
|
-
canonicalize_message20090919(options[:url])
|
99
|
-
|
100
|
-
signature = signature.toutf8 if(signature.respond_to? :toutf8)
|
101
|
-
Base64.encode64(HMAC::SHA256.new(Base64.decode64(self.access_key)).update(signature).digest)
|
102
|
-
end
|
103
|
-
|
104
|
-
def canonicalize_message20090919(url)
|
105
|
-
uri_component = url.gsub(/https?:\/\/[^\/]+\//i, '').gsub(/\?.*/i, '')
|
106
|
-
query_component = (url.scan(/\?(.*)/i).first() or []).first()
|
107
|
-
query_component = query_component.split('&').sort{|a, b| a <=> b}.map{ |p| CGI::unescape(p.split('=').join(':')) }.join("\n") if query_component
|
108
|
-
canonicalized_message = "/#{self.account_name}/#{uri_component}"
|
109
|
-
canonicalized_message << "\n#{query_component}" if query_component
|
110
|
-
return canonicalized_message
|
111
|
-
end
|
112
|
-
|
113
|
-
# Generates a Windows Azure Storage call, it internally calls url generation method
|
114
|
-
# and the request generation message.
|
115
|
-
def execute(verb, path, query = {}, headers = {}, payload = nil)
|
116
|
-
url = generate_request_uri(path, query)
|
117
|
-
request = generate_request(verb, url, headers, payload)
|
118
|
-
request.execute()
|
119
|
-
end
|
120
|
-
end
|
121
|
-
end
|
122
|
-
end
|
1
|
+
module WAZ
|
2
|
+
module Storage
|
3
|
+
# This module is imported by the specific services that use Shared Key authentication profile. On the current implementation
|
4
|
+
# this module is imported from WAZ::Queues::Service and WAZ::Blobs::Service.
|
5
|
+
module SharedKeyCoreService
|
6
|
+
attr_accessor :account_name, :access_key, :use_ssl, :base_url, :type_of_service, :use_devenv, :use_sas_auth_only, :sharedaccesssignature
|
7
|
+
|
8
|
+
# Creates an instance of the implementor service (internally used by the API).
|
9
|
+
def initialize(options = {})
|
10
|
+
# Flag to define the use of shared access signature only
|
11
|
+
self.use_sas_auth_only = options[:use_sas_auth_only] or false
|
12
|
+
self.sharedaccesssignature = options[:sharedaccesssignature]
|
13
|
+
|
14
|
+
self.account_name = options[:account_name]
|
15
|
+
self.access_key = options[:access_key]
|
16
|
+
self.type_of_service = options[:type_of_service]
|
17
|
+
self.use_ssl = options[:use_ssl] or false
|
18
|
+
self.use_devenv = !!options[:use_devenv]
|
19
|
+
self.base_url = "#{options[:type_of_service] or "blobs"}.#{options[:base_url] or "core.windows.net"}" unless self.use_devenv
|
20
|
+
self.base_url ||= (options[:base_url] or "core.windows.net")
|
21
|
+
end
|
22
|
+
|
23
|
+
# Generates a request based on Adam Wiggings' rest-client, including all the required headers
|
24
|
+
# for interacting with Windows Azure Storage API (except for Tables). This methods embeds the
|
25
|
+
# authorization key signature on the request based on the given access_key.
|
26
|
+
def generate_request(verb, url, headers = {}, payload = nil)
|
27
|
+
http_headers = {}
|
28
|
+
headers.each{ |k, v| http_headers[k.to_s.gsub(/_/, '-')] = v} unless headers.nil?
|
29
|
+
http_headers.merge!("x-ms-Date" => Time.new.httpdate)
|
30
|
+
http_headers.merge!("Content-Length" => (payload or "").size)
|
31
|
+
request = {:headers => http_headers, :method => verb.to_s.downcase.to_sym, :url => url, :payload => payload}
|
32
|
+
request[:headers].merge!("Authorization" => "SharedKey #{account_name}:#{generate_signature(request)}") unless self.use_sas_auth_only
|
33
|
+
return RestClient::Request.new(request)
|
34
|
+
end
|
35
|
+
|
36
|
+
# Generates the request uri based on the resource path, the protocol, the account name and the parameters passed
|
37
|
+
# on the options hash.
|
38
|
+
def generate_request_uri(path = nil, options = {})
|
39
|
+
protocol = use_ssl ? "https" : "http"
|
40
|
+
query_params = options.keys.sort{ |a, b| a.to_s <=> b.to_s}.map{ |k| "#{k.to_s.gsub(/_/, '')}=#{CGI.escape(options[k].to_s)}"}.join("&") unless options.nil? or options.empty?
|
41
|
+
uri = "#{protocol}://#{base_url}/#{path.start_with?(account_name) ? "" : account_name }#{((path or "").start_with?("/") or path.start_with?(account_name)) ? "" : "/"}#{(path or "")}" if !self.use_devenv.nil? and self.use_devenv
|
42
|
+
uri ||= "#{protocol}://#{account_name}.#{base_url}#{(path or "").start_with?("/") ? "" : "/"}#{(path or "")}"
|
43
|
+
if self.use_sas_auth_only
|
44
|
+
uri << "?#{self.sharedaccesssignature.gsub(/\?/,'')}"
|
45
|
+
else
|
46
|
+
uri << "?#{query_params}" if query_params
|
47
|
+
end
|
48
|
+
return uri
|
49
|
+
end
|
50
|
+
|
51
|
+
# Canonicalizes the request headers by following Microsoft's specification on how those headers have to be sorted
|
52
|
+
# and which of the given headers apply to be canonicalized.
|
53
|
+
def canonicalize_headers(headers)
|
54
|
+
cannonicalized_headers = headers.keys.select {|h| h.to_s.start_with? 'x-ms'}.map{ |h| "#{h.downcase.strip}:#{headers[h].strip}" }.sort{ |a, b| a <=> b }.join("\x0A")
|
55
|
+
return cannonicalized_headers
|
56
|
+
end
|
57
|
+
|
58
|
+
# Creates a canonical representation of the message by combining account_name/resource_path.
|
59
|
+
def canonicalize_message(url)
|
60
|
+
uri_component = url.gsub(/https?:\/\/[^\/]+\//i, '').gsub(/\?.*/i, '')
|
61
|
+
comp_component = url.scan(/comp=[^&]+/i).first()
|
62
|
+
uri_component << "?#{comp_component}" if comp_component
|
63
|
+
canonicalized_message = "/#{self.account_name}/#{uri_component}"
|
64
|
+
return canonicalized_message
|
65
|
+
end
|
66
|
+
|
67
|
+
# Generates the signature based on Micosoft specs for the REST API. It includes some special headers,
|
68
|
+
# the canonicalized header line and the canonical form of the message, all of the joined by \n character. Encoded with
|
69
|
+
# Base64 and encrypted with SHA256 using the access_key as the seed.
|
70
|
+
def generate_signature(options = {})
|
71
|
+
return generate_signature20090919(options) if options[:headers]["x-ms-version"] == "2011-08-18"
|
72
|
+
|
73
|
+
signature = options[:method].to_s.upcase + "\x0A" +
|
74
|
+
(options[:headers]["Content-MD5"] or "") + "\x0A" +
|
75
|
+
(options[:headers]["Content-Type"] or "") + "\x0A" +
|
76
|
+
(options[:headers]["Date"] or "")+ "\x0A"
|
77
|
+
|
78
|
+
signature += canonicalize_headers(options[:headers]) + "\x0A" unless self.type_of_service == 'table'
|
79
|
+
signature += canonicalize_message(options[:url])
|
80
|
+
signature = signature.toutf8 if(signature.respond_to? :toutf8)
|
81
|
+
Base64.encode64(HMAC::SHA256.new(Base64.decode64(self.access_key)).update(signature).digest)
|
82
|
+
end
|
83
|
+
|
84
|
+
def generate_signature20090919(options = {})
|
85
|
+
signature = options[:method].to_s.upcase + "\x0A" +
|
86
|
+
(options[:headers]["Content-Encoding"] or "") + "\x0A" +
|
87
|
+
(options[:headers]["Content-Language"] or "") + "\x0A" +
|
88
|
+
(options[:headers]["Content-Length"] or "").to_s + "\x0A" +
|
89
|
+
(options[:headers]["Content-MD5"] or "") + "\x0A" +
|
90
|
+
(options[:headers]["Content-Type"] or "") + "\x0A" +
|
91
|
+
(options[:headers]["Date"] or "")+ "\x0A" +
|
92
|
+
(options[:headers]["If-Modified-Since"] or "")+ "\x0A" +
|
93
|
+
(options[:headers]["If-Match"] or "")+ "\x0A" +
|
94
|
+
(options[:headers]["If-None-Match"] or "")+ "\x0A" +
|
95
|
+
(options[:headers]["If-Unmodified-Since"] or "")+ "\x0A" +
|
96
|
+
(options[:headers]["Range"] or "")+ "\x0A" +
|
97
|
+
canonicalize_headers(options[:headers]) + "\x0A" +
|
98
|
+
canonicalize_message20090919(options[:url])
|
99
|
+
|
100
|
+
signature = signature.toutf8 if(signature.respond_to? :toutf8)
|
101
|
+
Base64.encode64(HMAC::SHA256.new(Base64.decode64(self.access_key)).update(signature).digest)
|
102
|
+
end
|
103
|
+
|
104
|
+
def canonicalize_message20090919(url)
|
105
|
+
uri_component = url.gsub(/https?:\/\/[^\/]+\//i, '').gsub(/\?.*/i, '')
|
106
|
+
query_component = (url.scan(/\?(.*)/i).first() or []).first()
|
107
|
+
query_component = query_component.split('&').sort{|a, b| a <=> b}.map{ |p| CGI::unescape(p.split('=').join(':')) }.join("\n") if query_component
|
108
|
+
canonicalized_message = "/#{self.account_name}/#{uri_component}"
|
109
|
+
canonicalized_message << "\n#{query_component}" if query_component
|
110
|
+
return canonicalized_message
|
111
|
+
end
|
112
|
+
|
113
|
+
# Generates a Windows Azure Storage call, it internally calls url generation method
|
114
|
+
# and the request generation message.
|
115
|
+
def execute(verb, path, query = {}, headers = {}, payload = nil)
|
116
|
+
url = generate_request_uri(path, query)
|
117
|
+
request = generate_request(verb, URI.encode(url), headers, payload)
|
118
|
+
request.execute()
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
data/waz-storage.gemspec
CHANGED
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: waz-storage
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 21
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 1
|
8
8
|
- 1
|
9
|
-
-
|
10
|
-
version: 1.1.
|
9
|
+
- 3
|
10
|
+
version: 1.1.3
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Johnny G. Halife
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2012-
|
18
|
+
date: 2012-06-04 00:00:00 Z
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
21
21
|
name: rest-client
|