Skip to content

Commit

Permalink
Merge remote branch 'upstream/master'
Browse files Browse the repository at this point in the history
karlsson committed Jul 1, 2010
2 parents ca1c8b1 + 641939a commit 71ab858
Showing 5 changed files with 91 additions and 43 deletions.
31 changes: 22 additions & 9 deletions doc/yaws.tex
Original file line number Diff line number Diff line change
@@ -848,7 +848,8 @@ \section{POSTing files}
capabilities:
\begin{enumerate}
\item It reads all parameters --- files uploaded and other simple parameters.
\item It reads all parameters --- files uploaded and other simple
parameters.
\item It takes a few options to help file uploads. Specifically:
\begin{enumerate}
\item \verb+{max_file_size, MaxBytes}+: if the file size in bytes
@@ -884,18 +885,30 @@ \section{POSTing files}
\begin{verbatim}
[{filename, "name of the uploaded file as entered on the form"},
{value, Contents_of_the_file_all_in_memory}]
{value, Contents_of_the_file_all_in_memory} | _T]
\end{verbatim}
or:
\begin{verbatim}
[{filename, "name of the uploaded file as entered on the form"},
{temp_file, "full pathname of the temp file"}]
{temp_file, "full pathname of the temp file"} | _T]
\end{verbatim}
For the temporary file case, it's your responsibility to delete the
file when you're done with it.
Some multipart/form messages also include headers such as
\verb+Content-Type+ and \verb+Content-Transfer-Encoding+ for different
subparts of the message. If these headers are present in any subpart
of a multipart/form message, they're also included in that subpart's
parameter list, like this:
\begin{verbatim}
[{filename, "name of the uploaded file as entered on the form"},
{value, Contents_of_the_file_all_in_memory},
{content_type, "image/png"} | _T]
\end{verbatim}
Note that for the temporary file case, it's your responsibility to
delete the file when you're done with it.
Here's an example:
@@ -906,14 +919,14 @@ \section{POSTing files}
out(Arg) ->
Options = [no_temp_file],
case yaws_multipart:read_multipart_form(Arg, Options) of
{done, Params} ->
io:format("Params : ~p", [Params]),
[{filename, FileName},{value,FileContent}] =
{done, Params} ->
io:format("Params : ~p~n", [Params]),
{ok, [{filename, FileName},{value,FileContent}|_]} =
dict:find("my_file", Params),
AnotherParam = dict:find("another_param", Params);
%% do something with FileName, FileContent and AnotherParam
{error, Reason} ->
io:format("Error reading multipart form: ~s", [Reason]);
io:format("Error reading multipart form: ~s~n", [Reason]);
Other -> Other
end.
\end{verbatim}
15 changes: 14 additions & 1 deletion src/yaws_api.erl
Original file line number Diff line number Diff line change
@@ -360,7 +360,20 @@ do_header(Head) ->
{value, {_,"form-data"++Line}} ->
Parameters = parse_arg_line(Line),
{value, {_,Name}} = lists:keysearch(name, 1, Parameters),
{Name, Parameters};
{Name,
lists:map(fun({"content-type", Val}) ->
{content_type, Val};
({"content-transfer-encoding", Val}) ->
{content_transfer_encoding, Val};
({"content-id", Val}) ->
{content_id, Val};
({"content-description", Val}) ->
{content_description, Val};
(KV) ->
KV
end,
Parameters ++ lists:keydelete("content-disposition", 1,
Header))};
_ ->
{Header}
end.
53 changes: 30 additions & 23 deletions src/yaws_multipart.erl
Original file line number Diff line number Diff line change
@@ -16,7 +16,8 @@
max_file_size,
no_temp_file,
temp_dir = yaws:tmpdir("/tmp"),
temp_file
temp_file,
headers = []
}).

read_multipart_form(A, Options) when A#arg.state == undefined ->
@@ -45,7 +46,7 @@ multipart(A, State) ->
Parse = yaws_api:parse_multipart_post(A),
case Parse of
{cont, Cont, Res} ->
case addFileChunk(A, Res, State) of
case add_file_chunk(A, Res, State) of
{done, NewState} ->
{done, NewState#upload.params};
{cont, NewState} ->
@@ -54,7 +55,7 @@ multipart(A, State) ->
Error
end;
{result, Res} ->
case addFileChunk(A, Res, State#upload{last=true}) of
case add_file_chunk(A, Res, State#upload{last=true}) of
{done, S2} ->
{done,S2#upload.params};
Error={error, _Reason} ->
@@ -63,16 +64,16 @@ multipart(A, State) ->
end.


addFileChunk(A, [{part_body, Data}|Res], State) ->
addFileChunk(A, [{body, Data}|Res], State);
add_file_chunk(A, [{part_body, Data}|Res], State) ->
add_file_chunk(A, [{body, Data}|Res], State);

addFileChunk(_A, [], State) when State#upload.last == true ->
add_file_chunk(_A, [], State) when State#upload.last == true ->
{done, close_previous_param(State)};

addFileChunk(_A, [], State) ->
add_file_chunk(_A, [], State) ->
{cont, State};

addFileChunk(A, [{head, {_Name, Opts}}|Res], State ) ->
add_file_chunk(A, [{head, {_Name, Opts}}|Res], State ) ->
S1 = close_previous_param(State),
S2 = lists:foldl(
fun({filename, Fname0}, RunningState) ->
@@ -95,26 +96,29 @@ addFileChunk(A, [{head, {_Name, Opts}}|Res], State ) ->
({name, ParamName}, RunningState) ->
RunningState#upload{
param_name = ParamName,
param_running_value = undefined}
param_running_value = undefined};
(HdrVal, RunningState) ->
RunningState#upload{
headers = [HdrVal | RunningState#upload.headers]}
end,
S1,
Opts),
addFileChunk(A,Res,S2);
add_file_chunk(A,Res,S2);

addFileChunk(A, [{body, Data}|Res], State) when State#upload.fd /= undefined ->
add_file_chunk(A, [{body, Data}|Res], State) when State#upload.fd /= undefined ->
NewSize = compute_new_size(State,Data),
Check = check_param_size(State, NewSize),
Check = check_param_size(State, NewSize),
case Check of
ok ->
ok = file:write(State#upload.fd, Data),
addFileChunk(A, Res, State#upload{running_file_size = NewSize});
add_file_chunk(A, Res, State#upload{running_file_size = NewSize});
Error={error, _Reason} ->
Error
end;

addFileChunk(A, [{body, Data}|Res], State) ->
add_file_chunk(A, [{body, Data}|Res], State) ->
NewSize = compute_new_size(State,Data),
Check = check_param_size(State, NewSize),
Check = check_param_size(State, NewSize),
case Check of
ok ->
NewState =
@@ -125,7 +129,7 @@ addFileChunk(A, [{body, Data}|Res], State) ->
NewData = compute_new_value(PrevValue, Data),
State#upload{param_running_value = NewData}
end,
addFileChunk(A, Res, NewState#upload{running_file_size = NewSize});
add_file_chunk(A, Res, NewState#upload{running_file_size = NewSize});
Error={error, _Reason} ->
Error
end.
@@ -137,9 +141,10 @@ create_temp_file(State) ->
case State#upload.fixed_filename of
undefined ->
{A, B, C} = now(),
FileName = "yaws_"++ integer_to_list(A) ++
"_" ++ integer_to_list(B) ++
"_" ++ integer_to_list(C),
FileName = yaws:join_sep(["yaws",
integer_to_list(A),
integer_to_list(B),
integer_to_list(C)], "_"),
filename:join([State#upload.temp_dir, FileName]);
Filename ->
Filename
@@ -151,9 +156,9 @@ create_temp_file(State) ->
end
.

close_previous_param(State = #upload{param_name = undefined}) ->
close_previous_param(#upload{param_name = undefined} = State) ->
State;
close_previous_param(State = #upload{param_name = ParamName}) ->
close_previous_param(#upload{param_name = ParamName} = State) ->
S2 = case State#upload.filename of
undefined ->
ParamValue = State#upload.param_running_value,
@@ -174,17 +179,19 @@ close_previous_param(State = #upload{param_name = ParamName}) ->
[{temp_file,
State#upload.temp_file}])
end,
ParamInfo3 = lists:append(ParamInfo2, State#upload.headers),
State#upload{
filename = undefined,
fd = undefined,
temp_file= undefined,
running_file_size = 0,
params = dict:store(ParamName, ParamInfo2,
params = dict:store(ParamName, ParamInfo3,
State#upload.params)
}
end,
S2#upload{param_name = undefined,
param_running_value = undefined}.
param_running_value = undefined,
headers = []}.

compute_new_size(State, Data) ->
case Data of
30 changes: 22 additions & 8 deletions test/eunit/multipart_post_parsing.erl
Original file line number Diff line number Diff line change
@@ -3,17 +3,23 @@
-include("../../include/yaws_api.hrl").
-include_lib("eunit/include/eunit.hrl").

data_to_parse() ->
list_to_binary(
["--!!!\r\n",
"Content-Disposition: form-data; name=\"abc123\"; "
++ "filename=\"abc123\"\r\n"
++ "Content-Type: text/plain\r\n"
++ "Test-Header: sampledata\r\n\r\n",
"sometext\n\r\n--!!!--\r\n"]).

complete_parse() ->
Data = list_to_binary(
["--!!!\r\n",
"Content-Disposition: form-data; name=\"abc123\"; "
++ "filename=\"abc123\"\r\n\r\n",
"sometext\n\r\n--!!!--\r\n"]),
yaws_api:parse_multipart_post(mk_arg(Data)).
yaws_api:parse_multipart_post(mk_arg(data_to_parse())).

complete_parse_test() ->
{result,[{head,{"abc123", [{filename,"abc123"},{name,"abc123"}]}},
{body,"sometext\n"}]} = complete_parse().
{result,[{head,{"abc123", [{filename,"abc123"},{name,"abc123"},
{content_type,"text/plain"},
{"test-header","sampledata"}]}},
{body,"sometext\n"}]} = complete_parse().


incomplete_body_test() ->
@@ -57,6 +63,14 @@ incomplete_head_test() ->
[{head,{"ghi789",[{filename,"ghi789"},{name,"ghi789"}]}},
{body,"sometext\n"}]} = {Res1, Res2}.

read_multipart_form_test() ->
{done, Dict} = yaws_multipart:read_multipart_form(mk_arg(data_to_parse()),
[no_temp_file]),
{ok, Params} = dict:find("abc123", Dict),
"abc123" = proplists:get_value(filename, Params),
"sometext\n" = proplists:get_value(value, Params),
"text/plain" = proplists:get_value(content_type, Params),
"sampledata" = proplists:get_value("test-header", Params).

mk_arg(Data) ->
ContentType = "multipart/form-data; boundary=!!!",
5 changes: 3 additions & 2 deletions www/privbind.yaws
Original file line number Diff line number Diff line change
@@ -63,13 +63,14 @@ erlang::::type=normal;defaultpriv=basic,net_privaddr
</p>
<div class="box">
<verbatim>
$ setcap 'cap_net_bind_service=+ep' /usr/bin/erl
$ setcap 'cap_net_bind_service=+ep' /usr/lib/erlang/erts-5.7.4/bin/beam
</verbatim>
</div>

<p>
The above command grants the capability of binding
privileged ports to /usr/bin/erl
privileged ports to beam. Note, you have to grant the priviliges to the
actual exectuable you are using.
<p>

<p>

0 comments on commit 71ab858

Please sign in to comment.