lib/blather/client/client.rb in blather-0.4.15 vs lib/blather/client/client.rb in blather-0.4.16

- old
+ new

@@ -31,11 +31,12 @@ # client.write Blather::Stanza::Message.new(m.from, "You sent: #{m.body}") # end # class Client attr_reader :jid, - :roster + :roster, + :caps # Create a new client and set it up # # @param [Blather::JID, #to_s] jid the JID to authorize with # @param [String] password the password to authorize with @@ -54,10 +55,11 @@ @status = Stanza::Presence::Status.new @handlers = {} @tmp_handlers = {} @filters = {:before => [], :after => []} @roster = Roster.new self + @caps = Caps.new setup_initial_handlers end # Get the current status. Taken from the `state` attribute of Status @@ -311,8 +313,144 @@ else raise "Bad guard: #{guard.inspect}" end end end + + class Caps < Blather::Stanza::DiscoInfo + def self.new + super :result + end + + def ver + generate_ver identities, features + end + + def node=(node) + @bare_node = node + super "#{node}##{ver}" + end + + def identities=(identities) + super identities + regenerate_full_node + end + + def features=(features) + super features + regenerate_full_node + end + + def c + Blather::Stanza::Presence::C.new @bare_node, ver + end + + private + + def regenerate_full_node + self.node = @bare_node + end + + def generate_ver_str(identities, features, forms = []) + # 1. Initialize an empty string S. + s = '' + + # 2. Sort the service discovery identities by category and + # then by type (if it exists) and then by xml:lang (if it + # exists), formatted as CATEGORY '/' [TYPE] '/' [LANG] '/' + # [NAME]. Note that each slash is included even if the TYPE, + # LANG, or NAME is not included. + identities.sort! do |identity1, identity2| + cmp_result = nil + [:category, :type, :xml_lang, :name].each do |field| + value1 = identity1.send(field) + value2 = identity2.send(field) + + if value1 != value2 + cmp_result = value1 <=> value2 + break + end + end + cmp_result + end + + # 3. For each identity, append the 'category/type/lang/name' to + # S, followed by the '<' character. + s += identities.collect do |identity| + [:category, :type, :xml_lang, :name].collect do |field| + identity.send(field).to_s + end.join('/') + '<' + end.join + + # 4. Sort the supported service discovery features. + features.sort! { |feature1, feature2| feature1.var <=> feature2.var } + + # 5. For each feature, append the feature to S, followed by the + # '<' character. + s += features.collect { |feature| feature.var.to_s + '<' }.join + + # 6. If the service discovery information response includes + # XEP-0128 data forms, sort the forms by the FORM_TYPE (i.e., by + # the XML character data of the <value/> element). + forms.sort! do |form1, form2| + fform_type1 = form1.field 'FORM_TYPE' + fform_type2 = form2.field 'FORM_TYPE' + form_type1 = fform_type1 ? fform_type1.values.to_s : nil + form_type2 = fform_type2 ? fform_type2.values.to_s : nil + form_type1 <=> form_type2 + end + + # 7. For each extended service discovery information form: + forms.each do |form| + # 7.1. Append the XML character data of the FORM_TYPE field's + # <value/> element, followed by the '<' character. + fform_type = form.field 'FORM_TYPE' + form_type = fform_type ? fform_type.values.to_s : nil + s += "#{form_type}<" + + # 7.2. Sort the fields by the value of the "var" attribute + fields = form.fields.sort { |field1, field2| field1.var <=> field2.var } + + # 7.3. For each field: + fields.each do |field| + # 7.3.1. Append the value of the "var" attribute, followed by + # the '<' character. + s += "#{field.var}<" + + # 7.3.2. Sort values by the XML character data of the <value/> element + # values = field.values.sort { |value1, value2| value1 <=> value2 } + + # 7.3.3. For each <value/> element, append the XML character + # data, followed by the '<' character. + # s += values.collect { |value| "#{value}<" }.join + s += "#{field.value}<" + end + end + s + end + + def generate_ver(identities, features, forms = [], hash = 'sha-1') + s = generate_ver_str identities, features, forms + + # 9. Compute the verification string by hashing S using the + # algorithm specified in the 'hash' attribute (e.g., SHA-1 as + # defined in RFC 3174). The hashed data MUST be generated + # with binary output and encoded using Base64 as specified in + # Section 4 of RFC 4648 (note: the Base64 output MUST NOT + # include whitespace and MUST set padding bits to zero). + + # See http://www.iana.org/assignments/hash-function-text-names + hash_klass = case hash + when 'md2' then nil + when 'md5' then Digest::MD5 + when 'sha-1' then Digest::SHA1 + when 'sha-224' then nil + when 'sha-256' then Digest::SHA256 + when 'sha-384' then Digest::SHA384 + when 'sha-512' then Digest::SHA512 + end + hash_klass ? [hash_klass::digest(s)].pack('m').strip : nil + end + end # Caps end # Client end # Blather