package org.java_websocket.drafts; import java.nio.BufferUnderflowException; import java.nio.ByteBuffer; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Arrays; import java.util.LinkedList; import java.util.List; import java.util.Random; import org.java_websocket.WebSocket.Role; import org.java_websocket.exceptions.IncompleteHandshakeException; import org.java_websocket.exceptions.InvalidDataException; import org.java_websocket.exceptions.InvalidFrameException; import org.java_websocket.exceptions.InvalidHandshakeException; import org.java_websocket.framing.CloseFrame; import org.java_websocket.framing.CloseFrameBuilder; import org.java_websocket.framing.Framedata; import org.java_websocket.framing.Framedata.Opcode; import org.java_websocket.handshake.ClientHandshake; import org.java_websocket.handshake.ClientHandshakeBuilder; import org.java_websocket.handshake.HandshakeBuilder; import org.java_websocket.handshake.Handshakedata; import org.java_websocket.handshake.ServerHandshake; import org.java_websocket.handshake.ServerHandshakeBuilder; public class Draft_76 extends Draft_75 { private boolean failed = false; private static final byte[] closehandshake = { -1, 0 }; public static byte[] createChallenge(String key1, String key2, byte[] key3) throws InvalidHandshakeException { byte[] part1 = getPart(key1); byte[] part2 = getPart(key2); byte[] challenge = new byte[16]; challenge[0] = part1[0]; challenge[1] = part1[1]; challenge[2] = part1[2]; challenge[3] = part1[3]; challenge[4] = part2[0]; challenge[5] = part2[1]; challenge[6] = part2[2]; challenge[7] = part2[3]; challenge[8] = key3[0]; challenge[9] = key3[1]; challenge[10] = key3[2]; challenge[11] = key3[3]; challenge[12] = key3[4]; challenge[13] = key3[5]; challenge[14] = key3[6]; challenge[15] = key3[7]; MessageDigest md5; try { md5 = MessageDigest.getInstance("MD5"); } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); } return md5.digest(challenge); } private static String generateKey() { Random r = new Random(); long maxNumber = 4294967295L; long spaces = r.nextInt(12) + 1; int max = new Long(maxNumber / spaces).intValue(); max = Math.abs(max); int number = r.nextInt(max) + 1; long product = number * spaces; String key = Long.toString(product); // always insert atleast one random character int numChars = r.nextInt(12) + 1; for (int i = 0; i < numChars; i++) { int position = r.nextInt(key.length()); position = Math.abs(position); char randChar = (char) (r.nextInt(95) + 33); // exclude numbers here if (randChar >= 48 && randChar <= 57) { randChar -= 15; } key = new StringBuilder(key).insert(position, randChar).toString(); } for (int i = 0; i < spaces; i++) { int position = r.nextInt(key.length() - 1) + 1; position = Math.abs(position); key = new StringBuilder(key).insert(position, "\u0020").toString(); } return key; } private static byte[] getPart(String key) throws InvalidHandshakeException { try { long keyNumber = Long.parseLong(key.replaceAll("[^0-9]", "")); long keySpace = key.split("\u0020").length - 1; if (keySpace == 0) { throw new InvalidHandshakeException( "invalid Sec-WebSocket-Key (/key2/)"); } long part = new Long(keyNumber / keySpace); return new byte[] { (byte) (part >> 24), (byte) ((part << 8) >> 24), (byte) ((part << 16) >> 24), (byte) ((part << 24) >> 24) }; } catch (NumberFormatException e) { throw new InvalidHandshakeException( "invalid Sec-WebSocket-Key (/key1/ or /key2/)"); } } private final Random reuseableRandom = new Random(); @Override public HandshakeState acceptHandshakeAsClient(ClientHandshake request, ServerHandshake response) { if (failed) { return HandshakeState.NOT_MATCHED; } try { if (!response.getFieldValue("Sec-WebSocket-Origin").equals( request.getFieldValue("Origin")) || !basicAccept(response)) { return HandshakeState.NOT_MATCHED; } byte[] content = response.getContent(); if (content == null || content.length == 0) { throw new IncompleteHandshakeException(); } if (Arrays.equals( content, createChallenge( request.getFieldValue("Sec-WebSocket-Key1"), request.getFieldValue("Sec-WebSocket-Key2"), request.getContent()))) { return HandshakeState.MATCHED; } else { return HandshakeState.NOT_MATCHED; } } catch (InvalidHandshakeException e) { throw new RuntimeException("bad handshakerequest", e); } } @Override public HandshakeState acceptHandshakeAsServer(ClientHandshake handshakedata) { if (handshakedata.getFieldValue("Upgrade").equals("WebSocket") && handshakedata.getFieldValue("Connection") .contains("Upgrade") && handshakedata.getFieldValue("Sec-WebSocket-Key1").length() > 0 && !handshakedata.getFieldValue("Sec-WebSocket-Key2").isEmpty() && handshakedata.hasFieldValue("Origin")) return HandshakeState.MATCHED; return HandshakeState.NOT_MATCHED; } @Override public Draft copyInstance() { return new Draft_76(); } @Override public ByteBuffer createBinaryFrame(Framedata framedata) { if (framedata.getOpcode() == Opcode.CLOSING) return ByteBuffer.wrap(closehandshake); return super.createBinaryFrame(framedata); } @Override public CloseHandshakeType getCloseHandshakeType() { return CloseHandshakeType.ONEWAY; } @Override public ClientHandshakeBuilder postProcessHandshakeRequestAsClient( ClientHandshakeBuilder request) { request.put("Upgrade", "WebSocket"); request.put("Connection", "Upgrade"); request.put("Sec-WebSocket-Key1", generateKey()); request.put("Sec-WebSocket-Key2", generateKey()); if (!request.hasFieldValue("Origin")) { request.put("Origin", "random" + reuseableRandom.nextInt()); } byte[] key3 = new byte[8]; reuseableRandom.nextBytes(key3); request.setContent(key3); return request; } @Override public HandshakeBuilder postProcessHandshakeResponseAsServer( ClientHandshake request, ServerHandshakeBuilder response) throws InvalidHandshakeException { response.setHttpStatusMessage("WebSocket Protocol Handshake"); response.put("Upgrade", "WebSocket"); response.put("Connection", request.getFieldValue("Connection")); // to // respond // to // a // Connection // keep // alive response.put("Sec-WebSocket-Origin", request.getFieldValue("Origin")); String location = "ws://" + request.getFieldValue("Host") + request.getResourceDescriptor(); response.put("Sec-WebSocket-Location", location); String key1 = request.getFieldValue("Sec-WebSocket-Key1"); String key2 = request.getFieldValue("Sec-WebSocket-Key2"); byte[] key3 = request.getContent(); if (key1 == null || key2 == null || key3 == null || key3.length != 8) { throw new InvalidHandshakeException("Bad keys"); } response.setContent(createChallenge(key1, key2, key3)); return response; } @Override public List<Framedata> translateFrame(ByteBuffer buffer) throws InvalidDataException { buffer.mark(); List<Framedata> frames = super.translateRegularFrame(buffer); if (frames == null) { buffer.reset(); frames = readyframes; readingState = true; if (currentFrame == null) currentFrame = ByteBuffer.allocate(2); else { throw new InvalidFrameException(); } if (buffer.remaining() > currentFrame.remaining()) { throw new InvalidFrameException(); } else { currentFrame.put(buffer); } if (!currentFrame.hasRemaining()) { if (Arrays.equals(currentFrame.array(), closehandshake)) { frames.add(new CloseFrameBuilder(CloseFrame.NORMAL)); return frames; } else { throw new InvalidFrameException(); } } else { readyframes = new LinkedList<Framedata>(); return frames; } } else { return frames; } } @Override public Handshakedata translateHandshake(ByteBuffer buf) throws InvalidHandshakeException { HandshakeBuilder bui = translateHandshakeHttp(buf, role); // the first drafts are lacking a protocol number which makes them // difficult to distinguish. Sec-WebSocket-Key1 is typical for draft76 if ((bui.hasFieldValue("Sec-WebSocket-Key1") || role == Role.CLIENT) && !bui.hasFieldValue("Sec-WebSocket-Version")) { byte[] key3 = new byte[role == Role.SERVER ? 8 : 16]; try { buf.get(key3); } catch (BufferUnderflowException e) { throw new IncompleteHandshakeException(buf.capacity() + 16); } bui.setContent(key3); } return bui; } }