xrc 0.0.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.
@@ -0,0 +1,40 @@
1
+ module Xrc
2
+ class Connection
3
+ attr_reader :block, :domain, :port, :socket
4
+
5
+ def initialize(options, &block)
6
+ @domain = options[:domain]
7
+ @port = options[:port]
8
+ @block = block
9
+ end
10
+
11
+ def connect
12
+ @socket = Connector.connect(domain: domain, port: port)
13
+ start
14
+ end
15
+
16
+ def encrypt
17
+ @socket = TslConnector.connect(socket: socket)
18
+ start
19
+ end
20
+
21
+ def open
22
+ write Elements::Stream.new(domain: domain)
23
+ end
24
+
25
+ def write(object)
26
+ socket << object.to_s
27
+ end
28
+
29
+ private
30
+
31
+ def start
32
+ open
33
+ parse
34
+ end
35
+
36
+ def parse
37
+ Parser.new(socket: socket, &block).parse
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,42 @@
1
+ module Xrc
2
+ class Connection
3
+ class Connector
4
+ def self.connect(*args)
5
+ new(*args).connect
6
+ end
7
+
8
+ attr_reader :options
9
+
10
+ def initialize(options)
11
+ @options = options
12
+ end
13
+
14
+ def connect
15
+ hosts.find do |host|
16
+ if socket = connect_to(host)
17
+ break socket
18
+ end
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def domain
25
+ options[:domain]
26
+ end
27
+
28
+ def port
29
+ options[:port]
30
+ end
31
+
32
+ def hosts
33
+ HostsResolver.call(domain)
34
+ end
35
+
36
+ def connect_to(host)
37
+ TCPSocket.new(host, port)
38
+ rescue SocketError, Errno::ECONNREFUSED => exception
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,37 @@
1
+ module Xrc
2
+ class Connection
3
+ class TslConnector
4
+ def self.connect(*args)
5
+ new(*args).connect
6
+ end
7
+
8
+ attr_reader :options
9
+
10
+ def initialize(options = {})
11
+ @options = options
12
+ end
13
+
14
+ def connect
15
+ ssl_socket.connect
16
+ end
17
+
18
+ private
19
+
20
+ def ssl_socket
21
+ ssl_socket = Socket.new(socket, context)
22
+ ssl_socket.sync_close = true
23
+ ssl_socket
24
+ end
25
+
26
+ def socket
27
+ options[:socket]
28
+ end
29
+
30
+ def context
31
+ context = OpenSSL::SSL::SSLContext.new
32
+ context.verify_mode = OpenSSL::SSL::VERIFY_NONE
33
+ context
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,41 @@
1
+ module Xrc
2
+ module Elements
3
+ class Auth < REXML::Element
4
+ attr_reader :options
5
+
6
+ def initialize(options = {})
7
+ super("auth")
8
+ @options = options
9
+ add_sasl_namespace
10
+ add_plain_mechanism_attribute
11
+ add_credentials_text
12
+ end
13
+
14
+ private
15
+
16
+ def jid
17
+ options[:jid]
18
+ end
19
+
20
+ def password
21
+ options[:password]
22
+ end
23
+
24
+ def plain_credentials
25
+ "#{jid.strip}\x00#{jid.node}\x00#{password}"
26
+ end
27
+
28
+ def add_sasl_namespace
29
+ add_namespace(Namespaces::SASL)
30
+ end
31
+
32
+ def add_plain_mechanism_attribute
33
+ attributes["mechanism"] = "PLAIN"
34
+ end
35
+
36
+ def add_credentials_text
37
+ self.text = Base64.strict_encode64(plain_credentials)
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,18 @@
1
+ module Xrc
2
+ module Elements
3
+ class Bind < REXML::Element
4
+ def initialize(options = {})
5
+ super("iq")
6
+ bind = REXML::Element.new("bind")
7
+ bind.add_namespace(Namespaces::BIND)
8
+ if options[:resource]
9
+ resource = REXML::Element.new("resource")
10
+ resource.text = options[:resource]
11
+ bind.add(resource)
12
+ end
13
+ add_attributes("type" => "set")
14
+ add(bind)
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,20 @@
1
+ module Xrc
2
+ module Elements
3
+ class Join < Presence
4
+ def initialize(options)
5
+ super()
6
+ attributes["from"] = options[:from]
7
+ attributes["to"] = options[:to]
8
+ add(x)
9
+ end
10
+
11
+ private
12
+
13
+ def x
14
+ element = REXML::Element.new("x")
15
+ element.add_namespace(Namespaces::MUC)
16
+ element
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,20 @@
1
+ module Xrc
2
+ module Elements
3
+ class Message < REXML::Element
4
+ def initialize(options = {})
5
+ super("message")
6
+ attributes["from"] = options[:from]
7
+ attributes["to"] = options[:to]
8
+ attributes["type"] = options[:type]
9
+ body.text = options[:body]
10
+ add(body)
11
+ end
12
+
13
+ private
14
+
15
+ def body
16
+ @body ||= REXML::Element.new("body")
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,21 @@
1
+ module Xrc
2
+ module Elements
3
+ class Ping < REXML::Element
4
+ def initialize(options = {})
5
+ super("iq")
6
+ attributes["from"] = options[:from]
7
+ attributes["to"] = options[:to]
8
+ attributes["type"] = "get"
9
+ add(ping)
10
+ end
11
+
12
+ private
13
+
14
+ def ping
15
+ element = REXML::Element.new("ping")
16
+ element.add_namespace(Namespaces::PING)
17
+ element
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,9 @@
1
+ module Xrc
2
+ module Elements
3
+ class Presence < REXML::Element
4
+ def initialize
5
+ super("presence")
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,13 @@
1
+ module Xrc
2
+ module Elements
3
+ class Roster < REXML::Element
4
+ def initialize
5
+ super("iq")
6
+ query = REXML::Element.new("query")
7
+ query.add_namespace(Namespaces::ROSTER)
8
+ add_attributes("type" => "get")
9
+ add(query)
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ module Xrc
2
+ module Elements
3
+ class Session < REXML::Element
4
+ def initialize
5
+ super("iq")
6
+ session = REXML::Element.new("session")
7
+ session.add_namespace(Namespaces::SESSION)
8
+ add_attributes("type" => "set")
9
+ add(session)
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,10 @@
1
+ module Xrc
2
+ module Elements
3
+ class Starttls < REXML::Element
4
+ def initialize
5
+ super("starttls")
6
+ add_namespace(Namespaces::TLS)
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,22 @@
1
+ module Xrc
2
+ module Elements
3
+ class Stream
4
+ attr_reader :options
5
+
6
+ def initialize(options)
7
+ @options = options
8
+ end
9
+
10
+ def to_s
11
+ %W[
12
+ <stream:stream
13
+ xmlns:stream="http://etherx.jabber.org/streams"
14
+ xmlns="jabber:client"
15
+ to="#{options[:domain]}"
16
+ xml:lang="en"
17
+ version="1.0">
18
+ ].join(" ")
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,43 @@
1
+ module Xrc
2
+ class HostsResolver
3
+ def self.call(*args)
4
+ new(*args).call
5
+ end
6
+
7
+ attr_reader :domain
8
+
9
+ def initialize(domain)
10
+ @domain = domain
11
+ end
12
+
13
+ def call
14
+ resolved_hosts + [domain]
15
+ end
16
+
17
+ private
18
+
19
+ def resolved_hosts
20
+ sorted_srvs.lazy.map(&:target).map(&:to_s).to_a
21
+ end
22
+
23
+ def sorted_srvs
24
+ @sorted_srvs ||= srvs.sort_by do |resource|
25
+ [resource.priority, -resource.weight]
26
+ end
27
+ end
28
+
29
+ def srvs
30
+ Resolv::DNS.open do |dns|
31
+ dns.getresources(name, type_class)
32
+ end
33
+ end
34
+
35
+ def name
36
+ "_xmpp-client._tcp.#{domain}"
37
+ end
38
+
39
+ def type_class
40
+ Resolv::DNS::Resource::IN::SRV
41
+ end
42
+ end
43
+ end
data/lib/xrc/jid.rb ADDED
@@ -0,0 +1,44 @@
1
+ module Xrc
2
+ class Jid
3
+ PATTERN = /\A(?:([^@]*)@)??([^@\/]*)(?:\/(.*?))?\z/
4
+
5
+ attr_reader :raw
6
+
7
+ def initialize(raw)
8
+ @raw = raw
9
+ end
10
+
11
+ def node
12
+ sections[0]
13
+ end
14
+
15
+ def domain
16
+ sections[1]
17
+ end
18
+
19
+ def resource
20
+ sections[2]
21
+ end
22
+
23
+ def strip
24
+ "#{node}@#{domain}"
25
+ end
26
+
27
+ def to_s
28
+ str = strip
29
+ str << "/#{resource}" if resource
30
+ str
31
+ end
32
+
33
+ def ==(jid)
34
+ jid = Jid.new(jid) unless jid.is_a?(Jid)
35
+ to_s == jid.to_s || strip == jid.strip
36
+ end
37
+
38
+ private
39
+
40
+ def sections
41
+ @sections ||= raw.scan(PATTERN).first
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,62 @@
1
+ module Xrc
2
+ class MessageBuilder
3
+ # Builds a Message object from a REXML::Element.
4
+ # @param [REXML::Element] element A message element
5
+ # @return [Xrc::Messages::Base] An ancestor of Xrc::Messages::Base instance
6
+ def self.build(element)
7
+ new(element).build
8
+ end
9
+
10
+ # @param [REXML::Element] element A message element
11
+ def initialize(element)
12
+ @element = element
13
+ end
14
+
15
+ def build
16
+ message_class.new(@element)
17
+ end
18
+
19
+ private
20
+
21
+ def message_class
22
+ case
23
+ when has_room_message?
24
+ Messages::RoomMessage
25
+ when has_private_message?
26
+ Messages::PrivateMessage
27
+ when has_subject?
28
+ Messages::Subject
29
+ else
30
+ Messages::Null
31
+ end
32
+ end
33
+
34
+ def has_room_message?
35
+ has_groupchat_type? && has_body?
36
+ end
37
+
38
+ def has_private_message?
39
+ has_chat_type? && has_body?
40
+ end
41
+
42
+ def has_chat_type?
43
+ type == "chat"
44
+ end
45
+
46
+ def has_groupchat_type?
47
+ type == "groupchat"
48
+ end
49
+
50
+ def has_body?
51
+ !!@element.elements["body"]
52
+ end
53
+
54
+ def has_subject?
55
+ !!@element.elements["subject"]
56
+ end
57
+
58
+ def type
59
+ @element.attributes["type"].to_s
60
+ end
61
+ end
62
+ end