Commit 59998a2f authored by Alex Wenckus's avatar Alex Wenckus Committed by alex

Work on version 1.6 of BOSH. JM-1033

git-svn-id: http://svn.igniterealtime.org/svn/repos/openfire/trunk@8043 b35dd754-fafc-0310-a699-88a17e54d16e
parent feda358a
/**
* $Revision:$
* $Date:$
*
* Copyright (C) 2007 Jive Software. All rights reserved.
* This software is the proprietary information of Jive Software. Use is subject to license terms.
*/
package org.jivesoftware.openfire.http;
import javax.servlet.http.HttpServletResponse;
/**
* An enum defining all errors which can happen during a BOSH session.
*/
public enum BoshBindingError {
/**
* The format of an HTTP header or binding element received from the client is unacceptable
* (e.g., syntax error), or Script Syntax is not supported.
*/
badRequest(Type.terminal, "bad-request", HttpServletResponse.SC_BAD_REQUEST),
/**
* The target domain specified in the 'to' attribute or the target host or port specified in the
* 'route' attribute is no longer serviced by the connection manager.
*/
hostGone(Type.terminal, "host-gone"),
/**
* The target domain specified in the 'to' attribute or the target host or port specified in the
* 'route' attribute is unknown to the connection manager.
*/
hostUnknown(Type.terminal, "host-unknown"),
/**
* The initialization element lacks a 'to' or 'route' attribute (or the attribute has no value)
* but the connection manager requires one.
*/
improperAddressing(Type.terminal, "improper-addressing"),
/**
* The connection manager has experienced an internal error that prevents it from servicing the
* request.
*/
internalServerError(Type.terminal, "internal-server-error"),
/**
* (1) 'sid' is not valid, (2) 'stream' is not valid, (3) 'rid' is larger than the upper limit
* of the expected window, (4) connection manager is unable to resend response, (5) 'key'
* sequence is invalid
*/
itemNotFound(Type.terminal, "item-not-found", HttpServletResponse.SC_NOT_FOUND),
/**
* Another request being processed at the same time as this request caused the session to
* terminate.
*/
otherRequest(Type.terminal, "other-request"),
/**
* The client has broken the session rules (polling too frequently, requesting too frequently,
* too many simultaneous requests).
*/
policyViolation(Type.terminal, "policy-violation",
HttpServletResponse.SC_FORBIDDEN),
/**
* The connection manager was unable to connect to, or unable to connect securely to, or has
* lost its connection to, the server.
*/
remoteConnectionFailed(Type.terminal, "remote-connection-failed"),
/**
* Encapsulates an error in the protocol being transported.
*/
remoteStreamError(Type.terminal, "remote-stream-error"),
/**
* The connection manager does not operate at this URI (e.g., the connection manager accepts
* only SSL or TLS connections at some https: URI rather than the http: URI requested by the
* client). The client may try POSTing to the URI in the content of the <uri/> child
* element.
*/
seeOtherUri(Type.terminal, "see-other-uri"),
/**
* The connection manager is being shut down. All active HTTP sessions are being terminated. No
* new sessions can be created.
*/
systemShutdown(Type.terminal, "system-shutdown"),
/**
* The error is not one of those defined herein; the connection manager SHOULD include
* application-specific information in the content of the <body> wrapper.
*/
undefinedCondition(Type.terminal, "undefined-condition");
private Type errorType;
private String condition;
private int legacyErrorCode = HttpServletResponse.SC_BAD_REQUEST;
BoshBindingError(Type errorType, String condition, int legacyErrorCode) {
this(errorType, condition);
this.legacyErrorCode = legacyErrorCode;
}
BoshBindingError(Type errorType, String condition) {
this.errorType = errorType;
this.condition = condition;
}
public Type getErrorType() {
return errorType;
}
/**
* Returns the condition that caused the binding error. This should be returned to the client
* so that the client can take appropriate action.
*
* @return the condition that caused the binding error.
*/
public String getCondition() {
return condition;
}
/**
* Returns the legacy HTTP error code which is related to the binding error. With the 1.6
* version of BOSH the use of HTTP errors was deprecated in favor of using errors inside of the
* response to the client so that they could be more easily processed on the client side.
*
* @return the legacy HTTP error code which is related to the binding error.
*/
public int getLegacyErrorCode() {
return legacyErrorCode;
}
public enum Type {
/**
* The terminal error condition prevents the client from making any further requests until a
* new session is established.
*/
terminal(null),
/**
* In the case of a recoverable binding error the client MUST repeat the HTTP request and
* all the preceding HTTP requests that have not received responses. The content of these
* requests MUST be identical to the <body> elements of the original requests. This
* allows the connection manager to recover a session after the previous request was lost
* due to a communication failure.
*/
recoverable("error");
private String type;
Type(String type) {
this.type = type;
}
/**
* Returns the type that will be displayed to the client.
*
* @return the type that will be displayed to the client.
*/
public String getType() {
if (type == null) {
return name();
}
else {
return type;
}
}
}
}
......@@ -15,20 +15,18 @@ package org.jivesoftware.openfire.http;
*
*/
public class HttpBindException extends Exception {
private boolean shouldCloseSession;
private int httpError;
private BoshBindingError error;
public HttpBindException(String message, boolean shouldCloseSession, int httpError) {
public HttpBindException(String message, BoshBindingError error) {
super(message);
this.shouldCloseSession = shouldCloseSession;
this.httpError = httpError;
this.error = error;
}
public int getHttpError() {
return httpError;
public BoshBindingError getBindingError() {
return error;
}
public boolean shouldCloseSession() {
return shouldCloseSession;
return error.getErrorType() == BoshBindingError.Type.terminal;
}
}
......@@ -13,6 +13,7 @@ package org.jivesoftware.openfire.http;
import org.xmlpull.v1.XmlPullParserFactory;
import org.xmlpull.v1.XmlPullParserException;
import org.jivesoftware.util.Log;
import org.jivesoftware.util.JiveGlobals;
import org.jivesoftware.openfire.net.MXParser;
import org.jivesoftware.openfire.auth.UnauthorizedException;
import org.dom4j.io.XMPPPacketReader;
......@@ -80,13 +81,19 @@ public class HttpBindServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException
{
boolean isScriptSyntaxEnabled =
JiveGlobals.getBooleanProperty("xmpp.httpbind.scriptSyntax.enabled", false);
if(!isScriptSyntaxEnabled) {
sendLegacyError(response, BoshBindingError.itemNotFound);
return;
}
if (isContinuation(request, response)) {
return;
}
String queryString = request.getQueryString();
if (queryString == null || "".equals(queryString)) {
response.sendError(HttpServletResponse.SC_BAD_REQUEST,
"Unable to parse request content");
sendLegacyError(response, BoshBindingError.badRequest);
return;
}
queryString = URLDecoder.decode(queryString, "utf-8");
......@@ -94,6 +101,12 @@ public class HttpBindServlet extends HttpServlet {
parseDocument(request, response, new ByteArrayInputStream(queryString.getBytes()));
}
private void sendLegacyError(HttpServletResponse response, BoshBindingError error)
throws IOException
{
response.sendError(error.getLegacyErrorCode());
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
......@@ -115,16 +128,14 @@ public class HttpBindServlet extends HttpServlet {
}
catch (Exception e) {
Log.warn("Error parsing user request. [" + request.getRemoteAddr() + "]");
response.sendError(HttpServletResponse.SC_BAD_REQUEST,
"Unable to parse request content: " + e.getMessage());
sendLegacyError(response, BoshBindingError.badRequest);
return;
}
Element node = document.getRootElement();
if (node == null || !"body".equals(node.getName())) {
Log.warn("Body missing from request content. [" + request.getRemoteAddr() + "]");
response.sendError(HttpServletResponse.SC_BAD_REQUEST,
"Body missing from request content.");
sendLegacyError(response, BoshBindingError.badRequest);
return;
}
......@@ -151,15 +162,40 @@ public class HttpBindServlet extends HttpServlet {
request.getMethod());
}
catch (HttpBindException e) {
response.sendError(e.getHttpError(), e.getMessage());
if (e.shouldCloseSession()) {
session.close();
}
sendError(request, response, e.getBindingError(), session);
}
}
return true;
}
private void sendError(HttpServletRequest request, HttpServletResponse response,
BoshBindingError bindingError, HttpSession session)
throws IOException
{
try {
if (session.getVersion() >= 1.6) {
respond(response, createErrorBody(bindingError.getErrorType().getType(),
bindingError.getCondition()), request.getMethod());
}
else {
sendLegacyError(response, bindingError);
}
}
finally {
if (bindingError.getErrorType() == BoshBindingError.Type.terminal) {
session.close();
}
}
}
private String createErrorBody(String type, String condition) {
Element body = DocumentHelper.createElement("body");
body.addNamespace("", "http://jabber.org/protocol/httpbind");
body.addAttribute("type", type);
body.addAttribute("condition", condition);
return body.asXML();
}
private void handleSessionRequest(String sid, HttpServletRequest request,
HttpServletResponse response, Element rootNode)
throws IOException
......@@ -184,10 +220,7 @@ public class HttpBindServlet extends HttpServlet {
request.isSecure(), rootNode);
}
catch (HttpBindException e) {
response.sendError(e.getHttpError(), e.getMessage());
if (e.shouldCloseSession()) {
session.close();
}
sendError(request, response, e.getBindingError(), session);
return;
}
catch (HttpConnectionClosedException nc) {
......@@ -210,10 +243,7 @@ public class HttpBindServlet extends HttpServlet {
request.getMethod());
}
catch (HttpBindException e) {
response.sendError(e.getHttpError(), e.getMessage());
if (e.shouldCloseSession()) {
session.close();
}
sendError(request, response, e.getBindingError(), session);
}
}
}
......
......@@ -56,6 +56,7 @@ public class HttpSession extends ClientSession {
private long lastRequestID;
private int maxRequests;
private PacketDeliverer backupDeliverer;
private Double version = Double.NaN;
private final Queue<Collection<Element>> packetsToSend = new LinkedList<Collection<Element>>();
// Semaphore which protects the packets to send, so, there can only be one consumer at a time.
......@@ -324,6 +325,42 @@ public class HttpSession extends ClientSession {
}
}
/**
* Sets the version of BOSH which the client implements. Currently, the only versions supported
* by Openfire are 1.5 and 1.6. Any versions less than or equal to 1.5 will be interpreted as
* 1.5 and any values greater than or equal to 1.6 will be interpreted as 1.6.
*
* @param version the version of BOSH which the client implements, represented as a Double,
* {major version}.{minor version}.
*/
public void setVersion(double version) {
if(version <= 1.5) {
return;
}
else if(version >= 1.6) {
version = 1.6;
}
this.version = version;
}
/**
* Returns the BOSH version which this session utilizes. The version refers to the
* version of the XEP which the connecting client implements. If the client did not specify
* a version 1.5 is returned as this is the last version of the <a
* href="http://www.xmpp.org/extensions/xep-0124.html">XEP</a> that the client was not
* required to pass along its version information when creating a session.
*
* @return the version of the BOSH XEP which the client is utilizing.
*/
public double getVersion() {
if (this.version != Double.NaN) {
return this.version;
}
else {
return 1.5;
}
}
public String getResponse(long requestID) throws HttpBindException {
for (HttpConnection connection : connectionQueue) {
if (connection.getRequestId() == requestID) {
......@@ -348,7 +385,8 @@ public class HttpSession extends ClientSession {
catch (HttpBindTimeoutException e) {
// This connection timed out we need to increment the request count
if (connection.getRequestId() != lastRequestID + 1) {
throw new HttpBindException("Unexpected RID error.", true, 404);
throw new HttpBindException("Unexpected RID error.",
BoshBindingError.itemNotFound);
}
lastRequestID = connection.getRequestId();
}
......@@ -427,7 +465,8 @@ public class HttpSession extends ClientSession {
Delivered deliverable = retrieveDeliverable(rid);
if (deliverable == null) {
Log.warn("Deliverable unavailable for " + rid);
throw new HttpBindException("Unexpected RID Error", true, 404);
throw new HttpBindException("Unexpected RID error.",
BoshBindingError.itemNotFound);
}
connection.deliverBody(createDeliverable(deliverable.deliverables));
return connection;
......@@ -435,7 +474,8 @@ public class HttpSession extends ClientSession {
else if (rid > (lastRequestID + hold + 1)) {
// TODO handle the case of greater RID which basically has it wait
Log.warn("Request " + rid + " > " + (lastRequestID + hold + 1) + ", ending session.");
throw new HttpBindException("Unexpected RID Error", true, 404);
throw new HttpBindException("Unexpected RID error.",
BoshBindingError.itemNotFound);
}
if (packetsToBeSent.size() > 0) {
......@@ -466,7 +506,7 @@ public class HttpSession extends ClientSession {
if (isSecure && !connection.isSecure()) {
throw new HttpBindException("Session was started from secure connection, all " +
"connections on this session must be secured.", false, 403);
"connections on this session must be secured.", BoshBindingError.badRequest);
}
connection.setSession(this);
......@@ -540,7 +580,7 @@ public class HttpSession extends ClientSession {
if (((time - lastPoll) / 1000) < maxPollingInterval) {
throw new HttpBindException("Too frequent polling minimum interval is "
+ maxPollingInterval + ", current interval " + ((time - lastPoll) / 1000),
true, 403);
BoshBindingError.policyViolation);
}
lastPoll = time;
}
......
......@@ -16,8 +16,6 @@ import org.jivesoftware.util.JiveConstants;
import org.jivesoftware.util.TaskEngine;
import org.jivesoftware.openfire.SessionManager;
import org.jivesoftware.openfire.StreamID;
import org.jivesoftware.openfire.SessionPacketRouter;
import org.jivesoftware.openfire.multiplex.UnknownStanzaException;
import org.jivesoftware.openfire.auth.UnauthorizedException;
import org.dom4j.*;
......@@ -25,8 +23,6 @@ import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.io.UnsupportedEncodingException;
import java.net.InetAddress;
/**
......@@ -116,6 +112,7 @@ public class HttpSessionManager {
int wait = getIntAttribute(rootNode.attributeValue("wait"), 60);
int hold = getIntAttribute(rootNode.attributeValue("hold"), 1);
double version = getDoubleAttribute(rootNode.attributeValue("ver"), 1.5);
HttpSession session = createSession(connection.getRequestId(), address);
session.setWait(Math.min(wait, getMaxWait()));
......@@ -126,6 +123,7 @@ public class HttpSessionManager {
session.setInactivityTimeout(getInactivityTimeout());
// Store language and version information in the connection.
session.setLanaguage(language);
session.setVersion(version);
try {
connection.deliverBody(createSessionCreationResponse(session));
}
......@@ -134,7 +132,8 @@ public class HttpSessionManager {
}
catch (DocumentException e) {
Log.error("Error creating document", e);
throw new HttpBindException("Internal server error", true, 500);
throw new HttpBindException("Internal server error",
BoshBindingError.internalServerError);
}
return session;
}
......@@ -238,7 +237,7 @@ public class HttpSessionManager {
}
private static int getIntAttribute(String value, int defaultValue) {
if (value == null || "".equals(value)) {
if (value == null || "".equals(value.trim())) {
return defaultValue;
}
try {
......@@ -249,6 +248,18 @@ public class HttpSessionManager {
}
}
private double getDoubleAttribute(String doubleValue, double defaultValue) {
if (doubleValue == null || "".equals(doubleValue.trim())) {
return defaultValue;
}
try {
return Double.parseDouble(doubleValue);
}
catch (Exception ex) {
return defaultValue;
}
}
private String createSessionCreationResponse(HttpSession session) throws DocumentException {
Element response = DocumentHelper.createElement("body");
response.addNamespace("", "http://jabber.org/protocol/httpbind");
......@@ -260,6 +271,9 @@ public class HttpSessionManager {
response.addAttribute("inactivity", String.valueOf(session.getInactivityTimeout()));
response.addAttribute("polling", String.valueOf(session.getMaxPollingInterval()));
response.addAttribute("wait", String.valueOf(session.getWait()));
if(session.getVersion() >= 1.6) {
response.addAttribute("ver", String.valueOf(session.getVersion()));
}
Element features = response.addElement("stream:features");
for (Element feature : session.getAvailableStreamFeaturesElements()) {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment