Skip to content
Newer
Older
100644 184 lines (158 sloc) 5.82 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>
7fdc54a @davide HTML5 Web Sockets support.
davide authored
4 %%% Purpose :
bd0bf89 @klacke no utf8 in the author name
authored
5 %%% Created : 18 Dec 2009 by Davide Marques <nesrait@gmail.com>
7fdc54a @davide HTML5 Web Sockets support.
davide authored
6 %%% Modified:
7 %%%----------------------------------------------------------------------
8
9 -module(yaws_websockets).
10 -author('nesrait@gmail.com').
11
12 -include("../include/yaws.hrl").
13 -include("../include/yaws_api.hrl").
14 -include("yaws_debug.hrl").
15
16 -include_lib("kernel/include/file.hrl").
17 -export([handshake/3, unframe_one/1, unframe_all/2]).
18
19 handshake(Arg, ContentPid, SocketMode) ->
20 CliSock = Arg#arg.clisock,
fe2e42a @schemeway Added support for websockets v76.
schemeway authored
21 case origin_header(Arg#arg.headers) of
7fdc54a @davide HTML5 Web Sockets support.
davide authored
22 undefined ->
23 %% Yaws will take care of closing the socket
24 ContentPid ! discard;
25 Origin ->
fe2e42a @schemeway Added support for websockets v76.
schemeway authored
26 ProtocolVersion = ws_version(Arg#arg.headers),
27 Protocol = protocol_header(Arg#arg.headers),
7fdc54a @davide HTML5 Web Sockets support.
davide authored
28 Host = (Arg#arg.headers)#headers.host,
29 {abs_path, Path} = (Arg#arg.req)#http_request.path,
30 %% TODO: Support for wss://
31 WebSocketLocation = "ws://" ++ Host ++ Path,
fe2e42a @schemeway Added support for websockets v76.
schemeway authored
32 Handshake = handshake(ProtocolVersion, Arg, CliSock, WebSocketLocation, Origin, Protocol),
7fdc54a @davide HTML5 Web Sockets support.
davide authored
33 SC = get(sc),
34 case SC#sconf.ssl of
35 undefined ->
36 gen_tcp:send(CliSock, Handshake),
37 inet:setopts(CliSock, [{packet, raw}, {active, SocketMode}]),
38 TakeOverResult =
39 gen_tcp:controlling_process(CliSock, ContentPid);
40 _ ->
41 ssl:send(CliSock, Handshake),
42 ssl:setopts(CliSock, [{packet, raw}, {active, SocketMode}]),
43 TakeOverResult =
44 ssl:controlling_process(CliSock, ContentPid)
45 end,
46 case TakeOverResult of
47 ok ->
48 ContentPid ! {ok, CliSock};
49 {error, Reason} ->
50 ContentPid ! discard,
51 exit({websocket, Reason})
52 end
53 end,
54 exit(normal).
55
56
fe2e42a @schemeway Added support for websockets v76.
schemeway authored
57 handshake(ws_76, Arg, CliSock, WebSocketLocation, Origin, Protocol) ->
58 {ok, Challenge} = gen_tcp:recv(CliSock, 8),
59 Key1 = secret_key("sec-websocket-key1", Arg#arg.headers),
60 Key2 = secret_key("sec-websocket-key2", Arg#arg.headers),
61 ChallengeResponse = challenge(Key1, Key2, binary_to_list(Challenge)),
62 ["HTTP/1.1 101 Web Socket Protocol Handshake\r\n",
63 "Upgrade: WebSocket\r\n",
64 "Connection: Upgrade\r\n",
65 "Sec-WebSocket-Origin: ", Origin, "\r\n",
66 "Sec-WebSocket-Location: ", WebSocketLocation, "\r\n",
67 "Sec-WebSocket-Protocol: ", Protocol, "\r\n",
68 "\r\n", ChallengeResponse];
69
70 handshake(ws_75, _Arg, _CliSock, WebSocketLocation, Origin, Protocol) ->
71 ["HTTP/1.1 101 Web Socket Protocol Handshake\r\n",
72 "Upgrade: WebSocket\r\n",
73 "Connection: Upgrade\r\n",
74 "WebSocket-Origin: ", Origin, "\r\n",
75 "WebSocket-Location: ", WebSocketLocation, "\r\n",
76 "\r\n"].
77
78
79 ws_version(Headers) ->
80 case query_header("sec-websocket-key1", Headers) of
81 undefined -> ws_75;
82 _ -> ws_76
83 end.
84
85
7fdc54a @davide HTML5 Web Sockets support.
davide authored
86 %% This should take care of all the Data Framing scenarios specified in
87 %% http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-66#page-26
88 unframe_one(DataFrames) ->
89 <<Type, _/bitstring>> = DataFrames,
90 case Type of
91 T when (T =< 127) ->
92 %% {ok, FF_Ended_Frame} = re:compile("^.(.*)\\xFF(.*?)", [ungreedy]),
93 FF_Ended_Frame = {re_pattern,2,0,
94 <<69,82,67,80,71,0,0,0,16,2,0,0,5,0,0,0,2,0,0,0,0,0,255,2,40,
95 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,93,0,27,25,12,94,0,7,0,1,57,
96 12,84,0,7,27,255,94,0,7,0,2,56,12,84,0,7,84,0,27,0>>},
97 {match, [Data, NextFrame]} =
98 re:run(DataFrames, FF_Ended_Frame,
99 [{capture, all_but_first, binary}]),
100 {ok, Data, NextFrame};
101
102 _ -> %% Type band 16#80 =:= 16#80
103 {Length, LenBytes} = unpack_length(DataFrames, 0, 0),
104 <<_, _:LenBytes/bytes, Data:Length/bytes,
105 NextFrame/bitstring>> = DataFrames,
106 {ok, Data, NextFrame}
107 end.
108
109 unframe_all(<<>>, Acc) ->
110 lists:reverse(Acc);
111 unframe_all(DataFramesBin, Acc) ->
112 {ok, Msg, Rem} = unframe_one(DataFramesBin),
113 unframe_all(Rem, [Msg|Acc]).
114
115
116 %% Internal functions
fe2e42a @schemeway Added support for websockets v76.
schemeway authored
117 origin_header(Headers) ->
118 query_header("origin", Headers).
119
120 protocol_header(Headers) ->
121 query_header("sec-websocket-protocol", Headers, "unknown").
122
123
124 query_header(HeaderName, Headers) ->
125 query_header(HeaderName, Headers, undefined).
126
127 query_header(Header, #headers{other=L}, Default) ->
7fdc54a @davide HTML5 Web Sockets support.
davide authored
128 lists:foldl(fun({http_header,_,K0,_,V}, undefined) ->
129 K = case is_atom(K0) of
130 true ->
131 atom_to_list(K0);
132 false ->
133 K0
134 end,
135 case string:to_lower(K) of
fe2e42a @schemeway Added support for websockets v76.
schemeway authored
136 Header ->
7fdc54a @davide HTML5 Web Sockets support.
davide authored
137 V;
138 _ ->
fe2e42a @schemeway Added support for websockets v76.
schemeway authored
139 Default
7fdc54a @davide HTML5 Web Sockets support.
davide authored
140 end;
141 (_, Acc) ->
142 Acc
fe2e42a @schemeway Added support for websockets v76.
schemeway authored
143 end, Default, L).
144
145 secret_key(KeyName, Headers) ->
146 case query_header(KeyName, Headers) of
147 undefined ->
148 0;
149 Key ->
150 Digits = lists:filter(fun(C) -> (C >= 48) andalso (C =< 57) end, Key),
151 NumberOfSpaces = length(lists:filter(fun(C) -> C == 32 end, Key)),
152 list_to_integer(Digits) div NumberOfSpaces
153 end.
154
155
156 challenge(Key1, Key2, Challenge) ->
157 BinaryAnswer =
158 crypto:md5_final(
159 crypto:md5_update(
160 crypto:md5_init(),
161 digits32(Key1) ++ digits32(Key2) ++ Challenge)),
162 BinaryAnswer.
7fdc54a @davide HTML5 Web Sockets support.
davide authored
163
fe2e42a @schemeway Added support for websockets v76.
schemeway authored
164 digits32(Num) ->
165 Digit4 = Num rem 256,
166 Num2 = Num div 256,
167 Digit3 = Num2 rem 256,
168 Num3 = Num2 div 256,
169 Digit2 = Num3 rem 256,
170 Digit1 = Num3 div 256,
171 [Digit1, Digit2, Digit3, Digit4].
172
7fdc54a @davide HTML5 Web Sockets support.
davide authored
173 unpack_length(Binary, LenBytes, Length) ->
174 <<_, _:LenBytes/bytes, B, _/bitstring>> = Binary,
175 B_v = B band 16#7F,
176 NewLength = (Length * 128) + B_v,
177 case B band 16#80 of
178 16#80 ->
179 unpack_length(Binary, LenBytes + 1, NewLength);
180 0 ->
181 {NewLength, LenBytes + 1}
182 end.
183
Something went wrong with that request. Please try again.