diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/api/AcceptHash.java b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/api/AcceptHash.java new file mode 100644 index 00000000000..7d15be172ec --- /dev/null +++ b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/api/AcceptHash.java @@ -0,0 +1,56 @@ +package org.eclipse.jetty.websocket.api; + +import java.io.UnsupportedEncodingException; +import java.security.MessageDigest; + +import org.eclipse.jetty.util.B64Code; +import org.eclipse.jetty.util.StringUtil; + +/** + * Logic for working with the Sec-WebSocket-Accept and Sec-WebSocket-Accept headers. + *

+ * This is kept separate from Connection objects to facilitate difference in behavior between client and server, as well as making testing easier. + */ +public class AcceptHash +{ + /** + * Globally Unique Identifier for use in WebSocket handshake within Sec-WebSocket-Accept and Sec-WebSocket-Key http headers. + *

+ * See Opening Handshake (Section 1.3) + */ + private final static byte[] MAGIC; + + static + { + try + { + MAGIC = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11".getBytes(StringUtil.__ISO_8859_1); + } + catch (UnsupportedEncodingException e) + { + throw new RuntimeException(e); + } + } + + /** + * Concatenate the provided key with the Magic GUID and return the Base64 encoded form. + * + * @param key + * the key to hash + * @return the Sec-WebSocket-Accept header response (per opening handshake spec) + */ + public static String hashKey(String key) + { + try + { + MessageDigest md = MessageDigest.getInstance("SHA1"); + md.update(key.getBytes("UTF-8")); + md.update(MAGIC); + return new String(B64Code.encode(md.digest())); + } + catch (Exception e) + { + throw new RuntimeException(e); + } + } +} diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/api/PolicyViolationException.java b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/api/PolicyViolationException.java index 37540a0d2a1..1c0f50a7c99 100644 --- a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/api/PolicyViolationException.java +++ b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/api/PolicyViolationException.java @@ -5,16 +5,16 @@ public class PolicyViolationException extends CloseException { public PolicyViolationException(String message) { - super(WebSocket.CLOSE_POLICY_VIOLATION,message); + super(StatusCode.POLICY_VIOLATION,message); } public PolicyViolationException(String message, Throwable t) { - super(WebSocket.CLOSE_POLICY_VIOLATION,message,t); + super(StatusCode.POLICY_VIOLATION,message,t); } public PolicyViolationException(Throwable t) { - super(WebSocket.CLOSE_POLICY_VIOLATION,t); + super(StatusCode.POLICY_VIOLATION,t); } } diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/api/StatusCode.java b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/api/StatusCode.java new file mode 100644 index 00000000000..78e3559e326 --- /dev/null +++ b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/api/StatusCode.java @@ -0,0 +1,95 @@ +package org.eclipse.jetty.websocket.api; + +/** + * The RFC 6455 specified status codes. + */ +public class StatusCode +{ + /** + * 1000 indicates a normal closure, meaning that the purpose for which the connection was established has been fulfilled. + *

+ * See RFC 6455, Section 7.4.1 Defined Status Codes. + */ + public final static short NORMAL = 1000; + /** + * 1001 indicates that an endpoint is "going away", such as a server going down or a browser having navigated away from a page. + *

+ * See RFC 6455, Section 7.4.1 Defined Status Codes. + */ + public final static short SHUTDOWN = 1001; + /** + * 1002 indicates that an endpoint is terminating the connection due to a protocol error. + *

+ * See RFC 6455, Section 7.4.1 Defined Status Codes. + */ + public final static short PROTOCOL = 1002; + /** + * 1003 indicates that an endpoint is terminating the connection because it has received a type of data it cannot accept (e.g., an endpoint that understands + * only text data MAY send this if it receives a binary message). + *

+ * See RFC 6455, Section 7.4.1 Defined Status Codes. + */ + public final static short BAD_DATA = 1003; + /** + * Reserved. The specific meaning might be defined in the future. + *

+ * See RFC 6455, Section 7.4.1 Defined Status Codes. + */ + public final static short UNDEFINED = 1004; + /** + * 1005 is a reserved value and MUST NOT be set as a status code in a Close control frame by an endpoint. It is designated for use in applications expecting + * a status code to indicate that no status code was actually present. + *

+ * See RFC 6455, Section 7.4.1 Defined Status Codes. + */ + public final static short NO_CODE = 1005; + /** + * 1006 is a reserved value and MUST NOT be set as a status code in a Close control frame by an endpoint. It is designated for use in applications expecting + * a status code to indicate that the connection was closed abnormally, e.g., without sending or receiving a Close control frame. + *

+ * See RFC 6455, Section 7.4.1 Defined Status Codes. + */ + public final static short NO_CLOSE = 1006; + /** + * 1007 indicates that an endpoint is terminating the connection because it has received data within a message that was not consistent with the type of the + * message (e.g., non-UTF-8 [RFC3629] data within a text message). + *

+ * See RFC 6455, Section 7.4.1 Defined Status Codes. + */ + public final static short BAD_PAYLOAD = 1007; + /** + * 1008 indicates that an endpoint is terminating the connection because it has received a message that violates its policy. This is a generic status code + * that can be returned when there is no other more suitable status code (e.g., 1003 or 1009) or if there is a need to hide specific details about the + * policy. + *

+ * See RFC 6455, Section 7.4.1 Defined Status Codes. + */ + public final static short POLICY_VIOLATION = 1008; + /** + * 1009 indicates that an endpoint is terminating the connection because it has received a message that is too big for it to process. + *

+ * See RFC 6455, Section 7.4.1 Defined Status Codes. + */ + public final static short MESSAGE_TOO_LARGE = 1009; + /** + * 1010 indicates that an endpoint (client) is terminating the connection because it has expected the server to negotiate one or more extension, but the + * server didn't return them in the response message of the WebSocket handshake. The list of extensions that are needed SHOULD appear in the /reason/ part + * of the Close frame. Note that this status code is not used by the server, because it can fail the WebSocket handshake instead. + *

+ * See RFC 6455, Section 7.4.1 Defined Status Codes. + */ + public final static short REQUIRED_EXTENSION = 1010; + /** + * 1011 indicates that a server is terminating the connection because it encountered an unexpected condition that prevented it from fulfilling the request. + *

+ * See RFC 6455, Section 7.4.1 Defined Status Codes. + */ + public final static short SERVER_ERROR = 1011; + /** + * 1015 is a reserved value and MUST NOT be set as a status code in a Close control frame by an endpoint. It is designated for use in applications expecting + * a status code to indicate that the connection was closed due to a failure to perform a TLS handshake (e.g., the server certificate can't be verified). + *

+ * See RFC 6455, Section 7.4.1 Defined Status Codes. + */ + public final static short FAILED_TLS_HANDSHAKE = 1015; +} diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/api/WebSocket.java b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/api/WebSocket.java index 68938e71fd7..c83a9ed22c9 100644 --- a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/api/WebSocket.java +++ b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/api/WebSocket.java @@ -1,10 +1,5 @@ package org.eclipse.jetty.websocket.api; -import java.io.UnsupportedEncodingException; -import java.security.MessageDigest; - -import org.eclipse.jetty.util.B64Code; -import org.eclipse.jetty.util.StringUtil; /** * Constants for WebSocket protocol as-defined in RFC-6455. @@ -15,57 +10,4 @@ public class WebSocket * Per RFC 6455, section 1.3 - Opening Handshake - this version is "13" */ public final static int VERSION = 13; - - /** - * Globally Unique Identifier for use in WebSocket handshake within Sec-WebSocket-Accept and Sec-WebSocket-Key http headers. - *

- * See Opening Handshake (Section 1.3) - */ - private final static byte[] MAGIC; - - public final static short CLOSE_NORMAL = 1000; - public final static short CLOSE_SHUTDOWN = 1001; - public final static short CLOSE_PROTOCOL = 1002; - public final static short CLOSE_BAD_DATA = 1003; - public final static short CLOSE_UNDEFINED = 1004; - public final static short CLOSE_NO_CODE = 1005; - public final static short CLOSE_NO_CLOSE = 1006; - public final static short CLOSE_BAD_PAYLOAD = 1007; - public final static short CLOSE_POLICY_VIOLATION = 1008; - public final static short CLOSE_MESSAGE_TOO_LARGE = 1009; - public final static short CLOSE_REQUIRED_EXTENSION = 1010; - public final static short CLOSE_SERVER_ERROR = 1011; - public final static short CLOSE_FAILED_TLS_HANDSHAKE = 1015; - - static - { - try - { - MAGIC = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11".getBytes(StringUtil.__ISO_8859_1); - } - catch (UnsupportedEncodingException e) - { - throw new RuntimeException(e); - } - } - - /** - * Concatenate the provided key with the Magic GUID and return the Base64 encoded form. - * @param key the key to hash - * @return the Sec-WebSocket-Accept header response (per opening handshake spec) - */ - public static String hashKey(String key) - { - try - { - MessageDigest md = MessageDigest.getInstance("SHA1"); - md.update(key.getBytes("UTF-8")); - md.update(MAGIC); - return new String(B64Code.encode(md.digest())); - } - catch (Exception e) - { - throw new RuntimeException(e); - } - } } diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/frames/CloseFrame.java b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/frames/CloseFrame.java index acaa1a98401..0a8d079e0a5 100644 --- a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/frames/CloseFrame.java +++ b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/frames/CloseFrame.java @@ -1,7 +1,7 @@ package org.eclipse.jetty.websocket.frames; import org.eclipse.jetty.websocket.api.OpCode; -import org.eclipse.jetty.websocket.api.WebSocket; +import org.eclipse.jetty.websocket.api.StatusCode; /** * Representation of a Close Frame (0x08). @@ -13,7 +13,7 @@ public class CloseFrame extends ControlFrame public CloseFrame() { - this(WebSocket.CLOSE_NORMAL); // TODO: evaluate default (or unspecified status code) + this(StatusCode.NORMAL); // TODO: evaluate default (or unspecified status code) } public CloseFrame(short statusCode) diff --git a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/parser/ClosePayloadParserTest.java b/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/parser/ClosePayloadParserTest.java index 37e0e701ee8..d71861cbc87 100644 --- a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/parser/ClosePayloadParserTest.java +++ b/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/parser/ClosePayloadParserTest.java @@ -6,7 +6,7 @@ import java.nio.ByteBuffer; import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.websocket.Debug; -import org.eclipse.jetty.websocket.api.WebSocket; +import org.eclipse.jetty.websocket.api.StatusCode; import org.eclipse.jetty.websocket.api.WebSocketBehavior; import org.eclipse.jetty.websocket.api.WebSocketPolicy; import org.eclipse.jetty.websocket.frames.CloseFrame; @@ -24,7 +24,7 @@ public class ClosePayloadParserTest byte utf[] = expectedReason.getBytes(StringUtil.__UTF8_CHARSET); ByteBuffer payload = ByteBuffer.allocate(utf.length + 2); - payload.putShort(WebSocket.CLOSE_NORMAL); + payload.putShort(StatusCode.NORMAL); payload.put(utf,0,utf.length); payload.flip(); @@ -44,7 +44,7 @@ public class ClosePayloadParserTest capture.assertNoErrors(); capture.assertHasFrame(CloseFrame.class,1); CloseFrame txt = (CloseFrame)capture.getFrames().get(0); - Assert.assertThat("CloseFrame.statusCode",txt.getStatusCode(),is(WebSocket.CLOSE_NORMAL)); + Assert.assertThat("CloseFrame.statusCode",txt.getStatusCode(),is(StatusCode.NORMAL)); Assert.assertThat("CloseFrame.data",txt.getReason(),is(expectedReason)); } } diff --git a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/parser/TextPayloadParserTest.java b/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/parser/TextPayloadParserTest.java index 3cbe159efdb..5f12af567e1 100644 --- a/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/parser/TextPayloadParserTest.java +++ b/jetty-websocket/websocket-core/src/test/java/org/eclipse/jetty/websocket/parser/TextPayloadParserTest.java @@ -7,7 +7,7 @@ import java.util.Arrays; import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.websocket.api.PolicyViolationException; -import org.eclipse.jetty.websocket.api.WebSocket; +import org.eclipse.jetty.websocket.api.StatusCode; import org.eclipse.jetty.websocket.api.WebSocketBehavior; import org.eclipse.jetty.websocket.api.WebSocketPolicy; import org.eclipse.jetty.websocket.frames.TextFrame; @@ -43,7 +43,7 @@ public class TextPayloadParserTest capture.assertHasNoFrames(); PolicyViolationException err = (PolicyViolationException)capture.getErrors().get(0); - Assert.assertThat("Error.closeCode",err.getCloseCode(),is(WebSocket.CLOSE_POLICY_VIOLATION)); + Assert.assertThat("Error.closeCode",err.getCloseCode(),is(StatusCode.POLICY_VIOLATION)); } @Test diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketMessageRFC6455Test.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketMessageRFC6455Test.java index 15f78bea5da..849673c8a38 100644 --- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketMessageRFC6455Test.java +++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketMessageRFC6455Test.java @@ -40,7 +40,9 @@ import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.TypeUtil; import org.eclipse.jetty.util.Utf8StringBuilder; import org.eclipse.jetty.websocket.WebSocket; +import org.eclipse.jetty.websocket.api.AcceptHash; import org.eclipse.jetty.websocket.api.OpCode; +import org.eclipse.jetty.websocket.api.StatusCode; import org.junit.AfterClass; import org.junit.Assert; import org.junit.BeforeClass; @@ -929,7 +931,7 @@ public class WebSocketMessageRFC6455Test @Test public void testHash() { - assertEquals("s3pPLMBiTxaQ9kYGzzhZRbK+xOo=",org.eclipse.jetty.websocket.api.WebSocket.hashKey("dGhlIHNhbXBsZSBub25jZQ==")); + assertEquals("s3pPLMBiTxaQ9kYGzzhZRbK+xOo=",AcceptHash.hashKey("dGhlIHNhbXBsZSBub25jZQ==")); } @Test @@ -1096,7 +1098,7 @@ public class WebSocketMessageRFC6455Test assertEquals(0x80 | OpCode.CLOSE.getCode(),input.read()); assertEquals(19,input.read()); int code=((0xff&input.read())*0x100)+(0xff&input.read()); - assertEquals(org.eclipse.jetty.websocket.api.WebSocket.CLOSE_MESSAGE_TOO_LARGE,code); + assertEquals(StatusCode.MESSAGE_TOO_LARGE,code); lookFor("Message size > 15",input); } @@ -1146,7 +1148,7 @@ public class WebSocketMessageRFC6455Test assertEquals(0x80 | OpCode.CLOSE.getCode(),input.read()); assertEquals(19,input.read()); int code=((0xff&input.read())*0x100)+(0xff&input.read()); - assertEquals(org.eclipse.jetty.websocket.api.WebSocket.CLOSE_MESSAGE_TOO_LARGE,code); + assertEquals(StatusCode.MESSAGE_TOO_LARGE,code); lookFor("Message size > 15",input); } @@ -1210,7 +1212,7 @@ public class WebSocketMessageRFC6455Test assertEquals(0x80 | OpCode.CLOSE.getCode(),input.read()); assertEquals(30,input.read()); int code=((0xff&input.read())*0x100)+(0xff&input.read()); - assertEquals(org.eclipse.jetty.websocket.api.WebSocket.CLOSE_MESSAGE_TOO_LARGE,code); + assertEquals(StatusCode.MESSAGE_TOO_LARGE,code); lookFor("Text message size > 15 chars",input); } @@ -1264,7 +1266,7 @@ public class WebSocketMessageRFC6455Test assertEquals(0x80 | OpCode.CLOSE.getCode(),input.read()); assertEquals(30,input.read()); int code=((0xff&input.read())*0x100)+(0xff&input.read()); - assertEquals(org.eclipse.jetty.websocket.api.WebSocket.CLOSE_MESSAGE_TOO_LARGE,code); + assertEquals(StatusCode.MESSAGE_TOO_LARGE,code); lookFor("Text message size > 15 chars",input); } @@ -1319,7 +1321,7 @@ public class WebSocketMessageRFC6455Test assertEquals(0x80 | OpCode.CLOSE.getCode(),input.read()); assertEquals(33,input.read()); int code=((0xff&input.read())*0x100)+(0xff&input.read()); - assertEquals(org.eclipse.jetty.websocket.api.WebSocket.CLOSE_MESSAGE_TOO_LARGE,code); + assertEquals(StatusCode.MESSAGE_TOO_LARGE,code); lookFor("Text message size > 10240 chars",input); } @@ -1366,7 +1368,7 @@ public class WebSocketMessageRFC6455Test assertEquals(0x80 | OpCode.CLOSE.getCode(),input.read()); assertEquals(15,input.read()); int code=((0xff&input.read())*0x100)+(0xff&input.read()); - assertEquals(org.eclipse.jetty.websocket.api.WebSocket.CLOSE_BAD_PAYLOAD,code); + assertEquals(StatusCode.BAD_PAYLOAD,code); lookFor("Invalid UTF-8",input); } diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketServletTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketServletTest.java new file mode 100644 index 00000000000..793c6f6fd6c --- /dev/null +++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketServletTest.java @@ -0,0 +1,6 @@ +package org.eclipse.jetty.websocket.server; + +public class WebSocketServletTest +{ + +}