• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package fi.iki.elonen;
2 
3 /*
4  * #%L
5  * NanoHttpd-Websocket
6  * %%
7  * Copyright (C) 2012 - 2015 nanohttpd
8  * %%
9  * Redistribution and use in source and binary forms, with or without modification,
10  * are permitted provided that the following conditions are met:
11  *
12  * 1. Redistributions of source code must retain the above copyright notice, this
13  *    list of conditions and the following disclaimer.
14  *
15  * 2. Redistributions in binary form must reproduce the above copyright notice,
16  *    this list of conditions and the following disclaimer in the documentation
17  *    and/or other materials provided with the distribution.
18  *
19  * 3. Neither the name of the nanohttpd nor the names of its contributors
20  *    may be used to endorse or promote products derived from this software without
21  *    specific prior written permission.
22  *
23  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
24  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
25  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
26  * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
27  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
28  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
29  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
30  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
31  * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
32  * OF THE POSSIBILITY OF SUCH DAMAGE.
33  * #L%
34  */
35 
36 import java.io.EOFException;
37 import java.io.IOException;
38 import java.io.InputStream;
39 import java.io.OutputStream;
40 import java.nio.charset.CharacterCodingException;
41 import java.nio.charset.Charset;
42 import java.security.MessageDigest;
43 import java.security.NoSuchAlgorithmException;
44 import java.util.Arrays;
45 import java.util.LinkedList;
46 import java.util.List;
47 import java.util.Map;
48 import java.util.logging.Level;
49 import java.util.logging.Logger;
50 
51 import fi.iki.elonen.NanoWSD.WebSocketFrame.CloseCode;
52 import fi.iki.elonen.NanoWSD.WebSocketFrame.CloseFrame;
53 import fi.iki.elonen.NanoWSD.WebSocketFrame.OpCode;
54 
55 public abstract class NanoWSD extends NanoHTTPD {
56 
57     public static enum State {
58         UNCONNECTED,
59         CONNECTING,
60         OPEN,
61         CLOSING,
62         CLOSED
63     }
64 
65     public static abstract class WebSocket {
66 
67         private final InputStream in;
68 
69         private OutputStream out;
70 
71         private WebSocketFrame.OpCode continuousOpCode = null;
72 
73         private final List<WebSocketFrame> continuousFrames = new LinkedList<WebSocketFrame>();
74 
75         private State state = State.UNCONNECTED;
76 
77         private final NanoHTTPD.IHTTPSession handshakeRequest;
78 
79         private final NanoHTTPD.Response handshakeResponse = new NanoHTTPD.Response(NanoHTTPD.Response.Status.SWITCH_PROTOCOL, null, (InputStream) null, 0) {
80 
81             @Override
82             protected void send(OutputStream out) {
83                 WebSocket.this.out = out;
84                 WebSocket.this.state = State.CONNECTING;
85                 super.send(out);
86                 WebSocket.this.state = State.OPEN;
87                 WebSocket.this.onOpen();
88                 readWebsocket();
89             }
90         };
91 
WebSocket(NanoHTTPD.IHTTPSession handshakeRequest)92         public WebSocket(NanoHTTPD.IHTTPSession handshakeRequest) {
93             this.handshakeRequest = handshakeRequest;
94             this.in = handshakeRequest.getInputStream();
95 
96             this.handshakeResponse.addHeader(NanoWSD.HEADER_UPGRADE, NanoWSD.HEADER_UPGRADE_VALUE);
97             this.handshakeResponse.addHeader(NanoWSD.HEADER_CONNECTION, NanoWSD.HEADER_CONNECTION_VALUE);
98         }
99 
isOpen()100         public boolean isOpen() {
101             return state == State.OPEN;
102         }
103 
onOpen()104         protected abstract void onOpen();
105 
onClose(CloseCode code, String reason, boolean initiatedByRemote)106         protected abstract void onClose(CloseCode code, String reason, boolean initiatedByRemote);
107 
onMessage(WebSocketFrame message)108         protected abstract void onMessage(WebSocketFrame message);
109 
onPong(WebSocketFrame pong)110         protected abstract void onPong(WebSocketFrame pong);
111 
onException(IOException exception)112         protected abstract void onException(IOException exception);
113 
114         /**
115          * Debug method. <b>Do not Override unless for debug purposes!</b>
116          *
117          * @param frame
118          *            The received WebSocket Frame.
119          */
debugFrameReceived(WebSocketFrame frame)120         protected void debugFrameReceived(WebSocketFrame frame) {
121         }
122 
123         /**
124          * Debug method. <b>Do not Override unless for debug purposes!</b><br>
125          * This method is called before actually sending the frame.
126          *
127          * @param frame
128          *            The sent WebSocket Frame.
129          */
debugFrameSent(WebSocketFrame frame)130         protected void debugFrameSent(WebSocketFrame frame) {
131         }
132 
close(CloseCode code, String reason, boolean initiatedByRemote)133         public void close(CloseCode code, String reason, boolean initiatedByRemote) throws IOException {
134             State oldState = this.state;
135             this.state = State.CLOSING;
136             if (oldState == State.OPEN) {
137                 sendFrame(new CloseFrame(code, reason));
138             } else {
139                 doClose(code, reason, initiatedByRemote);
140             }
141         }
142 
doClose(CloseCode code, String reason, boolean initiatedByRemote)143         private void doClose(CloseCode code, String reason, boolean initiatedByRemote) {
144             if (this.state == State.CLOSED) {
145                 return;
146             }
147             if (this.in != null) {
148                 try {
149                     this.in.close();
150                 } catch (IOException e) {
151                     NanoWSD.LOG.log(Level.FINE, "close failed", e);
152                 }
153             }
154             if (this.out != null) {
155                 try {
156                     this.out.close();
157                 } catch (IOException e) {
158                     NanoWSD.LOG.log(Level.FINE, "close failed", e);
159                 }
160             }
161             this.state = State.CLOSED;
162             onClose(code, reason, initiatedByRemote);
163         }
164 
165         // --------------------------------IO--------------------------------------
166 
getHandshakeRequest()167         public NanoHTTPD.IHTTPSession getHandshakeRequest() {
168             return this.handshakeRequest;
169         }
170 
getHandshakeResponse()171         public NanoHTTPD.Response getHandshakeResponse() {
172             return this.handshakeResponse;
173         }
174 
handleCloseFrame(WebSocketFrame frame)175         private void handleCloseFrame(WebSocketFrame frame) throws IOException {
176             CloseCode code = CloseCode.NormalClosure;
177             String reason = "";
178             if (frame instanceof CloseFrame) {
179                 code = ((CloseFrame) frame).getCloseCode();
180                 reason = ((CloseFrame) frame).getCloseReason();
181             }
182             if (this.state == State.CLOSING) {
183                 // Answer for my requested close
184                 doClose(code, reason, false);
185             } else {
186                 close(code, reason, true);
187             }
188         }
189 
handleFrameFragment(WebSocketFrame frame)190         private void handleFrameFragment(WebSocketFrame frame) throws IOException {
191             if (frame.getOpCode() != OpCode.Continuation) {
192                 // First
193                 if (this.continuousOpCode != null) {
194                     throw new WebSocketException(CloseCode.ProtocolError, "Previous continuous frame sequence not completed.");
195                 }
196                 this.continuousOpCode = frame.getOpCode();
197                 this.continuousFrames.clear();
198                 this.continuousFrames.add(frame);
199             } else if (frame.isFin()) {
200                 // Last
201                 if (this.continuousOpCode == null) {
202                     throw new WebSocketException(CloseCode.ProtocolError, "Continuous frame sequence was not started.");
203                 }
204                 onMessage(new WebSocketFrame(this.continuousOpCode, this.continuousFrames));
205                 this.continuousOpCode = null;
206                 this.continuousFrames.clear();
207             } else if (this.continuousOpCode == null) {
208                 // Unexpected
209                 throw new WebSocketException(CloseCode.ProtocolError, "Continuous frame sequence was not started.");
210             } else {
211                 // Intermediate
212                 this.continuousFrames.add(frame);
213             }
214         }
215 
handleWebsocketFrame(WebSocketFrame frame)216         private void handleWebsocketFrame(WebSocketFrame frame) throws IOException {
217             debugFrameReceived(frame);
218             if (frame.getOpCode() == OpCode.Close) {
219                 handleCloseFrame(frame);
220             } else if (frame.getOpCode() == OpCode.Ping) {
221                 sendFrame(new WebSocketFrame(OpCode.Pong, true, frame.getBinaryPayload()));
222             } else if (frame.getOpCode() == OpCode.Pong) {
223                 onPong(frame);
224             } else if (!frame.isFin() || frame.getOpCode() == OpCode.Continuation) {
225                 handleFrameFragment(frame);
226             } else if (this.continuousOpCode != null) {
227                 throw new WebSocketException(CloseCode.ProtocolError, "Continuous frame sequence not completed.");
228             } else if (frame.getOpCode() == OpCode.Text || frame.getOpCode() == OpCode.Binary) {
229                 onMessage(frame);
230             } else {
231                 throw new WebSocketException(CloseCode.ProtocolError, "Non control or continuous frame expected.");
232             }
233         }
234 
235         // --------------------------------Close-----------------------------------
236 
ping(byte[] payload)237         public void ping(byte[] payload) throws IOException {
238             sendFrame(new WebSocketFrame(OpCode.Ping, true, payload));
239         }
240 
241         // --------------------------------Public
242         // Facade---------------------------
243 
readWebsocket()244         private void readWebsocket() {
245             try {
246                 while (this.state == State.OPEN) {
247                     handleWebsocketFrame(WebSocketFrame.read(this.in));
248                 }
249             } catch (CharacterCodingException e) {
250                 onException(e);
251                 doClose(CloseCode.InvalidFramePayloadData, e.toString(), false);
252             } catch (IOException e) {
253                 onException(e);
254                 if (e instanceof WebSocketException) {
255                     doClose(((WebSocketException) e).getCode(), ((WebSocketException) e).getReason(), false);
256                 }
257             } finally {
258                 doClose(CloseCode.InternalServerError, "Handler terminated without closing the connection.", false);
259             }
260         }
261 
send(byte[] payload)262         public void send(byte[] payload) throws IOException {
263             sendFrame(new WebSocketFrame(OpCode.Binary, true, payload));
264         }
265 
send(String payload)266         public void send(String payload) throws IOException {
267             sendFrame(new WebSocketFrame(OpCode.Text, true, payload));
268         }
269 
sendFrame(WebSocketFrame frame)270         public synchronized void sendFrame(WebSocketFrame frame) throws IOException {
271             debugFrameSent(frame);
272             frame.write(this.out);
273         }
274     }
275 
276     public static class WebSocketException extends IOException {
277 
278         private static final long serialVersionUID = 1L;
279 
280         private final CloseCode code;
281 
282         private final String reason;
283 
WebSocketException(CloseCode code, String reason)284         public WebSocketException(CloseCode code, String reason) {
285             this(code, reason, null);
286         }
287 
WebSocketException(CloseCode code, String reason, Exception cause)288         public WebSocketException(CloseCode code, String reason, Exception cause) {
289             super(code + ": " + reason, cause);
290             this.code = code;
291             this.reason = reason;
292         }
293 
WebSocketException(Exception cause)294         public WebSocketException(Exception cause) {
295             this(CloseCode.InternalServerError, cause.toString(), cause);
296         }
297 
getCode()298         public CloseCode getCode() {
299             return this.code;
300         }
301 
getReason()302         public String getReason() {
303             return this.reason;
304         }
305     }
306 
307     public static class WebSocketFrame {
308 
309         public static enum CloseCode {
310             NormalClosure(1000),
311             GoingAway(1001),
312             ProtocolError(1002),
313             UnsupportedData(1003),
314             NoStatusRcvd(1005),
315             AbnormalClosure(1006),
316             InvalidFramePayloadData(1007),
317             PolicyViolation(1008),
318             MessageTooBig(1009),
319             MandatoryExt(1010),
320             InternalServerError(1011),
321             TLSHandshake(1015);
322 
find(int value)323             public static CloseCode find(int value) {
324                 for (CloseCode code : values()) {
325                     if (code.getValue() == value) {
326                         return code;
327                     }
328                 }
329                 return null;
330             }
331 
332             private final int code;
333 
CloseCode(int code)334             private CloseCode(int code) {
335                 this.code = code;
336             }
337 
getValue()338             public int getValue() {
339                 return this.code;
340             }
341         }
342 
343         public static class CloseFrame extends WebSocketFrame {
344 
generatePayload(CloseCode code, String closeReason)345             private static byte[] generatePayload(CloseCode code, String closeReason) throws CharacterCodingException {
346                 if (code != null) {
347                     byte[] reasonBytes = text2Binary(closeReason);
348                     byte[] payload = new byte[reasonBytes.length + 2];
349                     payload[0] = (byte) (code.getValue() >> 8 & 0xFF);
350                     payload[1] = (byte) (code.getValue() & 0xFF);
351                     System.arraycopy(reasonBytes, 0, payload, 2, reasonBytes.length);
352                     return payload;
353                 } else {
354                     return new byte[0];
355                 }
356             }
357 
358             private CloseCode _closeCode;
359 
360             private String _closeReason;
361 
CloseFrame(CloseCode code, String closeReason)362             public CloseFrame(CloseCode code, String closeReason) throws CharacterCodingException {
363                 super(OpCode.Close, true, generatePayload(code, closeReason));
364             }
365 
CloseFrame(WebSocketFrame wrap)366             private CloseFrame(WebSocketFrame wrap) throws CharacterCodingException {
367                 super(wrap);
368                 assert wrap.getOpCode() == OpCode.Close;
369                 if (wrap.getBinaryPayload().length >= 2) {
370                     this._closeCode = CloseCode.find((wrap.getBinaryPayload()[0] & 0xFF) << 8 | wrap.getBinaryPayload()[1] & 0xFF);
371                     this._closeReason = binary2Text(getBinaryPayload(), 2, getBinaryPayload().length - 2);
372                 }
373             }
374 
getCloseCode()375             public CloseCode getCloseCode() {
376                 return this._closeCode;
377             }
378 
getCloseReason()379             public String getCloseReason() {
380                 return this._closeReason;
381             }
382         }
383 
384         public static enum OpCode {
385             Continuation(0),
386             Text(1),
387             Binary(2),
388             Close(8),
389             Ping(9),
390             Pong(10);
391 
find(byte value)392             public static OpCode find(byte value) {
393                 for (OpCode opcode : values()) {
394                     if (opcode.getValue() == value) {
395                         return opcode;
396                     }
397                 }
398                 return null;
399             }
400 
401             private final byte code;
402 
OpCode(int code)403             private OpCode(int code) {
404                 this.code = (byte) code;
405             }
406 
getValue()407             public byte getValue() {
408                 return this.code;
409             }
410 
isControlFrame()411             public boolean isControlFrame() {
412                 return this == Close || this == Ping || this == Pong;
413             }
414         }
415 
416         public static final Charset TEXT_CHARSET = Charset.forName("UTF-8");
417 
binary2Text(byte[] payload)418         public static String binary2Text(byte[] payload) throws CharacterCodingException {
419             return new String(payload, WebSocketFrame.TEXT_CHARSET);
420         }
421 
binary2Text(byte[] payload, int offset, int length)422         public static String binary2Text(byte[] payload, int offset, int length) throws CharacterCodingException {
423             return new String(payload, offset, length, WebSocketFrame.TEXT_CHARSET);
424         }
425 
checkedRead(int read)426         private static int checkedRead(int read) throws IOException {
427             if (read < 0) {
428                 throw new EOFException();
429             }
430             return read;
431         }
432 
read(InputStream in)433         public static WebSocketFrame read(InputStream in) throws IOException {
434             byte head = (byte) checkedRead(in.read());
435             boolean fin = (head & 0x80) != 0;
436             OpCode opCode = OpCode.find((byte) (head & 0x0F));
437             if ((head & 0x70) != 0) {
438                 throw new WebSocketException(CloseCode.ProtocolError, "The reserved bits (" + Integer.toBinaryString(head & 0x70) + ") must be 0.");
439             }
440             if (opCode == null) {
441                 throw new WebSocketException(CloseCode.ProtocolError, "Received frame with reserved/unknown opcode " + (head & 0x0F) + ".");
442             } else if (opCode.isControlFrame() && !fin) {
443                 throw new WebSocketException(CloseCode.ProtocolError, "Fragmented control frame.");
444             }
445 
446             WebSocketFrame frame = new WebSocketFrame(opCode, fin);
447             frame.readPayloadInfo(in);
448             frame.readPayload(in);
449             if (frame.getOpCode() == OpCode.Close) {
450                 return new CloseFrame(frame);
451             } else {
452                 return frame;
453             }
454         }
455 
text2Binary(String payload)456         public static byte[] text2Binary(String payload) throws CharacterCodingException {
457             return payload.getBytes(WebSocketFrame.TEXT_CHARSET);
458         }
459 
460         private OpCode opCode;
461 
462         private boolean fin;
463 
464         private byte[] maskingKey;
465 
466         private byte[] payload;
467 
468         // --------------------------------GETTERS---------------------------------
469 
470         private transient int _payloadLength;
471 
472         private transient String _payloadString;
473 
WebSocketFrame(OpCode opCode, boolean fin)474         private WebSocketFrame(OpCode opCode, boolean fin) {
475             setOpCode(opCode);
476             setFin(fin);
477         }
478 
WebSocketFrame(OpCode opCode, boolean fin, byte[] payload)479         public WebSocketFrame(OpCode opCode, boolean fin, byte[] payload) {
480             this(opCode, fin, payload, null);
481         }
482 
WebSocketFrame(OpCode opCode, boolean fin, byte[] payload, byte[] maskingKey)483         public WebSocketFrame(OpCode opCode, boolean fin, byte[] payload, byte[] maskingKey) {
484             this(opCode, fin);
485             setMaskingKey(maskingKey);
486             setBinaryPayload(payload);
487         }
488 
WebSocketFrame(OpCode opCode, boolean fin, String payload)489         public WebSocketFrame(OpCode opCode, boolean fin, String payload) throws CharacterCodingException {
490             this(opCode, fin, payload, null);
491         }
492 
WebSocketFrame(OpCode opCode, boolean fin, String payload, byte[] maskingKey)493         public WebSocketFrame(OpCode opCode, boolean fin, String payload, byte[] maskingKey) throws CharacterCodingException {
494             this(opCode, fin);
495             setMaskingKey(maskingKey);
496             setTextPayload(payload);
497         }
498 
WebSocketFrame(OpCode opCode, List<WebSocketFrame> fragments)499         public WebSocketFrame(OpCode opCode, List<WebSocketFrame> fragments) throws WebSocketException {
500             setOpCode(opCode);
501             setFin(true);
502 
503             long _payloadLength = 0;
504             for (WebSocketFrame inter : fragments) {
505                 _payloadLength += inter.getBinaryPayload().length;
506             }
507             if (_payloadLength < 0 || _payloadLength > Integer.MAX_VALUE) {
508                 throw new WebSocketException(CloseCode.MessageTooBig, "Max frame length has been exceeded.");
509             }
510             this._payloadLength = (int) _payloadLength;
511             byte[] payload = new byte[this._payloadLength];
512             int offset = 0;
513             for (WebSocketFrame inter : fragments) {
514                 System.arraycopy(inter.getBinaryPayload(), 0, payload, offset, inter.getBinaryPayload().length);
515                 offset += inter.getBinaryPayload().length;
516             }
517             setBinaryPayload(payload);
518         }
519 
WebSocketFrame(WebSocketFrame clone)520         public WebSocketFrame(WebSocketFrame clone) {
521             setOpCode(clone.getOpCode());
522             setFin(clone.isFin());
523             setBinaryPayload(clone.getBinaryPayload());
524             setMaskingKey(clone.getMaskingKey());
525         }
526 
getBinaryPayload()527         public byte[] getBinaryPayload() {
528             return this.payload;
529         }
530 
getMaskingKey()531         public byte[] getMaskingKey() {
532             return this.maskingKey;
533         }
534 
getOpCode()535         public OpCode getOpCode() {
536             return this.opCode;
537         }
538 
539         // --------------------------------SERIALIZATION---------------------------
540 
getTextPayload()541         public String getTextPayload() {
542             if (this._payloadString == null) {
543                 try {
544                     this._payloadString = binary2Text(getBinaryPayload());
545                 } catch (CharacterCodingException e) {
546                     throw new RuntimeException("Undetected CharacterCodingException", e);
547                 }
548             }
549             return this._payloadString;
550         }
551 
isFin()552         public boolean isFin() {
553             return this.fin;
554         }
555 
isMasked()556         public boolean isMasked() {
557             return this.maskingKey != null && this.maskingKey.length == 4;
558         }
559 
payloadToString()560         private String payloadToString() {
561             if (this.payload == null) {
562                 return "null";
563             } else {
564                 final StringBuilder sb = new StringBuilder();
565                 sb.append('[').append(this.payload.length).append("b] ");
566                 if (getOpCode() == OpCode.Text) {
567                     String text = getTextPayload();
568                     if (text.length() > 100) {
569                         sb.append(text.substring(0, 100)).append("...");
570                     } else {
571                         sb.append(text);
572                     }
573                 } else {
574                     sb.append("0x");
575                     for (int i = 0; i < Math.min(this.payload.length, 50); ++i) {
576                         sb.append(Integer.toHexString(this.payload[i] & 0xFF));
577                     }
578                     if (this.payload.length > 50) {
579                         sb.append("...");
580                     }
581                 }
582                 return sb.toString();
583             }
584         }
585 
readPayload(InputStream in)586         private void readPayload(InputStream in) throws IOException {
587             this.payload = new byte[this._payloadLength];
588             int read = 0;
589             while (read < this._payloadLength) {
590                 read += checkedRead(in.read(this.payload, read, this._payloadLength - read));
591             }
592 
593             if (isMasked()) {
594                 for (int i = 0; i < this.payload.length; i++) {
595                     this.payload[i] ^= this.maskingKey[i % 4];
596                 }
597             }
598 
599             // Test for Unicode errors
600             if (getOpCode() == OpCode.Text) {
601                 this._payloadString = binary2Text(getBinaryPayload());
602             }
603         }
604 
605         // --------------------------------ENCODING--------------------------------
606 
readPayloadInfo(InputStream in)607         private void readPayloadInfo(InputStream in) throws IOException {
608             byte b = (byte) checkedRead(in.read());
609             boolean masked = (b & 0x80) != 0;
610 
611             this._payloadLength = (byte) (0x7F & b);
612             if (this._payloadLength == 126) {
613                 // checkedRead must return int for this to work
614                 this._payloadLength = (checkedRead(in.read()) << 8 | checkedRead(in.read())) & 0xFFFF;
615                 if (this._payloadLength < 126) {
616                     throw new WebSocketException(CloseCode.ProtocolError, "Invalid data frame 2byte length. (not using minimal length encoding)");
617                 }
618             } else if (this._payloadLength == 127) {
619                 long _payloadLength =
620                         (long) checkedRead(in.read()) << 56 | (long) checkedRead(in.read()) << 48 | (long) checkedRead(in.read()) << 40 | (long) checkedRead(in.read()) << 32
621                                 | checkedRead(in.read()) << 24 | checkedRead(in.read()) << 16 | checkedRead(in.read()) << 8 | checkedRead(in.read());
622                 if (_payloadLength < 65536) {
623                     throw new WebSocketException(CloseCode.ProtocolError, "Invalid data frame 4byte length. (not using minimal length encoding)");
624                 }
625                 if (_payloadLength < 0 || _payloadLength > Integer.MAX_VALUE) {
626                     throw new WebSocketException(CloseCode.MessageTooBig, "Max frame length has been exceeded.");
627                 }
628                 this._payloadLength = (int) _payloadLength;
629             }
630 
631             if (this.opCode.isControlFrame()) {
632                 if (this._payloadLength > 125) {
633                     throw new WebSocketException(CloseCode.ProtocolError, "Control frame with payload length > 125 bytes.");
634                 }
635                 if (this.opCode == OpCode.Close && this._payloadLength == 1) {
636                     throw new WebSocketException(CloseCode.ProtocolError, "Received close frame with payload len 1.");
637                 }
638             }
639 
640             if (masked) {
641                 this.maskingKey = new byte[4];
642                 int read = 0;
643                 while (read < this.maskingKey.length) {
644                     read += checkedRead(in.read(this.maskingKey, read, this.maskingKey.length - read));
645                 }
646             }
647         }
648 
setBinaryPayload(byte[] payload)649         public void setBinaryPayload(byte[] payload) {
650             this.payload = payload;
651             this._payloadLength = payload.length;
652             this._payloadString = null;
653         }
654 
setFin(boolean fin)655         public void setFin(boolean fin) {
656             this.fin = fin;
657         }
658 
setMaskingKey(byte[] maskingKey)659         public void setMaskingKey(byte[] maskingKey) {
660             if (maskingKey != null && maskingKey.length != 4) {
661                 throw new IllegalArgumentException("MaskingKey " + Arrays.toString(maskingKey) + " hasn't length 4");
662             }
663             this.maskingKey = maskingKey;
664         }
665 
setOpCode(OpCode opcode)666         public void setOpCode(OpCode opcode) {
667             this.opCode = opcode;
668         }
669 
setTextPayload(String payload)670         public void setTextPayload(String payload) throws CharacterCodingException {
671             this.payload = text2Binary(payload);
672             this._payloadLength = payload.length();
673             this._payloadString = payload;
674         }
675 
676         // --------------------------------CONSTANTS-------------------------------
677 
setUnmasked()678         public void setUnmasked() {
679             setMaskingKey(null);
680         }
681 
682         @Override
toString()683         public String toString() {
684             final StringBuilder sb = new StringBuilder("WS[");
685             sb.append(getOpCode());
686             sb.append(", ").append(isFin() ? "fin" : "inter");
687             sb.append(", ").append(isMasked() ? "masked" : "unmasked");
688             sb.append(", ").append(payloadToString());
689             sb.append(']');
690             return sb.toString();
691         }
692 
693         // ------------------------------------------------------------------------
694 
write(OutputStream out)695         public void write(OutputStream out) throws IOException {
696             byte header = 0;
697             if (this.fin) {
698                 header |= 0x80;
699             }
700             header |= this.opCode.getValue() & 0x0F;
701             out.write(header);
702 
703             this._payloadLength = getBinaryPayload().length;
704             if (this._payloadLength <= 125) {
705                 out.write(isMasked() ? 0x80 | (byte) this._payloadLength : (byte) this._payloadLength);
706             } else if (this._payloadLength <= 0xFFFF) {
707                 out.write(isMasked() ? 0xFE : 126);
708                 out.write(this._payloadLength >>> 8);
709                 out.write(this._payloadLength);
710             } else {
711                 out.write(isMasked() ? 0xFF : 127);
712                 out.write(this._payloadLength >>> 56 & 0); // integer only
713                                                            // contains
714                 // 31 bit
715                 out.write(this._payloadLength >>> 48 & 0);
716                 out.write(this._payloadLength >>> 40 & 0);
717                 out.write(this._payloadLength >>> 32 & 0);
718                 out.write(this._payloadLength >>> 24);
719                 out.write(this._payloadLength >>> 16);
720                 out.write(this._payloadLength >>> 8);
721                 out.write(this._payloadLength);
722             }
723 
724             if (isMasked()) {
725                 out.write(this.maskingKey);
726                 for (int i = 0; i < this._payloadLength; i++) {
727                     out.write(getBinaryPayload()[i] ^ this.maskingKey[i % 4]);
728                 }
729             } else {
730                 out.write(getBinaryPayload());
731             }
732             out.flush();
733         }
734     }
735 
736     /**
737      * logger to log to.
738      */
739     private static final Logger LOG = Logger.getLogger(NanoWSD.class.getName());
740 
741     public static final String HEADER_UPGRADE = "upgrade";
742 
743     public static final String HEADER_UPGRADE_VALUE = "websocket";
744 
745     public static final String HEADER_CONNECTION = "connection";
746 
747     public static final String HEADER_CONNECTION_VALUE = "Upgrade";
748 
749     public static final String HEADER_WEBSOCKET_VERSION = "sec-websocket-version";
750 
751     public static final String HEADER_WEBSOCKET_VERSION_VALUE = "13";
752 
753     public static final String HEADER_WEBSOCKET_KEY = "sec-websocket-key";
754 
755     public static final String HEADER_WEBSOCKET_ACCEPT = "sec-websocket-accept";
756 
757     public static final String HEADER_WEBSOCKET_PROTOCOL = "sec-websocket-protocol";
758 
759     private final static String WEBSOCKET_KEY_MAGIC = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
760 
761     private final static char[] ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".toCharArray();
762 
763     /**
764      * Translates the specified byte array into Base64 string.
765      * <p>
766      * Android has android.util.Base64, sun has sun.misc.Base64Encoder, Java 8
767      * hast java.util.Base64, I have this from stackoverflow:
768      * http://stackoverflow.com/a/4265472
769      * </p>
770      *
771      * @param buf
772      *            the byte array (not null)
773      * @return the translated Base64 string (not null)
774      */
encodeBase64(byte[] buf)775     private static String encodeBase64(byte[] buf) {
776         int size = buf.length;
777         char[] ar = new char[(size + 2) / 3 * 4];
778         int a = 0;
779         int i = 0;
780         while (i < size) {
781             byte b0 = buf[i++];
782             byte b1 = i < size ? buf[i++] : 0;
783             byte b2 = i < size ? buf[i++] : 0;
784 
785             int mask = 0x3F;
786             ar[a++] = NanoWSD.ALPHABET[b0 >> 2 & mask];
787             ar[a++] = NanoWSD.ALPHABET[(b0 << 4 | (b1 & 0xFF) >> 4) & mask];
788             ar[a++] = NanoWSD.ALPHABET[(b1 << 2 | (b2 & 0xFF) >> 6) & mask];
789             ar[a++] = NanoWSD.ALPHABET[b2 & mask];
790         }
791         switch (size % 3) {
792             case 1:
793                 ar[--a] = '=';
794             case 2:
795                 ar[--a] = '=';
796         }
797         return new String(ar);
798     }
799 
makeAcceptKey(String key)800     public static String makeAcceptKey(String key) throws NoSuchAlgorithmException {
801         MessageDigest md = MessageDigest.getInstance("SHA-1");
802         String text = key + NanoWSD.WEBSOCKET_KEY_MAGIC;
803         md.update(text.getBytes(), 0, text.length());
804         byte[] sha1hash = md.digest();
805         return encodeBase64(sha1hash);
806     }
807 
NanoWSD(int port)808     public NanoWSD(int port) {
809         super(port);
810     }
811 
NanoWSD(String hostname, int port)812     public NanoWSD(String hostname, int port) {
813         super(hostname, port);
814     }
815 
isWebSocketConnectionHeader(Map<String, String> headers)816     private boolean isWebSocketConnectionHeader(Map<String, String> headers) {
817         String connection = headers.get(NanoWSD.HEADER_CONNECTION);
818         return connection != null && connection.toLowerCase().contains(NanoWSD.HEADER_CONNECTION_VALUE.toLowerCase());
819     }
820 
isWebsocketRequested(IHTTPSession session)821     protected boolean isWebsocketRequested(IHTTPSession session) {
822         Map<String, String> headers = session.getHeaders();
823         String upgrade = headers.get(NanoWSD.HEADER_UPGRADE);
824         boolean isCorrectConnection = isWebSocketConnectionHeader(headers);
825         boolean isUpgrade = NanoWSD.HEADER_UPGRADE_VALUE.equalsIgnoreCase(upgrade);
826         return isUpgrade && isCorrectConnection;
827     }
828 
829     // --------------------------------Listener--------------------------------
830 
openWebSocket(IHTTPSession handshake)831     protected abstract WebSocket openWebSocket(IHTTPSession handshake);
832 
833     @Override
serve(final IHTTPSession session)834     public Response serve(final IHTTPSession session) {
835         Map<String, String> headers = session.getHeaders();
836         if (isWebsocketRequested(session)) {
837             if (!NanoWSD.HEADER_WEBSOCKET_VERSION_VALUE.equalsIgnoreCase(headers.get(NanoWSD.HEADER_WEBSOCKET_VERSION))) {
838                 return newFixedLengthResponse(Response.Status.BAD_REQUEST, NanoHTTPD.MIME_PLAINTEXT,
839                         "Invalid Websocket-Version " + headers.get(NanoWSD.HEADER_WEBSOCKET_VERSION));
840             }
841 
842             if (!headers.containsKey(NanoWSD.HEADER_WEBSOCKET_KEY)) {
843                 return newFixedLengthResponse(Response.Status.BAD_REQUEST, NanoHTTPD.MIME_PLAINTEXT, "Missing Websocket-Key");
844             }
845 
846             WebSocket webSocket = openWebSocket(session);
847             Response handshakeResponse = webSocket.getHandshakeResponse();
848             try {
849                 handshakeResponse.addHeader(NanoWSD.HEADER_WEBSOCKET_ACCEPT, makeAcceptKey(headers.get(NanoWSD.HEADER_WEBSOCKET_KEY)));
850             } catch (NoSuchAlgorithmException e) {
851                 return newFixedLengthResponse(Response.Status.INTERNAL_ERROR, NanoHTTPD.MIME_PLAINTEXT,
852                         "The SHA-1 Algorithm required for websockets is not available on the server.");
853             }
854 
855             if (headers.containsKey(NanoWSD.HEADER_WEBSOCKET_PROTOCOL)) {
856                 handshakeResponse.addHeader(NanoWSD.HEADER_WEBSOCKET_PROTOCOL, headers.get(NanoWSD.HEADER_WEBSOCKET_PROTOCOL).split(",")[0]);
857             }
858 
859             return handshakeResponse;
860         } else {
861             return serveHttp(session);
862         }
863     }
864 
serveHttp(final IHTTPSession session)865     protected Response serveHttp(final IHTTPSession session) {
866         return super.serve(session);
867     }
868 
869     /**
870      * not all websockets implementations accept gzip compression.
871      */
872     @Override
useGzipWhenAccepted(Response r)873     protected boolean useGzipWhenAccepted(Response r) {
874         return false;
875     }
876 }
877