Skip to content

Commit 7f7e829

Browse files
committed
-added much stronger validation to ALL drafts
-made all drafts working as client and server -generalized handshake -minor requested changes
1 parent 0bc85a3 commit 7f7e829

File tree

9 files changed

+225
-100
lines changed

9 files changed

+225
-100
lines changed

example/ChatClient.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ public ChatClient() {
4141
layout.setRows(6);
4242
c.setLayout(layout);
4343

44-
Draft[] drafts = { new Draft_75(), new Draft_76(), new Draft_10() };
44+
Draft[] drafts = { new Draft_10(), new Draft_76(), new Draft_75() };
4545
draft = new JComboBox( drafts );
4646
c.add(draft);
4747

src/net/tootallnate/websocket/Draft.java

Lines changed: 35 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import java.nio.ByteBuffer;
44
import java.nio.charset.Charset;
55
import java.util.Arrays;
6+
import java.util.Collections;
67
import java.util.Iterator;
78
import java.util.List;
89

@@ -14,29 +15,34 @@ public abstract class Draft{
1415
/**
1516
* The WebSocket protocol expects UTF-8 encoded bytes.
1617
*/
17-
public final static Charset UTF8_CHARSET = Charset.forName ( "UTF-8" );
18+
public static final Charset UTF8_CHARSET = Charset.forName ( "UTF-8" );
19+
private static final byte[] FLASH_POLICY_REQUEST = "<policy-file-request/>".getBytes( UTF8_CHARSET );
1820

19-
public abstract boolean acceptHandshakeAsServer( Handshakedata handshakedata ) throws InvalidHandshakeException;
20-
public abstract boolean acceptHandshakeAsClient( Handshakedata request , Handshakedata response ) throws InvalidHandshakeException;
21+
public enum HandshakeState{
22+
/**Handshake matched this Draft successfully*/
23+
MATCHED,
24+
/**Handshake is does not match this Draft*/
25+
NOT_MATCHED,
26+
/**Handshake matches this Draft but is not complete*/
27+
MATCHING
28+
//,/**Can not yet say anything*/
29+
//PENDING not yet in use
30+
}
31+
32+
public abstract HandshakeState acceptHandshakeAsServer( Handshakedata handshakedata ) throws InvalidHandshakeException;
33+
public abstract HandshakeState acceptHandshakeAsClient( Handshakedata request , Handshakedata response ) throws InvalidHandshakeException;
2134
public abstract List<Framedata> translateFrame( ByteBuffer buffer, int read );
2235
public abstract ByteBuffer createBinaryFrame( Framedata framedata ); //TODO Allow to send data on the base of an Iterator or InputStream
2336
public abstract List<Framedata> createFrames( String text , boolean mask );
2437
public abstract List<Framedata> createFrames( byte[] binary , boolean mask );
38+
public abstract HandshakeBuilder postProcessHandshakeRequestAsClient( HandshakeBuilder request ) throws InvalidHandshakeException;
39+
public abstract HandshakeBuilder postProcessHandshakeResponseAsServer( Handshakedata request , HandshakeBuilder response ) throws InvalidHandshakeException;
2540

26-
public HandshakeBuilder postProcessHandshakeRequestAsClient( HandshakeBuilder request ){
27-
request.put ( "Upgrade" , "websocket" );
28-
request.put ( "Connection" , "Upgrade" );
29-
return request;
30-
}
31-
32-
public HandshakeBuilder postProcessHandshakeResponseAsServer( Handshakedata request , HandshakeBuilder response ) throws InvalidHandshakeException{
33-
//sb.append ( "HTTP/1.1 101 Switching Protocols\r\n" );
34-
response.put ( "Upgrade" , "websocket" );
35-
response.put ( "Connection" , /*"Upgrade"*/request.getFieldValue ( "Connection" ) ); //to respond a Connection keep alives
36-
return response;
41+
public List<ByteBuffer> createHandshake( Handshakedata handshakedata , Role ownrole ){
42+
return createHandshake( handshakedata , ownrole , true );
3743
}
3844

39-
public /*static*/ ByteBuffer createHandshake( Handshakedata handshakedata , Role ownrole ){
45+
public List<ByteBuffer> createHandshake( Handshakedata handshakedata , Role ownrole , boolean withcontent ){
4046
StringBuilder bui = new StringBuilder ( 100 );
4147
if( ownrole == Role.CLIENT){
4248
bui.append ( "GET " );
@@ -61,15 +67,15 @@ else if( ownrole == Role.SERVER ){
6167
}
6268
bui.append ( "\r\n" );
6369
byte[] httpheader = bui.toString ().getBytes ( UTF8_CHARSET );
64-
byte[] content = handshakedata.getContent ();
70+
byte[] content = withcontent ? handshakedata.getContent() : null;
6571
ByteBuffer bytebuffer = ByteBuffer.allocate ( ( content==null ? 0 : content.length ) + httpheader.length );
6672
bytebuffer.put ( httpheader );
6773
if( content!=null )
6874
bytebuffer.put ( content );
69-
return bytebuffer;
70-
75+
return Collections.singletonList( bytebuffer );
7176
}
72-
public static Handshakedata translateHandshake( byte[] buffer, int readcount ){
77+
78+
public static HandshakeBuilder translateHandshakeHttp( byte[] buffer, int readcount ) throws InvalidHandshakeException{
7379
HandshakedataImpl1 draft = new HandshakedataImpl1();
7480

7581
ByteBuffer message = ByteBuffer.allocate ( readcount );
@@ -78,9 +84,10 @@ public static Handshakedata translateHandshake( byte[] buffer, int readcount ){
7884
int previndex = 0;
7985
int index = findNewLine ( lines , previndex );
8086
if ( index == lines.length )
81-
return null;
87+
throw new InvalidHandshakeException("not an http header");;
8288
String line = new String ( lines , previndex , index - previndex );
8389
String[] firstLineTokens = line.split(" ");
90+
//if( firstLineTokens.length != 3)
8491
String path = firstLineTokens[1];
8592
draft.setResourceDescriptor( path );
8693
//TODO Care about resources here like: GET /chat HTTP/1.1
@@ -95,7 +102,7 @@ public static Handshakedata translateHandshake( byte[] buffer, int readcount ){
95102
if ( index != previndex ) {
96103
String[] pair = line.split ( ":" , 2 );
97104
if ( pair.length != 2 )
98-
return null;
105+
throw new InvalidHandshakeException("not an http header");
99106
draft.put ( pair[ 0 ] , pair[ 1 ].replaceFirst( "^ +" , "" ) );
100107
}
101108
previndex = index + 2;
@@ -107,6 +114,10 @@ public static Handshakedata translateHandshake( byte[] buffer, int readcount ){
107114
draft.setContent ( ByteBuffer.allocate ( length ).put ( lines, previndex , length ).array () );
108115
return draft;
109116
}
117+
118+
public Handshakedata translateHandshake( byte[] buffer, int readcount ) throws InvalidHandshakeException{
119+
return translateHandshakeHttp( buffer , readcount );
120+
}
110121

111122
/**will return the index of the first \r\n or the index off the last element in arr*/
112123
public static int findNewLine( byte[] arr , int offset ) {
@@ -119,13 +130,12 @@ public static int findNewLine( byte[] arr , int offset ) {
119130
}
120131

121132
public static boolean isFlashEdgeCase( byte[] request , int requestsize ){
122-
byte[] req = "<policy-file-request/>".getBytes( UTF8_CHARSET );
123-
for( int i = 0 ; i < requestsize && i < req.length ; i++ ){
124-
if( req[i] != request[i] ){
133+
for( int i = 0 ; i < requestsize && i < FLASH_POLICY_REQUEST.length ; i++ ){
134+
if( FLASH_POLICY_REQUEST[i] != request[i] ){
125135
return false;
126136
}
127137
}
128-
return requestsize >= req.length;
138+
return requestsize >= FLASH_POLICY_REQUEST.length;
129139
}
130140

131141
}

src/net/tootallnate/websocket/HandshakedataImpl1.java

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package net.tootallnate.websocket;
22

3+
import java.util.Collections;
34
import java.util.HashMap;
45
import java.util.Iterator;
56
import java.util.LinkedHashMap;
@@ -16,14 +17,26 @@ public HandshakedataImpl1() {
1617
map = new LinkedHashMap<String,String>();
1718
}
1819

20+
public HandshakedataImpl1( Handshakedata h ) {
21+
httpstatusmessage = h.getHttpStatusMessage();
22+
resourcedescriptor = h.getResourceDescriptor();
23+
content = h.getContent();
24+
map = new LinkedHashMap<String,String>( );
25+
Iterator<String> it = h.iterateHttpFields();
26+
while ( it.hasNext() ) {
27+
String key = ( String ) it.next();
28+
map.put( key , h.getFieldValue( key ) );
29+
}
30+
}
31+
1932
@Override
2033
public String getResourceDescriptor( ) {
2134
return resourcedescriptor == null ? "" : resourcedescriptor ;
2235
}
2336

2437
@Override
2538
public Iterator<String> iterateHttpFields( ) {
26-
return map.keySet ().iterator ();
39+
return Collections.unmodifiableSet( map.keySet () ).iterator ();//Safety first
2740
}
2841

2942
@Override

src/net/tootallnate/websocket/WebSocket.java

Lines changed: 61 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import java.util.ArrayList;
1010
import java.util.List;
1111

12+
import net.tootallnate.websocket.Draft.HandshakeState;
1213
import net.tootallnate.websocket.Framedata.Opcode;
1314
import net.tootallnate.websocket.drafts.*;
1415
import net.tootallnate.websocket.exeptions.InvalidFrameException;
@@ -184,34 +185,57 @@ else if ( bytesRead > 0) {
184185
return;
185186
}
186187
try{
187-
Handshakedata handshake = Draft.translateHandshake ( socketBuffer.array () , bytesRead );
188-
if( handshake == null){
189-
abort("This valid http header ");
190-
return;
191-
}
188+
Handshakedata handshake;
189+
HandshakeState handshakestate = null;
190+
192191
if( role == Role.SERVER ){
193-
for( Draft d : known_drafts ){
194-
if( d.acceptHandshakeAsServer( handshake ) ){
195-
HandshakeBuilder response = wsl.onHandshakeRecievedAsServer ( this , d , handshake );
196-
channelWrite ( d.createHandshake ( d.postProcessHandshakeResponseAsServer ( handshake , response ) , role ) );
197-
draft = d;
198-
this.handshakeComplete = true;
199-
wsl.onOpen ( this );
200-
break;
192+
if( draft == null ){
193+
handshake = Draft.translateHandshakeHttp( socketBuffer.array () , bytesRead );
194+
for( Draft d : known_drafts ){
195+
handshakestate = d.acceptHandshakeAsServer( handshake );
196+
if( handshakestate == HandshakeState.MATCHED ){
197+
HandshakeBuilder response = wsl.onHandshakeRecievedAsServer ( this , d , handshake );
198+
channelWrite ( d.createHandshake ( d.postProcessHandshakeResponseAsServer ( handshake , response ) , role ) );
199+
draft = d;
200+
this.handshakeComplete = true;
201+
open();
202+
return;
203+
}
204+
else if ( handshakestate == HandshakeState.MATCHING ){
205+
if( draft != null ){
206+
throw new InvalidHandshakeException( "multible drafts matching" );
207+
}
208+
draft = d;
209+
}
210+
}
211+
if( draft == null ){
212+
abort( "no draft matches");
201213
}
214+
return;
202215
}
203-
if( draft == null ){
204-
abort( "no draft matches");
216+
else{
217+
//special case for multiple step handshakes
218+
handshake = draft.translateHandshake( socketBuffer.array () , bytesRead );
219+
handshakestate = draft.acceptHandshakeAsServer( handshake );
220+
221+
if( handshakestate == HandshakeState.MATCHED ){
222+
open();
223+
}
224+
else if ( handshakestate != HandshakeState.MATCHING) {
225+
abort( "the handshake did finaly not match" );
226+
}
205227
return;
206228
}
207-
if(DEBUG) System.out.println ( "Using draft: " + draft.getClass ().getSimpleName () );
208229
}
209230
else if( role == Role.CLIENT){
210-
if( draft.acceptHandshakeAsClient ( handshakerequest , handshake )
211-
&& wsl.onHandshakeRecievedAsClient ( this , handshakerequest , handshake )
212-
){
231+
handshake= draft.translateHandshake( socketBuffer.array () , bytesRead );
232+
handshakestate = draft.acceptHandshakeAsClient ( handshakerequest , handshake );
233+
if( handshakestate == HandshakeState.MATCHED ){
213234
this.handshakeComplete = true;
214-
wsl.onOpen ( this );
235+
open();
236+
}
237+
else if ( handshakestate == HandshakeState.MATCHING ) {
238+
return;
215239
}
216240
else{
217241
abort( "draft "+draft.getClass ().getSimpleName ()+" or server refuses handshake");
@@ -223,6 +247,7 @@ else if( role == Role.CLIENT){
223247
}
224248
}
225249
else{
250+
//Receiving frames
226251
List<Framedata> frames = draft.translateFrame ( socketBuffer , bytesRead );
227252
for( Framedata f : frames){
228253
Opcode curop = f.getOpcode ();
@@ -270,17 +295,18 @@ else if( f.getOpcode() == Opcode.CONTINIOUS ){
270295
}
271296
}
272297
}
273-
274298
}
275-
}
299+
}
276300
}
277301

278302
// PUBLIC INSTANCE METHODS /////////////////////////////////////////////////
279303
public void abort( ) throws IOException {
280304
abort ( "" );
281305
}
282306
public void abort( String problemmessage ) throws IOException {
283-
if(DEBUG) System.out.println ( "Aborting: " + problemmessage );
307+
if(DEBUG){
308+
System.out.println ( "Aborting: " + problemmessage );
309+
}
284310
close();
285311
}
286312
/**
@@ -363,7 +389,7 @@ public SocketChannel socketChannel() {
363389
return this.socketChannel;
364390
}
365391

366-
public void startHandshake( HandshakeBuilder handshakedata ) throws IOException{
392+
public void startHandshake( HandshakeBuilder handshakedata ) throws IOException, InvalidHandshakeException{
367393
if( handshakeComplete )
368394
throw new IllegalStateException ( "Handshake has allready been sent." );
369395
this.handshakerequest = handshakedata;
@@ -377,11 +403,17 @@ private void channelWrite(ByteBuffer buf) throws IOException{
377403
socketChannel.write(buf);
378404
}
379405

380-
private void printBytes(ByteBuffer buf, int len){
381-
for( int i = 0 ; i < 2 && i < len; i++ ){
382-
System.out.println (Integer.toBinaryString ( buf.get ( i ) ));
383-
}
384-
406+
private void channelWrite( List<ByteBuffer> bufs) throws IOException{
407+
for( ByteBuffer b : bufs ){
408+
channelWrite( b );
409+
}
410+
}
411+
412+
413+
private void open(){
414+
if(DEBUG) System.out.println ( "open using draft: " + draft.getClass ().getSimpleName () );
415+
handshakeComplete = true;
416+
wsl.onOpen ( this );
385417
}
386418

387419
public int getPort(){

src/net/tootallnate/websocket/WebSocketAdapter.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ public void onClose( WebSocket conn ) {
2929

3030
@Override
3131
public void onError( Throwable ex ) {
32+
if(WebSocket.DEBUG)
33+
ex.printStackTrace();
3234
}
3335

3436
@Override

src/net/tootallnate/websocket/WebSocketClient.java

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
import net.tootallnate.websocket.WebSocket.Role;
1818
import net.tootallnate.websocket.drafts.*;
19+
import net.tootallnate.websocket.exeptions.InvalidHandshakeException;
1920

2021
/**
2122
* The <tt>WebSocketClient</tt> is an abstract class that expects a valid
@@ -224,10 +225,7 @@ public void run() {
224225
key.cancel();
225226
onIOError(conn, ex);
226227
} catch (Exception ex) {
227-
// NullPointerException is the most common error that can happen here
228-
// (e.g.when connection closes immediately)
229-
// TODO: user should handle that kind of events my himself
230-
ex.printStackTrace();
228+
onError( ex );
231229
}
232230
}
233231

@@ -239,7 +237,7 @@ private int getPort() {
239237
return port == -1 ? WebSocket.DEFAULT_PORT : port;
240238
}
241239

242-
private void finishConnect() throws IOException {
240+
private void finishConnect() throws IOException, InvalidHandshakeException {
243241
if (client.isConnectionPending()) {
244242
client.finishConnect();
245243
}
@@ -250,7 +248,7 @@ private void finishConnect() throws IOException {
250248
sendHandshake();
251249
}
252250

253-
private void sendHandshake() throws IOException {
251+
private void sendHandshake() throws IOException, InvalidHandshakeException {
254252
String path = uri.getPath();
255253
if (path.indexOf("/") != 0) {
256254
path = "/" + path;

0 commit comments

Comments
 (0)