Skip to content

HTTPS clone URL

Subversion checkout URL

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