Permalink
Browse files

Fix SSL socket wrapping

We must be careful when we rely on Arg#arg.clisock in Yaws modules
because the socket is wrapped in case of SSL connections.

Thanks to Jean-Charles Campagne.
  • Loading branch information...
1 parent faef983 commit ba34c61f75407339fef4ba5a23c3febfe871e192 @capflam capflam committed with vinoski Oct 2, 2012
Showing with 142 additions and 37 deletions.
  1. +4 −1 src/yaws_dav.erl
  2. +19 −8 src/yaws_server.erl
  3. +22 −28 src/yaws_websockets.erl
  4. +12 −0 test/conf/stdconf.conf
  5. +23 −0 test/t2/app_test.erl
  6. +62 −0 test/t2/www/test_upload_ssl.yaws
View
@@ -38,7 +38,10 @@ put(SC, ARG) ->
end,
SSL = yaws:is_ssl(SC),
FName = davpath(ARG),
- CliSock = ARG#arg.clisock,
+ CliSock = case yaws_api:get_sslsocket(ARG#arg.clisock) of
+ {ok, SslSock} -> SslSock;
+ undefined -> ARG#arg.clisock
+ end,
TmpName = FName ++ ".tmp",
%% FIXME: first check if we can write to original file??
case file:open(TmpName, [raw,write]) of
View
@@ -1926,7 +1926,11 @@ handle_auth(ARG, Auth_H, Auth_methods = #auth{mod = Mod}, Ret) when Mod /= [] ->
[Mod, ARG, Auth_methods, Reason,
erlang:get_stacktrace()]),
handle_crash(ARG, L),
- deliver_accumulated(ARG#arg.clisock),
+ CliSock = case yaws_api:get_sslsocket(ARG#arg.clisock) of
+ {ok, SslSock} -> SslSock;
+ undefined -> ARG#arg.clisock
+ end,
+ deliver_accumulated(CliSock),
exit(normal);
%% appmod means the auth headers are undefined, i.e. false.
@@ -2642,7 +2646,7 @@ deliver_dyn_part(CliSock, % essential params
case OutReply of
{get_more, Cont, State} when element(1, Arg#arg.clidata) == partial ->
CliDataPos1 = get(client_data_pos),
- More = get_more_post_data(CliDataPos1, Arg),
+ More = get_more_post_data(CliSock, CliDataPos1, Arg),
A2 = Arg#arg{clidata=More, cont=Cont, state=State},
deliver_dyn_part(
CliSock, LineNo, YawsFile, CliDataPos1+size(un_partial(More)),
@@ -3008,7 +3012,10 @@ handle_out_reply({yssi, Yfile}, LineNo, YawsFile, UT, ARG) ->
yaws ->
Mtime = mtime(UT2#urltype.finfo),
Key = UT2#urltype.getpath,
- CliSock = ARG#arg.clisock,
+ CliSock = case yaws_api:get_sslsocket(ARG#arg.clisock) of
+ {ok, SslSock} -> SslSock;
+ undefined -> ARG#arg.clisock
+ end,
N = 0,
case ets:lookup(SC#sconf.ets, Key) of
[{_Key, spec, Mtime1, Spec, Es}] when Mtime1 == Mtime,
@@ -3257,9 +3264,13 @@ handle_out_reply(Arg = #arg{}, _LineNo, _YawsFile, _UT, _ARG) ->
Arg;
handle_out_reply(flush, _LineNo, _YawsFile, _UT, ARG) ->
+ CliSock = case yaws_api:get_sslsocket(ARG#arg.clisock) of
+ {ok, SslSock} -> SslSock;
+ undefined -> ARG#arg.clisock
+ end,
Hdrs = ARG#arg.headers,
CliDataPos0 = get(client_data_pos),
- CliDataPos1 = flush(ARG#arg.clisock, CliDataPos0,
+ CliDataPos1 = flush(CliSock, CliDataPos0,
Hdrs#headers.content_length,
yaws:to_lower(Hdrs#headers.transfer_encoding)),
put(client_data_pos, CliDataPos1),
@@ -3663,24 +3674,24 @@ deflate_accumulated(Arg, Content, ContentLength, Mode) ->
end.
-get_more_post_data(PPS, ARG) ->
+get_more_post_data(CliSock, PPS, ARG) ->
SC = get(sc),
N = SC#sconf.partial_post_size,
case (ARG#arg.headers)#headers.content_length of
undefined ->
case yaws:to_lower((ARG#arg.headers)#headers.transfer_encoding) of
"chunked" ->
- get_chunked_client_data(ARG#arg.clisock, yaws:is_ssl(SC));
+ get_chunked_client_data(CliSock, yaws:is_ssl(SC));
_ ->
<<>>
end;
Len ->
Int_len = list_to_integer(Len),
if N + PPS < Int_len ->
- Bin = get_client_data(ARG#arg.clisock, N, yaws:is_ssl(SC)),
+ Bin = get_client_data(CliSock, N, yaws:is_ssl(SC)),
{partial, Bin};
true ->
- get_client_data(ARG#arg.clisock, Int_len - PPS,
+ get_client_data(CliSock, Int_len - PPS,
yaws:is_ssl(SC))
end
end.
@@ -40,18 +40,18 @@
%%
start(Arg, CallbackMod, Opts) ->
SC = get(sc),
- CliSock = Arg#arg.clisock,
PrepdOpts = preprocess_opts(Opts),
{ok, OwnerPid} = gen_server:start(?MODULE, [Arg, SC, CallbackMod, PrepdOpts], []),
CliSock = Arg#arg.clisock,
- TakeOverResult = case SC#sconf.ssl of
- undefined ->
- inet:setopts(CliSock, [{packet, raw}, {active, once}]),
- gen_tcp:controlling_process(CliSock, OwnerPid);
- _ ->
- ssl:setopts(CliSock, [{packet, raw}, {active, once}]),
- ssl:controlling_process(CliSock, OwnerPid)
- end,
+ TakeOverResult =
+ case yaws_api:get_sslsocket(CliSock) of
+ {ok, SslSocket} ->
+ ssl:setopts(SslSocket, [{packet, raw}, {active, once}]),
+ ssl:controlling_process(SslSocket, OwnerPid);
+ undefined ->
+ inet:setopts(CliSock, [{packet, raw}, {active, once}]),
+ gen_tcp:controlling_process(CliSock, OwnerPid)
+ end,
case TakeOverResult of
ok ->
gen_server:cast(OwnerPid, ok),
@@ -64,10 +64,8 @@ start(Arg, CallbackMod, Opts) ->
send(#ws_state{sock=Socket, vsn=ProtoVsn}, {Type, Data}) ->
DataFrame = frame(ProtoVsn, Type, Data),
case yaws_api:get_sslsocket(Socket) of
- {ok, SslSocket} ->
- ssl:send(SslSocket, DataFrame);
- _ ->
- gen_tcp:send(Socket, DataFrame)
+ {ok, SslSocket} -> ssl:send(SslSocket, DataFrame);
+ undefined -> gen_tcp:send(Socket, DataFrame)
end;
send(Pid, {Type, Data}) ->
gen_server:cast(Pid, {send, {Type, Data}}).
@@ -104,10 +102,8 @@ handle_cast(ok, #state{arg=Arg, sconf=SC, opts=Opts}=State) ->
Handshake = handshake(ProtocolVersion, Arg, CliSock,
WebSocketLocation, Origin, Protocol),
case yaws_api:get_sslsocket(CliSock) of
- {ok, SslSocket} ->
- ssl:send(SslSocket, Handshake);
- _ ->
- gen_tcp:send(CliSock, Handshake)
+ {ok, SslSocket} -> ssl:send(SslSocket, Handshake);
+ undefined -> gen_tcp:send(CliSock, Handshake)
end,
{callback, CallbackType} = lists:keyfind(callback, 1, Opts),
WSState = #ws_state{sock = CliSock,
@@ -200,11 +196,9 @@ code_change(_OldVsn, Data, _Extra) ->
do_send(#ws_state{sock=Socket, vsn=ProtoVsn}, {Type, Data}) ->
DataFrame = frame(ProtoVsn, Type, Data),
- case Socket of
- {sslsocket,_,_} ->
- ssl:send(Socket, DataFrame);
- _ ->
- gen_tcp:send(Socket, DataFrame)
+ case yaws_api:get_sslsocket(Socket) of
+ {ok, SslSocket} -> ssl:send(SslSocket, DataFrame);
+ undefined -> gen_tcp:send(Socket, DataFrame)
end.
preprocess_opts(GivenOpts) ->
@@ -405,9 +399,11 @@ buffer(Socket, Len, Buffered) ->
_ ->
%% not enough
%% debug(val, {buffering, "need:", Len, "waiting for more..."}),
- %% TODO: take care of ssl sockets
Needed = Len - binary_length(Buffered),
- {ok, More} = gen_tcp:recv(Socket, Needed),
+ {ok, More} = case yaws_api:get_sslsocket(Socket) of
+ {ok, SslSocket} -> ssl:recv(SslSocket, Needed);
+ undefined -> gen_tcp:recv(Socket, Needed)
+ end,
<<Buffered/binary, More/binary>>
end.
@@ -547,10 +543,8 @@ unframe_one(State = #ws_state{vsn=8}, FirstPacket) ->
websocket_setopts(#ws_state{sock=Socket}, Opts) ->
case yaws_api:get_sslsocket(Socket) of
- {ok, SslSocket} ->
- ssl:setopts(SslSocket, Opts);
- _ ->
- inet:setopts(Socket, Opts)
+ {ok, SslSocket} -> ssl:setopts(SslSocket, Opts);
+ undefined -> inet:setopts(Socket, Opts)
end.
is_control_op(Op) ->
@@ -208,3 +208,15 @@ keepalive_timeout = 10000
depth = 0
</ssl>
</server>
+
+<server localhost>
+ port = 8444
+ listen = 0.0.0.0
+ docroot = %YTOP%/test/t2/www
+ auth_log = true
+ <ssl>
+ keyfile = %YTOP%/ssl/yaws-key.pem
+ certfile = %YTOP%/ssl/yaws-cert.pem
+ depth = 0
+ </ssl>
+</server>
View
@@ -31,6 +31,7 @@ start() ->
test_arg_rewrite(),
test_shaper(),
test_sslaccept_timeout(),
+ test_ssl_multipart_post(),
test_throw(),
test_too_many_headers(),
test_index_files(),
@@ -808,6 +809,28 @@ test_sslaccept_timeout() ->
gen_tcp:close(Sock),
ok.
+test_ssl_multipart_post() ->
+ io:format("ssl_multipart_post_test\n", []),
+ ok = application:start(crypto),
+ ok = application:start(public_key),
+ ok = application:start(ssl),
+ Boundary = "----------------------------3e9876546ecf\r\n",
+ {ok, Bin0} = file:read_file("../../www/1000.txt"),
+ Data = list_to_binary([Boundary, Bin0]),
+ Size = size(Data),
+ Headers = [
+ {'Content-Type', "multipart/form-data; Boundary=" ++ Boundary},
+ {'Content-Length', Size}
+ ],
+ Uri = "https://localhost:8444/test_upload_ssl.yaws",
+ Options = [{is_ssl, true}, {ssl_options, [{verify, 0}]}],
+ ?line {ok, "200", _, _} = Reply = ibrowse:send_req(Uri, Headers, post, Data, Options),
+ ok = application:stop(ssl),
+ ok = application:stop(public_key),
+ ok = application:stop(crypto),
+ ok.
+
+
test_throw() ->
io:format("throw test\n", []),
Uri = "http://localhost:8009/",
@@ -0,0 +1,62 @@
+<erl>
+%% inspired from upload.yaws, slightly simplified.
+
+-record(state, {acc,
+ last
+ }).
+
+out(A) ->
+ %% make sure it is post method
+ 'POST' = yaws_api:http_request_method(yaws_api:arg_req(A)),
+ case get_props(A) of
+ {error, Reason} ->
+ {ehtml, "error"};
+ {get_more, Cont, NewState} = GetMore ->
+ GetMore;
+ {ok, AllParts} ->
+ {ehtml, "ok"}
+ end.
+
+
+get_state(A) ->
+ case A#arg.state of
+ undefined -> #state{acc = [], last = false};
+ State0 -> State0
+ end.
+
+
+get_props(A) ->
+ Parse = yaws_api:parse_multipart_post(A),
+ State = get_state(A),
+ case Parse of
+ {cont, Cont, Res} ->
+ case add_file_chunk(A, Res, State) of
+ {done, Result} ->
+ error_logger:info_msg("Ok, got: ~p", [Result]),
+ {ok, Result};
+ {cont, NewState} ->
+ {get_more, Cont, NewState}
+ end;
+ {result, Res} ->
+ case add_file_chunk(A, Res, State#state{last = true}) of
+ {done, Result} -> {ok, Result};
+ {cont, _} -> {error, ooops}
+ end;
+ {error, Reason} = Error ->
+ io:format("Error : ~p", [Reason]),
+ Error
+ end.
+
+
+add_file_chunk(A, [H | Tail], #state{acc = Acc0} = State0) ->
+ Acc1 = [H | Acc0],
+ State1 = State0#state{acc = Acc1},
+ add_file_chunk(A, Tail, State1);
+add_file_chunk(A, [], #state{last = false} = State) ->
+ {cont, State};
+add_file_chunk(A, [], #state{last = true, acc = RAcc} = State) ->
+ Data = lists:reverse(RAcc),
+ {done, Data}.
+
+
+</erl>

0 comments on commit ba34c61

Please sign in to comment.