vinesmod 0.4.5

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.
Files changed (151) hide show
  1. data/Gemfile +3 -0
  2. data/LICENSE +19 -0
  3. data/README.md +43 -0
  4. data/Rakefile +57 -0
  5. data/bin/vines +93 -0
  6. data/conf/certs/README +39 -0
  7. data/conf/certs/ca-bundle.crt +3366 -0
  8. data/conf/config.rb +149 -0
  9. data/lib/vines.rb +197 -0
  10. data/lib/vines/cluster.rb +246 -0
  11. data/lib/vines/cluster/connection.rb +26 -0
  12. data/lib/vines/cluster/publisher.rb +55 -0
  13. data/lib/vines/cluster/pubsub.rb +92 -0
  14. data/lib/vines/cluster/sessions.rb +125 -0
  15. data/lib/vines/cluster/subscriber.rb +108 -0
  16. data/lib/vines/command/bcrypt.rb +12 -0
  17. data/lib/vines/command/cert.rb +50 -0
  18. data/lib/vines/command/init.rb +68 -0
  19. data/lib/vines/command/register.rb +27 -0
  20. data/lib/vines/command/restart.rb +12 -0
  21. data/lib/vines/command/schema.rb +24 -0
  22. data/lib/vines/command/start.rb +28 -0
  23. data/lib/vines/command/stop.rb +18 -0
  24. data/lib/vines/command/unregister.rb +27 -0
  25. data/lib/vines/config.rb +213 -0
  26. data/lib/vines/config/host.rb +119 -0
  27. data/lib/vines/config/port.rb +132 -0
  28. data/lib/vines/config/pubsub.rb +108 -0
  29. data/lib/vines/contact.rb +111 -0
  30. data/lib/vines/daemon.rb +78 -0
  31. data/lib/vines/error.rb +150 -0
  32. data/lib/vines/jid.rb +95 -0
  33. data/lib/vines/kit.rb +35 -0
  34. data/lib/vines/log.rb +24 -0
  35. data/lib/vines/router.rb +179 -0
  36. data/lib/vines/stanza.rb +175 -0
  37. data/lib/vines/stanza/iq.rb +48 -0
  38. data/lib/vines/stanza/iq/auth.rb +18 -0
  39. data/lib/vines/stanza/iq/disco_info.rb +45 -0
  40. data/lib/vines/stanza/iq/disco_items.rb +29 -0
  41. data/lib/vines/stanza/iq/error.rb +16 -0
  42. data/lib/vines/stanza/iq/ping.rb +16 -0
  43. data/lib/vines/stanza/iq/private_storage.rb +83 -0
  44. data/lib/vines/stanza/iq/query.rb +10 -0
  45. data/lib/vines/stanza/iq/register.rb +42 -0
  46. data/lib/vines/stanza/iq/result.rb +16 -0
  47. data/lib/vines/stanza/iq/roster.rb +140 -0
  48. data/lib/vines/stanza/iq/session.rb +17 -0
  49. data/lib/vines/stanza/iq/vcard.rb +56 -0
  50. data/lib/vines/stanza/iq/version.rb +25 -0
  51. data/lib/vines/stanza/message.rb +43 -0
  52. data/lib/vines/stanza/presence.rb +156 -0
  53. data/lib/vines/stanza/presence/error.rb +23 -0
  54. data/lib/vines/stanza/presence/probe.rb +37 -0
  55. data/lib/vines/stanza/presence/subscribe.rb +42 -0
  56. data/lib/vines/stanza/presence/subscribed.rb +51 -0
  57. data/lib/vines/stanza/presence/unavailable.rb +15 -0
  58. data/lib/vines/stanza/presence/unsubscribe.rb +38 -0
  59. data/lib/vines/stanza/presence/unsubscribed.rb +38 -0
  60. data/lib/vines/stanza/pubsub.rb +22 -0
  61. data/lib/vines/stanza/pubsub/create.rb +39 -0
  62. data/lib/vines/stanza/pubsub/delete.rb +41 -0
  63. data/lib/vines/stanza/pubsub/publish.rb +66 -0
  64. data/lib/vines/stanza/pubsub/subscribe.rb +44 -0
  65. data/lib/vines/stanza/pubsub/unsubscribe.rb +30 -0
  66. data/lib/vines/storage.rb +188 -0
  67. data/lib/vines/storage/local.rb +165 -0
  68. data/lib/vines/storage/null.rb +39 -0
  69. data/lib/vines/storage/sql.rb +260 -0
  70. data/lib/vines/store.rb +94 -0
  71. data/lib/vines/stream.rb +247 -0
  72. data/lib/vines/stream/client.rb +84 -0
  73. data/lib/vines/stream/client/auth.rb +74 -0
  74. data/lib/vines/stream/client/auth_restart.rb +29 -0
  75. data/lib/vines/stream/client/bind.rb +72 -0
  76. data/lib/vines/stream/client/bind_restart.rb +24 -0
  77. data/lib/vines/stream/client/closed.rb +13 -0
  78. data/lib/vines/stream/client/ready.rb +17 -0
  79. data/lib/vines/stream/client/session.rb +210 -0
  80. data/lib/vines/stream/client/start.rb +27 -0
  81. data/lib/vines/stream/client/tls.rb +38 -0
  82. data/lib/vines/stream/component.rb +58 -0
  83. data/lib/vines/stream/component/handshake.rb +26 -0
  84. data/lib/vines/stream/component/ready.rb +23 -0
  85. data/lib/vines/stream/component/start.rb +19 -0
  86. data/lib/vines/stream/http.rb +157 -0
  87. data/lib/vines/stream/http/auth.rb +22 -0
  88. data/lib/vines/stream/http/bind.rb +32 -0
  89. data/lib/vines/stream/http/bind_restart.rb +37 -0
  90. data/lib/vines/stream/http/ready.rb +29 -0
  91. data/lib/vines/stream/http/request.rb +172 -0
  92. data/lib/vines/stream/http/session.rb +120 -0
  93. data/lib/vines/stream/http/sessions.rb +65 -0
  94. data/lib/vines/stream/http/start.rb +23 -0
  95. data/lib/vines/stream/parser.rb +78 -0
  96. data/lib/vines/stream/sasl.rb +92 -0
  97. data/lib/vines/stream/server.rb +150 -0
  98. data/lib/vines/stream/server/auth.rb +13 -0
  99. data/lib/vines/stream/server/auth_restart.rb +13 -0
  100. data/lib/vines/stream/server/final_restart.rb +21 -0
  101. data/lib/vines/stream/server/outbound/auth.rb +31 -0
  102. data/lib/vines/stream/server/outbound/auth_restart.rb +20 -0
  103. data/lib/vines/stream/server/outbound/auth_result.rb +32 -0
  104. data/lib/vines/stream/server/outbound/final_features.rb +28 -0
  105. data/lib/vines/stream/server/outbound/final_restart.rb +20 -0
  106. data/lib/vines/stream/server/outbound/start.rb +20 -0
  107. data/lib/vines/stream/server/outbound/tls.rb +30 -0
  108. data/lib/vines/stream/server/outbound/tls_result.rb +34 -0
  109. data/lib/vines/stream/server/ready.rb +24 -0
  110. data/lib/vines/stream/server/start.rb +13 -0
  111. data/lib/vines/stream/server/tls.rb +13 -0
  112. data/lib/vines/stream/state.rb +60 -0
  113. data/lib/vines/token_bucket.rb +55 -0
  114. data/lib/vines/user.rb +123 -0
  115. data/lib/vines/version.rb +5 -0
  116. data/lib/vines/xmpp_server.rb +43 -0
  117. data/vines.gemspec +36 -0
  118. data/web/404.html +51 -0
  119. data/web/apple-touch-icon.png +0 -0
  120. data/web/chat/coffeescripts/chat.coffee +362 -0
  121. data/web/chat/coffeescripts/init.coffee +15 -0
  122. data/web/chat/index.html +16 -0
  123. data/web/chat/javascripts/app.js +1 -0
  124. data/web/chat/stylesheets/chat.css +144 -0
  125. data/web/favicon.png +0 -0
  126. data/web/lib/coffeescripts/button.coffee +25 -0
  127. data/web/lib/coffeescripts/contact.coffee +32 -0
  128. data/web/lib/coffeescripts/filter.coffee +49 -0
  129. data/web/lib/coffeescripts/layout.coffee +30 -0
  130. data/web/lib/coffeescripts/login.coffee +68 -0
  131. data/web/lib/coffeescripts/logout.coffee +5 -0
  132. data/web/lib/coffeescripts/navbar.coffee +84 -0
  133. data/web/lib/coffeescripts/notification.coffee +14 -0
  134. data/web/lib/coffeescripts/router.coffee +40 -0
  135. data/web/lib/coffeescripts/session.coffee +229 -0
  136. data/web/lib/coffeescripts/transfer.coffee +106 -0
  137. data/web/lib/images/dark-gray.png +0 -0
  138. data/web/lib/images/default-user.png +0 -0
  139. data/web/lib/images/light-gray.png +0 -0
  140. data/web/lib/images/logo-large.png +0 -0
  141. data/web/lib/images/logo-small.png +0 -0
  142. data/web/lib/images/white.png +0 -0
  143. data/web/lib/javascripts/base.js +12 -0
  144. data/web/lib/javascripts/icons.js +110 -0
  145. data/web/lib/javascripts/jquery.cookie.js +91 -0
  146. data/web/lib/javascripts/jquery.js +4 -0
  147. data/web/lib/javascripts/raphael.js +6 -0
  148. data/web/lib/javascripts/strophe.js +1 -0
  149. data/web/lib/stylesheets/base.css +385 -0
  150. data/web/lib/stylesheets/login.css +68 -0
  151. metadata +423 -0
@@ -0,0 +1,26 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ class Stream
5
+ class Component
6
+ class Handshake < State
7
+ def initialize(stream, success=Ready)
8
+ super
9
+ end
10
+
11
+ def node(node)
12
+ raise StreamErrors::NotAuthorized unless handshake?(node)
13
+ stream.write('<handshake/>')
14
+ stream.router << stream
15
+ advance
16
+ end
17
+
18
+ private
19
+
20
+ def handshake?(node)
21
+ node.name == 'handshake' && node.text == stream.secret
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,23 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ class Stream
5
+ class Component
6
+ class Ready < State
7
+ def node(node)
8
+ stanza = to_stanza(node)
9
+ raise StreamErrors::UnsupportedStanzaType unless stanza
10
+ to, from = stanza.validate_to, stanza.validate_from
11
+ raise StreamErrors::ImproperAddressing unless to && from
12
+ raise StreamErrors::InvalidFrom unless from.domain == stream.remote_domain
13
+ stream.user = User.new(jid: from)
14
+ if stanza.local? || stanza.to_pubsub_domain?
15
+ stanza.process
16
+ else
17
+ stanza.route
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,19 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ class Stream
5
+ class Component
6
+ class Start < State
7
+ def initialize(stream, success=Handshake)
8
+ super
9
+ end
10
+
11
+ def node(node)
12
+ raise StreamErrors::NotAuthorized unless stream?(node)
13
+ stream.start(node)
14
+ advance
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,157 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ class Stream
5
+ class Http < Client
6
+ attr_accessor :session
7
+
8
+ def initialize(config)
9
+ super
10
+ @session = Http::Session.new(self)
11
+ end
12
+
13
+ # Override +Stream#create_parser+ to provide an HTTP parser rather than
14
+ # a Nokogiri XML parser.
15
+ def create_parser
16
+ @parser = ::Http::Parser.new.tap do |p|
17
+ body = ''
18
+ p.on_body = proc {|data| body << data }
19
+ p.on_message_complete = proc {
20
+ process_request(Request.new(self, @parser, body))
21
+ body = ''
22
+ }
23
+ end
24
+ end
25
+
26
+ # If the session ID is valid, switch this stream's session to the new
27
+ # ID and return true. Some clients, like Google Chrome, reuse one stream
28
+ # for multiple sessions.
29
+ def valid_session?(sid)
30
+ if session = Sessions[sid]
31
+ @session = session
32
+ end
33
+ !!session
34
+ end
35
+
36
+ %w[max_stanza_size max_resources_per_account bind root].each do |name|
37
+ define_method name do |*args|
38
+ config[:http].send(name, *args)
39
+ end
40
+ end
41
+
42
+ def process_request(request)
43
+ if request.path == self.bind && request.options?
44
+ request.reply_to_options
45
+ elsif request.path == self.bind
46
+ body = Nokogiri::XML(request.body).root
47
+ if session = Sessions[body['sid']]
48
+ @session = session
49
+ else
50
+ @session = Http::Session.new(self)
51
+ end
52
+ @session.request(request)
53
+ @nodes.push(body)
54
+ else
55
+ request.reply_with_file(self.root)
56
+ end
57
+ end
58
+
59
+ # Alias the Stream#write method before overriding it so we can call
60
+ # it later from a Session instance.
61
+ alias :stream_write :write
62
+
63
+ # Override Stream#write to queue stanzas rather than immediately writing
64
+ # to the stream. Stanza responses must be paired with a queued request.
65
+ def write(data)
66
+ @session.write(data)
67
+ end
68
+
69
+ # Return an array of Node objects inside the body element.
70
+ # TODO This parses the XML again just to strip namespaces. Figure out
71
+ # Nokogiri namespace handling instead.
72
+ def parse_body(body)
73
+ body.namespace = nil
74
+ body.elements.map do |node|
75
+ Nokogiri::XML(node.to_s.sub(' xmlns="jabber:client"', '')).root
76
+ end
77
+ end
78
+
79
+ def start(node)
80
+ domain, type, hold, wait, rid = %w[to content hold wait rid].map {|a| (node[a] || '').strip }
81
+ version = node.attribute_with_ns('version', NAMESPACES[:bosh]).value rescue nil
82
+
83
+ @session.inactivity = 20
84
+ @session.domain = domain
85
+ @session.content_type = type unless type.empty?
86
+ @session.hold = hold.to_i unless hold.empty?
87
+ @session.wait = wait.to_i unless wait.empty?
88
+
89
+ raise StreamErrors::UndefinedCondition.new('rid required') if rid.empty?
90
+ raise StreamErrors::UnsupportedVersion unless version == '1.0'
91
+ raise StreamErrors::ImproperAddressing unless valid_address?(domain)
92
+ raise StreamErrors::HostUnknown unless config.vhost?(domain)
93
+ raise StreamErrors::InvalidNamespace unless node.namespaces['xmlns'] == NAMESPACES[:http_bind]
94
+
95
+ Sessions[@session.id] = @session
96
+ send_stream_header
97
+ end
98
+
99
+ def terminate
100
+ doc = Nokogiri::XML::Document.new
101
+ node = doc.create_element('body',
102
+ 'type' => 'terminate',
103
+ 'xmlns' => NAMESPACES[:http_bind])
104
+ @session.reply(node)
105
+ close_stream
106
+ end
107
+
108
+ private
109
+
110
+ def send_stream_header
111
+ doc = Nokogiri::XML::Document.new
112
+ node = doc.create_element('body',
113
+ 'charsets' => 'UTF-8',
114
+ 'from' => @session.domain,
115
+ 'hold' => @session.hold,
116
+ 'inactivity' => @session.inactivity,
117
+ 'polling' => '5',
118
+ 'requests' => '2',
119
+ 'sid' => @session.id,
120
+ 'ver' => '1.6',
121
+ 'wait' => @session.wait,
122
+ 'xmpp:version' => '1.0',
123
+ 'xmlns' => NAMESPACES[:http_bind],
124
+ 'xmlns:xmpp' => NAMESPACES[:bosh],
125
+ 'xmlns:stream' => NAMESPACES[:stream])
126
+
127
+ node << doc.create_element('stream:features') do |el|
128
+ el << doc.create_element('mechanisms') do |mechanisms|
129
+ mechanisms.default_namespace = NAMESPACES[:sasl]
130
+ mechanisms << doc.create_element('mechanism', 'PLAIN')
131
+ end
132
+ end
133
+ @session.reply(node)
134
+ end
135
+
136
+ # Override +Stream#send_stream_error+ to wrap the error XML in a BOSH
137
+ # terminate body tag.
138
+ def send_stream_error(e)
139
+ doc = Nokogiri::XML::Document.new
140
+ node = doc.create_element('body',
141
+ 'condition' => 'remote-stream-error',
142
+ 'type' => 'terminate',
143
+ 'xmlns' => NAMESPACES[:http_bind],
144
+ 'xmlns:stream' => NAMESPACES[:stream])
145
+ node.inner_html = e.to_xml
146
+ @session.reply(node)
147
+ end
148
+
149
+ # Override +Stream#close_stream+ to simply close the connection without
150
+ # writing a closing stream tag.
151
+ def close_stream
152
+ close_connection_after_writing
153
+ @session.close
154
+ end
155
+ end
156
+ end
157
+ end
@@ -0,0 +1,22 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ class Stream
5
+ class Http
6
+ class Auth < Client::Auth
7
+ def initialize(stream, success=BindRestart)
8
+ super
9
+ end
10
+
11
+ def node(node)
12
+ unless stream.valid_session?(node['sid']) && body?(node) && node['rid']
13
+ raise StreamErrors::NotAuthorized
14
+ end
15
+ nodes = stream.parse_body(node)
16
+ raise StreamErrors::NotAuthorized unless nodes.size == 1
17
+ super(nodes.first)
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,32 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ class Stream
5
+ class Http
6
+ class Bind < Client::Bind
7
+ FEATURES = %Q{<stream:features xmlns:stream="#{NAMESPACES[:stream]}"/>}.freeze
8
+
9
+ def initialize(stream, success=Ready)
10
+ super
11
+ end
12
+
13
+ def node(node)
14
+ unless stream.valid_session?(node['sid']) && body?(node) && node['rid']
15
+ raise StreamErrors::NotAuthorized
16
+ end
17
+ nodes = stream.parse_body(node)
18
+ raise StreamErrors::NotAuthorized unless nodes.size == 1
19
+ super(nodes.first)
20
+ end
21
+
22
+ private
23
+
24
+ # Override Client::Bind#send_empty_features to properly namespace the
25
+ # empty features element.
26
+ def send_empty_features
27
+ stream.write(FEATURES)
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,37 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ class Stream
5
+ class Http
6
+ class BindRestart < State
7
+ def initialize(stream, success=Bind)
8
+ super
9
+ end
10
+
11
+ def node(node)
12
+ raise StreamErrors::NotAuthorized unless restart?(node)
13
+
14
+ doc = Document.new
15
+ body = doc.create_element('body') do |el|
16
+ el.add_namespace(nil, NAMESPACES[:http_bind])
17
+ el.add_namespace('stream', NAMESPACES[:stream])
18
+ el << doc.create_element('stream:features') do |features|
19
+ features << doc.create_element('bind', 'xmlns' => NAMESPACES[:bind])
20
+ end
21
+ end
22
+ stream.reply(body)
23
+ advance
24
+ end
25
+
26
+ private
27
+
28
+ def restart?(node)
29
+ session = stream.valid_session?(node['sid'])
30
+ restart = node.attribute_with_ns('restart', NAMESPACES[:bosh]).value rescue nil
31
+ domain = node['to'] == stream.domain
32
+ session && body?(node) && domain && restart == 'true' && node['rid']
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,29 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ class Stream
5
+ class Http
6
+ class Ready < Client::Ready
7
+ RID, SID, TYPE, TERMINATE = %w[rid sid type terminate].map {|s| s.freeze }
8
+
9
+ def node(node)
10
+ unless stream.valid_session?(node[SID]) && body?(node) && node[RID]
11
+ raise StreamErrors::NotAuthorized
12
+ end
13
+ stream.parse_body(node).each do |child|
14
+ begin
15
+ super(child)
16
+ rescue StanzaError => e
17
+ stream.error(e)
18
+ end
19
+ end
20
+ stream.terminate if terminate?(node)
21
+ end
22
+
23
+ def terminate?(node)
24
+ node[TYPE] == TERMINATE
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,172 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ class Stream
5
+ class Http
6
+ class Request
7
+ BUF_SIZE = 1024
8
+ MODIFIED = '%a, %d %b %Y %H:%M:%S GMT'.freeze
9
+ MOVED = 'Moved Permanently'.freeze
10
+ NOT_FOUND = 'Not Found'.freeze
11
+ NOT_MODIFIED = 'Not Modified'.freeze
12
+ IF_MODIFIED = 'If-Modified-Since'.freeze
13
+ TEXT_PLAIN = 'text/plain'.freeze
14
+ OPTIONS = 'OPTIONS'.freeze
15
+ CONTENT_TYPES = {
16
+ 'html' => 'text/html; charset="utf-8"',
17
+ 'js' => 'application/javascript; charset="utf-8"',
18
+ 'css' => 'text/css',
19
+ 'png' => 'image/png',
20
+ 'jpg' => 'image/jpeg',
21
+ 'jpeg' => 'image/jpeg',
22
+ 'gif' => 'image/gif',
23
+ 'manifest' => 'text/cache-manifest'
24
+ }.freeze
25
+
26
+ attr_reader :stream, :body, :headers, :method, :path, :url, :query
27
+
28
+ def initialize(stream, parser, body)
29
+ @stream, @body = stream, body
30
+ @headers = parser.headers
31
+ @method = parser.http_method
32
+ @path = parser.request_path
33
+ @url = parser.request_url
34
+ @query = parser.query_string
35
+ @received = Time.now
36
+ end
37
+
38
+ # Return the number of seconds since this request was received.
39
+ def age
40
+ Time.now - @received
41
+ end
42
+
43
+ # Write the requested file to the client out of the given document root
44
+ # directory. Take care to prevent directory traversal attacks with paths
45
+ # like ../../../etc/passwd. Use the If-Modified-Since request header
46
+ # to implement caching.
47
+ def reply_with_file(dir)
48
+ path = File.expand_path(File.join(dir, @path))
49
+
50
+ # redirect requests missing a slash so relative links work
51
+ if File.directory?(path) && !@path.end_with?('/')
52
+ send_status(301, MOVED, "Location: #{redirect_uri}")
53
+ return
54
+ end
55
+
56
+ path = File.join(path, 'index.html') if File.directory?(path)
57
+
58
+ if path.start_with?(dir) && File.exist?(path)
59
+ modified?(path) ? send_file(path) : send_status(304, NOT_MODIFIED)
60
+ else
61
+ missing = File.join(dir, '404.html')
62
+ if File.exist?(missing)
63
+ send_file(missing, 404, NOT_FOUND)
64
+ else
65
+ send_status(404, NOT_FOUND)
66
+ end
67
+ end
68
+ end
69
+
70
+ # Send an HTTP 200 OK response wrapping the XMPP node content back
71
+ # to the client.
72
+ def reply(node, content_type)
73
+ body = node.to_s
74
+ header = [
75
+ "HTTP/1.1 200 OK",
76
+ "Access-Control-Allow-Origin: *",
77
+ "Content-Type: #{content_type}",
78
+ "Content-Length: #{body.bytesize}",
79
+ vroute_cookie
80
+ ].compact.join("\r\n")
81
+ @stream.stream_write([header, body].join("\r\n\r\n"))
82
+ end
83
+
84
+ # Return true if the request method is OPTIONS, signaling a
85
+ # CORS preflight check.
86
+ def options?
87
+ @method == OPTIONS
88
+ end
89
+
90
+ # Send a 200 OK response, allowing any origin domain to connect to the
91
+ # server, in response to CORS preflight OPTIONS requests. This allows
92
+ # any web application using strophe.js to connect to our BOSH port.
93
+ def reply_to_options
94
+ allow = @headers['Access-Control-Request-Headers']
95
+ headers = [
96
+ "Access-Control-Allow-Origin: *",
97
+ "Access-Control-Allow-Methods: POST, GET, OPTIONS",
98
+ "Access-Control-Allow-Headers: #{allow}",
99
+ "Access-Control-Max-Age: #{60 * 60 * 24 * 30}"
100
+ ]
101
+ send_status(200, 'OK', headers)
102
+ end
103
+
104
+ private
105
+
106
+ # Attempt to rebuild the full request URI from the Host header. If it
107
+ # wasn't sent by the client, just return the relative path that
108
+ # was requested. The Location response header must contain the fully
109
+ # qualified URI, but most browsers will accept relative paths as well.
110
+ def redirect_uri
111
+ host = headers['Host']
112
+ uri = "#{path}/"
113
+ uri = "#{uri}?#{query}" unless (query || '').empty?
114
+ uri = "http://#{host}#{uri}" if host
115
+ uri
116
+ end
117
+
118
+ # Return true if the file has been modified since the client last
119
+ # requested it with the If-Modified-Since header.
120
+ def modified?(path)
121
+ @headers[IF_MODIFIED] != mtime(path)
122
+ end
123
+
124
+ def mtime(path)
125
+ File.mtime(path).utc.strftime(MODIFIED)
126
+ end
127
+
128
+ def send_status(status, message, *headers)
129
+ header = [
130
+ "HTTP/1.1 #{status} #{message}",
131
+ "Content-Length: 0",
132
+ *headers
133
+ ].join("\r\n")
134
+ @stream.stream_write("#{header}\r\n\r\n")
135
+ end
136
+
137
+ # Stream the contents of the file to the client in a 200 OK response.
138
+ # Send a Last-Modified response header so clients can send us an
139
+ # If-Modified-Since request header for caching.
140
+ def send_file(path, status=200, message='OK')
141
+ header = [
142
+ "HTTP/1.1 #{status} #{message}",
143
+ "Content-Type: #{content_type(path)}",
144
+ "Content-Length: #{File.size(path)}",
145
+ "Last-Modified: #{mtime(path)}"
146
+ ].join("\r\n")
147
+ @stream.stream_write("#{header}\r\n\r\n")
148
+
149
+ File.open(path) do |file|
150
+ while (buf = file.read(BUF_SIZE)) != nil
151
+ @stream.stream_write(buf)
152
+ end
153
+ end
154
+ end
155
+
156
+ def content_type(path)
157
+ ext = File.extname(path).sub('.', '')
158
+ CONTENT_TYPES[ext] || TEXT_PLAIN
159
+ end
160
+
161
+ # Provide a vroute cookie in each response that uniquely identifies this
162
+ # HTTP server. Reverse proxy servers (nginx/apache) can use this cookie
163
+ # to implement sticky sessions. Return nil if vroute was not set in
164
+ # config.rb and no cookie should be sent.
165
+ def vroute_cookie
166
+ route = @stream.config[:http].vroute
167
+ route ? "Set-Cookie: vroute=#{route}; path=/; HttpOnly" : nil
168
+ end
169
+ end
170
+ end
171
+ end
172
+ end