1 /* 2 * Copyright (c) 2017-2018 sel-project 3 * 4 * Permission is hereby granted, free of charge, to any person obtaining a copy 5 * of this software and associated documentation files (the "Software"), to deal 6 * in the Software without restriction, including without limitation the rights 7 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 * copies of the Software, and to permit persons to whom the Software is 9 * furnished to do so, subject to the following conditions: 10 * 11 * The above copyright notice and this permission notice shall be included in all 12 * copies or substantial portions of the Software. 13 * 14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 * SOFTWARE. 21 * 22 */ 23 /** 24 * Copyright: Copyright (c) 2017-2018 sel-project 25 * License: MIT 26 * Authors: Kripth 27 * Source: $(HTTP github.com/sel-project/sel-net/sel/net/websocket.d, sel/net/websocket.d) 28 */ 29 module sel.net.websocket; 30 31 import std.base64 : Base64; 32 import std.bitmanip : nativeToBigEndian, bigEndianToNative; 33 import std.conv : to; 34 import std.digest.sha : sha1Of; 35 import std.socket : Socket; 36 37 import sel.net.http : StatusCodes, Request, Response; 38 import sel.net.modifiers : ModifierStream; 39 import sel.net.stream : Stream, TcpStream; 40 41 private enum magicString = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; 42 43 public Response authWebSocketClient(Request request) { 44 Response response; 45 auto key = "sec-websocket-key" in request.headers; 46 if(key) { 47 response = Response(StatusCodes.switchingProtocols, [ 48 "Sec-WebSocket-Accept": Base64.encode(sha1Of(*key ~ magicString)).idup, 49 "Connection": "upgrade", 50 "Upgrade": "websocket" 51 ]); 52 response.valid = true; 53 } 54 return response; 55 } 56 57 /** 58 * Example: 59 * --- 60 * auto wss = new WebSocketServerStream(new TcpStream(socket)); 61 * --- 62 */ 63 class WebSocketServerStream : ModifierStream { 64 65 public this(Stream stream) { 66 super(stream); 67 } 68 69 public override ptrdiff_t send(ubyte[] payload) { 70 ubyte[] header = [0b10000001]; 71 if(payload.length < 0b01111110) { 72 header ~= payload.length & 255; 73 } else if(payload.length < ushort.max) { 74 header ~= 0b01111110; 75 header ~= nativeToBigEndian(cast(ushort)payload.length); 76 } else { 77 header ~= 0b01111111; 78 header ~= nativeToBigEndian(cast(ulong)payload.length); 79 } 80 return this.stream.send(header ~ payload); 81 } 82 83 public ptrdiff_t send(string payload) { 84 return this.send(cast(ubyte[])payload); 85 } 86 87 public override ubyte[] receive() { 88 ubyte[] payload = this.stream.receive(); 89 if(payload.length > 2 && (payload[0] & 0b1111) == 1) { 90 bool masked = (payload[1] & 0b10000000) != 0; 91 size_t length = payload[1] & 0b01111111; 92 size_t index = 2; 93 if(length == 0b01111110) { 94 if(payload.length >= index + 2) { 95 ubyte[2] bytes = payload[index..index+2]; 96 length = bigEndianToNative!ushort(bytes); 97 index += 2; 98 } 99 } else if(length == 0b01111111) { 100 if(payload.length >= index + 8) { 101 ubyte[8] bytes = payload[index..index+8]; 102 length = bigEndianToNative!ulong(bytes).to!size_t; 103 length += 8; 104 } 105 } 106 if(payload.length >= index + length) { 107 if(!masked) { 108 return payload[index..index+length]; 109 } else if(payload.length == index + length + 4) { 110 immutable index4 = index + 4; 111 ubyte[4] mask = payload[index..index4]; 112 payload = payload[index4..index4+length]; 113 foreach(i, ref ubyte p; payload) { 114 p ^= mask[i % 4]; 115 } 116 return payload; 117 } 118 } 119 } 120 return (ubyte[]).init; 121 } 122 123 }