rack-webdav 0.4.2 → 0.4.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 43db150f50bda6ebc40e2fcac2684ab96c1a90ac
4
- data.tar.gz: fe75dfa791f91c6d4489b268c47315ab509b10ff
3
+ metadata.gz: 9025207ede1c1f829fa216898d173661ac29aa2d
4
+ data.tar.gz: e6c001a417788e6a4134493ecb82031467c3ebaa
5
5
  SHA512:
6
- metadata.gz: 296a499b062662184e7da39840918ce7b1892be2a27e5ef8867ef0c068c2f4f7f1522a93332e8478af18b89c1ca457b76a89a6fa3130285134321f0bb3c575a5
7
- data.tar.gz: 238656df1091b804a97b9f2cb398dc4e322c6013266002461d8e382fc5b676f9f794603f43888790b32fc0742c6b48522c7d74086b11c482d27f9dc1a8c92afc
6
+ metadata.gz: 1be317ee0340db32fa1ebf40f663810b58fc50b29a3d08f7c9c745a2912d67849639ac9a4dde236e065119e7f83e90e8fb5b3f69160f90a61e07dd5bb226ac71
7
+ data.tar.gz: e01078ed80e0a6fb2ec0dd99c4c89fa4765a6c1a43d001083935031ac6a9191bd0bc179ec101ab320e8d39d67b5d0526e0b7660098c8b3b12807c47bae140e38
@@ -1,11 +1,11 @@
1
1
  require 'uri'
2
2
 
3
3
  module RackWebDAV
4
-
4
+
5
5
  class Controller
6
6
  include RackWebDAV::HTTPStatus
7
7
  include RackWebDAV::Utils
8
-
8
+
9
9
  attr_reader :request, :response, :resource
10
10
 
11
11
  # request:: Rack::Request
@@ -18,17 +18,17 @@ module RackWebDAV
18
18
  @request = request
19
19
  @response = response
20
20
  @options = options
21
-
21
+
22
22
  @dav_extensions = options.delete(:dav_extensions) || []
23
23
  @always_include_dav_header = options.delete(:always_include_dav_header)
24
-
24
+
25
25
  @resource = resource_class.new(actual_path, implied_path, @request, @response, @options)
26
-
26
+
27
27
  if(@always_include_dav_header)
28
28
  add_dav_header
29
29
  end
30
30
  end
31
-
31
+
32
32
  # s:: string
33
33
  # Escape URL string
34
34
  def url_format(resource)
@@ -38,20 +38,20 @@ module RackWebDAV
38
38
  end
39
39
  ret
40
40
  end
41
-
41
+
42
42
  # s:: string
43
43
  # Unescape URL string
44
- def url_unescape(s)
45
- URI.unescape(s)
44
+ def url_unescape(s, encoding = Encoding::UTF_8)
45
+ URI.decode_www_form_component(s, encoding)
46
46
  end
47
-
47
+
48
48
  def add_dav_header
49
49
  unless(response['Dav'])
50
50
  dav_support = %w(1 2) + @dav_extensions
51
51
  response['Dav'] = dav_support.join(', ')
52
52
  end
53
53
  end
54
-
54
+
55
55
  # Return response to OPTIONS
56
56
  def options
57
57
  add_dav_header
@@ -59,7 +59,7 @@ module RackWebDAV
59
59
  response['Ms-Author-Via'] = 'DAV'
60
60
  OK
61
61
  end
62
-
62
+
63
63
  # Return response to HEAD
64
64
  def head
65
65
  if(resource.exist?)
@@ -71,7 +71,7 @@ module RackWebDAV
71
71
  NotFound
72
72
  end
73
73
  end
74
-
74
+
75
75
  # Return response to GET
76
76
  def get
77
77
  if(resource.exist?)
@@ -117,7 +117,7 @@ module RackWebDAV
117
117
  NotFound
118
118
  end
119
119
  end
120
-
120
+
121
121
  # Return response to MKCOL
122
122
  def mkcol
123
123
  resource.lock_check if resource.supports_locking?
@@ -135,7 +135,7 @@ module RackWebDAV
135
135
  status
136
136
  end
137
137
  end
138
-
138
+
139
139
  # Return response to COPY
140
140
  def copy
141
141
  move(:copy)
@@ -180,7 +180,7 @@ module RackWebDAV
180
180
  end
181
181
  end
182
182
  end
183
-
183
+
184
184
  # Return response to PROPFIND
185
185
  def propfind
186
186
  unless(resource.exist?)
@@ -189,25 +189,28 @@ module RackWebDAV
189
189
  unless(request_document.xpath("//#{ns}propfind/#{ns}allprop").empty?)
190
190
  properties = resource.properties
191
191
  else
192
- check = request_document.xpath("//#{ns}propfind")
193
- if(check && !check.empty?)
194
- properties = request_document.xpath(
195
- "//#{ns}propfind/#{ns}prop"
196
- ).children.find_all{ |item|
197
- item.element?
198
- }.map{ |item|
199
- # We should do this, but Nokogiri transforms prefix w/ null href into
200
- # something valid. Oops.
201
- # TODO: Hacky grep fix that's horrible
202
- hsh = to_element_hash(item)
203
- if(hsh.namespace.nil? && !ns.empty?)
204
- raise BadRequest if request_document.to_s.scan(%r{<#{item.name}[^>]+xmlns=""}).empty?
205
- end
206
- hsh
207
- }.compact
192
+ if request.body.read.bytesize > 0
193
+ request.body.rewind
194
+ check = request_document.xpath("//#{ns}propfind")
195
+ if(check && !check.empty?)
196
+ properties = request_document.xpath(
197
+ "//#{ns}propfind/#{ns}prop"
198
+ ).children.find_all{ |item|
199
+ item.element?
200
+ }.map{ |item|
201
+ # We should do this, but Nokogiri transforms prefix w/ null href into
202
+ # something valid. Oops.
203
+ # TODO: Hacky grep fix that's horrible
204
+ hsh = to_element_hash(item)
205
+ if(hsh.namespace.nil? && !ns.empty?)
206
+ raise BadRequest if request_document.to_s.scan(%r{<#{item.name}[^>]+xmlns=""}).empty?
207
+ end
208
+ hsh
209
+ }.compact
210
+ else
211
+ raise BadRequest
212
+ end
208
213
  else
209
- # XXX: Compatibility for Windows.
210
- # raise BadRequest
211
214
  properties = []
212
215
  end
213
216
  end
@@ -225,7 +228,7 @@ module RackWebDAV
225
228
  end
226
229
  end
227
230
  end
228
-
231
+
229
232
  # Return response to PROPPATCH
230
233
  def proppatch
231
234
  unless(resource.exist?)
@@ -266,7 +269,7 @@ module RackWebDAV
266
269
 
267
270
  # Lock current resource
268
271
  # NOTE: This will pass an argument hash to Resource#lock and
269
- # wait for a success/failure response.
272
+ # wait for a success/failure response.
270
273
  def lock
271
274
  lockinfo = request_document.xpath("//#{ns}lockinfo")
272
275
  asked = {}
@@ -344,29 +347,29 @@ module RackWebDAV
344
347
  end
345
348
  raise Unauthorized unless authed
346
349
  end
347
-
350
+
348
351
  private
349
352
 
350
353
  # Request environment variables
351
354
  def env
352
355
  @request.env
353
356
  end
354
-
357
+
355
358
  # Current request scheme (http/https)
356
359
  def scheme
357
360
  request.scheme
358
361
  end
359
-
362
+
360
363
  # Request host
361
364
  def host
362
365
  request.host
363
366
  end
364
-
367
+
365
368
  # Request port
366
369
  def port
367
370
  request.port
368
371
  end
369
-
372
+
370
373
  # Class of the resource in use
371
374
  def resource_class
372
375
  @options[:resource_class]
@@ -376,12 +379,12 @@ module RackWebDAV
376
379
  def root_uri_path
377
380
  @options[:root_uri_path]
378
381
  end
379
-
382
+
380
383
  # Returns Resource path with root URI removed
381
384
  def implied_path
382
385
  clean_path(@request.path.dup)
383
386
  end
384
-
387
+
385
388
  # x:: request path
386
389
  # Unescapes path and removes root URI if applicable
387
390
  def clean_path(x)
@@ -389,7 +392,7 @@ module RackWebDAV
389
392
  ip.gsub!(/^#{Regexp.escape(root_uri_path)}/, '') if root_uri_path
390
393
  ip
391
394
  end
392
-
395
+
393
396
  # Unescaped request path
394
397
  def actual_path
395
398
  url_unescape(@request.path.dup)
@@ -399,7 +402,7 @@ module RackWebDAV
399
402
  def lock_token
400
403
  env['HTTP_LOCK_TOKEN'] || nil
401
404
  end
402
-
405
+
403
406
  # Requested depth
404
407
  def depth
405
408
  d = env['HTTP_DEPTH']
@@ -415,7 +418,7 @@ module RackWebDAV
415
418
  def http_version
416
419
  env['HTTP_VERSION'] || env['SERVER_PROTOCOL'] || 'HTTP/1.0'
417
420
  end
418
-
421
+
419
422
  # Overwrite is allowed
420
423
  def overwrite
421
424
  env['HTTP_OVERWRITE'].to_s.upcase != 'F'
@@ -434,9 +437,10 @@ module RackWebDAV
434
437
  end
435
438
  with_current_resource ? [resource] + ary : ary
436
439
  end
437
-
440
+
438
441
  # XML parsed request
439
442
  def request_document
443
+ request.body.rewind
440
444
  @request_document ||= Nokogiri.XML(request.body.read)
441
445
  rescue
442
446
  raise BadRequest
@@ -458,7 +462,7 @@ module RackWebDAV
458
462
  end
459
463
  _ns
460
464
  end
461
-
465
+
462
466
  # root_type:: Root tag name
463
467
  # Render XML and set Rack::Response#body= to final XML
464
468
  def render_xml(root_type)
@@ -470,7 +474,7 @@ module RackWebDAV
470
474
  yield xml
471
475
  end
472
476
  end
473
-
477
+
474
478
  if(@options[:pretty_xml])
475
479
  response.body = doc.to_xml
476
480
  else
@@ -481,7 +485,7 @@ module RackWebDAV
481
485
  response["Content-Type"] = 'text/xml; charset="utf-8"'
482
486
  response["Content-Length"] = response.body.size.to_s
483
487
  end
484
-
488
+
485
489
  # block:: block
486
490
  # Creates a multistatus response using #render_xml and
487
491
  # returns the correct status
@@ -489,7 +493,7 @@ module RackWebDAV
489
493
  render_xml(:multistatus, &block)
490
494
  MultiStatus
491
495
  end
492
-
496
+
493
497
  # xml:: Nokogiri::XML::Builder
494
498
  # errors:: Array of errors
495
499
  # Crafts responses for errors
@@ -545,7 +549,7 @@ module RackWebDAV
545
549
  end
546
550
  stats
547
551
  end
548
-
552
+
549
553
  # xml:: Nokogiri::XML::Builder
550
554
  # stats:: Array of stats
551
555
  # Build propstats response
@@ -590,14 +594,12 @@ module RackWebDAV
590
594
  end
591
595
  end
592
596
  end
593
-
597
+
594
598
  # xml:: Nokogiri::XML::Builder
595
599
  # element:: Nokogiri::XML::Element
596
600
  # Converts element into proper text
597
601
  def xml_convert(xml, element)
598
602
  xml.doc.root.add_child(element)
599
603
  end
600
-
601
604
  end
602
-
603
- end
605
+ end
@@ -1,3 +1,4 @@
1
+ # encoding: UTF-8
1
2
  require 'rack-webdav/logger'
2
3
 
3
4
  module RackWebDAV
@@ -181,7 +181,7 @@ module RackWebDAV
181
181
  if(::File.directory?(file_path))
182
182
  MethodNotAllowed
183
183
  else
184
- if(::File.directory?(::File.dirname(file_path)) && !::File.exists?(file_path))
184
+ if(::File.directory?(::File.dirname(file_path)) && !::File.exists?(file_path) && !::File.exists?(file_path.gsub(/\/$/, '')))
185
185
  Dir.mkdir(file_path)
186
186
  Created
187
187
  else
@@ -299,7 +299,12 @@ module RackWebDAV
299
299
  end
300
300
  raise failure
301
301
  else
302
- locks = FileResourceLock.implict_locks(@path).find(:all, :conditions => ["scope = 'exclusive' AND user_id != ?", @user.id])
302
+ if @user.respond_to?(:id)
303
+ locks = FileResourceLock.implict_locks(@path).find(:all, :conditions => ["scope = 'exclusive' AND user_id != ?", @user.id])
304
+ else
305
+ locks = []
306
+ end
307
+
303
308
  if(locks.size > 0)
304
309
  failure = LockFailure.new("Failed to lock: #{@path}")
305
310
  locks.each do |lock|
@@ -13,5 +13,5 @@ module RackWebDAV
13
13
  end
14
14
  end
15
15
 
16
- VERSION = Version.new('0.4.2')
16
+ VERSION = Version.new('0.4.3')
17
17
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rack-webdav
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.2
4
+ version: 0.4.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Koki Oyatsu
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-05-10 00:00:00.000000000 Z
11
+ date: 2017-05-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: nokogiri
@@ -176,7 +176,6 @@ files:
176
176
  - lib/rack-webdav/remote_file.rb
177
177
  - lib/rack-webdav/resource.rb
178
178
  - lib/rack-webdav/resources/file_resource.rb
179
- - lib/rack-webdav/resources/mongo_resource.rb
180
179
  - lib/rack-webdav/utils.rb
181
180
  - lib/rack-webdav/version.rb
182
181
  - rack-webdav.gemspec
@@ -1,341 +0,0 @@
1
- # coding: utf-8
2
- require 'mime/types'
3
-
4
- module RackWebDAV
5
-
6
- class MongoResource < RackWebDAV::Resource
7
-
8
- # @@logger = Rails.logger
9
-
10
- def initialize(public_path, path, request, response, options)
11
- # 'ASCII-8BIT'で渡される場合があるので'UTF-8'を指定しておく
12
- _force_encoding!(public_path)
13
- _force_encoding!(path)
14
- super(public_path, path, request, response, options)
15
- @filesystem = Mongo::GridFileSystem.new(Mongoid.database)
16
- @collection = Mongoid.database.collection('fs.files')
17
- if options[:bson]
18
- @bson = options[:bson]
19
- elsif path.length <= 1
20
- # ルートの場合 (''の場合と'/'の場合がある)
21
- @bson = {'filename' => root + '/'}
22
- else
23
- # ファイルかディレクトリが、パラメータだけでは判断できない。ので \/? が必要。
24
- # だから、ディレクトリと同名のファイルは、作成できない。
25
- @bson = @collection.find_one({:filename => /^#{Regexp.escape(file_path)}\/?$/}) rescue nil
26
- end
27
- end
28
-
29
- def child(bson)
30
- path = remove(bson['filename'], root)
31
- public_path = @options[:root_uri_path] + path
32
- @options[:bson] = bson
33
- self.class.new(public_path, path, @request, @response, @options)
34
- end
35
-
36
- # If this is a collection, return the child resources.
37
- def children
38
- # Dir[file_path + '/*'].map do |path|
39
- # child File.basename(path)
40
- # end
41
- @collection.find({:filename => /^#{Regexp.escape(@bson['filename'])}[^\/]+\/?$/}).map do |bson|
42
- child bson
43
- end
44
- end
45
-
46
- # Is this resource a collection?
47
- def collection?
48
- # File.directory?(file_path)
49
- @bson && _collection?(@bson['filename'])
50
- end
51
-
52
- # Does this recource exist?
53
- def exist?
54
- # File.exist?(file_path)
55
- @bson
56
- end
57
-
58
- # Return the creation time.
59
- def creation_date
60
- # stat.ctime
61
- @bson['uploadDate'] || Date.new
62
- end
63
-
64
- # Return the time of last modification.
65
- def last_modified
66
- # stat.mtime
67
- @bson['uploadDate'] || Date.new
68
- end
69
-
70
- # Set the time of last modification.
71
- def last_modified=(time)
72
- # File.utime(Time.now, time, file_path)
73
- end
74
-
75
- # Return an Etag, an unique hash value for this resource.
76
- def etag
77
- # sprintf('%x-%x-%x', stat.ino, stat.size, stat.mtime.to_i)
78
- @bson['_id'].to_s
79
- end
80
-
81
- # Return the mime type of this resource.
82
- def content_type
83
- # if stat.directory?
84
- # "text/html"
85
- # else
86
- # mime_type(file_path, DefaultMimeTypes)
87
- # end
88
- @bson['contentType'] || "text/html"
89
- end
90
-
91
- # Return the size in bytes for this resource.
92
- def content_length
93
- # stat.size
94
- @bson['length'] || 0
95
- end
96
-
97
- # HTTP GET request.
98
- #
99
- # Write the content of the resource to the response.body.
100
- def get(request, response)
101
- raise NotFound unless exist?
102
- # if stat.directory?
103
- # response.body = ""
104
- # Rack::Directory.new(root).call(request.env)[2].each do |line|
105
- # response.body << line
106
- # end
107
- # response['Content-Length'] = response.body.size.to_s
108
- # else
109
- # file = Rack::File.new(root)
110
- # response.body = file
111
- # end
112
- if collection?
113
- response.body = "<html>"
114
- response.body << "<h2>" + file_path.html_safe + "</h2>"
115
- children.each do |child|
116
- name = child.file_path.html_safe
117
- path = child.public_path
118
- response.body << "<a href='" + path + "'>" + name + "</a>"
119
- response.body << "</br>"
120
- end
121
- response.body << "</html>"
122
- response['Content-Length'] = response.body.size.to_s
123
- response['Content-Type'] = 'text/html'
124
- else
125
- @filesystem.open(file_path, 'r') do |f|
126
- response.body = f
127
- response['Content-Type'] = @bson['contentType']
128
- end
129
- end
130
-
131
- end
132
-
133
- # HTTP PUT request.
134
- #
135
- # Save the content of the request.body.
136
- def put(request, response)
137
- write(request.body)
138
- Created
139
- end
140
-
141
- # HTTP POST request.
142
- #
143
- # Usually forbidden.
144
- def post(request, response)
145
- raise HTTPStatus::Forbidden
146
- end
147
-
148
- # HTTP DELETE request.
149
- #
150
- # Delete this resource.
151
- def delete
152
- # if stat.directory?
153
- # FileUtils.rm_rf(file_path)
154
- # else
155
- # File.unlink(file_path)
156
- # end
157
- if collection?
158
- @collection.find({:filename => /^#{Regexp.escape(@bson['filename'])}/}).each do |bson|
159
- @collection.remove(bson)
160
- end
161
- else
162
- @collection.remove(@bson)
163
- end
164
- NoContent
165
- end
166
-
167
- # HTTP COPY request.
168
- #
169
- # Copy this resource to given destination resource.
170
- def copy(dest, overwrite = false)
171
- # if(dest.path == path)
172
- # Conflict
173
- # elsif(stat.directory?)
174
- # dest.make_collection
175
- # FileUtils.cp_r("#{file_path}/.", "#{dest.send(:file_path)}/")
176
- # OK
177
- # else
178
- # exists = File.exists?(file_path)
179
- # if(exists && !overwrite)
180
- # PreconditionFailed
181
- # else
182
- # open(file_path, "rb") do |file|
183
- # dest.write(file)
184
- # end
185
- # exists ? NoContent : Created
186
- # end
187
- # end
188
-
189
- # ディレクトリなら末尾に「/」をつける。
190
- # (dstにもともと「/」が付いているかどうか、クライアントに依存している)
191
- # CarotDAV : 「/」が付いていない
192
- # TeamFile : 「/」が付いている
193
- dest.collection! if collection?
194
-
195
- src = @bson['filename']
196
- dst = dest.file_path
197
- exists = nil
198
-
199
- @collection.find({:filename => /^#{Regexp.escape(src)}/}).each do |bson|
200
- src_name = bson['filename']
201
- dst_name = dst + src_name.slice(src.length, src_name.length)
202
-
203
- exists = @collection.find_one({:filename => dst_name}) rescue nil
204
-
205
- return PreconditionFailed if (exists && !overwrite && !collection?)
206
-
207
- @filesystem.open(src_name, "r") do |src|
208
- @filesystem.open(dst_name, "w") do |dst|
209
- dst.write(src) if src.file_length > 0
210
- end
211
- end
212
-
213
- @collection.remove(exists) if exists
214
- end
215
-
216
- collection? ? Created : (exists ? NoContent : Created)
217
- end
218
-
219
- # HTTP MOVE request.
220
- #
221
- # Move this resource to given destination resource.
222
- def move(dest, overwrite = false)
223
-
224
- # ディレクトリなら末尾に「/」をつける。
225
- # (dstにもともと「/」が付いているかどうか、クライアントに依存している)
226
- # CarotDAV : 「/」が付いていない
227
- # TeamFile : 「/」が付いている
228
- dest.collection! if collection?
229
-
230
- src = @bson['filename']
231
- dst = dest.file_path
232
- exists = nil
233
-
234
- @collection.find({:filename => /^#{Regexp.escape(src)}/}).each do |bson|
235
- src_name = bson['filename']
236
- dst_name = dst + src_name.slice(src.length, src_name.length)
237
-
238
- exists = @collection.find_one({:filename => dst_name}) rescue nil
239
-
240
- # http://mongoid.org/docs/persistence/atomic.html
241
- # http://rubydoc.info/github/mongoid/mongoid/master/Mongoid/Collection#update-instance_method
242
- @collection.update({'_id' => bson['_id']}, {'$set' => {'filename' => dst_name}}, :safe => true)
243
-
244
- @collection.remove(exists) if exists
245
- end
246
-
247
- collection? ? Created : (exists ? NoContent : Created)
248
- end
249
-
250
- # HTTP MKCOL request.
251
- #
252
- # Create this resource as collection.
253
- def make_collection
254
- # Dir.mkdir(file_path)
255
- # Created
256
-
257
- # ディレクトリなら末尾に「/」をつける。
258
- # (dstにもともと「/」が付いているかどうか、クライアントに依存している)
259
- # CarotDAV : 「/」が付いていない
260
- # TeamFile : 「/」が付いている
261
- collection!
262
-
263
- bson = @collection.find_one({:filename => file_path}) rescue nil
264
-
265
- # 0バイトのファイルを作成しディレクトリの代わりとする
266
- @filesystem.open(file_path, "w") { |f| } if !bson
267
-
268
- # @@logger.error('make_collection : ' + file_path)
269
-
270
- Created
271
- end
272
-
273
- # Write to this resource from given IO.
274
- def write(io)
275
- # tempfile = "#{file_path}.#{Process.pid}.#{object_id}"
276
- # open(tempfile, "wb") do |file|
277
- # while part = io.read(8192)
278
- # file << part
279
- # end
280
- # end
281
- # File.rename(tempfile, file_path)
282
- # ensure
283
- # File.unlink(tempfile) rescue nil
284
-
285
- # 同名のファイルができないように
286
- bson = @collection.find_one({:filename => file_path}) rescue nil
287
-
288
- @filesystem.open(file_path, "w", :content_type => _content_type(file_path)) { |f| f.write(io) }
289
-
290
- # 同名のファイルができないように
291
- @collection.remove(bson) if bson
292
-
293
- end
294
-
295
- protected
296
-
297
- def file_path
298
- root + path
299
- end
300
-
301
- # ファイル名の末尾に「/」を付加してディレクトリ(コレクション)とする
302
- def collection!
303
- path << '/' if !_collection?(path)
304
- end
305
-
306
- private
307
-
308
- def _content_type(filename)
309
- MIME::Types.type_for(filename).first.to_s || 'text/html'
310
- end
311
-
312
- def authenticate(user, pass)
313
- if(options[:username])
314
- options[:username] == user && options[:password] == pass
315
- else
316
- true
317
- end
318
- end
319
-
320
- # path1の先頭からpath2を取り除く
321
- def remove(path1, path2)
322
- path1.slice(path2.length, path1.length)
323
- end
324
-
325
- def root
326
- @options[:root]
327
- end
328
-
329
- # ファイル名の末尾が「/」のファイルをディレクトリ(コレクション)とする
330
- def _collection?(path)
331
- path && path[-1].chr == '/'
332
- end
333
-
334
- # 'ASCII-8BIT'で渡される場合があるので'UTF-8'を指定しておく
335
- def _force_encoding!(str)
336
- str.force_encoding('UTF-8')
337
- end
338
-
339
- end
340
-
341
- end