Skip to content

HTTPS clone URL

Subversion checkout URL

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