aboutsummaryrefslogtreecommitdiff
path: root/src/unstable/protocolwebsocket.cpp
diff options
context:
space:
mode:
authorMike Buland <mbuland@penny-arcade.com>2017-10-18 07:49:59 -0700
committerMike Buland <mbuland@penny-arcade.com>2017-10-18 07:49:59 -0700
commitea96e4decaa23fc8ddfb528d4851751ec9496490 (patch)
tree585ed55a2fda09b388e02f1b6812893ac234f30a /src/unstable/protocolwebsocket.cpp
parent317a740f0198085937bf4fdbf032cc481d20f4ed (diff)
downloadlibbu++-ea96e4decaa23fc8ddfb528d4851751ec9496490.tar.gz
libbu++-ea96e4decaa23fc8ddfb528d4851751ec9496490.tar.bz2
libbu++-ea96e4decaa23fc8ddfb528d4851751ec9496490.tar.xz
libbu++-ea96e4decaa23fc8ddfb528d4851751ec9496490.zip
I think this version works.
It would be really cool to add more features, like pre-websocket negotiation and callbacks for serving web content, etc.
Diffstat (limited to 'src/unstable/protocolwebsocket.cpp')
-rw-r--r--src/unstable/protocolwebsocket.cpp300
1 files changed, 300 insertions, 0 deletions
diff --git a/src/unstable/protocolwebsocket.cpp b/src/unstable/protocolwebsocket.cpp
new file mode 100644
index 0000000..9200904
--- /dev/null
+++ b/src/unstable/protocolwebsocket.cpp
@@ -0,0 +1,300 @@
1/*
2 * Copyright (C) 2007-2014 Xagasoft, All rights reserved.
3 *
4 * This file is part of the libbu++ library and is released under the
5 * terms of the license contained in the file LICENSE.
6 */
7
8#include "bu/config.h"
9#include "bu/protocolwebsocket.h"
10
11#include "bu/sio.h"
12#include "bu/client.h"
13
14#include "bu/membuf.h"
15#include "bu/base64.h"
16#include "bu/sha1.h"
17#include "bu/json.h"
18
19#include <stdlib.h>
20
21Bu::ProtocolWebSocket::ProtocolWebSocket() :
22 eStatus( stProtoId )
23{
24}
25
26Bu::ProtocolWebSocket::~ProtocolWebSocket()
27{
28}
29
30void Bu::ProtocolWebSocket::onNewConnection( Bu::Client *pClient )
31{
32 this->pClient = pClient;
33}
34
35void Bu::ProtocolWebSocket::onNewData( Bu::Client * /*pClient*/ )
36{
37 for(;;)
38 {
39 switch( eStatus )
40 {
41 case stProtoId:
42 if( !stateProtoId() )
43 return;
44 break;
45
46 case stHandshake:
47 if( !stateHandshake() )
48 return;
49 break;
50
51 case stReady:
52 if( !parseMessage() )
53 return;
54 break;
55 }
56 }
57}
58
59void Bu::ProtocolWebSocket::writeMessage( const Bu::String &sData,
60 Bu::ProtocolWebSocket::Operation eOp )
61{
62 uint8_t cHeader[32];
63 //uint8_t *cMask;
64 memset( cHeader, 0, 32 );
65
66 int idx = 2;
67
68 cHeader[0] = (((uint8_t)(eOp&0x0f)))|0x80;
69
70 uint64_t iLen = sData.getSize();
71 if( iLen < 126 )
72 {
73 cHeader[1] = ((uint8_t)iLen);
74 }
75 else if( iLen < 2147483648 )
76 {
77 cHeader[1] = ((uint8_t)126);
78 uint16_t uLen = iLen;
79 uLen = htobe16( uLen );
80 memcpy( cHeader+idx, &uLen, 2 );
81 idx += 2;
82 }
83 else
84 {
85 cHeader[1] = ((uint8_t)127);
86 uint64_t iTmp = htobe64( iLen );
87 memcpy( cHeader+idx, &iTmp, 8 );
88 idx += 8;
89 }
90/*
91 Bu::println("Message size: %1 (%2)").arg( iLen ).arg( iLen, Bu::Fmt::bin(4) );
92 for( int j = 0; j < idx; j++ )
93 {
94 Bu::print(" %1").arg( cHeader[j], Bu::Fmt::bin(8) );
95 }
96 Bu::println("");
97*/
98 pClient->write( cHeader, idx );
99 pClient->write( sData );
100}
101
102bool Bu::ProtocolWebSocket::stateProtoId()
103{
104 Bu::String sLine;
105 if( !readHttpHdrLine( sLine ) )
106 return false;
107
108 Bu::StringList lChunks = sLine.split(' ');
109 if( lChunks.getSize() != 3 )
110 {
111 pClient->disconnect();
112 return false;
113 }
114 Bu::StringList::iterator i = lChunks.begin();
115 if( *i != "GET" )
116 {
117 pClient->disconnect();
118 return false;
119 }
120 sPath = *(++i);
121 if( *(++i) != "HTTP/1.1" )
122 {
123 pClient->disconnect();
124 return false;
125 }
126
127 eStatus = stHandshake;
128
129 return true;
130}
131
132bool Bu::ProtocolWebSocket::stateHandshake()
133{
134 Bu::String sLine;
135 if( !readHttpHdrLine( sLine ) )
136 return false;
137
138 if( sLine.getSize() == 0 )
139 {
140 if( !processHeaders() )
141 return false;
142 onHandshakeComplete();
143 eStatus = stReady;
144 return true;
145 }
146
147 int iPos = sLine.findIdx(':');
148 if( iPos < 0 )
149 {
150 pClient->disconnect();
151 return false;
152 }
153 Bu::String sKey( sLine, iPos );
154 Bu::String sValue( sLine.getSubStrIdx( iPos+2 ) );
155
156 if( !hHeader.has( sKey ) )
157 {
158 hHeader.insert( sKey, Bu::StringList() );
159 }
160 hHeader.get( sKey ).append( sValue );
161
162// Bu::println("Hdr: >>%1<<").arg( sLine );
163// Bu::println("%1 = %2").arg( sKey ).arg( sValue );
164
165 return true;
166}
167
168bool Bu::ProtocolWebSocket::readHttpHdrLine( Bu::String &sLine )
169{
170 char buf[1024];
171 int iSize = pClient->peek( buf, 1024 );
172 for( int j = 0; j < iSize-1; j++ )
173 {
174 if( buf[j] == '\r' && buf[j+1] == '\n' )
175 {
176 pClient->seek(j+2);
177 sLine.set( buf, j );
178 return true;
179 }
180 }
181 return false;
182}
183
184bool Bu::ProtocolWebSocket::processHeaders()
185{
186 if( !headerMatch("Connection", "Upgrade") ||
187 !headerMatch("Upgrade", "websocket") ||
188 !headerMatch("Sec-WebSocket-Version", "13") )
189 {
190 pClient->disconnect();
191 return false;
192 }
193
194 Bu::String sNonce;
195 if( !hHeader.has("Sec-WebSocket-Key") )
196 {
197 pClient->disconnect();
198 return false;
199 }
200
201 sNonce = hHeader.get("Sec-WebSocket-Key").first();
202
203 Bu::String sGuid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
204
205 Bu::Sha1 sum;
206 sum.addData( sNonce+sGuid );
207 Bu::MemBuf mbOut;
208 Bu::Base64 bOut( mbOut );
209 sum.writeResult( bOut );
210 bOut.stop();
211
212 Bu::println("accept: %1").arg( mbOut.getString() );
213
214 pClient->write("HTTP/1.1 101 Switching Protocols\r\n"
215 "Upgrade: websocket\r\n"
216 "Connection: Upgrade\r\n"
217 "Sec-WebSocket-Accept: " + mbOut.getString() + "\r\n"
218 "\r\n"
219 );
220
221 return true;
222}
223
224bool Bu::ProtocolWebSocket::headerMatch( const Bu::String &sKey, const Bu::String &sValue )
225{
226 if( !hHeader.has( sKey ) )
227 return false;
228
229 for( Bu::StringList::iterator i = hHeader.get( sKey ).begin(); i; i++ )
230 {
231 if( *i == sValue )
232 return true;
233 }
234
235 return false;
236}
237
238bool Bu::ProtocolWebSocket::parseMessage()
239{
240 if( pClient->getInputSize() < 2 )
241 return false;
242
243 uint8_t buf[32];
244 int64_t iTgtLength = 2;
245 pClient->peek( buf, 2, 0 );
246 Operation eOp = (Operation)(buf[0]&0x0f);
247 int64_t iLen = buf[1]&(~0x80);
248 bool bMasked = (buf[1]&0x80) == 0x80;
249 if( iLen == 126 )
250 {
251 uint16_t iLenBuf;
252 if( pClient->getInputSize() < iTgtLength+2 )
253 return false;
254 pClient->peek( &iLenBuf, 2, iTgtLength );
255 iTgtLength += 2;
256 iLen = be16toh( iLenBuf );
257 }
258 else if( iLen == 127 )
259 {
260 if( pClient->getInputSize() < iTgtLength+8 )
261 return false;
262 pClient->peek( &iLen, 8, iTgtLength );
263 iTgtLength += 8;
264 iLen = be64toh( iLen );
265 }
266 char cMask[4];
267 if( bMasked )
268 {
269 if( pClient->getInputSize() < iTgtLength+4 )
270 return false;
271 pClient->peek( cMask, 4, iTgtLength );
272 iTgtLength += 4;
273 }
274 if( pClient->getInputSize() < iTgtLength+iLen )
275 return false;
276
277 pClient->seek( iTgtLength );
278
279 Bu::String sData( iLen );
280 int iRead = 0;
281 do
282 {
283 iRead += pClient->read( sData.getStr()+iRead, iLen-iRead );
284 } while( iRead < iLen );
285 if( bMasked )
286 {
287 for( int j = 0; j < iLen; j++ )
288 {
289 sData[j] = sData[j]^cMask[j%4];
290 }
291 }
292
293 Bu::println("");
294 Bu::println("Data: >>%1<<").arg( sData );
295
296 onNewMessage( sData, eOp );
297
298 return true;
299}
300