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 }