public List<ByteBuffer> createHandshake( Handshakedata handshakedata, Role ownrole, boolean withcontent) { StringBuilder bui = new StringBuilder(100); if (handshakedata instanceof ClientHandshake) { bui.append("GET "); bui.append(((ClientHandshake) handshakedata).getResourceDescriptor()); bui.append(" HTTP/1.1"); } else if (handshakedata instanceof ServerHandshake) { bui.append("HTTP/1.1 101 " + ((ServerHandshake) handshakedata).getHttpStatusMessage()); } else { throw new RuntimeException("unknow role"); } bui.append("\r\n"); Iterator<String> it = handshakedata.iterateHttpFields(); while (it.hasNext()) { String fieldname = it.next(); String fieldvalue = handshakedata.getFieldValue(fieldname); bui.append(fieldname); bui.append(": "); bui.append(fieldvalue); bui.append("\r\n"); } bui.append("\r\n"); byte[] httpheader = Charsetfunctions.asciiBytes(bui.toString()); byte[] content = withcontent ? handshakedata.getContent() : null; ByteBuffer bytebuffer = ByteBuffer.allocate((content == null ? 0 : content.length) + httpheader.length); bytebuffer.put(httpheader); if (content != null) bytebuffer.put(content); bytebuffer.flip(); return Collections.singletonList(bytebuffer); }
@Override public String toString() { return "Framedata{ optcode:" + getOpcode() + ", fin:" + isFin() + ", payloadlength:" + unmaskedpayload.limit() + ", payload:" + Arrays.toString(Charsetfunctions.utf8Bytes(new String(unmaskedpayload.array()))) + "}"; }
@Override public List<Framedata> createFrames(String text, boolean mask) { FrameBuilder curframe = new FramedataImpl1(); try { curframe.setPayload(ByteBuffer.wrap(Charsetfunctions.utf8Bytes(text))); } catch (InvalidDataException e) { throw new NotSendableException(e); } curframe.setFin(true); curframe.setOptcode(Opcode.TEXT); curframe.setTransferemasked(mask); return Collections.singletonList((Framedata) curframe); }
private void decodeFrames(ByteBuffer socketBuffer) { if (flushandclosestate) return; List<Framedata> frames; try { frames = draft.translateFrame(socketBuffer); for (Framedata f : frames) { if (DEBUG) System.out.println("matched frame: " + f); if (flushandclosestate) return; Opcode curop = f.getOpcode(); boolean fin = f.isFin(); if (curop == Opcode.CLOSING) { int code = CloseFrame.NOCODE; String reason = ""; if (f instanceof CloseFrame) { CloseFrame cf = (CloseFrame) f; code = cf.getCloseCode(); reason = cf.getMessage(); } if (readystate == READYSTATE.CLOSING) { // complete the close handshake by disconnecting closeConnection(code, reason, true); } else { // echo close handshake if (draft.getCloseHandshakeType() == CloseHandshakeType.TWOWAY) close(code, reason, true); else flushAndClose(code, reason, false); } continue; } else if (curop == Opcode.PING) { wsl.onWebsocketPing(this, f); continue; } else if (curop == Opcode.PONG) { wsl.onWebsocketPong(this, f); continue; } else if (!fin || curop == Opcode.CONTINUOUS) { if (curop != Opcode.CONTINUOUS) { if (current_continuous_frame_opcode != null) throw new InvalidDataException( CloseFrame.PROTOCOL_ERROR, "Previous continuous frame sequence not completed."); current_continuous_frame_opcode = curop; } else if (fin) { if (current_continuous_frame_opcode == null) throw new InvalidDataException( CloseFrame.PROTOCOL_ERROR, "Continuous frame sequence was not started."); current_continuous_frame_opcode = null; } else if (current_continuous_frame_opcode == null) { throw new InvalidDataException( CloseFrame.PROTOCOL_ERROR, "Continuous frame sequence was not started."); } try { wsl.onWebsocketMessageFragment(this, f); } catch (RuntimeException e) { wsl.onWebsocketError(this, e); } } else if (current_continuous_frame_opcode != null) { throw new InvalidDataException( CloseFrame.PROTOCOL_ERROR, "Continuous frame sequence not completed."); } else if (curop == Opcode.TEXT) { try { wsl.onWebsocketMessage(this, Charsetfunctions.stringUtf8(f.getPayloadData())); } catch (RuntimeException e) { wsl.onWebsocketError(this, e); } } else if (curop == Opcode.BINARY) { try { wsl.onWebsocketMessage(this, f.getPayloadData()); } catch (RuntimeException e) { wsl.onWebsocketError(this, e); } } else { throw new InvalidDataException( CloseFrame.PROTOCOL_ERROR, "non control or continious frame expected"); } } } catch (InvalidDataException e1) { wsl.onWebsocketError(this, e1); close(e1); return; } }
/** * Returns whether the handshake phase has is completed. In case of a broken handshake this will * be never the case. */ private boolean decodeHandshake(ByteBuffer socketBufferNew) { ByteBuffer socketBuffer; if (tmpHandshakeBytes == null) { socketBuffer = socketBufferNew; } else { if (tmpHandshakeBytes.remaining() < socketBufferNew.remaining()) { ByteBuffer buf = ByteBuffer.allocate(tmpHandshakeBytes.capacity() + socketBufferNew.remaining()); tmpHandshakeBytes.flip(); buf.put(tmpHandshakeBytes); tmpHandshakeBytes = buf; } tmpHandshakeBytes.put(socketBufferNew); tmpHandshakeBytes.flip(); socketBuffer = tmpHandshakeBytes; } socketBuffer.mark(); try { if (draft == null) { HandshakeState isflashedgecase = isFlashEdgeCase(socketBuffer); if (isflashedgecase == HandshakeState.MATCHED) { write(ByteBuffer.wrap(Charsetfunctions.utf8Bytes(wsl.getFlashPolicy(this)))); close(CloseFrame.FLASHPOLICY, ""); return false; } } HandshakeState handshakestate = null; try { if (role == Role.SERVER) { if (draft == null) { for (Draft d : knownDrafts) { d = d.copyInstance(); try { d.setParseMode(role); socketBuffer.reset(); Handshakedata tmphandshake = d.translateHandshake(socketBuffer); if (tmphandshake instanceof ClientHandshake == false) { flushAndClose(CloseFrame.PROTOCOL_ERROR, "wrong http function", false); return false; } ClientHandshake handshake = (ClientHandshake) tmphandshake; handshakestate = d.acceptHandshakeAsServer(handshake); if (handshakestate == HandshakeState.MATCHED) { ServerHandshakeBuilder response; try { response = wsl.onWebsocketHandshakeReceivedAsServer(this, d, handshake); } catch (InvalidDataException e) { flushAndClose(e.getCloseCode(), e.getMessage(), false); return false; } catch (RuntimeException e) { wsl.onWebsocketError(this, e); flushAndClose(CloseFrame.NEVER_CONNECTED, e.getMessage(), false); return false; } write( d.createHandshake( d.postProcessHandshakeResponseAsServer(handshake, response), role)); draft = d; open(handshake); return true; } } catch (InvalidHandshakeException e) { // go on with an other draft } } if (draft == null) { close(CloseFrame.PROTOCOL_ERROR, "no draft matches"); } return false; } else { // special case for multiple step handshakes Handshakedata tmphandshake = draft.translateHandshake(socketBuffer); if (tmphandshake instanceof ClientHandshake == false) { flushAndClose(CloseFrame.PROTOCOL_ERROR, "wrong http function", false); return false; } ClientHandshake handshake = (ClientHandshake) tmphandshake; handshakestate = draft.acceptHandshakeAsServer(handshake); if (handshakestate == HandshakeState.MATCHED) { open(handshake); return true; } else { close(CloseFrame.PROTOCOL_ERROR, "the handshake did finaly not match"); } return false; } } else if (role == Role.CLIENT) { draft.setParseMode(role); Handshakedata tmphandshake = draft.translateHandshake(socketBuffer); if (tmphandshake instanceof ServerHandshake == false) { flushAndClose(CloseFrame.PROTOCOL_ERROR, "Wwrong http function", false); return false; } ServerHandshake handshake = (ServerHandshake) tmphandshake; handshakestate = draft.acceptHandshakeAsClient(handshakerequest, handshake); if (handshakestate == HandshakeState.MATCHED) { try { wsl.onWebsocketHandshakeReceivedAsClient(this, handshakerequest, handshake); } catch (InvalidDataException e) { flushAndClose(e.getCloseCode(), e.getMessage(), false); return false; } catch (RuntimeException e) { wsl.onWebsocketError(this, e); flushAndClose(CloseFrame.NEVER_CONNECTED, e.getMessage(), false); return false; } open(handshake); return true; } else { close(CloseFrame.PROTOCOL_ERROR, "draft " + draft + " refuses handshake"); } } } catch (InvalidHandshakeException e) { close(e); } } catch (IncompleteHandshakeException e) { if (tmpHandshakeBytes == null) { socketBuffer.reset(); int newsize = e.getPreferedSize(); if (newsize == 0) { newsize = socketBuffer.capacity() + 16; } else { assert (e.getPreferedSize() >= socketBuffer.remaining()); } tmpHandshakeBytes = ByteBuffer.allocate(newsize); tmpHandshakeBytes.put(socketBufferNew); // tmpHandshakeBytes.flip(); } else { tmpHandshakeBytes.position(tmpHandshakeBytes.limit()); tmpHandshakeBytes.limit(tmpHandshakeBytes.capacity()); } } return false; }
public static String readStringLine(ByteBuffer buf) { ByteBuffer b = readLine(buf); return b == null ? null : Charsetfunctions.stringAscii(b.array(), 0, b.limit()); }
/** * Base class for everything of a websocket specification which is not common such as the way the * handshake is read or frames are transfered. */ public abstract class Draft { public enum HandshakeState { /** Handshake matched this Draft successfully */ MATCHED, /** Handshake is does not match this Draft */ NOT_MATCHED } public enum CloseHandshakeType { NONE, ONEWAY, TWOWAY } public static int MAX_FAME_SIZE = 1000 * 1; public static int INITIAL_FAMESIZE = 64; public static final byte[] FLASH_POLICY_REQUEST = Charsetfunctions.utf8Bytes("<policy-file-request/>\0"); /** In some cases the handshake will be parsed different depending on whether */ protected Role role = null; public static ByteBuffer readLine(ByteBuffer buf) { ByteBuffer sbuf = ByteBuffer.allocate(buf.remaining()); byte prev = '0'; byte cur = '0'; while (buf.hasRemaining()) { prev = cur; boolean p = false; if (buf.position() >= 159) p = true; cur = buf.get(); sbuf.put(cur); if (prev == (byte) '\r' && cur == (byte) '\n') { sbuf.limit(sbuf.position() - 2); sbuf.position(0); return sbuf; } } // ensure that there wont be any bytes skipped buf.position(buf.position() - sbuf.position()); return null; } public static String readStringLine(ByteBuffer buf) { ByteBuffer b = readLine(buf); return b == null ? null : Charsetfunctions.stringAscii(b.array(), 0, b.limit()); } public static HandshakeBuilder translateHandshakeHttp(ByteBuffer buf, Role role) throws InvalidHandshakeException, IncompleteHandshakeException { HandshakeBuilder handshake; String line = readStringLine(buf); if (line == null) throw new IncompleteHandshakeException(buf.capacity() + 128); String[] firstLineTokens = line.split(" ", 3); // eg. HTTP/1.1 101 Switching the Protocols if (firstLineTokens.length != 3) { throw new InvalidHandshakeException(); } if (role == Role.CLIENT) { // translating/parsing the response from the SERVER handshake = new HandshakeImpl1Server(); ServerHandshakeBuilder serverhandshake = (ServerHandshakeBuilder) handshake; serverhandshake.setHttpStatus(Short.parseShort(firstLineTokens[1])); serverhandshake.setHttpStatusMessage(firstLineTokens[2]); } else { // translating/parsing the request from the CLIENT ClientHandshakeBuilder clienthandshake = new HandshakeImpl1Client(); clienthandshake.setResourceDescriptor(firstLineTokens[1]); handshake = clienthandshake; } line = readStringLine(buf); while (line != null && line.length() > 0) { String[] pair = line.split(":", 2); if (pair.length != 2) throw new InvalidHandshakeException("not an http header"); handshake.put(pair[0], pair[1].replaceFirst("^ +", "")); line = readStringLine(buf); } if (line == null) throw new IncompleteHandshakeException(); return handshake; } public abstract HandshakeState acceptHandshakeAsClient( ClientHandshake request, ServerHandshake response) throws InvalidHandshakeException; public abstract HandshakeState acceptHandshakeAsServer(ClientHandshake handshakedata) throws InvalidHandshakeException; protected boolean basicAccept(Handshakedata handshakedata) { return handshakedata.getFieldValue("Upgrade").equalsIgnoreCase("websocket") && handshakedata .getFieldValue("Connection") .toLowerCase(Locale.ENGLISH) .contains("upgrade"); } public abstract ByteBuffer createBinaryFrame( Framedata framedata); // TODO Allow to send data on the base of an Iterator or InputStream public abstract List<Framedata> createFrames(ByteBuffer binary, boolean mask); public abstract List<Framedata> createFrames(String text, boolean mask); public abstract void reset(); public List<ByteBuffer> createHandshake(Handshakedata handshakedata, Role ownrole) { return createHandshake(handshakedata, ownrole, true); } public List<ByteBuffer> createHandshake( Handshakedata handshakedata, Role ownrole, boolean withcontent) { StringBuilder bui = new StringBuilder(100); if (handshakedata instanceof ClientHandshake) { bui.append("GET "); bui.append(((ClientHandshake) handshakedata).getResourceDescriptor()); bui.append(" HTTP/1.1"); } else if (handshakedata instanceof ServerHandshake) { bui.append("HTTP/1.1 101 " + ((ServerHandshake) handshakedata).getHttpStatusMessage()); } else { throw new RuntimeException("unknow role"); } bui.append("\r\n"); Iterator<String> it = handshakedata.iterateHttpFields(); while (it.hasNext()) { String fieldname = it.next(); String fieldvalue = handshakedata.getFieldValue(fieldname); bui.append(fieldname); bui.append(": "); bui.append(fieldvalue); bui.append("\r\n"); } bui.append("\r\n"); byte[] httpheader = Charsetfunctions.asciiBytes(bui.toString()); byte[] content = withcontent ? handshakedata.getContent() : null; ByteBuffer bytebuffer = ByteBuffer.allocate((content == null ? 0 : content.length) + httpheader.length); bytebuffer.put(httpheader); if (content != null) bytebuffer.put(content); bytebuffer.flip(); return Collections.singletonList(bytebuffer); } public abstract ClientHandshakeBuilder postProcessHandshakeRequestAsClient( ClientHandshakeBuilder request) throws InvalidHandshakeException; public abstract HandshakeBuilder postProcessHandshakeResponseAsServer( ClientHandshake request, ServerHandshakeBuilder response) throws InvalidHandshakeException; public abstract List<Framedata> translateFrame(ByteBuffer buffer) throws InvalidDataException; public abstract CloseHandshakeType getCloseHandshakeType(); public Handshakedata translateHandshake(ByteBuffer buf) throws InvalidHandshakeException { return translateHandshakeHttp(buf, role); } public int checkAlloc(int bytecount) throws LimitExedeedException, InvalidDataException { if (bytecount < 0) throw new InvalidDataException(CloseFrame.PROTOCOL_ERROR, "Negative count"); return bytecount; } public void setParseMode(Role role) { this.role = role; } }