diaspora-vines 0.1.2
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.
- checksums.yaml +7 -0
- data/Gemfile +3 -0
- data/LICENSE +19 -0
- data/README.md +7 -0
- data/Rakefile +23 -0
- data/bin/vines +4 -0
- data/conf/certs/README +39 -0
- data/conf/certs/ca-bundle.crt +3895 -0
- data/conf/config.rb +42 -0
- data/lib/vines/cli.rb +132 -0
- data/lib/vines/cluster/connection.rb +26 -0
- data/lib/vines/cluster/publisher.rb +55 -0
- data/lib/vines/cluster/pubsub.rb +92 -0
- data/lib/vines/cluster/sessions.rb +125 -0
- data/lib/vines/cluster/subscriber.rb +108 -0
- data/lib/vines/cluster.rb +246 -0
- data/lib/vines/command/bcrypt.rb +12 -0
- data/lib/vines/command/cert.rb +50 -0
- data/lib/vines/command/init.rb +68 -0
- data/lib/vines/command/ldap.rb +38 -0
- data/lib/vines/command/restart.rb +12 -0
- data/lib/vines/command/schema.rb +24 -0
- data/lib/vines/command/start.rb +28 -0
- data/lib/vines/command/stop.rb +18 -0
- data/lib/vines/config/host.rb +125 -0
- data/lib/vines/config/port.rb +132 -0
- data/lib/vines/config/pubsub.rb +108 -0
- data/lib/vines/config.rb +223 -0
- data/lib/vines/contact.rb +111 -0
- data/lib/vines/daemon.rb +78 -0
- data/lib/vines/error.rb +150 -0
- data/lib/vines/jid.rb +95 -0
- data/lib/vines/kit.rb +23 -0
- data/lib/vines/log.rb +24 -0
- data/lib/vines/router.rb +179 -0
- data/lib/vines/stanza/iq/auth.rb +18 -0
- data/lib/vines/stanza/iq/disco_info.rb +45 -0
- data/lib/vines/stanza/iq/disco_items.rb +29 -0
- data/lib/vines/stanza/iq/error.rb +16 -0
- data/lib/vines/stanza/iq/ping.rb +16 -0
- data/lib/vines/stanza/iq/private_storage.rb +83 -0
- data/lib/vines/stanza/iq/query.rb +10 -0
- data/lib/vines/stanza/iq/result.rb +16 -0
- data/lib/vines/stanza/iq/roster.rb +140 -0
- data/lib/vines/stanza/iq/session.rb +17 -0
- data/lib/vines/stanza/iq/vcard.rb +56 -0
- data/lib/vines/stanza/iq/version.rb +25 -0
- data/lib/vines/stanza/iq.rb +48 -0
- data/lib/vines/stanza/message.rb +40 -0
- data/lib/vines/stanza/presence/error.rb +23 -0
- data/lib/vines/stanza/presence/probe.rb +37 -0
- data/lib/vines/stanza/presence/subscribe.rb +42 -0
- data/lib/vines/stanza/presence/subscribed.rb +51 -0
- data/lib/vines/stanza/presence/unavailable.rb +15 -0
- data/lib/vines/stanza/presence/unsubscribe.rb +38 -0
- data/lib/vines/stanza/presence/unsubscribed.rb +38 -0
- data/lib/vines/stanza/presence.rb +141 -0
- data/lib/vines/stanza/pubsub/create.rb +39 -0
- data/lib/vines/stanza/pubsub/delete.rb +41 -0
- data/lib/vines/stanza/pubsub/publish.rb +66 -0
- data/lib/vines/stanza/pubsub/subscribe.rb +44 -0
- data/lib/vines/stanza/pubsub/unsubscribe.rb +30 -0
- data/lib/vines/stanza/pubsub.rb +22 -0
- data/lib/vines/stanza.rb +175 -0
- data/lib/vines/storage/ldap.rb +71 -0
- data/lib/vines/storage/local.rb +139 -0
- data/lib/vines/storage/null.rb +39 -0
- data/lib/vines/storage/sql.rb +138 -0
- data/lib/vines/storage.rb +239 -0
- data/lib/vines/store.rb +110 -0
- data/lib/vines/stream/client/auth.rb +74 -0
- data/lib/vines/stream/client/auth_restart.rb +29 -0
- data/lib/vines/stream/client/bind.rb +72 -0
- data/lib/vines/stream/client/bind_restart.rb +24 -0
- data/lib/vines/stream/client/closed.rb +13 -0
- data/lib/vines/stream/client/ready.rb +17 -0
- data/lib/vines/stream/client/session.rb +210 -0
- data/lib/vines/stream/client/start.rb +27 -0
- data/lib/vines/stream/client/tls.rb +38 -0
- data/lib/vines/stream/client.rb +84 -0
- data/lib/vines/stream/component/handshake.rb +26 -0
- data/lib/vines/stream/component/ready.rb +23 -0
- data/lib/vines/stream/component/start.rb +19 -0
- data/lib/vines/stream/component.rb +58 -0
- data/lib/vines/stream/http/auth.rb +22 -0
- data/lib/vines/stream/http/bind.rb +32 -0
- data/lib/vines/stream/http/bind_restart.rb +37 -0
- data/lib/vines/stream/http/ready.rb +29 -0
- data/lib/vines/stream/http/request.rb +172 -0
- data/lib/vines/stream/http/session.rb +120 -0
- data/lib/vines/stream/http/sessions.rb +65 -0
- data/lib/vines/stream/http/start.rb +23 -0
- data/lib/vines/stream/http.rb +157 -0
- data/lib/vines/stream/parser.rb +79 -0
- data/lib/vines/stream/sasl.rb +128 -0
- data/lib/vines/stream/server/auth.rb +13 -0
- data/lib/vines/stream/server/auth_restart.rb +13 -0
- data/lib/vines/stream/server/final_restart.rb +21 -0
- data/lib/vines/stream/server/outbound/auth.rb +31 -0
- data/lib/vines/stream/server/outbound/auth_restart.rb +20 -0
- data/lib/vines/stream/server/outbound/auth_result.rb +32 -0
- data/lib/vines/stream/server/outbound/final_features.rb +28 -0
- data/lib/vines/stream/server/outbound/final_restart.rb +20 -0
- data/lib/vines/stream/server/outbound/start.rb +20 -0
- data/lib/vines/stream/server/outbound/tls.rb +30 -0
- data/lib/vines/stream/server/outbound/tls_result.rb +34 -0
- data/lib/vines/stream/server/ready.rb +24 -0
- data/lib/vines/stream/server/start.rb +13 -0
- data/lib/vines/stream/server/tls.rb +13 -0
- data/lib/vines/stream/server.rb +150 -0
- data/lib/vines/stream/state.rb +60 -0
- data/lib/vines/stream.rb +247 -0
- data/lib/vines/token_bucket.rb +55 -0
- data/lib/vines/user.rb +123 -0
- data/lib/vines/version.rb +6 -0
- data/lib/vines/xmpp_server.rb +25 -0
- data/lib/vines.rb +203 -0
- data/test/cluster/publisher_test.rb +57 -0
- data/test/cluster/sessions_test.rb +47 -0
- data/test/cluster/subscriber_test.rb +109 -0
- data/test/config/host_test.rb +369 -0
- data/test/config/pubsub_test.rb +187 -0
- data/test/config_test.rb +732 -0
- data/test/contact_test.rb +102 -0
- data/test/error_test.rb +58 -0
- data/test/ext/nokogiri.rb +14 -0
- data/test/jid_test.rb +147 -0
- data/test/kit_test.rb +31 -0
- data/test/router_test.rb +243 -0
- data/test/stanza/iq/disco_info_test.rb +78 -0
- data/test/stanza/iq/disco_items_test.rb +49 -0
- data/test/stanza/iq/private_storage_test.rb +184 -0
- data/test/stanza/iq/roster_test.rb +229 -0
- data/test/stanza/iq/session_test.rb +25 -0
- data/test/stanza/iq/vcard_test.rb +146 -0
- data/test/stanza/iq/version_test.rb +64 -0
- data/test/stanza/iq_test.rb +70 -0
- data/test/stanza/message_test.rb +126 -0
- data/test/stanza/presence/probe_test.rb +50 -0
- data/test/stanza/presence/subscribe_test.rb +83 -0
- data/test/stanza/pubsub/create_test.rb +116 -0
- data/test/stanza/pubsub/delete_test.rb +169 -0
- data/test/stanza/pubsub/publish_test.rb +309 -0
- data/test/stanza/pubsub/subscribe_test.rb +205 -0
- data/test/stanza/pubsub/unsubscribe_test.rb +148 -0
- data/test/stanza_test.rb +85 -0
- data/test/storage/ldap_test.rb +201 -0
- data/test/storage/local_test.rb +59 -0
- data/test/storage/mock_redis.rb +97 -0
- data/test/storage/null_test.rb +29 -0
- data/test/storage/storage_tests.rb +182 -0
- data/test/storage_test.rb +85 -0
- data/test/store_test.rb +130 -0
- data/test/stream/client/auth_test.rb +137 -0
- data/test/stream/client/ready_test.rb +47 -0
- data/test/stream/client/session_test.rb +27 -0
- data/test/stream/component/handshake_test.rb +52 -0
- data/test/stream/component/ready_test.rb +103 -0
- data/test/stream/component/start_test.rb +39 -0
- data/test/stream/http/auth_test.rb +70 -0
- data/test/stream/http/ready_test.rb +86 -0
- data/test/stream/http/request_test.rb +209 -0
- data/test/stream/http/sessions_test.rb +49 -0
- data/test/stream/http/start_test.rb +50 -0
- data/test/stream/parser_test.rb +122 -0
- data/test/stream/sasl_test.rb +195 -0
- data/test/stream/server/auth_test.rb +61 -0
- data/test/stream/server/outbound/auth_test.rb +75 -0
- data/test/stream/server/ready_test.rb +98 -0
- data/test/test_helper.rb +42 -0
- data/test/token_bucket_test.rb +44 -0
- data/test/user_test.rb +96 -0
- data/vines.gemspec +30 -0
- metadata +387 -0
@@ -0,0 +1,58 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module Vines
|
4
|
+
class Stream
|
5
|
+
|
6
|
+
# Implements the XMPP protocol for trusted, external component (XEP-0114)
|
7
|
+
# streams. This serves connected streams using the jabber:component:accept
|
8
|
+
# namespace.
|
9
|
+
class Component < Stream
|
10
|
+
attr_reader :remote_domain
|
11
|
+
|
12
|
+
def initialize(config)
|
13
|
+
super
|
14
|
+
@remote_domain = nil
|
15
|
+
@stream_id = Kit.uuid
|
16
|
+
advance(Start.new(self))
|
17
|
+
end
|
18
|
+
|
19
|
+
def max_stanza_size
|
20
|
+
config[:component].max_stanza_size
|
21
|
+
end
|
22
|
+
|
23
|
+
def ready?
|
24
|
+
state.class == Component::Ready
|
25
|
+
end
|
26
|
+
|
27
|
+
def stream_type
|
28
|
+
:component
|
29
|
+
end
|
30
|
+
|
31
|
+
def start(node)
|
32
|
+
@remote_domain = node['to']
|
33
|
+
send_stream_header
|
34
|
+
raise StreamErrors::ImproperAddressing unless valid_address?(@remote_domain)
|
35
|
+
raise StreamErrors::HostUnknown unless config.component?(@remote_domain)
|
36
|
+
raise StreamErrors::InvalidNamespace unless node.namespaces['xmlns'] == NAMESPACES[:component]
|
37
|
+
raise StreamErrors::InvalidNamespace unless node.namespaces['xmlns:stream'] == NAMESPACES[:stream]
|
38
|
+
end
|
39
|
+
|
40
|
+
def secret
|
41
|
+
password = config.component_password(@remote_domain)
|
42
|
+
Digest::SHA1.hexdigest(@stream_id + password)
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def send_stream_header
|
48
|
+
attrs = {
|
49
|
+
'xmlns' => NAMESPACES[:component],
|
50
|
+
'xmlns:stream' => NAMESPACES[:stream],
|
51
|
+
'id' => @stream_id,
|
52
|
+
'from' => @remote_domain
|
53
|
+
}
|
54
|
+
write "<stream:stream %s>" % attrs.to_a.map{|k,v| "#{k}='#{v}'"}.join(' ')
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
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
|
@@ -0,0 +1,120 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module Vines
|
4
|
+
class Stream
|
5
|
+
class Http
|
6
|
+
class Session < Client::Session
|
7
|
+
include Nokogiri::XML
|
8
|
+
|
9
|
+
attr_accessor :content_type, :hold, :inactivity, :wait
|
10
|
+
|
11
|
+
CONTENT_TYPE = 'text/xml; charset=utf-8'.freeze
|
12
|
+
|
13
|
+
def initialize(stream)
|
14
|
+
super
|
15
|
+
@state = Http::Start.new(stream)
|
16
|
+
@inactivity, @wait, @hold = 20, 60, 1
|
17
|
+
@replied = Time.now
|
18
|
+
@requests, @responses = [], []
|
19
|
+
@content_type = CONTENT_TYPE
|
20
|
+
end
|
21
|
+
|
22
|
+
def close
|
23
|
+
Sessions.delete(@id)
|
24
|
+
router.delete(self)
|
25
|
+
delete_from_cluster
|
26
|
+
unsubscribe_pubsub
|
27
|
+
@requests.each {|req| req.stream.close_connection }
|
28
|
+
@requests.clear
|
29
|
+
@responses.clear
|
30
|
+
@state = Client::Closed.new(nil)
|
31
|
+
@unbound = true
|
32
|
+
@available = false
|
33
|
+
broadcast_unavailable
|
34
|
+
end
|
35
|
+
|
36
|
+
def ready?
|
37
|
+
@state.class == Http::Ready
|
38
|
+
end
|
39
|
+
|
40
|
+
def requests
|
41
|
+
@requests.clone
|
42
|
+
end
|
43
|
+
|
44
|
+
def expired?
|
45
|
+
respond_to_expired_requests
|
46
|
+
@requests.empty? && (Time.now - @replied > @inactivity)
|
47
|
+
end
|
48
|
+
|
49
|
+
# Resume this session from its most recent state with a new client
|
50
|
+
# stream and incoming node.
|
51
|
+
def resume(stream, node)
|
52
|
+
stream.session.requests.each do |req|
|
53
|
+
request(req)
|
54
|
+
end
|
55
|
+
stream.session = self
|
56
|
+
@state.stream = stream
|
57
|
+
@state.node(node)
|
58
|
+
end
|
59
|
+
|
60
|
+
def request(request)
|
61
|
+
if @responses.any?
|
62
|
+
request.reply(wrap_body(@responses.join), @content_type)
|
63
|
+
@replied = Time.now
|
64
|
+
@responses.clear
|
65
|
+
else
|
66
|
+
while @requests.size >= @hold
|
67
|
+
@requests.shift.reply(wrap_body(''), @content_type)
|
68
|
+
@replied = Time.now
|
69
|
+
end
|
70
|
+
@requests << request
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# Send an HTTP 200 OK response wrapping the XMPP node content back
|
75
|
+
# to the client.
|
76
|
+
def reply(node)
|
77
|
+
if request = @requests.shift
|
78
|
+
request.reply(node, @content_type)
|
79
|
+
@replied = Time.now
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# Write the XMPP node to the client stream after wrapping it in a BOSH
|
84
|
+
# body tag. If there's a waiting request, the node is written
|
85
|
+
# immediately. If not, it's queued until the next request arrives.
|
86
|
+
def write(node)
|
87
|
+
if request = @requests.shift
|
88
|
+
request.reply(wrap_body(node), @content_type)
|
89
|
+
@replied = Time.now
|
90
|
+
else
|
91
|
+
@responses << node.to_s
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def unbind!(stream)
|
96
|
+
@requests.reject! {|req| req.stream == stream }
|
97
|
+
end
|
98
|
+
|
99
|
+
private
|
100
|
+
|
101
|
+
def respond_to_expired_requests
|
102
|
+
expired = @requests.select {|req| req.age > @wait }
|
103
|
+
expired.each do |request|
|
104
|
+
request.reply(wrap_body(''), @content_type)
|
105
|
+
@requests.delete(request)
|
106
|
+
@replied = Time.now
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def wrap_body(data)
|
111
|
+
doc = Document.new
|
112
|
+
doc.create_element('body') do |node|
|
113
|
+
node.add_namespace(nil, NAMESPACES[:http_bind])
|
114
|
+
node.inner_html = data.to_s
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module Vines
|
4
|
+
class Stream
|
5
|
+
class Http
|
6
|
+
# Sessions is a cache of Http::Session objects for transient HTTP
|
7
|
+
# connections. The cache is monitored for expired client connections.
|
8
|
+
class Sessions
|
9
|
+
include Vines::Log
|
10
|
+
|
11
|
+
@@instance = nil
|
12
|
+
def self.instance
|
13
|
+
@@instance ||= self.new
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.[](sid)
|
17
|
+
instance[sid]
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.[]=(sid, session)
|
21
|
+
instance[sid] = session
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.delete(sid)
|
25
|
+
instance.delete(sid)
|
26
|
+
end
|
27
|
+
|
28
|
+
def initialize
|
29
|
+
@sessions = {}
|
30
|
+
start_timer
|
31
|
+
end
|
32
|
+
|
33
|
+
def []=(sid, session)
|
34
|
+
@sessions[sid] = session
|
35
|
+
end
|
36
|
+
|
37
|
+
def [](sid)
|
38
|
+
@sessions[sid]
|
39
|
+
end
|
40
|
+
|
41
|
+
def delete(sid)
|
42
|
+
@sessions.delete(sid)
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
# Check for expired clients to cleanup every second.
|
48
|
+
def start_timer
|
49
|
+
@timer ||= EventMachine::PeriodicTimer.new(1) { cleanup }
|
50
|
+
end
|
51
|
+
|
52
|
+
# Remove cached information for all expired connections. An expired
|
53
|
+
# HTTP client is one that has no queued requests and has had no activity
|
54
|
+
# for over 20 seconds.
|
55
|
+
def cleanup
|
56
|
+
@sessions.each_value do |session|
|
57
|
+
session.close if session.expired?
|
58
|
+
end
|
59
|
+
rescue => e
|
60
|
+
log.error("Expired session cleanup failed: #{e}")
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module Vines
|
4
|
+
class Stream
|
5
|
+
class Http
|
6
|
+
class Start < State
|
7
|
+
def initialize(stream, success=Auth)
|
8
|
+
super
|
9
|
+
end
|
10
|
+
|
11
|
+
def node(node)
|
12
|
+
raise StreamErrors::NotAuthorized unless body?(node)
|
13
|
+
if session = Sessions[node['sid']]
|
14
|
+
session.resume(stream, node)
|
15
|
+
else
|
16
|
+
stream.start(node)
|
17
|
+
advance
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|