|
25 | 25 |
|
26 | 26 | package org.java_websocket.drafts;
|
27 | 27 |
|
| 28 | +import org.java_websocket.WebSocketImpl; |
| 29 | +import org.java_websocket.exceptions.InvalidDataException; |
| 30 | +import org.java_websocket.exceptions.InvalidFrameException; |
28 | 31 | import org.java_websocket.exceptions.InvalidHandshakeException;
|
29 |
| -import org.java_websocket.handshake.ClientHandshake; |
30 |
| -import org.java_websocket.handshake.HandshakeBuilder; |
31 |
| -import org.java_websocket.handshake.ServerHandshakeBuilder; |
| 32 | +import org.java_websocket.exceptions.LimitExedeedException; |
| 33 | +import org.java_websocket.extensions.DefaultExtension; |
| 34 | +import org.java_websocket.extensions.IExtension; |
| 35 | +import org.java_websocket.framing.Framedata; |
| 36 | +import org.java_websocket.framing.FramedataImpl1; |
| 37 | +import org.java_websocket.handshake.*; |
| 38 | +import org.java_websocket.util.Charsetfunctions; |
32 | 39 |
|
| 40 | +import java.math.BigInteger; |
| 41 | +import java.nio.ByteBuffer; |
33 | 42 | import java.text.SimpleDateFormat;
|
34 |
| -import java.util.Calendar; |
35 |
| -import java.util.Locale; |
36 |
| -import java.util.TimeZone; |
| 43 | +import java.util.*; |
37 | 44 |
|
38 | 45 | /**
|
39 | 46 | * Implementation for the RFC 6455 websocket protocol
|
|
42 | 49 | @SuppressWarnings("deprecation")
|
43 | 50 | public class Draft_6455 extends Draft_17 {
|
44 | 51 |
|
45 |
| - @Override |
46 |
| - public HandshakeBuilder postProcessHandshakeResponseAsServer(ClientHandshake request, ServerHandshakeBuilder response) throws InvalidHandshakeException { |
47 |
| - super.postProcessHandshakeResponseAsServer(request, response); |
48 |
| - response.setHttpStatusMessage("Web Socket Protocol Handshake"); |
49 |
| - response.put("Server", "TooTallNate Java-WebSocket"); |
50 |
| - response.put("Date", getServerTime()); |
51 |
| - return response; |
52 |
| - } |
53 |
| - |
54 |
| - @Override |
55 |
| - public Draft copyInstance() { |
56 |
| - return new Draft_6455(); |
57 |
| - } |
58 |
| - |
59 |
| - private String getServerTime() { |
60 |
| - Calendar calendar = Calendar.getInstance(); |
61 |
| - SimpleDateFormat dateFormat = new SimpleDateFormat( |
62 |
| - "EEE, dd MMM yyyy HH:mm:ss z", Locale.US); |
63 |
| - dateFormat.setTimeZone(TimeZone.getTimeZone("GMT")); |
64 |
| - return dateFormat.format(calendar.getTime()); |
65 |
| - } |
| 52 | + /** |
| 53 | + * Attribute for the used extension in this draft |
| 54 | + */ |
| 55 | + private IExtension extension; |
| 56 | + |
| 57 | + /** |
| 58 | + * Attribute for all available extension in this draft |
| 59 | + */ |
| 60 | + private List<IExtension> knownExtensions; |
| 61 | + |
| 62 | + /** |
| 63 | + * Constructor for the websocket protocol specified by RFC 6455 with default extensions |
| 64 | + */ |
| 65 | + public Draft_6455() { |
| 66 | + this( Collections.<IExtension>emptyList() ); |
| 67 | + } |
| 68 | + |
| 69 | + /** |
| 70 | + * Constructor for the websocket protocol specified by RFC 6455 with custom extensions |
| 71 | + * |
| 72 | + * @param inputExtension the extension which should be used for this draft |
| 73 | + */ |
| 74 | + public Draft_6455( IExtension inputExtension ) { |
| 75 | + this( Collections.singletonList( inputExtension ) ); |
| 76 | + } |
| 77 | + |
| 78 | + /** |
| 79 | + * Constructor for the websocket protocol specified by RFC 6455 with custom extensions |
| 80 | + * |
| 81 | + * @param inputExtensions the extensions which should be used for this draft |
| 82 | + */ |
| 83 | + public Draft_6455( List<IExtension> inputExtensions ) { |
| 84 | + knownExtensions = new ArrayList<IExtension>(); |
| 85 | + boolean hasDefault = false; |
| 86 | + for( IExtension inputExtension : inputExtensions ) { |
| 87 | + if( inputExtension.getClass().equals( DefaultExtension.class ) ) { |
| 88 | + hasDefault = true; |
| 89 | + } |
| 90 | + } |
| 91 | + knownExtensions.addAll( inputExtensions ); |
| 92 | + //We always add the DefaultExtension to implement the normal RFC 6455 specification |
| 93 | + if( !hasDefault ) { |
| 94 | + DefaultExtension defaultExtension = new DefaultExtension(); |
| 95 | + knownExtensions.add( this.knownExtensions.size(), defaultExtension ); |
| 96 | + } |
| 97 | + } |
| 98 | + |
| 99 | + @Override |
| 100 | + public HandshakeState acceptHandshakeAsServer( ClientHandshake handshakedata ) throws InvalidHandshakeException { |
| 101 | + if( super.acceptHandshakeAsServer( handshakedata ) == HandshakeState.NOT_MATCHED ) { |
| 102 | + return HandshakeState.NOT_MATCHED; |
| 103 | + } |
| 104 | + String requestedExtension = handshakedata.getFieldValue( "Sec-WebSocket-Extensions" ); |
| 105 | + for( IExtension knownExtension : knownExtensions ) { |
| 106 | + if( knownExtension.acceptProvidedExtensionAsServer( requestedExtension ) ) { |
| 107 | + extension = knownExtension; |
| 108 | + return HandshakeState.MATCHED; |
| 109 | + } |
| 110 | + } |
| 111 | + return HandshakeState.NOT_MATCHED; |
| 112 | + } |
| 113 | + |
| 114 | + |
| 115 | + @Override |
| 116 | + public HandshakeState acceptHandshakeAsClient( ClientHandshake request, ServerHandshake response ) throws InvalidHandshakeException { |
| 117 | + if( super.acceptHandshakeAsClient( request, response ) == HandshakeState.NOT_MATCHED ) { |
| 118 | + return HandshakeState.NOT_MATCHED; |
| 119 | + } |
| 120 | + String requestedExtension = response.getFieldValue( "Sec-WebSocket-Extensions" ); |
| 121 | + for( IExtension knownExtension : knownExtensions ) { |
| 122 | + if( knownExtension.acceptProvidedExtensionAsClient( requestedExtension ) ) { |
| 123 | + extension = knownExtension; |
| 124 | + return HandshakeState.MATCHED; |
| 125 | + } |
| 126 | + } |
| 127 | + return HandshakeState.NOT_MATCHED; |
| 128 | + } |
| 129 | + |
| 130 | + /** |
| 131 | + * Getter for the extension which is used by this draft |
| 132 | + * |
| 133 | + * @return the extension which is used or null, if handshake is not yet done |
| 134 | + */ |
| 135 | + public IExtension getExtension() { |
| 136 | + return extension; |
| 137 | + } |
| 138 | + |
| 139 | + @Override |
| 140 | + public ClientHandshakeBuilder postProcessHandshakeRequestAsClient( ClientHandshakeBuilder request ) { |
| 141 | + super.postProcessHandshakeRequestAsClient( request ); |
| 142 | + StringBuilder requestedExtensions = new StringBuilder(); |
| 143 | + for( IExtension knownExtension : knownExtensions ) { |
| 144 | + if( knownExtension.getProvidedExtensionAsClient() != null && !knownExtension.getProvidedExtensionAsClient().equals( "" ) ) { |
| 145 | + requestedExtensions.append( knownExtension.getProvidedExtensionAsClient() ).append( "; " ); |
| 146 | + } |
| 147 | + } |
| 148 | + if( requestedExtensions.length() != 0 ) { |
| 149 | + request.put( "Sec-WebSocket-Extensions", requestedExtensions.toString() ); |
| 150 | + } |
| 151 | + return request; |
| 152 | + } |
| 153 | + |
| 154 | + @Override |
| 155 | + public HandshakeBuilder postProcessHandshakeResponseAsServer( ClientHandshake request, ServerHandshakeBuilder |
| 156 | + response ) throws InvalidHandshakeException { |
| 157 | + super.postProcessHandshakeResponseAsServer( request, response ); |
| 158 | + if( getExtension().getProvidedExtensionAsServer().length() != 0 ) { |
| 159 | + response.put( "Sec-WebSocket-Extensions", getExtension().getProvidedExtensionAsServer() ); |
| 160 | + } |
| 161 | + response.setHttpStatusMessage( "Web Socket Protocol Handshake" ); |
| 162 | + response.put( "Server", "TooTallNate Java-WebSocket" ); |
| 163 | + response.put( "Date", getServerTime() ); |
| 164 | + return response; |
| 165 | + } |
| 166 | + |
| 167 | + |
| 168 | + @Override |
| 169 | + public Draft copyInstance() { |
| 170 | + ArrayList<IExtension> newExtensions = new ArrayList<IExtension>(); |
| 171 | + for( IExtension extension : knownExtensions ) { |
| 172 | + newExtensions.add( extension.copyInstance() ); |
| 173 | + } |
| 174 | + return new Draft_6455( newExtensions ); |
| 175 | + } |
| 176 | + |
| 177 | + @Override |
| 178 | + public ByteBuffer createBinaryFrame( Framedata framedata ) { |
| 179 | + getExtension().encodeFrame( framedata ); |
| 180 | + return super.createBinaryFrame( framedata ); |
| 181 | + } |
| 182 | + |
| 183 | + @Override |
| 184 | + public Framedata translateSingleFrame( ByteBuffer buffer ) throws IncompleteException, InvalidDataException { |
| 185 | + int maxpacketsize = buffer.remaining(); |
| 186 | + int realpacketsize = 2; |
| 187 | + if( maxpacketsize < realpacketsize ) |
| 188 | + throw new IncompleteException( realpacketsize ); |
| 189 | + byte b1 = buffer.get( /*0*/ ); |
| 190 | + boolean FIN = b1 >> 8 != 0; |
| 191 | + boolean rsv1 = false, rsv2 = false, rsv3 = false; |
| 192 | + if( ( b1 & 0x40 ) != 0 ) { |
| 193 | + rsv1 = true; |
| 194 | + } |
| 195 | + if( ( b1 & 0x20 ) != 0 ) { |
| 196 | + rsv2 = true; |
| 197 | + } |
| 198 | + if( ( b1 & 0x10 ) != 0 ) { |
| 199 | + rsv3 = true; |
| 200 | + } |
| 201 | + byte b2 = buffer.get( /*1*/ ); |
| 202 | + boolean MASK = ( b2 & -128 ) != 0; |
| 203 | + int payloadlength = ( byte ) ( b2 & ~( byte ) 128 ); |
| 204 | + Framedata.Opcode optcode = toOpcode( ( byte ) ( b1 & 15 ) ); |
| 205 | + |
| 206 | + if( !( payloadlength >= 0 && payloadlength <= 125 ) ) { |
| 207 | + if( optcode == Framedata.Opcode.PING || optcode == Framedata.Opcode.PONG || optcode == Framedata.Opcode.CLOSING ) { |
| 208 | + throw new InvalidFrameException( "more than 125 octets" ); |
| 209 | + } |
| 210 | + if( payloadlength == 126 ) { |
| 211 | + realpacketsize += 2; // additional length bytes |
| 212 | + if( maxpacketsize < realpacketsize ) |
| 213 | + throw new IncompleteException( realpacketsize ); |
| 214 | + byte[] sizebytes = new byte[3]; |
| 215 | + sizebytes[1] = buffer.get( /*1 + 1*/ ); |
| 216 | + sizebytes[2] = buffer.get( /*1 + 2*/ ); |
| 217 | + payloadlength = new BigInteger( sizebytes ).intValue(); |
| 218 | + } else { |
| 219 | + realpacketsize += 8; // additional length bytes |
| 220 | + if( maxpacketsize < realpacketsize ) |
| 221 | + throw new IncompleteException( realpacketsize ); |
| 222 | + byte[] bytes = new byte[8]; |
| 223 | + for( int i = 0; i < 8; i++ ) { |
| 224 | + bytes[i] = buffer.get( /*1 + i*/ ); |
| 225 | + } |
| 226 | + long length = new BigInteger( bytes ).longValue(); |
| 227 | + if( length > Integer.MAX_VALUE ) { |
| 228 | + throw new LimitExedeedException( "Payloadsize is to big..." ); |
| 229 | + } else { |
| 230 | + payloadlength = ( int ) length; |
| 231 | + } |
| 232 | + } |
| 233 | + } |
| 234 | + |
| 235 | + // int maskskeystart = foff + realpacketsize; |
| 236 | + realpacketsize += ( MASK ? 4 : 0 ); |
| 237 | + // int payloadstart = foff + realpacketsize; |
| 238 | + realpacketsize += payloadlength; |
| 239 | + |
| 240 | + if( maxpacketsize < realpacketsize ) |
| 241 | + throw new IncompleteException( realpacketsize ); |
| 242 | + |
| 243 | + ByteBuffer payload = ByteBuffer.allocate( checkAlloc( payloadlength ) ); |
| 244 | + if( MASK ) { |
| 245 | + byte[] maskskey = new byte[4]; |
| 246 | + buffer.get( maskskey ); |
| 247 | + for( int i = 0; i < payloadlength; i++ ) { |
| 248 | + payload.put( ( byte ) ( buffer.get( /*payloadstart + i*/ ) ^ maskskey[i % 4] ) ); |
| 249 | + } |
| 250 | + } else { |
| 251 | + payload.put( buffer.array(), buffer.position(), payload.limit() ); |
| 252 | + buffer.position( buffer.position() + payload.limit() ); |
| 253 | + } |
| 254 | + |
| 255 | + FramedataImpl1 frame = FramedataImpl1.get( optcode ); |
| 256 | + frame.setFin( FIN ); |
| 257 | + frame.setRSV1( rsv1 ); |
| 258 | + frame.setRSV2( rsv2 ); |
| 259 | + frame.setRSV3( rsv3 ); |
| 260 | + payload.flip(); |
| 261 | + frame.setPayload( payload ); |
| 262 | + getExtension().isFrameValid( frame ); |
| 263 | + getExtension().decodeFrame( frame ); |
| 264 | + if( WebSocketImpl.DEBUG ) |
| 265 | + System.out.println( "Decode Payload after: " + Arrays.toString( Charsetfunctions.utf8Bytes( new String( frame.getPayloadData().array() ) ) ) ); |
| 266 | + frame.isValid(); |
| 267 | + return frame; |
| 268 | + } |
| 269 | + |
| 270 | + |
| 271 | + @Override |
| 272 | + public List<Framedata> translateFrame( ByteBuffer buffer ) throws InvalidDataException { |
| 273 | + while( true ) { |
| 274 | + List<Framedata> frames = new LinkedList<Framedata>(); |
| 275 | + Framedata cur; |
| 276 | + if( incompleteframe != null ) { |
| 277 | + // complete an incomplete frame |
| 278 | + try { |
| 279 | + buffer.mark(); |
| 280 | + int available_next_byte_count = buffer.remaining();// The number of bytes received |
| 281 | + int expected_next_byte_count = incompleteframe.remaining();// The number of bytes to complete the incomplete frame |
| 282 | + |
| 283 | + if( expected_next_byte_count > available_next_byte_count ) { |
| 284 | + // did not receive enough bytes to complete the frame |
| 285 | + incompleteframe.put( buffer.array(), buffer.position(), available_next_byte_count ); |
| 286 | + buffer.position( buffer.position() + available_next_byte_count ); |
| 287 | + return Collections.emptyList(); |
| 288 | + } |
| 289 | + incompleteframe.put( buffer.array(), buffer.position(), expected_next_byte_count ); |
| 290 | + buffer.position( buffer.position() + expected_next_byte_count ); |
| 291 | + cur = translateSingleFrame( ( ByteBuffer ) incompleteframe.duplicate().position( 0 ) ); |
| 292 | + frames.add( cur ); |
| 293 | + incompleteframe = null; |
| 294 | + } catch ( IncompleteException e ) { |
| 295 | + // extending as much as suggested |
| 296 | + int oldsize = incompleteframe.limit(); |
| 297 | + ByteBuffer extendedframe = ByteBuffer.allocate( checkAlloc( e.getPreferedSize() ) ); |
| 298 | + assert ( extendedframe.limit() > incompleteframe.limit() ); |
| 299 | + incompleteframe.rewind(); |
| 300 | + extendedframe.put( incompleteframe ); |
| 301 | + incompleteframe = extendedframe; |
| 302 | + continue; |
| 303 | + } |
| 304 | + } |
| 305 | + |
| 306 | + while( buffer.hasRemaining() ) {// Read as much as possible full frames |
| 307 | + buffer.mark(); |
| 308 | + try { |
| 309 | + cur = translateSingleFrame( buffer ); |
| 310 | + frames.add( cur ); |
| 311 | + } catch ( IncompleteException e ) { |
| 312 | + // remember the incomplete data |
| 313 | + buffer.reset(); |
| 314 | + int pref = e.getPreferedSize(); |
| 315 | + incompleteframe = ByteBuffer.allocate( checkAlloc( pref ) ); |
| 316 | + incompleteframe.put( buffer ); |
| 317 | + break; |
| 318 | + } |
| 319 | + } |
| 320 | + return frames; |
| 321 | + } |
| 322 | + } |
| 323 | + |
| 324 | + /** |
| 325 | + * Generate a date for for the date-header |
| 326 | + * |
| 327 | + * @return the server time |
| 328 | + */ |
| 329 | + private String getServerTime() { |
| 330 | + Calendar calendar = Calendar.getInstance(); |
| 331 | + SimpleDateFormat dateFormat = new SimpleDateFormat( |
| 332 | + "EEE, dd MMM yyyy HH:mm:ss z", Locale.US ); |
| 333 | + dateFormat.setTimeZone( TimeZone.getTimeZone( "GMT" ) ); |
| 334 | + return dateFormat.format( calendar.getTime() ); |
| 335 | + } |
| 336 | + |
| 337 | + @Override |
| 338 | + public String toString() { |
| 339 | + return super.toString() + " extension: " + getExtension().toString(); |
| 340 | + } |
66 | 341 | }
|
0 commit comments