local sm = require "core.sessionmanager";
local sm_destroy_session = sm.destroy_session;
local new_uuid = require "util.uuid".generate;
-local fire_event = prosody.events.fire_event;
local core_process_stanza = prosody.core_process_stanza;
local st = require "util.stanza";
local logger = require "util.logger";
local log = logger.init("mod_bosh");
local initialize_filters = require "util.filters".initialize;
local math_min = math.min;
-local xpcall, tostring, type = xpcall, tostring, type;
+local tostring, type = tostring, type;
local traceback = debug.traceback;
local runner = require"util.async".runner;
+local nameprep = require "util.encodings".stringprep.nameprep;
local xmlns_streams = "http://etherx.jabber.org/streams";
local xmlns_xmpp_streams = "urn:ietf:params:xml:ns:xmpp-streams";
local os_time = os.time;
-- All sessions, and sessions that have no requests open
-local sessions = module:shared("sessions");
+local sessions, inactive_sessions = module:shared("sessions", "inactive_sessions");
-- Used to respond to idle sessions (those with waiting requests)
+local waiting_requests = module:shared("waiting_requests");
function on_destroy_request(request)
log("debug", "Request destroyed: %s", tostring(request));
+ waiting_requests[request] = nil;
local session = sessions[request.context.sid];
if session then
local requests = session.requests;
-- If this session now has no requests open, mark it as inactive
local max_inactive = session.bosh_max_inactive;
if max_inactive and #requests == 0 then
- if session.inactive_timer then
- session.inactive_timer:stop();
- end
- session.inactive_timer = module:add_timer(max_inactive, check_inactive, session, request.context,
- "BOSH client silent for over "..max_inactive.." seconds");
+ inactive_sessions[session] = os_time() + max_inactive;
(session.log or log)("debug", "BOSH session marked as inactive (for %ds)", max_inactive);
end
- if session.bosh_wait_timer then
- session.bosh_wait_timer:stop();
- session.bosh_wait_timer = nil;
- end
- end
-end
-
-function check_inactive(now, session, context, reason)
- if not sessions.destroyed then
- sessions[context.sid] = nil;
- sm_destroy_session(session, reason);
end
end
local headers = response.headers;
headers.content_type = "text/xml; charset=utf-8";
- if cross_domain and request.headers.origin then
+ if cross_domain and event.request.headers.origin then
set_cross_domain_headers(response);
end
-- In particular, the streamopened() stream callback is where
-- much of the session logic happens, because it's where we first
-- get to see the 'sid' of this request.
- if not stream:feed(body) then
- module:log("warn", "Error parsing BOSH payload")
- return 400;
+ local ok, err = stream:feed(body);
+ if not ok then
+ module:log("warn", "Error parsing BOSH payload; %s", err)
+ local close_reply = st.stanza("body", { xmlns = xmlns_bosh, type = "terminate",
+ ["xmlns:stream"] = xmlns_streams, condition = "bad-request" });
+ return tostring(close_reply);
end
-- Stanzas (if any) in the request have now been processed, and
else
return true; -- Inform http server we shall reply later
end
+ elseif response.finished then
+ return; -- A response has been sent already
end
module:log("warn", "Unable to associate request with a session (incomplete request?)");
- return 400;
+ local close_reply = st.stanza("body", { xmlns = xmlns_bosh, type = "terminate",
+ ["xmlns:stream"] = xmlns_streams, condition = "item-not-found" });
+ return tostring(close_reply) .. "\n";
end
-function after_bosh_wait(now, request, session)
+function after_bosh_wait(now, request, session) -- luacheck: ignore 212
if request.conn then
session.send("");
end
-- New session request
context.notopen = nil; -- Signals that we accept this opening tag
- -- TODO: Sanity checks here (rid, to, known host, etc.)
- if not hosts[attr.to] then
+ local to_host = nameprep(attr.to);
+ local rid = tonumber(attr.rid);
+ local wait = tonumber(attr.wait);
+ if not to_host then
+ log("debug", "BOSH client tried to connect to invalid host: %s", tostring(attr.to));
+ local close_reply = st.stanza("body", { xmlns = xmlns_bosh, type = "terminate",
+ ["xmlns:stream"] = xmlns_streams, condition = "improper-addressing" });
+ response:send(tostring(close_reply));
+ return;
+ elseif not hosts[to_host] then
-- Unknown host
log("debug", "BOSH client tried to connect to unknown host: %s", tostring(attr.to));
local close_reply = st.stanza("body", { xmlns = xmlns_bosh, type = "terminate",
response:send(tostring(close_reply));
return;
end
+ if not rid or (not wait and attr.wait or wait < 0 or wait % 1 ~= 0) then
+ log("debug", "BOSH client sent invalid rid or wait attributes: rid=%s, wait=%s", tostring(attr.rid), tostring(attr.wait));
+ local close_reply = st.stanza("body", { xmlns = xmlns_bosh, type = "terminate",
+ ["xmlns:stream"] = xmlns_streams, condition = "bad-request" });
+ response:send(tostring(close_reply));
+ return;
+ end
+
+ rid = rid - 1;
+ wait = math_min(wait, bosh_max_wait);
-- New session
sid = new_uuid();
local session = {
- type = "c2s_unauthed", conn = {}, sid = sid, rid = tonumber(attr.rid)-1, host = attr.to,
- bosh_version = attr.ver, bosh_wait = math_min(attr.wait, bosh_max_wait), streamid = sid,
+ type = "c2s_unauthed", conn = {}, sid = sid, rid = rid, host = attr.to,
+ bosh_version = attr.ver, bosh_wait = wait, streamid = sid,
bosh_hold = BOSH_DEFAULT_HOLD, bosh_max_inactive = BOSH_DEFAULT_INACTIVITY,
requests = { }, send_buffer = {}, reset_stream = bosh_reset_stream,
close = bosh_close_stream, dispatch_stanza = core_process_stanza, notopen = true,
end
s = filter("stanzas/out", s);
--log("debug", "Sending BOSH data: %s", tostring(s));
+ if not s then return true end
t_insert(session.send_buffer, tostring(s));
local oldest_request = r[1];
local function handleerr(err) log("error", "Traceback[bosh]: %s", traceback(tostring(err), 2)); end
-function runner_callbacks:error(err)
+function runner_callbacks:error(err) -- luacheck: ignore 212/self
return handleerr(err);
end
log("debug", "Error parsing BOSH request payload; %s", error);
if not context.sid then
local response = context.response;
- response.status_code = 400;
- response:send();
+ local close_reply = st.stanza("body", { xmlns = xmlns_bosh, type = "terminate",
+ ["xmlns:stream"] = xmlns_streams, condition = "bad-request" });
+ response:send(tostring(close_reply));
return;
end
};
body = [[<html><body>
<p>It works! Now point your BOSH client to this URL to connect to Prosody.</p>
- <p>For more information see <a href="http://prosody.im/doc/setting_up_bosh">Prosody: Setting up BOSH</a>.</p>
+ <p>For more information see <a href="https://prosody.im/doc/setting_up_bosh">Prosody: Setting up BOSH</a>.</p>
</body></html>]];
};