Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Newer
Older
100644 695 lines (611 sloc) 24.902 kb
7fdc54a @davide HTML5 Web Sockets support.
davide authored
1 %%%----------------------------------------------------------------------
2 %%% File : yaws_websockets.erl
bd0bf89 @klacke no utf8 in the author name
authored
3 %%% Author : Davide Marques <nesrait@gmail.com>
fd690a6 @vinoski refactor yaws_websockets as a gen_server
vinoski authored
4 %%% Purpose : support for WebSockets
5 %%% Created : 18 Dec 2009 by Davide Marques <nesrait@gmail.com>
6 %%% Modified: extensively revamped in 2011 by J.D. Bothma <jbothma@gmail.com>
7fdc54a @davide HTML5 Web Sockets support.
davide authored
7 %%%----------------------------------------------------------------------
8
9 -module(yaws_websockets).
10 -author('nesrait@gmail.com').
fd690a6 @vinoski refactor yaws_websockets as a gen_server
vinoski authored
11 -author('jbothma@gmail.com').
12 -behaviour(gen_server).
7fdc54a @davide HTML5 Web Sockets support.
davide authored
13
14 -include("../include/yaws.hrl").
15 -include("../include/yaws_api.hrl").
16 -include("yaws_debug.hrl").
17
18 -include_lib("kernel/include/file.hrl").
19
5b34d48 @vinoski wrap SSL sockets in tuple
vinoski authored
20 -define(MAX_PAYLOAD, 16*1024*1024). % 16MB
39c8d49 @jbothma update WebSockets implementation to support RFC 6455
jbothma authored
21
c0af90a @vinoski add callback for abnormal websocket close
vinoski authored
22 %% RFC 6455 section 7.4.1: status code 1000 means "normal"
23 -define(NORMAL_WS_STATUS, 1000).
24
fd690a6 @vinoski refactor yaws_websockets as a gen_server
vinoski authored
25 -record(state, {
26 arg,
27 sconf,
28 cbmod,
29 opts,
30 wsstate,
31 cbstate,
32 cbtype
33 }).
39c8d49 @jbothma update WebSockets implementation to support RFC 6455
jbothma authored
34
fd690a6 @vinoski refactor yaws_websockets as a gen_server
vinoski authored
35 -export([start/3, send/2]).
36 -export([init/1, handle_call/3, handle_cast/2, handle_info/2,
37 terminate/2, code_change/3]).
38 %%
39 %% API
40 %%
39c8d49 @jbothma update WebSockets implementation to support RFC 6455
jbothma authored
41 start(Arg, CallbackMod, Opts) ->
42 SC = get(sc),
43 PrepdOpts = preprocess_opts(Opts),
fd690a6 @vinoski refactor yaws_websockets as a gen_server
vinoski authored
44 {ok, OwnerPid} = gen_server:start(?MODULE, [Arg, SC, CallbackMod, PrepdOpts], []),
39c8d49 @jbothma update WebSockets implementation to support RFC 6455
jbothma authored
45 CliSock = Arg#arg.clisock,
ba34c61 Fix SSL socket wrapping
Christopher Faulet authored
46 TakeOverResult =
47 case yaws_api:get_sslsocket(CliSock) of
48 {ok, SslSocket} ->
49 ssl:setopts(SslSocket, [{packet, raw}, {active, once}]),
50 ssl:controlling_process(SslSocket, OwnerPid);
51 undefined ->
52 inet:setopts(CliSock, [{packet, raw}, {active, once}]),
53 gen_tcp:controlling_process(CliSock, OwnerPid)
54 end,
39c8d49 @jbothma update WebSockets implementation to support RFC 6455
jbothma authored
55 case TakeOverResult of
56 ok ->
fd690a6 @vinoski refactor yaws_websockets as a gen_server
vinoski authored
57 gen_server:cast(OwnerPid, ok),
39c8d49 @jbothma update WebSockets implementation to support RFC 6455
jbothma authored
58 exit(normal);
fd690a6 @vinoski refactor yaws_websockets as a gen_server
vinoski authored
59 {error, Reason}=Error ->
60 gen_server:cast(OwnerPid, Error),
39c8d49 @jbothma update WebSockets implementation to support RFC 6455
jbothma authored
61 exit({websocket, Reason})
62 end.
63
5b34d48 @vinoski wrap SSL sockets in tuple
vinoski authored
64 send(#ws_state{sock=Socket, vsn=ProtoVsn}, {Type, Data}) ->
65 DataFrame = frame(ProtoVsn, Type, Data),
66 case yaws_api:get_sslsocket(Socket) of
ba34c61 Fix SSL socket wrapping
Christopher Faulet authored
67 {ok, SslSocket} -> ssl:send(SslSocket, DataFrame);
68 undefined -> gen_tcp:send(Socket, DataFrame)
5b34d48 @vinoski wrap SSL sockets in tuple
vinoski authored
69 end;
39c8d49 @jbothma update WebSockets implementation to support RFC 6455
jbothma authored
70 send(Pid, {Type, Data}) ->
fd690a6 @vinoski refactor yaws_websockets as a gen_server
vinoski authored
71 gen_server:cast(Pid, {send, {Type, Data}}).
39c8d49 @jbothma update WebSockets implementation to support RFC 6455
jbothma authored
72
fd690a6 @vinoski refactor yaws_websockets as a gen_server
vinoski authored
73 %%
74 %% gen_server functions
75 %%
76 init([Arg, SC, CallbackMod, Opts]) ->
77 {ok, #state{arg=Arg, sconf=SC, cbmod=CallbackMod, opts=Opts}}.
39c8d49 @jbothma update WebSockets implementation to support RFC 6455
jbothma authored
78
fd690a6 @vinoski refactor yaws_websockets as a gen_server
vinoski authored
79 handle_call(_Req, _From, State) ->
80 {reply, ok, State}.
39c8d49 @jbothma update WebSockets implementation to support RFC 6455
jbothma authored
81
fd690a6 @vinoski refactor yaws_websockets as a gen_server
vinoski authored
82 handle_cast(ok, #state{arg=Arg, sconf=SC, opts=Opts}=State) ->
7fdc54a @davide HTML5 Web Sockets support.
davide authored
83 CliSock = Arg#arg.clisock,
39c8d49 @jbothma update WebSockets implementation to support RFC 6455
jbothma authored
84 OriginOpt = lists:keyfind(origin, 1, Opts),
85 Origin = get_origin_header(Arg#arg.headers),
86 case origin_check(Origin, OriginOpt) of
87 {error, Error} ->
88 error_logger:error_msg(Error),
fd690a6 @vinoski refactor yaws_websockets as a gen_server
vinoski authored
89 {stop, {error, Error}, State};
39c8d49 @jbothma update WebSockets implementation to support RFC 6455
jbothma authored
90 ok ->
91 ProtocolVersion = ws_version(Arg#arg.headers),
92 Protocol = get_protocol_header(Arg#arg.headers),
93 Host = (Arg#arg.headers)#headers.host,
94 {abs_path, Path} = (Arg#arg.req)#http_request.path,
95
96 WebSocketLocation =
97 case SC#sconf.ssl of
6e96a14 @klacke indendation cleanup
authored
98 undefined -> "ws://" ++ Host ++ Path;
99 _ -> "wss://" ++ Host ++ Path
39c8d49 @jbothma update WebSockets implementation to support RFC 6455
jbothma authored
100 end,
101
102 Handshake = handshake(ProtocolVersion, Arg, CliSock,
4ebda22 @vinoski minor code cleanup
vinoski authored
103 WebSocketLocation, Origin, Protocol),
5b34d48 @vinoski wrap SSL sockets in tuple
vinoski authored
104 case yaws_api:get_sslsocket(CliSock) of
ba34c61 Fix SSL socket wrapping
Christopher Faulet authored
105 {ok, SslSocket} -> ssl:send(SslSocket, Handshake);
106 undefined -> gen_tcp:send(CliSock, Handshake)
5b34d48 @vinoski wrap SSL sockets in tuple
vinoski authored
107 end,
39c8d49 @jbothma update WebSockets implementation to support RFC 6455
jbothma authored
108 {callback, CallbackType} = lists:keyfind(callback, 1, Opts),
109 WSState = #ws_state{sock = CliSock,
110 vsn = ProtocolVersion,
111 frag_type = none},
112 CallbackState = case CallbackType of
113 basic ->
114 {none, <<>>};
115 {advanced, InitialState} ->
116 InitialState
117 end,
fd690a6 @vinoski refactor yaws_websockets as a gen_server
vinoski authored
118 NState = State#state{wsstate=WSState,
119 cbstate=CallbackState,
120 cbtype=CallbackType},
121 {noreply, NState}
122 end;
123 handle_cast({send, {Type, Data}}, #state{wsstate=WSState}=State) ->
124 do_send(WSState, {Type, Data}),
125 {noreply, State};
126 handle_cast(_Msg, State) ->
127 {noreply, State}.
128
129 handle_info({tcp, Socket, FirstPacket}, #state{wsstate=#ws_state{sock=Socket}}=State) ->
130 #state{cbmod=CallbackMod, wsstate=WSState,
131 cbstate=CallbackState, cbtype=CallbackType} = State,
132 FrameInfos = unframe_active_once(WSState, FirstPacket),
133 {Results, NewCallbackState} =
134 case CallbackType of
135 basic ->
136 {BasicMessages, NewCallbackState0} =
137 basic_messages(FrameInfos, CallbackState),
138 CallbackResults = lists:map(
139 fun(M) ->
140 CallbackMod:handle_message(M)
141 end,
142 BasicMessages),
b80121d @vinoski fix websockets reply status codes for client close messages
vinoski authored
143 FoldFun = handle_result_fun(WSState),
144 {catch lists:foldl(FoldFun, ok, lists:zip(FrameInfos, CallbackResults)),
fd690a6 @vinoski refactor yaws_websockets as a gen_server
vinoski authored
145 NewCallbackState0};
146 {advanced,_} ->
147 catch lists:foldl(do_callback_fun(WSState, CallbackMod),
148 {ok, CallbackState},
149 FrameInfos)
150 end,
151 case Results of
152 {close, Reason} ->
153 do_close(WSState, Reason),
154 {stop, Reason, State#state{cbstate=NewCallbackState}};
155 _ ->
156 Last = lists:last(FrameInfos),
157 NewWSState = Last#ws_frame_info.ws_state,
158 {noreply, State#state{wsstate=NewWSState, cbstate=NewCallbackState}}
159 end;
160 handle_info({tcp_closed, Socket}, #state{wsstate=#ws_state{sock=Socket}}=State) ->
161 %% The only way we should get here is due to an abnormal close.
162 %% Section 7.1.5 of RFC 6455 specifies 1006 as the connection
163 %% close code for abnormal closure. It's also described in
164 %% section 7.4.1.
165 #state{cbmod=CallbackMod, wsstate=WSState,
166 cbstate=CallbackState, cbtype=CallbackType} = State,
167 CloseStatus = 1006,
168 case CallbackType of
169 basic ->
170 CallbackMod:handle_message({close, CloseStatus, <<>>});
171 {advanced, _} ->
172 ClosePayload = <<CloseStatus:16/big>>,
173 CloseWSState = WSState#ws_state{sock=undefined,
174 frag_type=none},
175 CloseFrameInfo = #ws_frame_info{fin=1,
176 rsv=0,
177 opcode=close,
178 masked=0,
179 masking_key=0,
180 length=byte_size(ClosePayload),
181 payload=ClosePayload,
182 ws_state=CloseWSState},
183 CallbackMod:handle_message(CloseFrameInfo, CallbackState)
184 end,
185 {stop, normal, State};
186 handle_info(_Info, State) ->
187 {noreply, State}.
188
189 terminate(_Reason, _State) ->
190 ok.
191
192 code_change(_OldVsn, Data, _Extra) ->
193 {ok, Data}.
194
195 %% internal functions
196
197 do_send(#ws_state{sock=Socket, vsn=ProtoVsn}, {Type, Data}) ->
198 DataFrame = frame(ProtoVsn, Type, Data),
ba34c61 Fix SSL socket wrapping
Christopher Faulet authored
199 case yaws_api:get_sslsocket(Socket) of
200 {ok, SslSocket} -> ssl:send(SslSocket, DataFrame);
201 undefined -> gen_tcp:send(Socket, DataFrame)
39c8d49 @jbothma update WebSockets implementation to support RFC 6455
jbothma authored
202 end.
203
fd690a6 @vinoski refactor yaws_websockets as a gen_server
vinoski authored
204 preprocess_opts(GivenOpts) ->
205 Fun = fun({Key, Default}, Opts) ->
206 case lists:keyfind(Key, 1, Opts) of
207 false ->
208 [{Key, Default}|Opts];
209 _ -> Opts
210 end
211 end,
212 Defaults = [{origin, any},
213 {callback, basic}],
214 lists:foldl(Fun, GivenOpts, Defaults).
215
39c8d49 @jbothma update WebSockets implementation to support RFC 6455
jbothma authored
216 origin_check(_Origin, {origin, any}) ->
217 ok;
218 origin_check(Actual, {origin, _Expected=Actual}) ->
219 ok;
220 origin_check(Actual, {origin, Expected}) ->
221 Error = io_lib:format("Expected origin ~p but found ~p.",
222 [Expected, Actual]),
223 {error, Error}.
224
225 handshake(8, Arg, _CliSock, _WebSocketLocation, _Origin, _Protocol) ->
226 Key = get_nonce_header(Arg#arg.headers),
227 AcceptHash = hash_nonce(Key),
228 ["HTTP/1.1 101 Switching Protocols\r\n",
229 "Upgrade: websocket\r\n",
fe2e42a @schemeway Added support for websockets v76.
schemeway authored
230 "Connection: Upgrade\r\n",
39c8d49 @jbothma update WebSockets implementation to support RFC 6455
jbothma authored
231 "Sec-WebSocket-Accept: ", AcceptHash , "\r\n",
fe2e42a @schemeway Added support for websockets v76.
schemeway authored
232 "\r\n"].
233
39c8d49 @jbothma update WebSockets implementation to support RFC 6455
jbothma authored
234 handle_result_fun(WSState) ->
b80121d @vinoski fix websockets reply status codes for client close messages
vinoski authored
235 fun({FrameInfo, Result}, Acc) ->
39c8d49 @jbothma update WebSockets implementation to support RFC 6455
jbothma authored
236 case Result of
237 {reply, {Type, Data}} ->
fd690a6 @vinoski refactor yaws_websockets as a gen_server
vinoski authored
238 do_send(WSState, {Type, Data}),
239 Acc;
39c8d49 @jbothma update WebSockets implementation to support RFC 6455
jbothma authored
240 noreply ->
fd690a6 @vinoski refactor yaws_websockets as a gen_server
vinoski authored
241 Acc;
39c8d49 @jbothma update WebSockets implementation to support RFC 6455
jbothma authored
242 {close, Reason} ->
b80121d @vinoski fix websockets reply status codes for client close messages
vinoski authored
243 %% check if client sent a close, and if so, verify that the
244 %% close code is legal
245 NReason = check_close_code(FrameInfo, Reason),
246 throw({close, NReason})
39c8d49 @jbothma update WebSockets implementation to support RFC 6455
jbothma authored
247 end
248 end.
249
250 do_callback_fun(WSState, CallbackMod) ->
fd690a6 @vinoski refactor yaws_websockets as a gen_server
vinoski authored
251 fun(FrameInfo, {Results, CallbackState}) ->
39c8d49 @jbothma update WebSockets implementation to support RFC 6455
jbothma authored
252 case CallbackMod:handle_message(FrameInfo, CallbackState) of
253 {reply, {Type, Data}, NewCallbackState} ->
fd690a6 @vinoski refactor yaws_websockets as a gen_server
vinoski authored
254 do_send(WSState, {Type, Data}),
255 {Results, NewCallbackState};
39c8d49 @jbothma update WebSockets implementation to support RFC 6455
jbothma authored
256 {noreply, NewCallbackState} ->
fd690a6 @vinoski refactor yaws_websockets as a gen_server
vinoski authored
257 {Results, NewCallbackState};
39c8d49 @jbothma update WebSockets implementation to support RFC 6455
jbothma authored
258 {close, Reason} ->
b80121d @vinoski fix websockets reply status codes for client close messages
vinoski authored
259 %% check if client sent a close, and if so, verify that the
260 %% close code is legal
261 NReason = check_close_code(FrameInfo, Reason),
262 throw({{close, NReason}, CallbackState})
39c8d49 @jbothma update WebSockets implementation to support RFC 6455
jbothma authored
263 end
264 end.
265
b80121d @vinoski fix websockets reply status codes for client close messages
vinoski authored
266 %% The checks for close status codes here are based on RFC 6455 and on
267 %% the autobahn testsuite (http://autobahn.ws/testsuite).
268 check_close_code(#ws_frame_info{opcode=close, length=Len, data=Data}, Reason) ->
269 if
270 Len == 0 ->
271 Reason;
272 Len < 2 ->
273 1002;
274 true ->
275 <<Code:16/big, _/binary>> = Data,
276 if
277 Code >= 3000 andalso Code =< 4999 ->
278 Reason;
279 Code < 1000 ->
280 1002;
281 Code >= 1004 andalso Code =< 1006 ->
282 1002;
283 Code >= 1012 andalso Code =< 1016 ->
284 1002;
285 Code > 1016 ->
286 1002;
287 true ->
288 Reason
289 end
290 end;
291 check_close_code(_, Reason) ->
292 Reason.
293
16834ce @vinoski add close callback for websockets
vinoski authored
294 do_close(WSState, Reason) ->
295 Status = case Reason of
296 _ when is_integer(Reason) -> Reason;
c0af90a @vinoski add callback for abnormal websocket close
vinoski authored
297 _ -> ?NORMAL_WS_STATUS
16834ce @vinoski add close callback for websockets
vinoski authored
298 end,
fd690a6 @vinoski refactor yaws_websockets as a gen_server
vinoski authored
299 do_send(WSState, {close, <<Status:16/big>>}).
16834ce @vinoski add close callback for websockets
vinoski authored
300
39c8d49 @jbothma update WebSockets implementation to support RFC 6455
jbothma authored
301 basic_messages(FrameInfos, {FragType, FragAcc}) ->
fd690a6 @vinoski refactor yaws_websockets as a gen_server
vinoski authored
302 {Messages, NewFragType, NewFragAcc} =
303 lists:foldl(fun handle_message/2, {[], FragType, FragAcc}, FrameInfos),
39c8d49 @jbothma update WebSockets implementation to support RFC 6455
jbothma authored
304 {Messages, {NewFragType, NewFragAcc}}.
305
306 %% start of a fragmented message
307 handle_message(#ws_frame_info{fin=0,
308 opcode=FragType,
309 data=Data},
310 {Messages, none, <<>>}) ->
311 {Messages, FragType, Data};
312
313 %% non-final continuation of a fragmented message
314 handle_message(#ws_frame_info{fin=0,
315 data=Data,
316 opcode=continuation},
317 {Messages, FragType, FragAcc}) ->
318 {Messages, FragType, <<FragAcc/binary,Data/binary>>};
319
320 %% end of text fragmented message
321 handle_message(#ws_frame_info{fin=1,
322 opcode=continuation,
323 data=Data},
324 {Messages, text, FragAcc}) ->
325 Unfragged = <<FragAcc/binary, Data/binary>>,
326 NewMessage = {text, Unfragged},
16834ce @vinoski add close callback for websockets
vinoski authored
327 {Messages ++ [NewMessage], none, <<>>};
39c8d49 @jbothma update WebSockets implementation to support RFC 6455
jbothma authored
328
329 %% unfragmented text message
330 handle_message(#ws_frame_info{opcode=text, data=Data},
331 {Messages, none, <<>>}) ->
332 NewMessage = {text, Data},
16834ce @vinoski add close callback for websockets
vinoski authored
333 {Messages ++ [NewMessage], none, <<>>};
39c8d49 @jbothma update WebSockets implementation to support RFC 6455
jbothma authored
334
335 %% end of binary fragmented message
336 handle_message(#ws_frame_info{fin=1,
337 opcode=continuation,
338 data=Data},
339 {Messages, binary, FragAcc}) ->
340 Unfragged = <<FragAcc/binary, Data/binary>>,
341 NewMessage = {binary, Unfragged},
16834ce @vinoski add close callback for websockets
vinoski authored
342 {Messages ++ [NewMessage], none, <<>>};
39c8d49 @jbothma update WebSockets implementation to support RFC 6455
jbothma authored
343
344 handle_message(#ws_frame_info{opcode=binary,
345 data=Data},
346 {Messages, none, <<>>}) ->
347 NewMessage = {binary, Data},
16834ce @vinoski add close callback for websockets
vinoski authored
348 {Messages ++ [NewMessage], none, <<>>};
39c8d49 @jbothma update WebSockets implementation to support RFC 6455
jbothma authored
349
350 handle_message(#ws_frame_info{opcode=ping,
351 data=Data,
fd690a6 @vinoski refactor yaws_websockets as a gen_server
vinoski authored
352 ws_state=WSState},
39c8d49 @jbothma update WebSockets implementation to support RFC 6455
jbothma authored
353 Acc) ->
fd690a6 @vinoski refactor yaws_websockets as a gen_server
vinoski authored
354 do_send(WSState, {pong, Data}),
39c8d49 @jbothma update WebSockets implementation to support RFC 6455
jbothma authored
355 Acc;
356
357 handle_message(#ws_frame_info{opcode=pong}, Acc) ->
358 %% A response to an unsolicited pong frame is not expected.
359 %% http://tools.ietf.org/html/\
360 %% draft-ietf-hybi-thewebsocketprotocol-08#section-4
361 Acc;
362
16834ce @vinoski add close callback for websockets
vinoski authored
363 %% According to RFC 6455 section 5.4, control messages like close
364 %% MAY be injected in the middle of a fragmented message, which is
365 %% why we pass FragType and FragAcc along below. Whether any clients
366 %% actually do this in practice, I don't know.
367 handle_message(#ws_frame_info{opcode=close,
368 length=Len,
369 data=Data},
370 {Messages, FragType, FragAcc}) ->
371 NewMessage = case Len of
372 0 ->
c0af90a @vinoski add callback for abnormal websocket close
vinoski authored
373 {close, ?NORMAL_WS_STATUS, <<>>};
16834ce @vinoski add close callback for websockets
vinoski authored
374 _ ->
375 <<Status:16/big, Msg/binary>> = Data,
376 {close, Status, Msg}
377 end,
378 {Messages ++ [NewMessage], FragType, FragAcc};
379
39c8d49 @jbothma update WebSockets implementation to support RFC 6455
jbothma authored
380 handle_message(#ws_frame_info{}, Acc) ->
381 Acc.
382
fe2e42a @schemeway Added support for websockets v76.
schemeway authored
383 ws_version(Headers) ->
39c8d49 @jbothma update WebSockets implementation to support RFC 6455
jbothma authored
384 VersionVal = query_header("sec-websocket-version", Headers),
385 case VersionVal of
386 "8" -> 8;
387 "13" -> 8 % treat 13 like 8. Right now 13 support is as good as that
388 % of 8, according to autobahn 0.4.3
fe2e42a @schemeway Added support for websockets v76.
schemeway authored
389 end.
390
39c8d49 @jbothma update WebSockets implementation to support RFC 6455
jbothma authored
391 buffer(Socket, Len, Buffered) ->
392 case Buffered of
393 <<_Expected:Len/binary>> = Return -> % exactly enough
394 %% debug(val, {buffering, "got:", Len}),
395 Return;
396 <<_Expected:Len/binary,_Extra/binary>> = Return-> % more than expected
397 %% debug(val, {buffering, "got:", Len, "and more!"}),
398 Return;
399 _ ->
400 %% not enough
401 %% debug(val, {buffering, "need:", Len, "waiting for more..."}),
402 Needed = Len - binary_length(Buffered),
ba34c61 Fix SSL socket wrapping
Christopher Faulet authored
403 {ok, More} = case yaws_api:get_sslsocket(Socket) of
404 {ok, SslSocket} -> ssl:recv(SslSocket, Needed);
405 undefined -> gen_tcp:recv(Socket, Needed)
406 end,
39c8d49 @jbothma update WebSockets implementation to support RFC 6455
jbothma authored
407 <<Buffered/binary, More/binary>>
7fdc54a @davide HTML5 Web Sockets support.
davide authored
408 end.
409
39c8d49 @jbothma update WebSockets implementation to support RFC 6455
jbothma authored
410 binary_length(<<>>) ->
411 0;
412 binary_length(<<_First:1/binary, Rest/binary>>) ->
413 1 + binary_length(Rest).
414
415
416 checks(Unframed) ->
417 check_reserved_bits(Unframed).
418
419 check_control_frame(Len, Opcode, Fin) ->
420 if
421 (Len > 125) and (Opcode > 7) ->
422 %% http://tools.ietf.org/html/\
423 %% draft-ietf-hybi-thewebsocketprotocol-08#section-4.5
424 {fail_connection, "control frame > 125 bytes"};
425 (Fin == 0) and (Opcode > 7) ->
426 {fail_connection, "control frame may not be fragmented"};
427 true ->
428 ok
429 end.
430
431 %% no extensions are supported yet.
432 %% http://tools.ietf.org/html/\
433 %% draft-ietf-hybi-thewebsocketprotocol-08#section-4.2
434 check_reserved_bits(Unframed = #ws_frame_info{rsv=0}) ->
435 check_utf8(Unframed);
436 check_reserved_bits(#ws_frame_info{rsv=RSV}) ->
437 {fail_connection,
438 "rsv bits were " ++ integer_to_list(RSV) ++ " but should be unset."}.
439
440 %% http://www.erlang.org/doc/apps/stdlib/unicode_usage.html#id191467
441 %% Heuristic identification of UTF-8
0944464 @vinoski minor cleanup
vinoski authored
442 check_utf8(Unframed = #ws_frame_info{opcode=text, data=Bin})
39c8d49 @jbothma update WebSockets implementation to support RFC 6455
jbothma authored
443 when is_binary(Bin) ->
444 case unicode:characters_to_binary(Bin,utf8,utf8) of
445 Bin ->
446 Unframed;
447 _ ->
448 {fail_connection, "not valid utf-8."}
449 end;
450 check_utf8(Unframed) ->
451 check_reserved_opcode(Unframed).
452
453 check_reserved_opcode(#ws_frame_info{opcode = undefined}) ->
454 {fail_connection, "Reserved opcode."};
455 check_reserved_opcode(Unframed) ->
456 Unframed.
457
458
459 ws_frame_info(#ws_state{sock=Socket},
460 <<Fin:1, Rsv:3, Opcode:4, Masked:1, Len1:7, Rest/binary>>) ->
461 case check_control_frame(Len1, Opcode, Fin) of
462 ok ->
463 {ws_frame_info_secondary, Length, MaskingKey, Payload, Excess}
464 = ws_frame_info_secondary(Socket, Len1, Rest),
465 FrameInfo = #ws_frame_info{fin=Fin,
0944464 @vinoski minor cleanup
vinoski authored
466 rsv=Rsv,
467 opcode=opcode_to_atom(Opcode),
468 masked=Masked,
469 masking_key=MaskingKey,
470 length=Length,
471 payload=Payload},
39c8d49 @jbothma update WebSockets implementation to support RFC 6455
jbothma authored
472 {FrameInfo, Excess};
473 Other ->
474 Other
475 end;
476
477 ws_frame_info(State = #ws_state{sock=Socket}, FirstPacket) ->
478 ws_frame_info(State, buffer(Socket, 2,FirstPacket)).
479
480 ws_frame_info_secondary(Socket, Len1, Rest) ->
481 case Len1 of
482 126 ->
483 <<Len:16, MaskingKey:4/binary, Rest2/binary>> =
484 buffer(Socket, 6, Rest);
485 127 ->
486 <<Len:64, MaskingKey:4/binary, Rest2/binary>> =
487 buffer(Socket, 12, Rest);
488 Len ->
489 <<MaskingKey:4/binary, Rest2/binary>> = buffer(Socket, 4, Rest)
490 end,
491 if
492 Len > ?MAX_PAYLOAD ->
493 Error = io_lib:format(
494 "Payload length ~p longer than max allowed of ~p",
495 [Len, ?MAX_PAYLOAD]),
496 exit({error, Error});
497 true ->
498 <<Payload:Len/binary, Excess/binary>> = buffer(Socket, Len, Rest2),
499 {ws_frame_info_secondary, Len, MaskingKey, Payload, Excess}
500 end.
501
502 unframe_active_once(State, FirstPacket) ->
503 Frames = unframe(State, FirstPacket),
504 websocket_setopts(State, [{active, once}]),
505 Frames.
506
507 %% Returns all the WebSocket frames fully or partially contained in FirstPacket,
508 %% reading exactly as many more bytes from Socket as are needed to finish
509 %% unframing the last frame partially included in FirstPacket, if needed.
510 %%
511 %% The length of this list and depth of this recursion is limited by
512 %% the size of your socket receive buffer.
513 %%
514 %% -> { #ws_state, [#ws_frame_info,...,#ws_frame_info] }
515 unframe(_State, <<>>) ->
516 [];
517 unframe(State, FirstPacket) ->
518 case unframe_one(State, FirstPacket) of
519 {FrameInfo = #ws_frame_info{ws_state = NewState}, RestBin} ->
520 %% Every new recursion uses the #ws_state from the calling recursion.
521 [FrameInfo | unframe(NewState, RestBin)];
522 Fail ->
523 [Fail]
524 end.
525
526 %% -> {#ws_frame_info, RestBin} | {fail_connection, Reason}
527 unframe_one(State = #ws_state{vsn=8}, FirstPacket) ->
528 {FrameInfo = #ws_frame_info{}, RestBin} = ws_frame_info(State, FirstPacket),
529 Unmasked = mask(FrameInfo#ws_frame_info.masking_key,
530 FrameInfo#ws_frame_info.payload),
531 NewState = frag_state_machine(State, FrameInfo),
532 Unframed = FrameInfo#ws_frame_info{data = Unmasked,
533 ws_state = NewState},
534
535 case checks(Unframed) of
536 #ws_frame_info{} when is_record(NewState, ws_state) ->
537 {Unframed, RestBin};
538 #ws_frame_info{} when not is_record(NewState, ws_state) ->
c0af90a @vinoski add callback for abnormal websocket close
vinoski authored
539 NewState; % pass back the error details
39c8d49 @jbothma update WebSockets implementation to support RFC 6455
jbothma authored
540 Fail ->
541 Fail
542 end.
543
544 websocket_setopts(#ws_state{sock=Socket}, Opts) ->
5b34d48 @vinoski wrap SSL sockets in tuple
vinoski authored
545 case yaws_api:get_sslsocket(Socket) of
ba34c61 Fix SSL socket wrapping
Christopher Faulet authored
546 {ok, SslSocket} -> ssl:setopts(SslSocket, Opts);
547 undefined -> inet:setopts(Socket, Opts)
5b34d48 @vinoski wrap SSL sockets in tuple
vinoski authored
548 end.
39c8d49 @jbothma update WebSockets implementation to support RFC 6455
jbothma authored
549
550 is_control_op(Op) ->
551 atom_to_opcode(Op) > 7.
552
553 %% Unfragmented message
554 frag_state_machine(State = #ws_state{frag_type = none},
555 #ws_frame_info{fin = 1}) ->
556 State;
557
558 %% Beginning of fragmented text message
559 frag_state_machine(State = #ws_state{frag_type = none},
560 #ws_frame_info{fin = 0,
561 opcode = text}) ->
562 State#ws_state{frag_type = text};
563
564 %% Beginning of fragmented binary message
565 frag_state_machine(State = #ws_state{frag_type = none},
566 #ws_frame_info{fin = 0,
567 opcode = binary}) ->
568 State#ws_state{frag_type = binary};
569
570 %% Expecting text continuation
571 frag_state_machine(State = #ws_state{frag_type = text},
572 #ws_frame_info{fin = 0,
573 opcode = continuation}) ->
574 State;
575
576 %% Expecting binary continuation
577 frag_state_machine(State = #ws_state{frag_type = binary},
578 #ws_frame_info{fin = 0,
579 opcode = continuation}) ->
580 State;
581
582 %% End of fragmented text message
583 frag_state_machine(State = #ws_state{frag_type = text},
584 #ws_frame_info{fin = 1,
585 opcode = continuation}) ->
586 State#ws_state{frag_type = none};
587
588 %% End of fragmented binary message
589 frag_state_machine(State = #ws_state{frag_type = binary},
590 #ws_frame_info{fin = 1,
591 opcode = continuation}) ->
592 State#ws_state{frag_type = none};
593
594
595 frag_state_machine(State, #ws_frame_info{opcode = Op}) ->
596 IsControl = is_control_op(Op),
597 if
598 IsControl == true ->
599 %% Control message never changes fragmentation state
600 State;
601 true ->
602 %% Everything else is wrong
603 {error, "fragmentation rules violated"}
604 end.
605
606
607 opcode_to_atom(16#0) -> continuation;
608 opcode_to_atom(16#1) -> text;
609 opcode_to_atom(16#2) -> binary;
610 opcode_to_atom(16#8) -> close;
611 opcode_to_atom(16#9) -> ping;
612 opcode_to_atom(16#A) -> pong;
613 opcode_to_atom(_) -> undefined.
614
615 atom_to_opcode(continuation) -> 16#0;
616 atom_to_opcode(text) -> 16#1;
617 atom_to_opcode(binary) -> 16#2;
618 atom_to_opcode(close) -> 16#8;
619 atom_to_opcode(ping) -> 16#9;
620 atom_to_opcode(pong) -> 16#A.
621
622
623 frame(8, Type, Data) ->
624 %% FIN=true because we're not fragmenting.
625 %% OPCODE=1 for text
626 FirstByte = 128 bor atom_to_opcode(Type),
c0af90a @vinoski add callback for abnormal websocket close
vinoski authored
627 Length = byte_size(Data),
39c8d49 @jbothma update WebSockets implementation to support RFC 6455
jbothma authored
628 if
629 Length < 126 ->
630 << FirstByte, 0:1, Length:7, Data:Length/binary >>;
631 Length =< 65535 ->
632 << FirstByte, 0:1, 126:7, Length:16, Data:Length/binary >>;
633 true ->
634 Defined = Length =< math:pow(2,64),
635 %% TODO: Is the correctness of this pow call
636 %% better than the speed and danger of not checking?
637 case Defined of
638 true ->
639 << FirstByte, 0:1, 127:7, Length:64, Data:Length/binary >>;
640 _ ->
641 undefined
642 end
643 end.
644
645
646 mask(MaskBin, Data) ->
647 list_to_binary(rmask(MaskBin, Data)).
648
649 %% unmask == mask. It's XOR of the four-byte masking key.
650 rmask(_,<<>>) ->
651 [<<>>];
652
653 rmask(MaskBin = <<Mask:4/integer-unit:8>>,
654 <<Data:4/integer-unit:8, Rest/binary>>) ->
655 Masked = Mask bxor Data,
656 MaskedRest = rmask(MaskBin, Rest),
657 [<<Masked:4/integer-unit:8>> | MaskedRest ];
658
659 rmask(<<Mask:3/integer-unit:8, _Rest/binary>>, <<Data:3/integer-unit:8>>) ->
660 Masked = Mask bxor Data,
661 [<<Masked:3/integer-unit:8>>];
662
663 rmask(<<Mask:2/integer-unit:8, _Rest/binary>>, <<Data:2/integer-unit:8>>) ->
664 Masked = Mask bxor Data,
665 [<<Masked:2/integer-unit:8>>];
666
667 rmask(<<Mask:1/integer-unit:8, _Rest/binary>>, <<Data:1/integer-unit:8>>) ->
668 Masked = Mask bxor Data,
669 [<<Masked:1/integer-unit:8>>].
7fdc54a @davide HTML5 Web Sockets support.
davide authored
670
671
672 %% Internal functions
5a70e44 @schemeway Reverted some names to ease merge.
schemeway authored
673 get_origin_header(Headers) ->
39c8d49 @jbothma update WebSockets implementation to support RFC 6455
jbothma authored
674 case query_header("origin", Headers) of
675 undefined -> query_header("sec-websocket-origin", Headers);
676 Origin -> Origin
677 end.
fe2e42a @schemeway Added support for websockets v76.
schemeway authored
678
5a70e44 @schemeway Reverted some names to ease merge.
schemeway authored
679 get_protocol_header(Headers) ->
fe2e42a @schemeway Added support for websockets v76.
schemeway authored
680 query_header("sec-websocket-protocol", Headers, "unknown").
681
39c8d49 @jbothma update WebSockets implementation to support RFC 6455
jbothma authored
682 get_nonce_header(Headers) ->
683 query_header("sec-websocket-key", Headers).
684
fe2e42a @schemeway Added support for websockets v76.
schemeway authored
685 query_header(HeaderName, Headers) ->
686 query_header(HeaderName, Headers, undefined).
4ebda22 @vinoski minor code cleanup
vinoski authored
687
cccc578 @vinoski add reverse proxy intercept module capability
vinoski authored
688 query_header(Header, Headers, Default) ->
689 yaws_api:get_header(Headers, Header, Default).
fe2e42a @schemeway Added support for websockets v76.
schemeway authored
690
39c8d49 @jbothma update WebSockets implementation to support RFC 6455
jbothma authored
691 hash_nonce(Nonce) ->
692 Salted = Nonce ++ "258EAFA5-E914-47DA-95CA-C5AB0DC85B11",
693 HashBin = crypto:sha(Salted),
694 base64:encode_to_string(HashBin).
Something went wrong with that request. Please try again.