xcapclient 1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,584 @@
1
+ module XCAPClient
2
+
3
+
4
+ # The base class of the library. A program using this library must instantiate it.
5
+ #
6
+ # === Common notes for methods <tt>get*</tt>, <tt>put*</tt> and <tt>delete*</tt>
7
+ #
8
+ # ==== Shared parameters
9
+ #
10
+ # *_auid_*: String, the auid of the application. Example:"pres-rules".
11
+ #
12
+ # *_document_*: nil by default. It can be:
13
+ # * String, so the application must contain a document called as it.
14
+ # * XCAPClient::Document object.
15
+ # * nil, so the application default document is selected.
16
+ #
17
+ # *_check_etag_*: true by default. If true the client adds the header "If-None-Match" or "If-Match" to the HTTP request containing the last ETag received.
18
+ #
19
+ # *_selector_*: Document/node selector. The path that identifies the XML document or node within the XCAP root URL. It's automatically converted to ASCII and percent encoded if needed. Example:
20
+ #
21
+ # '/cp:ruleset/cp:rule/cp:conditions/cp:identity/cp:one[@id="sip:alice@example.org"]'
22
+ #
23
+ # *_xml_namespaces_*: nil by default. It's a hash containing the prefixes and namespaces used in the query. Example:
24
+ #
25
+ # {"pr"=>"urn:ietf:params:xml:ns:pres-rules", "cp"=>"urn:ietf:params:xml:ns:common-policy"}
26
+ #
27
+ # ==== Exceptions
28
+ #
29
+ # If these methods receive a non HTTP 2XX response they generate an exception. Check ERRORS.rdoc for detailed info.
30
+ #
31
+ # ==== Access to the full response
32
+ #
33
+ # In any case, the HTTP response is stored in the document _last_response_ attribute as an HTTP::Message[http://dev.ctor.org/doc/httpclient/] object. To get it:
34
+ #
35
+ # @xcapclient.application("pres-rules").document.last_response
36
+ #
37
+ class Client
38
+
39
+
40
+ USER_AGENT = "Ruby-XCAPClient"
41
+ COMMON_HEADERS = {
42
+ "User-Agent" => "#{USER_AGENT}/#{VERSION}",
43
+ "Connection" => "close"
44
+ }
45
+ GLOBAL_XUI = "global"
46
+ XCAP_CAPS_XMLNS = "urn:ietf:params:xml:ns:xcap-caps"
47
+ HTTP_TIMEOUT = 6
48
+ CONF_PARAMETERS = [:xcap_root, :user, :auth_user, :password, :ssl_verify_cert]
49
+
50
+
51
+ attr_reader :xcap_root, :user, :auth_user, :password, :ssl_verify_cert
52
+
53
+
54
+ # Create a new XCAP client. It requires two parameters:
55
+ #
56
+ # * _conf_: A hash containing settings related to the server:
57
+ # * _xcap_root_: The URL where the documents hold.
58
+ # * _user_: The client username. Depending on the server it could look like "sip:alice@domain.org", "alice@domain.org" or "alice".
59
+ # * _auth_user_: Username for authentication (if required).
60
+ # * _ssl_verify_cert_: If true and the server uses SSL, the certificate is inspected (expiration time, signature...).
61
+ #
62
+ # * _applications_: A hash of hashes containing each XCAP application available for the client. Each application is an entry of the hash containing a key whose value is the "auid" of the application and whose value is a hast with the following fields:
63
+ # * _xmlns_: The XML namespace uri of the application.
64
+ # * _document_name_: The name of the default document for this application ("index" if not set).
65
+ # * _scope_: Can be :user or :global (:user if not set).
66
+ # * _:user_: Each user has his own document(s) for this application.
67
+ # * _:global_: The document(s) is shared for all the users.
68
+ #
69
+ # Example:
70
+ # xcap_conf = {
71
+ # :xcap_root => "https://xcap.domain.org/xcap-root",
72
+ # :user => "sip:alice@domain.org",
73
+ # :auth_user => "alice",
74
+ # :password => "1234",
75
+ # :ssl_verify_cert => false
76
+ # }
77
+ # xcap_apps = {
78
+ # "pres-rules" => {
79
+ # :xmlns => "urn:ietf:params:xml:ns:pres-rules",
80
+ # :mime_type => "application/auth-policy+xml",
81
+ # :document_name => "index",
82
+ # :scope => :user
83
+ # },
84
+ # "rls-services" => {
85
+ # :xmlns => "urn:ietf:params:xml:ns:rls-services",
86
+ # :mime_type => "application/rls-services+xml",
87
+ # :document_name => "index",
88
+ # :scope => :user
89
+ # }
90
+ # }
91
+ #
92
+ # @client = Client.new(xcap_conf, xcap_apps)
93
+ #
94
+ # A XCAP application called "xcap-caps" is automatically added to the list of applications of the new client. This application is defined in the {RFC 4825}[http://tools.ietf.org/html/rfc4825].
95
+ #
96
+ def initialize(conf={}, applications={})
97
+
98
+ # Check conf hash.
99
+ raise ConfigError, "`conf' must be a hash" unless (Hash === conf)
100
+
101
+ # Check non existing parameter names.
102
+ conf.each_key do |key|
103
+ raise ConfigError, "Uknown parameter name '#{key}' in `conf' hash" unless CONF_PARAMETERS.include?(key)
104
+ end
105
+
106
+ # Check xcap_root parameter.
107
+ @xcap_root = URI.parse(conf[:xcap_root].to_s)
108
+ raise ConfigError, "`xcap_root' must be http or https URI" unless [URI::HTTP, URI::HTTPS].include?(@xcap_root.class)
109
+
110
+ # Check user.
111
+ @user = conf[:user].freeze
112
+ raise ConfigError, "`user' must be a non empty string" unless (String === @user && ! @user.empty?)
113
+
114
+ @auth_user = conf[:auth_user].freeze || @user
115
+ @password = conf[:password].freeze
116
+
117
+ # Initialize the HTTP client.
118
+ @http_client = HTTPClient.new
119
+ @http_client.set_auth(@xcap_root, @auth_user, @password)
120
+ @http_client.protocol_retry_count = 3 ### TODO: Set an appropiate value (min 2 for 401).
121
+ @http_client.connect_timeout = HTTP_TIMEOUT
122
+ @http_client.send_timeout = HTTP_TIMEOUT
123
+ @http_client.receive_timeout = HTTP_TIMEOUT
124
+
125
+ @xcap_root.freeze # Freeze now as it has been modified in @http_client.set_auth.
126
+
127
+ # Check ssl_verify_cert parameter.
128
+ if URI::HTTPS === @xcap_root
129
+ @ssl_verify_cert = conf[:ssl_verify_cert] || false
130
+ raise ConfigError, "`ssl_verify_cert' must be true or false" unless [TrueClass, FalseClass].include?(@ssl_verify_cert.class)
131
+ @http_client.ssl_config.verify_mode = ( @ssl_verify_cert ? 3 : 0 )
132
+ end
133
+
134
+ # Generate applications.
135
+ @applications = {}
136
+
137
+ # Add the "xcap-caps" application.
138
+ @applications["xcap-caps"] = Application.new("xcap-caps", {
139
+ :xmlns => "urn:ietf:params:xml:ns:xcap-caps",
140
+ :mime_type => "application/xcap-caps+xml",
141
+ :scope => :global,
142
+ :document_name => "index"
143
+ })
144
+
145
+ # Add custom applications.
146
+ applications.each do |auid, data|
147
+ @applications[auid] = Application.new(auid, data)
148
+ end
149
+
150
+ @applications.freeze
151
+
152
+ end # def initialize
153
+
154
+
155
+ # Returns the XCAPClient::Application whose auid mathes the _auid_ parameter.
156
+ #
157
+ # Example:
158
+ #
159
+ # @xcapclient.application("pres-rules")
160
+ #
161
+ def application(auid)
162
+ @applications[auid]
163
+ end
164
+
165
+
166
+ # Returns an Array with all the applications configured in the client.
167
+ def applications
168
+ @applications
169
+ end
170
+
171
+
172
+ # Fetch a document from the server.
173
+ #
174
+ # Example:
175
+ #
176
+ # @xcapclient.get("pres-rules")
177
+ #
178
+ # If success:
179
+ # * The method returns true.
180
+ # * Received XML plain document is stored in <tt>@xcapclient.application("pres-rules").document.plain</tt> (the default document).
181
+ # * Received ETag is stored in <tt>@xcapclient.application("pres-rules").document.etag</tt>.
182
+ #
183
+ def get(auid, document=nil, check_etag=true)
184
+
185
+ application, document = get_app_doc(auid, document)
186
+ response = send_request(:get, application, document, nil, nil, nil, nil, check_etag)
187
+
188
+ # Check Content-Type.
189
+ check_content_type(response, application.mime_type)
190
+
191
+ # Store the plain document.
192
+ document.plain = response.body.content
193
+
194
+ # Update ETag.
195
+ document.etag = response.header["ETag"].first
196
+
197
+ return true
198
+
199
+ end
200
+
201
+
202
+ # Create/replace a document in the server.
203
+ #
204
+ # Example:
205
+ #
206
+ # @xcapclient.put("pres-rules")
207
+ #
208
+ # If success:
209
+ # * The method returns true.
210
+ # * Local plain document in <tt>@xcapclient.application("pres-rules").document.plain</tt> is uploaded to the server.
211
+ # * Received ETag is stored in <tt>@xcapclient.application("pres-rules").document.etag</tt>.
212
+ #
213
+ def put(auid, document=nil, check_etag=true)
214
+
215
+ application, document = get_app_doc(auid, document)
216
+ response = send_request(:put, application, document, nil, nil, nil, application.mime_type, check_etag)
217
+
218
+ # Update ETag.
219
+ document.etag = response.header["ETag"].first
220
+
221
+ return true
222
+
223
+ end
224
+
225
+
226
+ # Delete a document in the server.
227
+ #
228
+ # Example:
229
+ #
230
+ # @xcapclient.delete("pres-rules")
231
+ #
232
+ # If success:
233
+ # * The method returns true.
234
+ # * Local plain document and ETag are deleted.
235
+ #
236
+ def delete(auid, document=nil, check_etag=true)
237
+
238
+ application, document = get_app_doc(auid, document)
239
+ response = send_request(:delete, application, document, nil, nil, nil, nil, check_etag)
240
+
241
+ # Reset the local document.
242
+ document.plain = nil
243
+ document.etag = nil
244
+
245
+ return true
246
+
247
+ end
248
+
249
+
250
+ # Fetch an element from the document stored in the server.
251
+ #
252
+ # Example:
253
+ #
254
+ # @xcapclient.get_element("pres-rules", nil,
255
+ # '/cp:ruleset/cp:rule[@id="pres_whitelist"]/cp:conditions/cp:identity/cp:one[@id="sip:alice@example.org"]',
256
+ # {"cp" => "urn:ietf:params:xml:ns:common-policy"})
257
+ #
258
+ # If success:
259
+ # * The method returns the response body as a String.
260
+ #
261
+ def get_element(auid, document, selector, xml_namespaces=nil, check_etag=true)
262
+
263
+ application, document = get_app_doc(auid, document)
264
+ response = send_request(:get, application, document, selector, nil, xml_namespaces, nil, check_etag)
265
+
266
+ # Check Content-Type.
267
+ check_content_type(response, "application/xcap-el+xml")
268
+
269
+ return response.body.content
270
+
271
+ end
272
+
273
+ # Create/replace an element in the document stored in the server.
274
+ #
275
+ # Example:
276
+ #
277
+ # @xcapclient.put_element("pres-rules", nil,
278
+ # '/cp:ruleset/cp:rule[@id="pres_whitelist"]/cp:conditions/cp:identity/cp:one[@id="sip:bob@example.org"]',
279
+ # '<cp:one id="sip:bob@example.org"/>',
280
+ # {"cp"=>"urn:ietf:params:xml:ns:common-policy"})
281
+ #
282
+ # If success:
283
+ # * The method returns true.
284
+ # * Local plain document and ETag are deleted (as the document has changed).
285
+ #
286
+ def put_element(auid, document, selector, selector_body, xml_namespaces=nil, check_etag=true)
287
+
288
+ application, document = get_app_doc(auid, document)
289
+ response = send_request(:put, application, document, selector, selector_body, xml_namespaces, "application/xcap-el+xml", check_etag)
290
+
291
+ # Reset local plain document and ETag as we have modified it.
292
+ document.plain = nil
293
+ document.etag = nil
294
+
295
+ return true
296
+
297
+ end
298
+
299
+
300
+ # Delete an element in the document stored in the server.
301
+ #
302
+ # Example:
303
+ #
304
+ # @xcapclient.delete_element("pres-rules", nil,
305
+ # '/cp:ruleset/cp:rule[@id="pres_whitelist"]/cp:conditions/cp:identity/cp:one[@id="sip:alice@example.org"]',
306
+ # {"cp" => "urn:ietf:params:xml:ns:common-policy"})
307
+ #
308
+ # If success:
309
+ # * The method returns true.
310
+ # * Local plain document and ETag are deleted (as the document has changed).
311
+ #
312
+ def delete_element(auid, document, selector, xml_namespaces=nil, check_etag=true)
313
+
314
+ application, document = get_app_doc(auid, document)
315
+ response = send_request(:delete, application, document, selector, nil, xml_namespaces, nil, check_etag)
316
+
317
+ # Reset local plain document and ETag as we have modified it
318
+ document.plain = nil
319
+ document.etag = nil
320
+
321
+ return true
322
+
323
+ end
324
+
325
+
326
+ # Fetch the namespace prefixes of an element.
327
+ #
328
+ # If the client wants to create/replace an element, the body of the PUT request must use the same namespaces and prefixes as those used in the document stored in the server. This methods allows the client to fetch these namespaces and prefixes.
329
+ #
330
+ # Related documentation: {RFC 4825 section 7.10}[http://tools.ietf.org/html/rfc4825#section-7.10], {RFC 4825 section 10}[http://tools.ietf.org/html/rfc4825#section-10]
331
+ #
332
+ # Example:
333
+ #
334
+ # @xcapclient.get_element_namespaces("pres-rules", nil,
335
+ # '/ccpp:ruleset/ccpp:rule[@id="pres_whitelist"]',
336
+ # { "ccpp" => "urn:ietf:params:xml:ns:common-policy" })
337
+ #
338
+ # Assuming the server uses "cp" for that namespace, it would reply:
339
+ #
340
+ # HTTP/1.1 200 OK
341
+ # Content-Type: application/xcap-ns+xml
342
+ #
343
+ # <cp:identity xmlns:pr="urn:ietf:params:xml:ns:pres-rules"
344
+ # xmlns:cp="urn:ietf:params:xml:ns:common-policy" />
345
+ #
346
+ # If Nokogiri[http://wiki.github.com/tenderlove/nokogiri] is available the method returns a hash:
347
+ # {"pr"=>"urn:ietf:params:xml:ns:pres-rules", "cp"=>"urn:ietf:params:xml:ns:common-policy"}
348
+ # If not, the method returns the response body as a String and the application must parse it.
349
+ #
350
+ def get_element_namespaces(auid, document, selector, xml_namespaces=nil, check_etag=true)
351
+
352
+ application, document = get_app_doc(auid, document)
353
+ response = send_request(:get, application, document, selector + "/namespace::*", nil, xml_namespaces, nil, check_etag)
354
+
355
+ # Check Content-Type.
356
+ check_content_type(response, "application/xcap-ns+xml")
357
+
358
+ return case NOKOGIRI_INSTALLED
359
+ when true
360
+ Nokogiri::XML.parse(response.body.content).namespaces
361
+ when false
362
+ response.body.content
363
+ end
364
+
365
+ end
366
+
367
+
368
+ # Fetch the XCAP applications (auids) supported by the server.
369
+ #
370
+ # Related documentation: {RFC 4825 section 12}[http://tools.ietf.org/html/rfc4825#section-12]
371
+ #
372
+ # If Nokogiri[http://wiki.github.com/tenderlove/nokogiri] is available the method returns an Array containing the auids. If not, the response body is returned as a String.
373
+ #
374
+ def get_xcap_auids
375
+
376
+ body = get_element("xcap-caps", nil, '/xcap-caps/auids')
377
+
378
+ return case NOKOGIRI_INSTALLED
379
+ when true
380
+ parse(body).xpath("/auids/auid", {"xmlns" => XCAP_CAPS_XMLNS}).map {|auid| auid.content}
381
+ when false
382
+ body
383
+ end
384
+
385
+ end
386
+
387
+
388
+ # Fetch the XCAP extensions supported by the server.
389
+ #
390
+ # Same as XCAPClient::Client::get_xcap_auids but fetching the supported extensions.
391
+ #
392
+ def get_xcap_extensions
393
+
394
+ body = get_element("xcap-caps", nil, '/xcap-caps/extensions')
395
+
396
+ return case NOKOGIRI_INSTALLED
397
+ when true
398
+ parse(body).xpath("/extensions/extension", {"xmlns" => XCAP_CAPS_XMLNS}).map {|extension| extension.content}
399
+ when false
400
+ body
401
+ end
402
+
403
+ end
404
+
405
+
406
+ # Fetch the XCAP namespaces supported by the server.
407
+ #
408
+ # Same as XCAPClient::Client::get_xcap_auids but fetching the supported namespaces.
409
+ #
410
+ def get_xcap_namespaces
411
+
412
+ body = get_element("xcap-caps", nil, '/xcap-caps/namespaces')
413
+
414
+ return case NOKOGIRI_INSTALLED
415
+ when true
416
+ parse(body).xpath("/namespaces/namespace", {"xmlns" => XCAP_CAPS_XMLNS}).map {|namespace| namespace.content}
417
+ when false
418
+ body
419
+ end
420
+
421
+ end
422
+
423
+
424
+ private
425
+
426
+
427
+ def get_app_doc(auid, document=nil)
428
+
429
+ # Get the application.
430
+ application = @applications[auid]
431
+ raise WrongAUID, "There is no application with auid '#{auid}'" unless application
432
+
433
+ # Get the document.
434
+ case document
435
+ when Document
436
+ when String
437
+ document_name = document
438
+ document = application.document(document_name)
439
+ raise DocumentError, "document '#{document_name}' doesn't exist in application '#{auid}'" unless document
440
+ when NilClass
441
+ document = application.document # Default document.
442
+ else
443
+ raise ArgumentError, "`document' must be Document, String or nil"
444
+ end
445
+
446
+ return [application, document]
447
+
448
+ end
449
+
450
+
451
+ # Converts the hash namespaces hash into a HTTP query.
452
+ #
453
+ # get_xmlns_query( { "a"=>"urn:test:default-namespace", "b"=>"urn:test:namespace1-uri" } )
454
+ # => "?xmlns(a=urn:test:default-namespace)xmlns(b=urn:test:namespace1-uri)"
455
+ #
456
+ def get_xmlns_query(xml_namespaces)
457
+
458
+ return "" unless xml_namespaces
459
+
460
+ xmlns_query="?"
461
+ xml_namespaces.each do |prefix, uri|
462
+ xmlns_query += "xmlns(#{prefix}=#{uri})"
463
+ end
464
+
465
+ return xmlns_query
466
+
467
+ end
468
+
469
+
470
+ def send_request(method, application, document, selector, selector_body, xml_namespaces, content_type, check_etag)
471
+
472
+ # Set extra headers.
473
+ extra_headers = {}.merge(COMMON_HEADERS)
474
+ if check_etag && document.etag
475
+ case method
476
+ when :get
477
+ extra_headers["If-None-Match"] = document.etag
478
+ when :put
479
+ extra_headers["If-Match"] = document.etag
480
+ when :delete
481
+ extra_headers["If-Match"] = document.etag
482
+ end
483
+ end
484
+ extra_headers["Content-Type"] = content_type if content_type
485
+
486
+ # XUI.
487
+ xui = case application.scope
488
+ when :user
489
+ "users/#{user_encode(@user)}"
490
+ when :global
491
+ GLOBAL_XUI
492
+ end
493
+
494
+ # URI.
495
+ uri = "#{@xcap_root}/#{application.auid}/#{xui}/#{document.name}"
496
+ uri += "/~~#{percent_encode(selector)}" if selector
497
+ uri += get_xmlns_query(xml_namespaces) if xml_namespaces
498
+
499
+ # Body (just in case of PUT).
500
+ body = ( selector_body || document.plain || nil ) if method == :put
501
+ raise ArgumentError, "PUT body empty" unless String === body if method == :put
502
+
503
+ begin
504
+ response = @http_client.request(method, uri, nil, body, extra_headers)
505
+ rescue => e
506
+ raise ConnectionError, "Error contacting the server <#{e.class}: #{e.message}>"
507
+ end
508
+
509
+ document.last_response = response
510
+
511
+ # Process the response.
512
+ case response.status.to_s
513
+
514
+ when /^2[0-9]{2}$/
515
+ return response
516
+
517
+ when "304"
518
+ raise HTTPDocumentNotModified
519
+
520
+ when "400"
521
+ raise HTTPBadRequest
522
+
523
+ when "404"
524
+ raise HTTPDocumentNotFound
525
+
526
+ when /^(401|407)$/
527
+ raise HTTPAuthenticationError, "Couldn't authenticate for URI '#{uri}' [#{response.status} #{response.reason}]"
528
+
529
+ when "409"
530
+ raise HTTPConflictError
531
+
532
+ when "412"
533
+ raise HTTPNoMatchingETag
534
+
535
+ when "500"
536
+ raise HTTPServerError
537
+
538
+ else
539
+ raise HTTPUnknownError, "Unknown error for URI '#{uri}' [#{response.status} #{response.reason}]"
540
+
541
+ end
542
+
543
+ end # def send_request
544
+
545
+
546
+ def percent_encode(str)
547
+ return case RUBY_VERSION_CORE
548
+ when :RUBY_1_9
549
+ str.dup.force_encoding('ASCII-8BIT').gsub(/[^a-zA-Z0-9\.,:;\-_?!@$&=+*\/~'()]/) { '%%%02x' % $&.ord }
550
+ when :RUBY_1_8
551
+ str.gsub(/[^a-zA-Z0-9\.,:;\-_?!@$&=+*\/~'()]/n) {|s| sprintf('%%%02x', s[0]) }
552
+ end
553
+ end
554
+
555
+
556
+ def user_encode(str)
557
+ return case RUBY_VERSION_CORE
558
+ when :RUBY_1_9
559
+ str.gsub(/[?\/]/) { '%%%02x' % $&.ord }
560
+ when :RUBY_1_8
561
+ str.gsub(/[?\/]/) {|s| sprintf('%%%02x', s[0]) }
562
+ end
563
+ end
564
+
565
+
566
+ def check_content_type(response, valid_content_type)
567
+ content_type = response.header["Content-Type"].first
568
+ raise HTTPWrongContentType, "Wrong Content-Type ('#{content_type})" unless content_type =~ /^#{Regexp.escape(valid_content_type)};?/i
569
+ end
570
+
571
+
572
+ def parse(str)
573
+ begin
574
+ Nokogiri::XML.parse(str, nil, nil, PARSE_OPTIONS)
575
+ rescue Nokogiri::SyntaxError => e
576
+ raise XMLParsingError, "Couldn't parse the XML file <#{e.class}: #{e.message}>"
577
+ end
578
+ end
579
+
580
+
581
+ end # class Client
582
+
583
+
584
+ end
@@ -0,0 +1,40 @@
1
+ module XCAPClient
2
+
3
+ class Document
4
+
5
+ # The name of the document as it exists in the server.
6
+ attr_reader :name
7
+
8
+ # Contains the plain document fetched from the server or manually set.
9
+ attr_accessor :plain
10
+
11
+ # The last received ETag value in a response from the server. It can be set manually.
12
+ attr_accessor :etag
13
+
14
+ # This attribute could contain the parsed instance of the XML document. It's not required by XCAPClient itself, but could be useful for the program in top of it.
15
+ attr_accessor :parsed
16
+
17
+ # The last response received from the server. It's a HTTP::Message[http://dev.ctor.org/doc/httpclient/] object.
18
+ attr_accessor :last_response
19
+
20
+ # Create a new instance. _name_, _plain_ and _etag_ are String.
21
+ def initialize(name, plain=nil, etag=nil, parsed=nil)
22
+ @name = name
23
+ @plain = plain
24
+ @parsed = parsed
25
+ @etag = etag
26
+
27
+ # Check name.
28
+ raise ConfigError, "Document `name' must be a non empty string" unless String === @name && ! @name.empty?
29
+ end
30
+
31
+ # Delete the local plain and parsed document and the ETag.
32
+ def reset
33
+ @plain = nil
34
+ @parsed = nil
35
+ @etag = nil
36
+ end
37
+
38
+ end
39
+
40
+ end
@@ -0,0 +1,21 @@
1
+ module XCAPClient
2
+
3
+ class XCAPClientError < StandardError ; end
4
+ class ConfigError < XCAPClientError ; end
5
+ class ArgumentError < XCAPClientError ; end
6
+ class ConnectionError < XCAPClientError ; end
7
+ class WrongAUID < XCAPClientError ; end
8
+ class DocumentError < XCAPClientError ; end
9
+ class HTTPError < XCAPClientError ; end
10
+ class HTTPAuthenticationError < HTTPError ; end
11
+ class HTTPNoMatchingETag < HTTPError ; end
12
+ class HTTPConflictError < HTTPError ; end
13
+ class HTTPDocumentNotModified < HTTPError ; end
14
+ class HTTPDocumentNotFound < HTTPError ; end
15
+ class HTTPWrongContentType < HTTPError ; end
16
+ class HTTPBadRequest < HTTPError ; end
17
+ class HTTPServerError < HTTPError ; end
18
+ class HTTPUnknownError < HTTPError ; end
19
+ class XMLParsingError < XCAPClientError ; end
20
+
21
+ end
data/lib/xcapclient.rb ADDED
@@ -0,0 +1,30 @@
1
+ module XCAPClient
2
+
3
+ VERSION = "1.0"
4
+
5
+ RUBY_VERSION_CORE = case RUBY_VERSION
6
+ when /^1\.9\./
7
+ :RUBY_1_9
8
+ when /^1\.8\./
9
+ :RUBY_1_8
10
+ end
11
+
12
+ end
13
+
14
+
15
+ require "httpclient"
16
+ begin
17
+ require "nokogiri"
18
+ XCAPClient::NOKOGIRI_INSTALLED = true
19
+ XCAPClient::PARSE_OPTIONS = Nokogiri::XML::ParseOptions::NONET + Nokogiri::XML::ParseOptions::NOERROR
20
+ rescue LoadError
21
+ STDERR.puts "WARNING: Nokogiri XML parser is not installed. Some non vital features are disabled."
22
+ XCAPClient::NOKOGIRI_INSTALLED = false
23
+ end
24
+
25
+
26
+ xcapclient_root = File.join(File.dirname(__FILE__), 'xcapclient')
27
+ require File.join(xcapclient_root, "client")
28
+ require File.join(xcapclient_root, "errors")
29
+ require File.join(xcapclient_root, "application")
30
+ require File.join(xcapclient_root, "document")