azure-storage-file2 2.0.8 → 2.0.9

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,346 @@
1
+ # frozen_string_literal: true
2
+
3
+ #-------------------------------------------------------------------------
4
+ # # Copyright (c) Microsoft and contributors. All rights reserved.
5
+ #
6
+ # The MIT License(MIT)
7
+
8
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
9
+ # of this software and associated documentation files(the "Software"), to deal
10
+ # in the Software without restriction, including without limitation the rights
11
+ # to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
12
+ # copies of the Software, and to permit persons to whom the Software is
13
+ # furnished to do so, subject to the following conditions :
14
+
15
+ # The above copyright notice and this permission notice shall be included in
16
+ # all copies or substantial portions of the Software.
17
+
18
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
21
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24
+ # THE SOFTWARE.
25
+ #--------------------------------------------------------------------------
26
+ require "azure/storage/file/file"
27
+
28
+ module Azure::Storage
29
+ include Azure::Storage::Common::Service
30
+ StorageService = Azure::Storage::Common::Service::StorageService
31
+
32
+ module File
33
+ class FileService < StorageService
34
+ include Azure::Storage::Common::Core::Utility
35
+ include Azure::Storage::File::Share
36
+ include Azure::Storage::File::Directory
37
+ include Azure::Storage::File
38
+
39
+ class << self
40
+ # Public: Creates an instance of [Azure::Storage::File::FileService]
41
+ #
42
+ # ==== Attributes
43
+ #
44
+ # * +options+ - Hash. Optional parameters.
45
+ #
46
+ # ==== Options
47
+ #
48
+ # Accepted key/value pairs in options parameter are:
49
+ #
50
+ # * +:use_development_storage+ - TrueClass|FalseClass. Whether to use storage emulator.
51
+ # * +:development_storage_proxy_uri+ - String. Used with +:use_development_storage+ if emulator is hosted other than localhost.
52
+ # * +:storage_account_name+ - String. The name of the storage account.
53
+ # * +:storage_access_key+ - Base64 String. The access key of the storage account.
54
+ # * +:storage_sas_token+ - String. The signed access signature for the storage account or one of its service.
55
+ # * +:storage_file_host+ - String. Specified File service endpoint or hostname
56
+ # * +:storage_dns_suffix+ - String. The suffix of a regional Storage Service, to
57
+ # * +:default_endpoints_protocol+ - String. http or https
58
+ # * +:use_path_style_uri+ - String. Whether use path style URI for specified endpoints
59
+ # * +:ca_file+ - String. File path of the CA file if having issue with SSL
60
+ # * +:ssl_version+ - Symbol. The ssl version to be used, sample: :TLSv1_1, :TLSv1_2, for the details, see https://github.com/ruby/openssl/blob/master/lib/openssl/ssl.rb
61
+ # * +:ssl_min_version+ - Symbol. The min ssl version supported, only supported in Ruby 2.5+
62
+ # * +:ssl_max_version+ - Symbol. The max ssl version supported, only supported in Ruby 2.5+
63
+ # * +:user_agent_prefix+ - String. The user agent prefix that can identify the application calls the library
64
+ #
65
+ # The valid set of options include:
66
+ # * Storage Emulator: +:use_development_storage+ required, +:development_storage_proxy_uri+ optionally
67
+ # * Storage account name and key: +:storage_account_name+ and +:storage_access_key+ required, set +:storage_dns_suffix+ necessarily
68
+ # * Storage account name and SAS token: +:storage_account_name+ and +:storage_sas_token+ required, set +:storage_dns_suffix+ necessarily
69
+ # * Specified hosts and SAS token: At least one of the service host and SAS token. It's up to user to ensure the SAS token is suitable for the serivce
70
+ # * Anonymous File: only +:storage_file_host+, if it is to only access files within a container
71
+ #
72
+ # Additional notes:
73
+ # * Specified hosts can be set when use account name with access key or sas token
74
+ # * +:default_endpoints_protocol+ can be set if the scheme is not specified in hosts
75
+ # * Storage emulator always use path style URI
76
+ # * +:ca_file+ is independent.
77
+ #
78
+ # When empty options are given, it will try to read settings from Environment Variables. Refer to [Azure::Storage::Common::ClientOptions.env_vars_mapping] for the mapping relationship
79
+ #
80
+ # @return [Azure::Storage::File::FileService]
81
+ def create(options = {}, &block)
82
+ service_options = { client: Azure::Storage::Common::Client::create(options, &block), api_version: Azure::Storage::File::Default::STG_VERSION }
83
+ service_options[:user_agent_prefix] = options[:user_agent_prefix] if options[:user_agent_prefix]
84
+ Azure::Storage::File::FileService.new(service_options, &block)
85
+ end
86
+
87
+ # Public: Creates an instance of [Azure::Storage::File::FileService] with Storage Emulator
88
+ #
89
+ # ==== Attributes
90
+ #
91
+ # * +proxy_uri+ - String. Used with +:use_development_storage+ if emulator is hosted other than localhost.
92
+ #
93
+ # @return [Azure::Storage::File::FileService]
94
+ def create_development(proxy_uri = nil, &block)
95
+ service_options = { client: Azure::Storage::Common::Client::create_development(proxy_uri, &block), api_version: Azure::Storage::File::Default::STG_VERSION }
96
+ Azure::Storage::File::FileService.new(service_options, &block)
97
+ end
98
+
99
+ # Public: Creates an instance of [Azure::Storage::File::FileService] from Environment Variables
100
+ #
101
+ # @return [Azure::Storage::File::FileService]
102
+ def create_from_env(&block)
103
+ service_options = { client: Azure::Storage::Common::Client::create_from_env(&block), api_version: Azure::Storage::File::Default::STG_VERSION }
104
+ Azure::Storage::File::FileService.new(service_options, &block)
105
+ end
106
+
107
+ # Public: Creates an instance of [Azure::Storage::File::FileService] from Environment Variables
108
+ #
109
+ # ==== Attributes
110
+ #
111
+ # * +connection_string+ - String. Please refer to https://azure.microsoft.com/en-us/documentation/articles/storage-configure-connection-string/.
112
+ #
113
+ # @return [Azure::Storage::File::FileService]
114
+ def create_from_connection_string(connection_string, &block)
115
+ service_options = { client: Azure::Storage::Common::Client::create_from_connection_string(connection_string, &block), api_version: Azure::Storage::File::Default::STG_VERSION }
116
+ Azure::Storage::File::FileService.new(service_options, &block)
117
+ end
118
+ end
119
+
120
+ # Public: Initializes an instance of [Azure::Storage::File::FileService]
121
+ #
122
+ # ==== Attributes
123
+ #
124
+ # * +options+ - Hash. Optional parameters.
125
+ #
126
+ # ==== Options
127
+ #
128
+ # Accepted key/value pairs in options parameter are:
129
+ #
130
+ # * +:use_development_storage+ - TrueClass|FalseClass. Whether to use storage emulator.
131
+ # * +:development_storage_proxy_uri+ - String. Used with +:use_development_storage+ if emulator is hosted other than localhost.
132
+ # * +:storage_connection_string+ - String. The storage connection string.
133
+ # * +:storage_account_name+ - String. The name of the storage account.
134
+ # * +:storage_access_key+ - Base64 String. The access key of the storage account.
135
+ # * +:storage_sas_token+ - String. The signed access signature for the storage account or one of its service.
136
+ # * +:storage_file_host+ - String. Specified File serivce endpoint or hostname
137
+ # * +:storage_table_host+ - String. Specified Table serivce endpoint or hostname
138
+ # * +:storage_queue_host+ - String. Specified Queue serivce endpoint or hostname
139
+ # * +:storage_dns_suffix+ - String. The suffix of a regional Storage Serivce, to
140
+ # * +:default_endpoints_protocol+ - String. http or https
141
+ # * +:use_path_style_uri+ - String. Whether use path style URI for specified endpoints
142
+ # * +:ca_file+ - String. File path of the CA file if having issue with SSL
143
+ # * +:user_agent_prefix+ - String. The user agent prefix that can identify the application calls the library
144
+ # * +:client+ - Azure::Storage::Common::Client. The common client used to initalize the service.
145
+ #
146
+ # The valid set of options include:
147
+ # * Storage Emulator: +:use_development_storage+ required, +:development_storage_proxy_uri+ optionally
148
+ # * Storage account name and key: +:storage_account_name+ and +:storage_access_key+ required, set +:storage_dns_suffix+ necessarily
149
+ # * Storage account name and SAS token: +:storage_account_name+ and +:storage_sas_token+ required, set +:storage_dns_suffix+ necessarily
150
+ # * Specified hosts and SAS token: At least one of the service host and SAS token. It's up to user to ensure the SAS token is suitable for the serivce
151
+ # * Azure::Storage::Common::Client: The common client used to initalize the service. This client can be initalized and used repeatedly.
152
+ # * Anonymous File: only +:storage_file_host+, if it is to only access files within a container
153
+ #
154
+ # Additional notes:
155
+ # * Specified hosts can be set when use account name with access key or sas token
156
+ # * +:default_endpoints_protocol+ can be set if the scheme is not specified in hosts
157
+ # * Storage emulator always use path style URI
158
+ # * +:ca_file+ is independent.
159
+ #
160
+ # When empty options are given, it will try to read settings from Environment Variables. Refer to [Azure::Storage::Common::ClientOptions.env_vars_mapping] for the mapping relationship
161
+ def initialize(options = {}, &block)
162
+ service_options = options.clone
163
+ client_config = service_options[:client] ||= Azure::Storage::Common::Client::create(service_options, &block)
164
+ @user_agent_prefix = service_options[:user_agent_prefix] if service_options[:user_agent_prefix]
165
+ @api_version = service_options[:api_version] || Azure::Storage::File::Default::STG_VERSION
166
+ signer = service_options[:signer] || client_config.signer || Azure::Storage::Common::Core::Auth::SharedKey.new(client_config.storage_account_name, client_config.storage_access_key)
167
+ signer.api_ver = @api_version if signer.is_a? Azure::Storage::Common::Core::Auth::SharedAccessSignatureSigner
168
+ super(signer, client_config.storage_account_name, service_options, &block)
169
+ @storage_service_host[:primary] = client.storage_file_host
170
+ @storage_service_host[:secondary] = client.storage_file_host true
171
+ end
172
+
173
+ def call(method, uri, body = nil, headers = {}, options = {})
174
+ content_type = get_or_apply_content_type(body, headers[Azure::Storage::Common::HeaderConstants::FILE_CONTENT_TYPE])
175
+ headers[Azure::Storage::Common::HeaderConstants::FILE_CONTENT_TYPE] = content_type if content_type
176
+ headers["x-ms-version"] = @api_version ? @api_version : Default::STG_VERSION
177
+ headers["User-Agent"] = @user_agent_prefix ? "#{@user_agent_prefix}; #{Default::USER_AGENT}" : Default::USER_AGENT
178
+
179
+ response = super
180
+
181
+ # Force the response.body to the content charset of specified in the header.
182
+ # Content-Type is echo'd back for the file and is used to store the encoding of the octet stream
183
+ if !response.nil? && !response.body.nil? && response.headers["Content-Type"]
184
+ charset = parse_charset_from_content_type(response.headers["Content-Type"])
185
+ response.body.force_encoding(charset) if charset && charset.length > 0
186
+ end
187
+
188
+ response
189
+ end
190
+
191
+ # Public: Get a list of Shares from the server.
192
+ #
193
+ # ==== Attributes
194
+ #
195
+ # * +options+ - Hash. Optional parameters.
196
+ #
197
+ # ==== Options
198
+ #
199
+ # Accepted key/value pairs in options parameter are:
200
+ # * +:prefix+ - String. Filters the results to return only shares
201
+ # whose name begins with the specified prefix. (optional)
202
+ #
203
+ # * +:marker+ - String. An identifier the specifies the portion of the
204
+ # list to be returned. This value comes from the property
205
+ # Azure::Storage::Common::EnumerationResults.continuation_token when there
206
+ # are more shares available than were returned. The
207
+ # marker value may then be used here to request the next set
208
+ # of list items. (optional)
209
+ #
210
+ # * +:max_results+ - Integer. Specifies the maximum number of shares to return.
211
+ # If max_results is not specified, or is a value greater than
212
+ # 5,000, the server will return up to 5,000 items. If it is set
213
+ # to a value less than or equal to zero, the server will return
214
+ # status code 400 (Bad Request). (optional)
215
+ #
216
+ # * +:metadata+ - Boolean. Specifies whether or not to return the share metadata.
217
+ # (optional, Default=false)
218
+ #
219
+ # * +:timeout+ - Integer. A timeout in seconds.
220
+ #
221
+ # * +:request_id+ - String. Provides a client-generated, opaque value with a 1 KB character limit that is recorded
222
+ # in the analytics logs when storage analytics logging is enabled.
223
+ #
224
+ # * +:location_mode+ - LocationMode. Specifies the location mode used to decide
225
+ # which location the request should be sent to.
226
+ #
227
+ # See: https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/list-shares
228
+ #
229
+ # Returns an Azure::Storage::Common::EnumerationResults
230
+ #
231
+ def list_shares(options = {})
232
+ query = {}
233
+ if options
234
+ StorageService.with_query query, "prefix", options[:prefix]
235
+ StorageService.with_query query, "marker", options[:marker]
236
+ StorageService.with_query query, "maxresults", options[:max_results].to_s if options[:max_results]
237
+ StorageService.with_query query, "include", "metadata" if options[:metadata] == true
238
+ StorageService.with_query query, "timeout", options[:timeout].to_s if options[:timeout]
239
+ end
240
+
241
+ options[:request_location_mode] = Azure::Storage::Common::RequestLocationMode::PRIMARY_OR_SECONDARY
242
+ uri = shares_uri(query, options)
243
+ response = call(:get, uri, nil, {}, options)
244
+
245
+ Serialization.share_enumeration_results_from_xml(response.body)
246
+ end
247
+
248
+ # Protected: Generate the URI for the collection of shares.
249
+ #
250
+ # ==== Attributes
251
+ #
252
+ # * +query+ - A Hash of key => value query parameters.
253
+ #
254
+ # Returns a URI.
255
+ #
256
+ protected
257
+ def shares_uri(query = {}, options = {})
258
+ query = { "comp" => "list" }.merge(query)
259
+ generate_uri("", query, options)
260
+ end
261
+
262
+ # Protected: Generate the URI for a specific share.
263
+ #
264
+ # ==== Attributes
265
+ #
266
+ # * +name+ - The share name. If this is a URI, we just return this.
267
+ # * +query+ - A Hash of key => value query parameters.
268
+ #
269
+ # Returns a URI.
270
+ #
271
+ protected
272
+ def share_uri(name, query = {}, options = {})
273
+ return name if name.kind_of? ::URI
274
+ query = { restype: "share" }.merge(query)
275
+ generate_uri(name, query, options)
276
+ end
277
+
278
+ # Protected: Generate the URI for a specific directory.
279
+ #
280
+ # ==== Attributes
281
+ #
282
+ # * +share+ - String representing the name of the share.
283
+ # * +directory_path+ - String representing the path to the directory.
284
+ # * +directory+ - String representing the name to the directory.
285
+ # * +query+ - A Hash of key => value query parameters.
286
+ #
287
+ # Returns a URI.
288
+ #
289
+ protected
290
+ def directory_uri(share, directory_path, query = {}, options = {})
291
+ path = directory_path.nil? ? share : ::File.join(share, directory_path)
292
+ query = { restype: "directory" }.merge(query)
293
+ options = { encode: true }.merge(options)
294
+ generate_uri(path, query, options)
295
+ end
296
+
297
+ # Protected: Generate the URI for a specific file.
298
+ #
299
+ # ==== Attributes
300
+ #
301
+ # * +share+ - String representing the name of the share.
302
+ # * +directory_path+ - String representing the path to the directory.
303
+ # * +file+ - String representing the name to the file.
304
+ # * +query+ - A Hash of key => value query parameters.
305
+ #
306
+ # Returns a URI.
307
+ #
308
+ protected
309
+ def file_uri(share, directory_path, file, query = {}, options = {})
310
+ if directory_path.nil?
311
+ path = ::File.join(share, file)
312
+ else
313
+ path = ::File.join(share, directory_path, file)
314
+ end
315
+ options = { encode: true }.merge(options)
316
+ generate_uri(path, query, options)
317
+ end
318
+
319
+ # Get the content type according to the content type header and request body.
320
+ #
321
+ # headers - The request body
322
+ # content_type - The request content type
323
+ protected
324
+ def get_or_apply_content_type(body, content_type = nil)
325
+ unless body.nil?
326
+ if (body.is_a? String) && body.encoding.to_s != "ASCII_8BIT" && !body.empty?
327
+ if content_type.nil?
328
+ content_type = "text/plain; charset=#{body.encoding}"
329
+ else
330
+ # Force the request.body to the content encoding of specified in the header
331
+ charset = parse_charset_from_content_type(content_type)
332
+ body.force_encoding(charset) if charset
333
+ end
334
+ else
335
+ # It is either that the body is not a string, or that the body's encoding is ASCII_8BIT, which is a binary
336
+ # In this case, set the content type to be default content-type
337
+ content_type = Default::CONTENT_TYPE_VALUE unless content_type
338
+ end
339
+ end
340
+ content_type
341
+ end
342
+ end
343
+ end
344
+ end
345
+
346
+ Azure::Storage::FileService = Azure::Storage::File::FileService
@@ -0,0 +1,228 @@
1
+ # frozen_string_literal: true
2
+
3
+ #-------------------------------------------------------------------------
4
+ # # Copyright (c) Microsoft and contributors. All rights reserved.
5
+ #
6
+ # The MIT License(MIT)
7
+
8
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
9
+ # of this software and associated documentation files(the "Software"), to deal
10
+ # in the Software without restriction, including without limitation the rights
11
+ # to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
12
+ # copies of the Software, and to permit persons to whom the Software is
13
+ # furnished to do so, subject to the following conditions :
14
+
15
+ # The above copyright notice and this permission notice shall be included in
16
+ # all copies or substantial portions of the Software.
17
+
18
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
21
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24
+ # THE SOFTWARE.
25
+ #--------------------------------------------------------------------------
26
+ require "azure/storage/common/service/serialization"
27
+
28
+ module Azure::Storage
29
+ module File
30
+ module Serialization
31
+ include Azure::Storage::Common::Service::Serialization
32
+
33
+ def self.share_enumeration_results_from_xml(xml)
34
+ xml = slopify(xml)
35
+ expect_node("EnumerationResults", xml)
36
+
37
+ results = enumeration_results_from_xml(xml, Azure::Storage::Common::Service::EnumerationResults.new)
38
+
39
+ return results unless (xml > "Shares").any? && ((xml > "Shares") > "Share").any?
40
+
41
+ if xml.Shares.Share.count == 0
42
+ results.push(share_from_xml(xml.Shares.Share))
43
+ else
44
+ xml.Shares.Share.each { |share_node|
45
+ results.push(share_from_xml(share_node))
46
+ }
47
+ end
48
+
49
+ results
50
+ end
51
+
52
+ def self.share_from_xml(xml)
53
+ xml = slopify(xml)
54
+ expect_node("Share", xml)
55
+
56
+ Share::Share.new do |share|
57
+ share.name = xml.Name.text if (xml > "Name").any?
58
+ share.properties = share_properties_from_xml(xml.Properties) if (xml > "Properties").any?
59
+ share.metadata = metadata_from_xml(xml.Metadata) if (xml > "Metadata").any?
60
+ end
61
+ end
62
+
63
+ def self.share_properties_from_xml(xml)
64
+ xml = slopify(xml)
65
+ expect_node("Properties", xml)
66
+
67
+ props = {}
68
+ props[:last_modified] = (xml > "Last-Modified").text if (xml > "Last-Modified").any?
69
+ props[:etag] = xml.ETag.text if (xml > "ETag").any?
70
+ props[:quota] = xml.Quota.text if (xml > "Quota").any?
71
+ props
72
+ end
73
+
74
+ def self.share_from_headers(headers)
75
+ Share::Share.new do |share|
76
+ share.properties = share_properties_from_headers(headers)
77
+ share.quota = quota_from_headers(headers)
78
+ share.metadata = metadata_from_headers(headers)
79
+ end
80
+ end
81
+
82
+ def self.share_properties_from_headers(headers)
83
+ props = {}
84
+ props[:last_modified] = headers["Last-Modified"]
85
+ props[:etag] = headers["ETag"]
86
+ props
87
+ end
88
+
89
+ def self.quota_from_headers(headers)
90
+ headers["x-ms-share-quota"] ? headers["x-ms-share-quota"].to_i : nil
91
+ end
92
+
93
+ def self.share_stats_from_xml(xml)
94
+ xml = slopify(xml)
95
+ expect_node("ShareStats", xml)
96
+ xml.ShareUsage.text.to_i
97
+ end
98
+
99
+ def self.directories_and_files_enumeration_results_from_xml(xml)
100
+ xml = slopify(xml)
101
+ expect_node("EnumerationResults", xml)
102
+
103
+ results = enumeration_results_from_xml(xml, Azure::Storage::Common::Service::EnumerationResults.new)
104
+
105
+ return results unless (xml > "Entries").any?
106
+
107
+ if ((xml > "Entries") > "File").any?
108
+ if xml.Entries.File.count == 0
109
+ results.push(file_from_xml(xml.Entries.File))
110
+ else
111
+ xml.Entries.File.each { |file_node|
112
+ results.push(file_from_xml(file_node))
113
+ }
114
+ end
115
+ end
116
+
117
+ if ((xml > "Entries") > "Directory").any?
118
+ if xml.Entries.Directory.count == 0
119
+ results.push(directory_from_xml(xml.Entries.Directory))
120
+ else
121
+ xml.Entries.Directory.each { |directory_node|
122
+ results.push(directory_from_xml(directory_node))
123
+ }
124
+ end
125
+ end
126
+
127
+ results
128
+ end
129
+
130
+ def self.file_from_xml(xml)
131
+ xml = slopify(xml)
132
+ expect_node("File", xml)
133
+
134
+ File.new do |file|
135
+ file.name = xml.Name.text if (xml > "Name").any?
136
+ file.properties = file_properties_from_xml(xml.Properties) if (xml > "Properties").any?
137
+ end
138
+ end
139
+
140
+ def self.file_properties_from_xml(xml)
141
+ xml = slopify(xml)
142
+ expect_node("Properties", xml)
143
+
144
+ props = {}
145
+ props[:content_length] = (xml > "Content-Length").text.to_i if (xml > "Content-Length").any?
146
+ props
147
+ end
148
+
149
+ def self.directory_from_xml(xml)
150
+ xml = slopify(xml)
151
+ expect_node("Directory", xml)
152
+
153
+ Directory::Directory.new do |directory|
154
+ directory.name = xml.Name.text if (xml > "Name").any?
155
+ end
156
+ end
157
+
158
+ def self.directory_from_headers(headers)
159
+ Directory::Directory.new do |directory|
160
+ directory.properties = directory_properties_from_headers(headers)
161
+ directory.metadata = metadata_from_headers(headers)
162
+ end
163
+ end
164
+
165
+ def self.directory_properties_from_headers(headers)
166
+ props = {}
167
+ props[:last_modified] = headers["Last-Modified"]
168
+ props[:etag] = headers["ETag"]
169
+ props
170
+ end
171
+
172
+ def self.file_from_headers(headers)
173
+ File.new do |file|
174
+ file.properties = file_properties_from_headers(headers)
175
+ file.metadata = metadata_from_headers(headers)
176
+ end
177
+ end
178
+
179
+ def self.file_properties_from_headers(headers)
180
+ props = {}
181
+
182
+ props[:last_modified] = headers["Last-Modified"]
183
+ props[:etag] = headers["ETag"]
184
+ props[:type] = headers["x-ms-type"]
185
+
186
+ props[:content_length] = headers["Content-Length"].to_i unless headers["Content-Length"].nil?
187
+ props[:content_length] = headers["x-ms-content-length"].to_i unless headers["x-ms-content-length"].nil?
188
+
189
+ props[:content_type] = headers["Content-Type"]
190
+ props[:content_encoding] = headers["Content-Encoding"]
191
+ props[:content_language] = headers["Content-Language"]
192
+ props[:content_disposition] = headers["Content-Disposition"]
193
+ props[:content_md5] = headers["x-ms-content-md5"] || headers["Content-MD5"]
194
+ props[:range_md5] = headers["Content-MD5"] if headers["x-ms-content-md5"] && headers["Content-MD5"]
195
+ props[:cache_control] = headers["Cache-Control"]
196
+
197
+ props[:copy_id] = headers["x-ms-copy-id"]
198
+ props[:copy_status] = headers["x-ms-copy-status"]
199
+ props[:copy_source] = headers["x-ms-copy-source"]
200
+ props[:copy_progress] = headers["x-ms-copy-progress"]
201
+ props[:copy_completion_time] = headers["x-ms-copy-completion-time"]
202
+ props[:copy_status_description] = headers["x-ms-copy-status-description"]
203
+
204
+ props[:accept_ranges] = headers["Accept-Ranges"].to_i if headers["Accept-Ranges"]
205
+
206
+ props
207
+ end
208
+
209
+ def self.range_list_from_xml(xml)
210
+ xml = slopify(xml)
211
+ expect_node("Ranges", xml)
212
+
213
+ range_list = []
214
+ return range_list unless (xml > "Range").any?
215
+
216
+ if xml.Range.count == 0
217
+ range_list.push [xml.Range.Start.text.to_i, xml.Range.End.text.to_i]
218
+ else
219
+ xml.Range.each { |range|
220
+ range_list.push [range.Start.text.to_i, range.End.text.to_i]
221
+ }
222
+ end
223
+
224
+ range_list
225
+ end
226
+ end
227
+ end
228
+ end