package org.java_websocket.drafts; import java.nio.ByteBuffer; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Locale; import org.java_websocket.WebSocket.Role; import org.java_websocket.exceptions.IncompleteHandshakeException; import org.java_websocket.exceptions.InvalidDataException; import org.java_websocket.exceptions.InvalidHandshakeException; import org.java_websocket.exceptions.LimitExedeedException; import org.java_websocket.framing.CloseFrame; import org.java_websocket.framing.FrameBuilder; import org.java_websocket.framing.Framedata; import org.java_websocket.framing.Framedata.Opcode; import org.java_websocket.framing.FramedataImpl1; import org.java_websocket.handshake.ClientHandshake; import org.java_websocket.handshake.ClientHandshakeBuilder; import org.java_websocket.handshake.HandshakeBuilder; import org.java_websocket.handshake.HandshakeImpl1Client; import org.java_websocket.handshake.HandshakeImpl1Server; import org.java_websocket.handshake.Handshakedata; import org.java_websocket.handshake.ServerHandshake; import org.java_websocket.handshake.ServerHandshakeBuilder; import org.java_websocket.util.Charsetfunctions; /** * 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 CloseHandshakeType { NONE, ONEWAY, TWOWAY } public enum HandshakeState { /** Handshake matched this Draft successfully */ MATCHED, /** Handshake is does not match this Draft */ NOT_MATCHED } 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"); public static ByteBuffer readLine(ByteBuffer buf) { ByteBuffer sbuf = ByteBuffer.allocate(buf.remaining()); byte prev = '0'; byte cur = '0'; while (buf.hasRemaining()) { prev = cur; 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; } /** * In some cases the handshake will be parsed different depending on whether */ protected Role role = null; protected Opcode continuousFrameType = null; 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 int checkAlloc(int bytecount) throws LimitExedeedException, InvalidDataException { if (bytecount < 0) throw new InvalidDataException(CloseFrame.PROTOCOL_ERROR, "Negative count"); return bytecount; } public List<Framedata> continuousFrame(Opcode op, ByteBuffer buffer, boolean fin) { if (op != Opcode.BINARY && op != Opcode.TEXT && op != Opcode.TEXT) { throw new IllegalArgumentException( "Only Opcode.BINARY or Opcode.TEXT are allowed"); } if (continuousFrameType != null) { continuousFrameType = Opcode.CONTINUOUS; } else { continuousFrameType = op; } FrameBuilder bui = new FramedataImpl1(continuousFrameType); try { bui.setPayload(buffer); } catch (InvalidDataException e) { throw new RuntimeException(e); // can only happen when one builds // close frames(Opcode.Close) } bui.setFin(fin); if (fin) { continuousFrameType = null; } else { continuousFrameType = op; } return Collections.singletonList((Framedata) bui); } /** * Drafts must only be by one websocket at all. To prevent drafts to be used * more than once the Websocket implementation should call this method in * order to create a new usable version of a given draft instance.<br> * The copy can be safely used in conjunction with a new websocket * connection. * */ public abstract Draft copyInstance(); 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 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 CloseHandshakeType getCloseHandshakeType(); public Role getRole() { return role; } public abstract ClientHandshakeBuilder postProcessHandshakeRequestAsClient( ClientHandshakeBuilder request) throws InvalidHandshakeException; public abstract HandshakeBuilder postProcessHandshakeResponseAsServer( ClientHandshake request, ServerHandshakeBuilder response) throws InvalidHandshakeException; public abstract void reset(); public void setParseMode(Role role) { this.role = role; } public abstract List<Framedata> translateFrame(ByteBuffer buffer) throws InvalidDataException; public Handshakedata translateHandshake(ByteBuffer buf) throws InvalidHandshakeException { return translateHandshakeHttp(buf, role); } }