webdav.rb 0.2.1

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 11276a2f0788ae73a2174746bc48900ef91355d946f0af27b7bd36b00674e8b8
4
+ data.tar.gz: '08912af75301ee512c4d9c08b52f836e94f055e255762890a5ea03b604cd379c'
5
+ SHA512:
6
+ metadata.gz: 3997386c5837b0070a49b5a01901e3cc54e2bca7860668375a863753b2b7a12b7ccebac405d327b4349de6a27b3c1b5256eb480b072b3c6ed5bc58ff7d5435c8
7
+ data.tar.gz: 6a74951f911f9ba0d8e9463f4a17da9bb0348e761728a23fa3e9f93d5afb9935ba4923b1f3cf5407193dcadc04385159b3328bbcea213e56d83ad6b35d33cb22
data/CHANGELOG ADDED
@@ -0,0 +1,67 @@
1
+ # CHANGELOG
2
+
3
+ ## 20260821
4
+
5
+ 0.2.1: + webdav.rb.gemspec, so that the gem resolves under both names.
6
+
7
+ 1. + webdav.rb.gemspec: the same code published as webdav.rb, caldav being published as caldav.rb. rubygems has no notion of an alias, so the two are separate gems which must be kept at the same version by hand.
8
+ 2. ~ webdav.gemspec: + a comment naming the second gemspec, this being the gem which already exists on rubygems.
9
+ 3. gem-publish 0.2.0 is what makes this work: gem build and gem push each take one file, so the earlier `gem build *.gemspec` built whichever the shell listed first and webdav.rb was never built at all.
10
+ 4. ~ WebDAV::VERSION: /0.2.0/0.2.1/
11
+
12
+ ## 20260522
13
+
14
+ 0.2.0: Rewrite MultiStatus#parse; align dependencies with http.rb 1.0.0; document WebDAV concepts.
15
+
16
+ 1. ~ WebDAV::MultiStatus#parse: Walk propstat blocks individually; preserve per-propstat status; detect response-level status (for COPY/MOVE/DELETE); preserve namespace URIs on properties. Now private; resources remains the public accessor.
17
+ 2. ~ WebDAV::MultiStatus resource shape: Properties move from flat hash keyed by local name to nested hash keyed by namespace URI then local name. Resources now have :propstats (array of {properties:, status:}) and :status (response-level, may be nil) instead of a single :properties and :status pair.
18
+ 3. ~ test/WebDAV/MultiStatus_test.rb: Update existing tests for new shape; + tests for per-propstat status, response-level status, namespace preservation, and same-local-name collision across namespaces.
19
+ 4. ~ README.md: Document new MultiStatus#resources shape, with paired wire-XML and parsed-Ruby examples for both PROPFIND and collection-cascade Multi-Status; + WebDAV::Response wire example; + Concepts section (properties vs content, collections and the trailing slash, why 207 Multi-Status exists, namespaces).
20
+ 5. ~ webdav.gemspec: required_ruby_version />= 2.7/>= 3.2/; http.rb dependency /[]/['~> 1.0']/; minitest pin /'~> 5.27'/'~> 6.0'/; + minitest-mock (minitest 6 split it out).
21
+ 6. ~ WebDAV::VERSION: /0.1.2/0.2.0/
22
+ 7. ~ CHANGELOG: + 0.2.0 entry
23
+
24
+ ## 20260520
25
+
26
+ 0.1.2: Fix Response#success? to return true only for 2xx responses.
27
+
28
+ 1. ~ WebDAV::Response#success?: Was `code < 400` which incorrectly reported 1xx and 3xx as successful. Now `code >= 200 && code < 300`.
29
+ 2. ~ test/WebDAV/Response_test.rb: + tests for 3xx and 5xx behaviour.
30
+ 3. ~ webdav.gemspec: Remove http.rb version constraint (was `>= 0.18.0`; the original rationale, Net::HTTP::Report being in http.rb 0.18.0, was reverted in webdav 0.1.1 and http.rb 0.18.1).
31
+ 4. ~ webdav.gemspec: + rexml runtime dependency (default gem on Ruby 3+; required by WebDAV::MultiStatus).
32
+ 5. ~ webdav.gemspec: Pin minitest development dependency to `~> 5.27` (Minitest 6 split `minitest/mock` into a separate gem).
33
+ 6. ~ WebDAV::VERSION: /0.1.1/0.1.2/
34
+ 7. ~ CHANGELOG: + 0.1.2 entry
35
+
36
+ ## 20260508
37
+
38
+ 0.1.1: Reinstate Net::HTTP::Report.
39
+
40
+ 1. + lib/Net/HTTP/Report.rb: Reinstated after checking after it was incorrectly removed in 0.1.0. REPORT is from RFC 3253 (WebDAV versioning) and is not a core HTTP method.
41
+ 2. ~ lib/webdav.rb: + require_relative './Net/HTTP/Report'
42
+ 3. + test/Net/HTTP/Report_test.rb
43
+ 4. ~ WebDAV::VERSION: /0.1.0/0.1.1/
44
+ 5. ~ CHANGELOG: + 0.1.1
45
+
46
+ ## 20260508
47
+
48
+ 0.1.0: Remove Net::HTTP::Report; now provided by http.rb 0.18.0.
49
+
50
+ 1. - lib/Net/HTTP/Report.rb: Now provided by http.rb 0.18.0.
51
+ 2. ~ lib/webdav.rb: - require_relative './Net/HTTP/Report'
52
+ 3. ~ webdav.gemspec: Pin http.rb dependency to >= 0.18.0; + development dependencies; + CHANGELOG and Rakefile to files; reformat.
53
+ 4. ~ .gitignore: Previously empty. Now with a default and comprehensive set of defaults.
54
+ 5. ~ WebDAV::VERSION: /0.0.0/0.1.0/
55
+ 6. ~ lib/webdav.rb: - require 'rexml/document'
56
+ 7. ~ lib/WebDAV/MultiStatus.rb: + require 'rexml/document'
57
+
58
+ ## 20260325
59
+
60
+ 0.0.0: Initial release.
61
+
62
+ 1. + WebDAV client with 16 verbs
63
+ a. 8 WebDAV-specific verbs: copy, lock, mkcol, move, propfind, proppatch, report, unlock.
64
+ b. 8 standard HTTP verbs: delete, get, head, options, patch, post, put, and trace.
65
+ 2. + WebDAV::Response response object
66
+ 3. + WebDAV::MultiStatus response object
67
+ 4. + WebDAV::Error for 4xx/5xx responses
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 thoran
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,305 @@
1
+ # webdav
2
+
3
+ A WebDAV client library for Ruby.
4
+
5
+
6
+ ## Installation
7
+
8
+ ```
9
+ gem install webdav
10
+ ```
11
+
12
+ Or in your Gemfile:
13
+
14
+ ```ruby
15
+ gem 'webdav'
16
+ ```
17
+
18
+
19
+ ## Usage
20
+
21
+ ```ruby
22
+ require 'webdav'
23
+
24
+ dav = WebDAV.new('https://dav.example.com/files/', username: 'user', password: 'pass')
25
+ ```
26
+
27
+ ### Discovering resources
28
+
29
+ ```ruby
30
+ response = dav.propfind('/', depth: '1')
31
+ response.resources.each do |resource|
32
+ puts resource[:href]
33
+ resource[:propstats].each do |propstat|
34
+ puts propstat[:status]
35
+ propstat[:properties].each do |namespace, properties|
36
+ properties.each do |name, value|
37
+ puts " #{namespace} #{name} = #{value}"
38
+ end
39
+ end
40
+ end
41
+ end
42
+ ```
43
+
44
+ ### Reading a resource
45
+
46
+ ```ruby
47
+ response = dav.get('/documents/report.txt')
48
+ puts response.body
49
+ ```
50
+
51
+ ### Writing a resource
52
+
53
+ ```ruby
54
+ dav.put('/documents/report.txt', body: 'Hello, world.', content_type: 'text/plain')
55
+ ```
56
+
57
+ ### Deleting a resource
58
+
59
+ ```ruby
60
+ dav.delete('/documents/report.txt')
61
+ ```
62
+
63
+ ### Creating a collection
64
+
65
+ ```ruby
66
+ dav.mkcol('/documents/archive/')
67
+ ```
68
+
69
+ ### Copying and moving
70
+
71
+ ```ruby
72
+ dav.copy('/documents/report.txt', to: '/archive/report.txt')
73
+ dav.move('/documents/draft.txt', to: '/documents/final.txt')
74
+ ```
75
+
76
+ ### Locking and unlocking
77
+
78
+ ```ruby
79
+ lock_body = <<~XML
80
+ <?xml version="1.0" encoding="UTF-8"?>
81
+ <d:lockinfo xmlns:d="DAV:">
82
+ <d:lockscope><d:exclusive/></d:lockscope>
83
+ <d:locktype><d:write/></d:locktype>
84
+ </d:lockinfo>
85
+ XML
86
+
87
+ response = dav.lock('/documents/report.txt', body: lock_body)
88
+ # ...
89
+ dav.unlock('/documents/report.txt', token: 'urn:uuid:...')
90
+ ```
91
+
92
+ ### Reporting
93
+
94
+ ```ruby
95
+ response = dav.report('/calendars/user/', body: report_xml, depth: '1')
96
+ response.resources.each do |resource|
97
+ puts resource[:href]
98
+ end
99
+ ```
100
+
101
+
102
+ ## Concepts
103
+
104
+ WebDAV extends HTTP with a few ideas that don't have direct REST analogues. The ones below explain why the API is shaped the way it is.
105
+
106
+ ### Properties vs content
107
+
108
+ A WebDAV resource has two faces. **Content** is what GET returns — the bytes of the file. **Properties** are metadata associated with the resource: display name, creation date, lock state, content type, and any custom properties the server defines. The same URL identifies both, but different verbs reach them — GET/PUT for content, PROPFIND/PROPPATCH for properties.
109
+
110
+ ### Collections and the trailing slash
111
+
112
+ A **collection** is WebDAV's directory: a resource that contains other resources. MKCOL creates one. By convention, collection URLs end in `/` and ordinary resources don't; servers that care about the distinction will redirect or 404 if you get it wrong. The distinction matters because COPY, MOVE, and DELETE on a collection cascade to its children — which is also why those verbs can return 207 Multi-Status when children succeed and fail independently.
113
+
114
+ ### Why 207 Multi-Status exists
115
+
116
+ HTTP assumes one request maps to one status. WebDAV breaks that assumption: PROPFIND on a folder asks about many resources at once; COPY of a tree may succeed on some children and fail on others. The 207 Multi-Status response code says "the request touched many things; here are per-thing outcomes." The XML body carries one `<d:response>` per affected resource. This gem returns those as `WebDAV::MultiStatus`; the `resources` accessor exposes the per-resource detail (see [Responses](#responses)).
117
+
118
+ ### Namespaces
119
+
120
+ WebDAV properties are XML elements, and XML elements belong to namespaces. The core RFC 4918 properties live in the `DAV:` namespace. Extensions — CalDAV (`urn:ietf:params:xml:ns:caldav`), CardDAV, Exchange, ownCloud, custom server vocabularies — each define their own. Properties from different namespaces can share local names (`<d:displayname>` and `<x:displayname>` are different properties), so the parser preserves namespace URIs as the outer key in the properties hash.
121
+
122
+
123
+ ## Methods
124
+
125
+ WebDAV extends HTTP with additional methods for distributed authoring. This gem provides all the methods defined in RFC 4918 ("HTTP Extensions for Web Distributed Authoring and Versioning") and the REPORT method from RFC 3253 ("Versioning Extensions to WebDAV"), which is essential for CalDAV and CardDAV queries.
126
+
127
+ Ruby's standard library includes request classes for the RFC 4918 methods (Propfind, Proppatch, Mkcol, Copy, Move, Lock, Unlock) but not for REPORT. This gem defines `Net::HTTP::Report` to fill that gap.
128
+
129
+ These methods are not provided by the `http.rb` gem, which deliberately limits itself to the core HTTP methods defined in RFC 9110 ("HTTP Semantics") and RFC 5789 ("PATCH Method for HTTP").
130
+
131
+ ### Properties (RFC 4918)
132
+
133
+ - `propfind(path, body:, depth:)` — retrieve properties from a resource
134
+ - `proppatch(path, body:)` — set or remove properties on a resource
135
+
136
+ ### Versioning (RFC 3253)
137
+
138
+ - `report(path, body:, depth:)` — query for information about a resource; used by CalDAV and CardDAV
139
+
140
+ ### Collections (RFC 4918)
141
+
142
+ - `mkcol(path)` — create a new collection (directory)
143
+
144
+ ### Namespace (RFC 4918)
145
+
146
+ - `copy(path, to:, depth:, overwrite:)` — copy a resource
147
+ - `move(path, to:, overwrite:)` — move a resource
148
+
149
+ ### Locking (RFC 4918)
150
+
151
+ - `lock(path, body:)` — lock a resource
152
+ - `unlock(path, token:)` — unlock a resource
153
+
154
+ ### Standard HTTP
155
+
156
+ - `get(path)`
157
+ - `head(path)`
158
+ - `post(path, body:, content_type:)`
159
+ - `put(path, body:, content_type:)`
160
+ - `patch(path, body:, content_type:)`
161
+ - `delete(path)`
162
+ - `options(path)`
163
+ - `trace(path)`
164
+
165
+
166
+ ## Responses
167
+
168
+ All methods return either a `WebDAV::Response` or a `WebDAV::MultiStatus`.
169
+
170
+ `WebDAV::Response` provides
171
+
172
+ - `code`
173
+ - `message`
174
+ - `headers`
175
+ - `body`
176
+ - `etag`
177
+ - `content_type`
178
+ - `success?`
179
+
180
+ A `GET` response on the wire:
181
+
182
+ ```
183
+ HTTP/1.1 200 OK
184
+ Content-Type: text/plain
185
+ Content-Length: 13
186
+ ETag: "5d41402a"
187
+
188
+ Hello, world.
189
+ ```
190
+
191
+ Parses to:
192
+
193
+ ```ruby
194
+ response.code # => 200
195
+ response.message # => "OK"
196
+ response.body # => "Hello, world."
197
+ response.etag # => "\"5d41402a\""
198
+ response.content_type # => "text/plain"
199
+ response.success? # => true
200
+ ```
201
+
202
+ `WebDAV::MultiStatus` additionally provides:
203
+
204
+ - `resources` — an array of hashes, each with:
205
+ - `href` — the resource URL
206
+ - `propstats` — an array of `{properties:, status:}` hashes (PROPFIND / PROPPATCH / REPORT). May be empty when the response carries a response-level status instead.
207
+ - `status` — the response-level status string (COPY / MOVE / DELETE). `nil` when the response has propstats instead.
208
+
209
+ Within a propstat, `properties` is a nested hash keyed first by XML namespace URI, then by local name. For example, a CalDAV calendar property appears as `propstat[:properties]['urn:ietf:params:xml:ns:caldav']['calendar-data']` and a DAV property as `propstat[:properties]['DAV:']['getetag']`. Keeping the namespace explicit prevents collisions between properties from different namespaces that share a local name.
210
+
211
+ A PROPFIND response — properties grouped by namespace, status per propstat, response-level status `nil`. The wire XML:
212
+
213
+ ```xml
214
+ <?xml version="1.0" encoding="UTF-8"?>
215
+ <d:multistatus xmlns:d="DAV:" xmlns:c="urn:ietf:params:xml:ns:caldav">
216
+ <d:response>
217
+ <d:href>/calendar/event.ics</d:href>
218
+ <d:propstat>
219
+ <d:prop>
220
+ <d:getetag>"abc123"</d:getetag>
221
+ <c:calendar-data>BEGIN:VCALENDAR...</c:calendar-data>
222
+ </d:prop>
223
+ <d:status>HTTP/1.1 200 OK</d:status>
224
+ </d:propstat>
225
+ <d:propstat>
226
+ <d:prop>
227
+ <d:getctag/>
228
+ </d:prop>
229
+ <d:status>HTTP/1.1 404 Not Found</d:status>
230
+ </d:propstat>
231
+ </d:response>
232
+ </d:multistatus>
233
+ ```
234
+
235
+ Parses to:
236
+
237
+ ```ruby
238
+ [
239
+ {
240
+ href: '/calendar/event.ics',
241
+ propstats: [
242
+ {
243
+ properties: {
244
+ 'DAV:' => {'getetag' => '"abc123"'},
245
+ 'urn:ietf:params:xml:ns:caldav' => {'calendar-data' => 'BEGIN:VCALENDAR...'}
246
+ },
247
+ status: 'HTTP/1.1 200 OK'
248
+ },
249
+ {
250
+ properties: {'DAV:' => {'getctag' => ''}},
251
+ status: 'HTTP/1.1 404 Not Found'
252
+ }
253
+ ],
254
+ status: nil
255
+ }
256
+ ]
257
+ ```
258
+
259
+ A COPY / MOVE / DELETE on a collection where a child resource failed — the server returns 207 Multi-Status with one `<d:response>` per affected child, each carrying a response-level status rather than propstats. Single-resource lifecycle operations don't go through this path; they return a plain `WebDAV::Response` with the status code as the whole story. The wire XML:
260
+
261
+ ```xml
262
+ <?xml version="1.0" encoding="UTF-8"?>
263
+ <d:multistatus xmlns:d="DAV:">
264
+ <d:response>
265
+ <d:href>/dir/file.txt</d:href>
266
+ <d:status>HTTP/1.1 403 Forbidden</d:status>
267
+ </d:response>
268
+ </d:multistatus>
269
+ ```
270
+
271
+ Parses to:
272
+
273
+ ```ruby
274
+ [
275
+ {
276
+ href: '/dir/file.txt',
277
+ propstats: [],
278
+ status: 'HTTP/1.1 403 Forbidden'
279
+ }
280
+ ]
281
+ ```
282
+
283
+
284
+ ## Errors
285
+
286
+ Responses with status >= 400 raise `WebDAV::Error`, which has `code`, `message`, and `body`.
287
+
288
+
289
+ ## Dependencies
290
+
291
+ - [http.rb](https://github.com/thoran/http.rb)
292
+
293
+
294
+ ## Contributing
295
+
296
+ 1. Fork it [https://github.com/thoran/webdav/fork](https://github.com/thoran/webdav/fork)
297
+ 2. Create your feature branch (git checkout -b my-new-feature)
298
+ 3. Commit your changes (git commit -am 'Add some feature')
299
+ 4. Push to the branch (git push origin my-new-feature)
300
+ 5. Create a new pull request
301
+
302
+
303
+ ## Licence
304
+
305
+ MIT
data/Rakefile ADDED
@@ -0,0 +1,9 @@
1
+ # Rakefile
2
+
3
+ require 'rake/testtask'
4
+
5
+ Rake::TestTask.new do |t|
6
+ t.test_files = FileList['test/**/*_test.rb']
7
+ end
8
+
9
+ task default: :test
@@ -0,0 +1,16 @@
1
+ # Net/HTTP/Report.rb
2
+ # Net::HTTP::Report
3
+
4
+ require 'net/http'
5
+
6
+ module Net
7
+ class HTTP
8
+ class Report < Net::HTTPRequest
9
+
10
+ METHOD = 'REPORT'
11
+ REQUEST_HAS_BODY = true
12
+ RESPONSE_HAS_BODY = true
13
+
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,7 @@
1
+ # String/to_const.rb
2
+ # String#to_const
3
+
4
+ # 20250819
5
+ # 0.2.0
6
+
7
+ require 'Thoran/String/ToConst/to_const'
@@ -0,0 +1,28 @@
1
+ # Thoran/Array/AllButFirst/all_but_first.rb
2
+ # Thoran::Array::AllButFirst#all_but_first
3
+
4
+ # 20141223
5
+ # 0.1.0
6
+
7
+ # Description: This returns a copy of the receiving array with the first element removed.
8
+
9
+ # Changes:
10
+ # 1. + Thoran namespace.
11
+
12
+ require 'Thoran/Array/FirstX/firstX'
13
+
14
+ module Thoran
15
+ module Array
16
+ module AllButFirst
17
+
18
+ def all_but_first
19
+ d = self.dup
20
+ d.first!
21
+ d
22
+ end
23
+
24
+ end
25
+ end
26
+ end
27
+
28
+ Array.send(:include, Thoran::Array::AllButFirst)
@@ -0,0 +1,32 @@
1
+ # Thoran/Array/FirstX/firstX.rb
2
+ # Thoran::Array::FirstX#first!
3
+
4
+ # 20180804
5
+ # 0.3.3
6
+
7
+ # Description: Sometimes it makes more sense to treat arrays this way.
8
+
9
+ # Changes since 0.2:
10
+ # 1. Added the original version 0.1.0 of the implementation to the later 0.1.0!
11
+ # 0/1
12
+ # 2. Switched the tests to spec-style.
13
+ # 1/2
14
+ # 3. Added a test for the state of the array afterward, since this is meant to be an in place change.
15
+ # 2/3
16
+ # 4. Added tests for the extended functionality introduced in the first version 0.1.0.
17
+
18
+ module Thoran
19
+ module Array
20
+ module FirstX
21
+
22
+ def first!(n = 1)
23
+ return_value = []
24
+ n.times{return_value << self.shift}
25
+ return_value.size == 1 ? return_value[0] : return_value
26
+ end
27
+
28
+ end
29
+ end
30
+ end
31
+
32
+ Array.send(:include, Thoran::Array::FirstX)
@@ -0,0 +1,47 @@
1
+ # Thoran/String/ToConst/to_const.rb
2
+ # Thoran::String::ToConst#to_const
3
+
4
+ # 20141223
5
+ # 0.2.0
6
+
7
+ # Description: This takes a string and returns a constant, with unlimited namespacing.
8
+
9
+ # History: Derived from Object#to_const 0.3.0, and superceding Object#to_const.
10
+
11
+ # Changes:
12
+ # 1. + Thoran namespace.
13
+
14
+ # Todo:
15
+ # 1. This only works for two levels of constants. Three and you're stuffed. So, this needs to be recursive...
16
+ # Done iteratively as of 0.1.0.
17
+ # 2. Make this work for symbols. However, this will only work if there's no namespacing. ie. :A is OK, but :A::B is not.
18
+
19
+ # Discussion:
20
+ # 1. Should this go separately into classes for which ::const_get will work and be removed from Object? Done as of 0.1.0.
21
+
22
+ require 'Thoran/Array/AllButFirst/all_but_first'
23
+
24
+ module Thoran
25
+ module String
26
+ module ToConst
27
+
28
+ def to_const
29
+ if self =~ /::/
30
+ constants = self.split('::')
31
+ constant = Object.const_get(constants.first)
32
+ constants = constants.all_but_first
33
+ until constants.empty? do
34
+ constant = constant.const_get(constants.shift)
35
+ end
36
+ else
37
+ constant = Object.const_get(self)
38
+ end
39
+ constant
40
+ end
41
+ alias_method :to_constant, :to_const
42
+
43
+ end
44
+ end
45
+ end
46
+
47
+ String.send(:include, Thoran::String::ToConst)
@@ -0,0 +1,17 @@
1
+ # WebDAV/Error.rb
2
+ # WebDAV::Error.rb
3
+
4
+ class WebDAV
5
+ class Error < StandardError
6
+ attr_reader :code, :message, :body
7
+
8
+ private
9
+
10
+ def initialize(response)
11
+ @code = response.code.to_i
12
+ @message = response.message
13
+ @body = response.body
14
+ super("HTTP #{@code}: #{@message}")
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,56 @@
1
+ # WebDAV/MultiStatus.rb
2
+ # WebDAV::MultiStatus
3
+
4
+ require 'rexml/document'
5
+
6
+ require_relative './Response'
7
+
8
+ class WebDAV
9
+ class MultiStatus < Response
10
+ attr_reader :resources
11
+
12
+ private
13
+
14
+ def initialize(response)
15
+ super
16
+ @resources = parse
17
+ end
18
+
19
+ def parse
20
+ doc = REXML::Document.new(body)
21
+ doc.elements.collect('//d:response'){|response_element| parse_response(response_element)}
22
+ end
23
+
24
+ def parse_response(response_element)
25
+ {
26
+ href: response_element.elements['d:href']&.text,
27
+ propstats: parse_propstats(response_element),
28
+ status: parse_response_status(response_element)
29
+ }
30
+ end
31
+
32
+ def parse_propstats(response_element)
33
+ response_element.elements.collect('d:propstat'){|propstat_element| parse_propstat(propstat_element)}
34
+ end
35
+
36
+ def parse_propstat(propstat_element)
37
+ {
38
+ properties: parse_properties(propstat_element.elements['d:prop']),
39
+ status: propstat_element.elements['d:status']&.text
40
+ }
41
+ end
42
+
43
+ def parse_properties(prop_element)
44
+ return {} unless prop_element
45
+ prop_element.elements.to_a.each_with_object({}) do |property_element, result|
46
+ result[property_element.namespace] ||= {}
47
+ result[property_element.namespace][property_element.name] = property_element.text || property_element.to_s
48
+ end
49
+ end
50
+
51
+ def parse_response_status(response_element)
52
+ return nil if response_element.elements['d:propstat']
53
+ response_element.elements['d:status']&.text
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,29 @@
1
+ # WebDAV/Response.rb
2
+ # WebDAV::Response.rb
3
+
4
+ class WebDAV
5
+ class Response
6
+ attr_reader :code, :message, :headers, :body
7
+
8
+ def success?
9
+ code >= 200 && code < 300
10
+ end
11
+
12
+ def etag
13
+ headers['ETag']
14
+ end
15
+
16
+ def content_type
17
+ headers['Content-Type']
18
+ end
19
+
20
+ private
21
+
22
+ def initialize(response)
23
+ @code = response.code.to_i
24
+ @message = response.message
25
+ @headers = response
26
+ @body = response.body
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,6 @@
1
+ # WebDAV/VERSION.rb
2
+ # WebDAV::VERSION
3
+
4
+ class WebDAV
5
+ VERSION = '0.2.1'
6
+ end