xcapclient 1.3.1 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc CHANGED
@@ -1,4 +1,4 @@
1
- = XCAPClient (version 1.3.1)
1
+ = XCAPClient (version 1.4.0)
2
2
 
3
3
  * http://dev.sipdoc.net/projects/ruby-xcapclient/wiki/
4
4
  * http://rubyforge.org/projects/xcapclient/
@@ -6,9 +6,9 @@
6
6
 
7
7
  == Description
8
8
 
9
- XCAP ({RFC 4825}[http://tools.ietf.org/html/rfc4825]) is a protocol on top of HTTP which allows a client to manipulate the contents of Presence Information Data Format (PIDF) based presence documents. These documents are stored in a server in XML format and are fetched, modified, replaced or deleted by the client. The protocol allows multiple clients to manipulate the data, provided that they are authorized to do so. XCAP is already used in SIMPLE-based presence systems for manipulation of presence lists and presence authorization policies.
9
+ XCAP ({RFC 4825}[http://tools.ietf.org/html/rfc4825]) is a protocol on top of HTTP allowing a client (usually built within a SIP user agent) to manipulate the content of XML documents stored in a server. These documents represent per user buddy list, presence authorization policy, media content (i.e. user avatar) and other kind of features.
10
10
 
11
- XCAPClient library implements the XCAP protocol in client side, allowing the applications to get, store, modify and delete XML documents in the server.
11
+ Ruby XCAPClient library implements the XCAP protocol in client side, allowing the application to get, store, modify and delete XML documents (totally or partially) in the server.
12
12
 
13
13
 
14
14
  == Features
@@ -60,36 +60,55 @@ A developer interested in this library should study the following documents:
60
60
  ==== Create a client
61
61
 
62
62
  xcap_conf = {
63
- :xcap_root => "https://xcap.myserver.org/xcap-root",
63
+ :xcap_root => "https://xcap.myserver.org",
64
64
  :user => "sip:me@mydomain.org",
65
- :auth_user => "me",
66
65
  :password => "xxxxxx",
67
66
  :ssl_verify_cert => true
68
67
  }
69
68
 
70
69
  xcap_apps = {
71
- "pres-rules" => {
72
- :xmlns => "urn:ietf:params:xml:ns:pres-rules",
73
- :mime_type => "application/auth-policy+xml",
74
- :scope => :user,
75
- :document_name => "index"
70
+ "resource-lists" => {
71
+ :xmlns => "urn:ietf:params:xml:ns:resource-lists",
72
+ :mime_type => "application/resource-lists+xml"
76
73
  }
77
74
  }
78
75
 
79
- @xcapclient = Client.new(xcap_conf, xcap_apps)
76
+ xcapclient = Client.new(xcap_conf, xcap_apps)
80
77
 
81
78
 
82
- ==== Fetch the "pres-rules" document from the server
79
+ ==== Create a XCAPClient::Document with name "index" for "resource-lists" application.
83
80
 
84
- @xcapclient.get("pres-rules")
81
+ xcapclient.application("resource-lists").add_document("index")
85
82
 
83
+ It's the same as doing:
86
84
 
87
- ==== Fetch again the "pres-rules" document (now including the stored ETag)
85
+ xcapclient.application("resource-lists").add_document("index", scope=nil, xui=nil)
86
+
87
+ As _scope_ is nil it's set to :users. As _xui_ is nil it's a document owned by our user.
88
+
89
+
90
+ ==== Fetch the "index" document (auid "resource-lists") from the server
91
+
92
+ This works because the document we are interested in is the first one, so it's fetched:
93
+
94
+ xcapclient.get("resource-lists")
95
+
96
+ It's the same as doing:
97
+
98
+ xcapclient.get("resource-lists", "index")
99
+
100
+ We can also use a XCAPClient::Document object rather than a String (required when fetching 3rd party documents):
101
+
102
+ doc = xcapclient.application("resource-lists").document("index")
103
+ xcapclient.get("resource-lists", doc)
104
+
105
+
106
+ ==== Fetch again the "resource-lists" document (now including the stored ETag)
88
107
 
89
108
  By default, the methods accesing the XCAP server include the ETag if it's available. Since it was already got on the previous request, it's included in the new request. If the document has not been modified in the server by other client, the server replies "304 Not Modified" and the method generates a XCAPClient::XCAPClientError::HTTPDocumentNotModified exception.
90
109
 
91
110
  begin
92
- @xcapclient.get("pres-rules")
111
+ xcapclient.get("resource-lists")
93
112
  rescue XCAPClientError::HTTPDocumentNotModified
94
113
  puts "The document has not been modified in the server."
95
114
  end
@@ -97,48 +116,78 @@ By default, the methods accesing the XCAP server include the ETag if it's availa
97
116
 
98
117
  ==== Replace the local document and upload it to the server
99
118
 
100
- @xcapclient.application("pres-rules").document.reset
101
- @xcapclient.application("pres-rules").document.plain = "<?xml version='1.0' encoding='UTF-8'?> ..."
102
- @xcapclient.put("pres-rules")
119
+ xcapclient.application("resource-lists").document.reset
120
+ xcapclient.application("resource-lists").document.plain = "<?xml version='1.0' encoding='UTF-8'?> ..."
121
+
122
+ xcapclient.put("resource-lists")
123
+
124
+ or:
125
+
126
+ xcapclient.put("resource-lists", "index")
127
+
128
+ or:
129
+
130
+ doc = xcapclient.application("resource-lists").document("index")
131
+ xcapclient.put("resource-lists", doc)
103
132
 
104
133
 
105
134
  ==== Delete the document in the server
106
135
 
107
- @xcapclient.delete("pres-rules")
136
+ xcapclient.delete("resource-lists")
137
+
138
+
139
+ ==== Create a new document and upload it (a new "friends" document for "resource-lists" application)
140
+
141
+ friends_doc = xcapclient.application("resource-lists").add_document("friends")
142
+ friends_doc.plain = "<?xml version='1.0' encoding='UTF-8'?> ..."
143
+
144
+ xcapclient.put("resource-lists", friends_doc)
145
+
146
+ or:
147
+
148
+ xcapclient.put("resource-lists", "friends")
149
+
150
+
151
+ ==== Fetch a node (with "uri" = "sip:alice@example.org")
152
+
153
+ xcapclient.get_node("resource-lists", "index",
154
+ 'rl:resource-lists/rl:list[@name="mybuddies"]/rl:entry[@uri="sip:alice@example.org"]',
155
+ {"rl" => "urn:ietf:params:xml:ns:resource-lists"})
156
+
157
+ or using the application default namespace:
158
+
159
+ xcapclient.get_node("resource-lists", "index",
160
+ 'resource-lists/list[@name="mybuddies"]/entry[@uri="sip:alice@example.org"]')
161
+
162
+
163
+ ==== Add a new node (a new buddy)
108
164
 
165
+ xcapclient.put_node("resource-lists", "index",
166
+ 'resource-lists/list[@name="mybuddies"]/entry[@uri="sip:bob@example.org"]',
167
+ '<entry uri="sip:bob@example.org"/>'
109
168
 
110
- ==== Create a new document and upload it (a new "vacation-rules" document for "pres-rules" application)
111
169
 
112
- my_pres_rules_2 = @xcapclient.application("pres-rules").add_document("vacation-rules")
113
- my_pres_rules_2.plain = "<?xml version='1.0' encoding='UTF-8'?> ..."
114
- @xcapclient.put("pres-rules", my_pres_rules_2)
115
- # or
116
- @xcapclient.put("pres-rules", "vacation-rules")
170
+ ==== Fetch a node attribute (the "uri" attribute of "sip:alice@example.org")
117
171
 
172
+ xcapclient.get_attribute("resource-lists", "index",
173
+ 'resource-lists/list[@name="mybuddies"]/entry[@uri="sip:alice@example.org"]',
174
+ "uri")
118
175
 
119
- ==== Fetch a node (with "id" = "sip:alice@example.org")
176
+ It would return "sip:alice@example.org" as obvious.
120
177
 
121
- @xcapclient.get_node("pres-rules", nil,
122
- 'cp:ruleset/cp:rule[@id="pres_whitelist"]/cp:conditions/cp:identity/cp:one[@id="sip:alice@example.org"]',
123
- {"cp" => "urn:ietf:params:xml:ns:common-policy"})
124
178
 
179
+ ==== Get a 3rd party document owned by a different user (different "xui") within the same XCAP server.
125
180
 
126
- ==== Add a new node (a new allowed user in "pres-rules" document)
181
+ This is useful to implement shared contact lists or when fetching a OMA icon (avatar) of a different user (OMA icon is a XCAP document rather than a pure image).
127
182
 
128
- @xcapclient.put_node("pres-rules", nil,
129
- 'cp:ruleset/cp:rule[@id="pres_whitelist"]/cp:conditions/cp:identity/cp:one[@id="sip:bob@example.org"]',
130
- '<cp:one id="sip:bob@example.org"/>',
131
- {"cp"=>"urn:ietf:params:xml:ns:common-policy"})
183
+ shared_contact_list_doc = xcapclient.application("resource-lists").add_document("shared_contact_list", :users, "sip:bob@example.org")
132
184
 
133
- ==== Fetch a node attribute (node "name" from node with "id" = "sip:alice@example.org")
185
+ In this case it's required to in indicate the document by a XCAPClient::Document object rather than a String:
134
186
 
135
- @xcapclient.get_attribute("pres-rules", nil,
136
- 'cp:ruleset/cp:rule[@id="pres_whitelist"]/cp:conditions/cp:identity/cp:one[@id="sip:alice@example.org"]',
137
- "name",
138
- {"cp" => "urn:ietf:params:xml:ns:common-policy"})
187
+ xcapclient.get("resource-lists", shared_contact_list_doc)
139
188
 
140
189
 
141
- == ToDo
190
+ == TODO
142
191
 
143
192
  * Parse 409 error response ("application/xcap-error+xml") as it contains the explanation of the conflict ({RFC 4825 section 11}[http://tools.ietf.org/html/rfc4825#section-11]).
144
193
 
@@ -9,39 +9,35 @@ module XCAPClient
9
9
  @auid = auid
10
10
 
11
11
  # Check application data.
12
- raise ConfigError, "Application `data' must be a hash ('#{@auid}')" unless (Hash === data)
12
+ raise ConfigError, "application `data' must be a hash ('#{@auid}')" unless (Hash === data)
13
13
 
14
14
  @xmlns = data[:xmlns].freeze
15
15
  @mime_type = data[:mime_type].freeze
16
- @document_name = data[:document_name] || "index"
17
- @scope = data[:scope] || :user
18
- @scope.freeze
19
16
 
20
17
  # Check auid.
21
- raise ConfigError, "Application `auid' must be a non empty string ('#{@auid}')" unless String === @auid && ! @auid.empty?
18
+ raise ConfigError, "application `auid' must be a non empty string ('#{@auid}')" unless String === @auid && ! @auid.empty?
22
19
 
23
20
  # Check xmlns.
24
- raise ConfigError, "Application `xmlns' must be a non empty string ('#{@auid}')" unless String === @xmlns && ! @xmlns.empty?
21
+ raise ConfigError, "application `xmlns' must be a non empty string ('#{@auid}')" unless String === @xmlns && ! @xmlns.empty?
25
22
 
26
23
  # Check mime-type.
27
- raise ConfigError, "Application `mime_type' must be a non empty string ('#{@auid}')" unless String === @mime_type && ! @mime_type.empty?
24
+ raise ConfigError, "application `mime_type' must be a non empty string ('#{@auid}')" unless String === @mime_type && ! @mime_type.empty?
28
25
 
29
- # Check document_name
30
- raise ConfigError, "Application `document_name' must be a non empty string ('#{@auid}')" unless String === @document_name && ! @document_name.empty?
31
-
32
- # Check scope.
33
- raise ConfigError, "Application `scope' must be :user or :global ('#{@auid}')" unless [:user, :global].include?(@scope)
34
-
35
- # Create first document.
36
- @documents = {}
37
- @documents[@document_name] = Document.new(@document_name)
26
+ # Create the _documents_ Array.
27
+ @documents = []
38
28
 
39
29
  end
40
30
 
41
- # Get the XCAPClient::Document with name _document_name_ within this application. If the parameter is not set, the default document is got.
42
- #
43
- def document(document_name=nil)
44
- @documents[document_name || @document_name]
31
+ # Get the XCAPClient::Document with name _name_, scope _scope_ (:users or :global) belonging to user _xui_.
32
+ # If _name_ is nil then the first available document is fetched (careful).
33
+ # If _scope_ is not set then it's :users by default. If _xui_ is nil then it's a document owned by us.
34
+ def document(name=nil, scope=nil, xui=nil)
35
+ scope = :users unless scope
36
+ if name
37
+ @documents.find { |doc| doc.name == name and doc.scope == scope and doc.xui == xui }
38
+ else
39
+ @documents.first
40
+ end
45
41
  end
46
42
 
47
43
  # Get an Array containing all the documents created for this application.
@@ -49,12 +45,14 @@ module XCAPClient
49
45
  @documents
50
46
  end
51
47
 
52
- # Creates a new XCAPClient::Document for this application with name _document_name_.
53
- def add_document(document_name)
54
- raise DocumentError, "document '#{document_name}' already exists" if @documents[document_name]
55
- @documents[document_name] = Document.new(document_name)
48
+ # Creates a new XCAPClient::Document for this application with name _document_name_, scope _scope_ and xui _xui_.
49
+ # _scope_ can be :users (default) or :global Symbol. If _xui_ is nil then the documents belongs to our user.
50
+ def add_document(name, scope=nil, xui=nil)
51
+ scope = :users unless scope
52
+ raise DocumentError, "document '#{name}' with scope '#{scope} and xui '#{xui}' already exists" if document(name, scope, xui)
53
+ @documents << document = Document.new(name, scope, xui)
56
54
 
57
- return @documents[document_name]
55
+ return document
58
56
  end
59
57
 
60
58
  end
@@ -42,7 +42,6 @@ module XCAPClient
42
42
  "User-Agent" => "#{USER_AGENT}/#{VERSION}",
43
43
  "Connection" => "close"
44
44
  }
45
- GLOBAL_XUI = "global"
46
45
  XCAP_CAPS_XMLNS = "urn:ietf:params:xml:ns:xcap-caps"
47
46
  HTTP_TIMEOUT = 6
48
47
  CONF_PARAMETERS = [:xcap_root, :user, :auth_user, :password, :identity_header, :identity_user, :ssl_verify_cert]
@@ -63,10 +62,6 @@ module XCAPClient
63
62
  #
64
63
  # * _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:
65
64
  # * _xmlns_: The XML namespace uri of the application.
66
- # * _document_name_: The name of the default document for this application ("index" if not set).
67
- # * _scope_: Can be :user or :global (:user if not set).
68
- # * _:user_: Each user has his own document(s) for this application.
69
- # * _:global_: The document(s) is shared for all the users.
70
65
  #
71
66
  # Example:
72
67
  # xcap_conf = {
@@ -79,15 +74,11 @@ module XCAPClient
79
74
  # xcap_apps = {
80
75
  # "pres-rules" => {
81
76
  # :xmlns => "urn:ietf:params:xml:ns:pres-rules",
82
- # :mime_type => "application/auth-policy+xml",
83
- # :document_name => "index",
84
- # :scope => :user
77
+ # :mime_type => "application/auth-policy+xml"
85
78
  # },
86
79
  # "rls-services" => {
87
80
  # :xmlns => "urn:ietf:params:xml:ns:rls-services",
88
- # :mime_type => "application/rls-services+xml",
89
- # :document_name => "index",
90
- # :scope => :user
81
+ # :mime_type => "application/rls-services+xml"
91
82
  # }
92
83
  # }
93
84
  #
@@ -103,7 +94,8 @@ module XCAPClient
103
94
  # :ssl_verify_cert => true
104
95
  # }
105
96
  #
106
- # 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].
97
+ # A XCAP application called "xcap-caps" is automatically added to the list of applications of the new client. This
98
+ # application is defined in the {RFC 4825}[http://tools.ietf.org/html/rfc4825].
107
99
  #
108
100
  def initialize(conf={}, applications={})
109
101
 
@@ -150,13 +142,12 @@ module XCAPClient
150
142
  # Generate applications.
151
143
  @applications = {}
152
144
 
153
- # Add the "xcap-caps" application.
145
+ # Add the "xcap-caps" application with a document "index".
154
146
  @applications["xcap-caps"] = Application.new("xcap-caps", {
155
147
  :xmlns => "urn:ietf:params:xml:ns:xcap-caps",
156
- :mime_type => "application/xcap-caps+xml",
157
- :scope => :global,
158
- :document_name => "index"
148
+ :mime_type => "application/xcap-caps+xml"
159
149
  })
150
+ @applications["xcap-caps"].add_document("index", :global)
160
151
 
161
152
  # Add custom applications.
162
153
  applications.each do |auid, data|
@@ -556,6 +547,9 @@ module XCAPClient
556
547
  raise WrongAUID, "There is no application with auid '#{auid}'" unless application
557
548
 
558
549
  # Get the document.
550
+ # NOTE: If _document_ is a String then it's supposed to be a document owned by our user with scope :users.
551
+ # NOTE: If _document_ is a Document then it can be whatever document.
552
+ # NOTE: If _document_ is nil then the first document is used (careful).
559
553
  case document
560
554
  when Document
561
555
  when String
@@ -563,11 +557,13 @@ module XCAPClient
563
557
  document = application.document(document_name)
564
558
  raise DocumentError, "document '#{document_name}' doesn't exist in application '#{auid}'" unless document
565
559
  when NilClass
566
- document = application.document # Default document.
560
+ document = application.documents.first
567
561
  else
568
562
  raise ArgumentError, "`document' must be Document, String or nil"
569
563
  end
570
564
 
565
+ raise ArgumentError, "no document provided" unless document
566
+
571
567
  return [application, document]
572
568
 
573
569
  end
@@ -609,15 +605,23 @@ module XCAPClient
609
605
  extra_headers["Content-Type"] = content_type if content_type
610
606
 
611
607
  # XUI.
612
- xui = case application.scope
613
- when :user
614
- "users/#{user_encode(@user)}"
608
+ xui = document.xui ? document.xui : @user
609
+
610
+ # scope/XUI.
611
+ scope_xui = case document.scope
612
+ when :users
613
+ "users/#{xui}"
615
614
  when :global
616
- GLOBAL_XUI
615
+ "global"
617
616
  end
618
617
 
619
618
  # URI.
620
- uri = "#{@xcap_root}/#{application.auid}/#{xui}/#{percent_encode(document.name)}"
619
+ uri = case document.scope
620
+ when :users
621
+ "#{@xcap_root}/#{application.auid}/users/#{xui}/#{percent_encode(document.name)}"
622
+ when :global
623
+ "#{@xcap_root}/#{application.auid}/global/#{percent_encode(document.name)}"
624
+ end
621
625
  uri += "/~~/#{percent_encode(selector)}" if selector
622
626
  uri += get_xmlns_query(xml_namespaces) if xml_namespaces
623
627
 
@@ -5,6 +5,12 @@ module XCAPClient
5
5
  # The name of the document as it exists in the server.
6
6
  attr_reader :name
7
7
 
8
+ # The scope of this document (:users or :global).
9
+ attr_reader :scope
10
+
11
+ # The XUI this document belongs to. This allow fetching a document owner by other user (for example a icon).
12
+ attr_reader :xui
13
+
8
14
  # Contains the plain document fetched from the server or manually set.
9
15
  attr_accessor :plain
10
16
 
@@ -17,15 +23,31 @@ module XCAPClient
17
23
  # The last response received from the server. It's a HTTP::Message[http://dev.ctor.org/doc/httpclient/] object.
18
24
  attr_accessor :last_response
19
25
 
20
- # Create a new instance. _name_, _plain_ and _etag_ are String.
21
- def initialize(name, plain=nil, etag=nil, parsed=nil)
26
+ # Convenient field to indicate that the document has been modified locally but it hasn't been commited
27
+ # yet to the server (true then).
28
+ # This field must be set manually by the application if it wants to use it (for example after giving a new
29
+ # value to @plain or after modifyng @parsed).
30
+ attr_accessor :local_modified
31
+
32
+ # Convenient field to indicate that the document has been modified in the server but it hasn't been fetched
33
+ # so the local copy isn't up-to-date (true then).
34
+ # This field must be set manually by the application if it wants to use it (for example after the SIP UA receives
35
+ # a NOTIFY indicating that this document has been modified in the server).
36
+ attr_accessor :server_modified
37
+
38
+ # Create a new instance. _name_ and _xui_ are String. _scope_ can be :users (default) or :group Symbol. _xui_ is the user
39
+ # owning the document, if it's nil then is a document owned by our user.
40
+ def initialize(name, scope=nil, xui=nil)
41
+ scope = :users unless scope
22
42
  @name = name
23
- @plain = plain
24
- @parsed = parsed
25
- @etag = etag
43
+ @scope = scope
44
+ @xui = xui
26
45
 
27
46
  # Check name.
28
- raise ConfigError, "Document `name' must be a non empty string" unless String === @name && ! @name.empty?
47
+ raise ConfigError, "document `name' must be a non empty string" unless String === @name && ! @name.empty?
48
+
49
+ # Check scope.
50
+ raise ConfigError, "document `scope' must be :users or _global" unless @scope == :users or @scope == :global
29
51
  end
30
52
 
31
53
  # Delete the local plain and parsed document and the ETag.
@@ -39,7 +61,7 @@ module XCAPClient
39
61
  # Parse the plain XML document using Nokogiri and store it into @parsed attribute.
40
62
  # NOTE: This method is just available if Nokogiri is installed.
41
63
  def parse
42
- raise DocumentError, "Cannot parse the document as the plain document doesn't exist"
64
+ raise DocumentError, "cannot parse the document as the plain document doesn't exist" unless @plain
43
65
  begin
44
66
  @parsed = ::Nokogiri::XML::Document.parse(@plain, nil, "UTF-8", PARSE_OPTIONS)
45
67
  rescue ::Nokogiri::SyntaxError => e
@@ -50,4 +72,4 @@ module XCAPClient
50
72
 
51
73
  end
52
74
 
53
- end
75
+ end
@@ -1,8 +1,8 @@
1
1
  module XCAPClient
2
2
 
3
3
  MAJOR = 1
4
- MINOR = 3
5
- TINY = 1
4
+ MINOR = 4
5
+ TINY = 0
6
6
 
7
7
  VERSION = [MAJOR, MINOR, TINY].join('.')
8
8
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: xcapclient
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.1
4
+ version: 1.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - "I\xC3\xB1aki Baz Castillo"
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2010-02-05 00:00:00 +01:00
12
+ date: 2010-02-12 00:00:00 +01:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -22,7 +22,7 @@ dependencies:
22
22
  - !ruby/object:Gem::Version
23
23
  version: "0"
24
24
  version:
25
- description: " XCAP (RFC 4825) is a protocol on top of HTTP which allows a client (usually a VoIP/SIP device) to manipulate the contents of Presence Information Data Format (PIDF) based presence documents. These documents are stored in a server in XML format.\n\n Ruby xcapclient library implements the XCAP protocol in client side, allowing the applications to get, store, modify and delete XML documents in the server.'\n"
25
+ description: XCAP (RFC 4825) is a protocol on top of HTTP allowing a client (usually built within a SIP user agent) to manipulate the content of XML documents stored in a server. These documents represent per user buddy list, presence authorization policy, media content (i.e. user avatar) and other kind of features.Ruby XCAPClient library implements the XCAP protocol in client side, allowing the application to get, store, modify and delete XML documents (totally or partially) in the server.
26
26
  email: ibc@aliax.net
27
27
  executables: []
28
28