%%% Michael Leonhard (http://tamale.net/)
%%% Based on upload.yaws from YAWS 1.57 by Claes Wikstrom
-record(upload, % represents state for a partially processed upload
{filename % name of the file
,last % indicates that the last part is being processed
,rlist % reversed order list of binaries comprising file data
,data % binary of file data
}).
%%% Outputs a form, with any feedback from a previous request
%%% returns Ehtml
show_form(A, Feedback) -> {ehtml,
[ {form
,[{enctype,"multipart/form-data"},{action,"upload2.yaws"},{method,"post"}]
, [{p,[],"Choose a file and click Upload."}
,{p,[],{input,[{type,"file"},{name,"file"}],[]}}
,{p,[],{input,[{type,"submit"},{value,"Upload"}],[]}}
]
}
,{p,[],Feedback}
]}.
%%% Process POST data from client, state=#upload
%%% returns Ehtml | {get_more, Continuation, NewState}
handle_post(A) when is_record(A#arg.state,upload) ->
io:fwrite("upload.yaws:handle_post/2 State=~s~n", [upload_to_string(A#arg.state)]),
multipart(A, A#arg.state);
handle_post(A) ->
io:fwrite("upload.yaws:handle_post/2 creating state~n"),
State = #upload{},
multipart(A, State).
%%% Processes result of upload
%%% returns Ehtml
result_ehtml(A, {error,Reason}) when is_list(Reason) ->
io:fwrite("upload.yaws:result_ehtml/3 error Reason=~s~n", [Reason]),
show_form(A, Reason);
result_ehtml(A, {upload,U}) when is_record(U,upload) ->
io:fwrite("upload.yaws:result_ehtml/3 upload U=~s~n", [upload_to_string(U)]),
ResultEhtml =
[{hr,[],[]}
,{p,[],"Upload Complete!"}
,{pre,[],f
("filename=~s data=<<~w bytes>>~n"
,[U#upload.filename, size(U#upload.data)])}],
show_form(A, ResultEhtml).
%%% Process part of a multi-part form post
%%% returns Ehtml | {get_more, Continuation, NewState}
multipart(A, State) when is_record(State,upload) ->
io:fwrite("upload.yaws:multipart/3 State=~s~n", [upload_to_string(State)]),
case yaws_api:parse_multipart_post(A) of
{cont, Cont, Part} ->
io:fwrite("upload.yaws:multipart/3 cont~n"),
case process_part(A, Part, State) of
{done, Result} ->
io:fwrite("upload.yaws:multipart/3 done~n"),
result_ehtml(A, Result);
{cont, NewState} ->
io:fwrite("upload.yaws:multipart/3 get_more NewState=~s~n", [upload_to_string(NewState)]),
{get_more, Cont, NewState}
end;
{result, Part} ->
io:fwrite("upload.yaws:multipart/3 result~n"),
case process_part(A, Part, State#upload{last=true}) of
{done, Result} ->
io:fwrite("upload.yaws:multipart/3 done~n"),
result_ehtml(A, Result);
{cont, _} ->
io:fwrite("upload.yaws:multipart/3 error~n"),
result_ehtml(A, {error, "Error During Upload"})
end;
[] -> result_ehtml(A, {error,"You must select a file to upload."})
end.
%% Converts path into a safe htmlized filename, with directories removed
%% returns non-empty String
sanitize_filename(Input) ->
FilePath =
case Input of
List when is_list(List) -> List;
_ -> "unnamed"
end,
StripWindowsDirsFun = fun(L) -> lists:last(string:tokens(L,"\\")) end,
StripUnixDirsFun = fun(L) -> lists:last(string:tokens(L,"/")) end,
EmptyToUnnamedFun = fun(L) -> case L of [] -> "unnamed"; _-> L end end,
yaws_api:htmlize(
EmptyToUnnamedFun(
StripUnixDirsFun(
StripWindowsDirsFun(FilePath)))).
%% Returns string representation of the upload record, suitable for printing to console
%% returns String
upload_to_string(Upload) when is_record(Upload,upload) ->
FileNameString = f("filename=~s",[Upload#upload.filename]),
RlistString =
case Upload#upload.rlist of
undefined -> " rlist=undefined";
List when is_list(List) ->
f(" rlist=~p bytes in ~p chunks",[iolist_size(List), length(List)])
end,
DataString =
case Upload#upload.data of
undefined -> " data=undefined";
Bin when is_binary(Bin) -> f(" data=<<~p bytes>>",[size(Bin)])
end,
lists:flatten([FileNameString, RlistString, DataString]).
%%% Processes a part of the multipart post
%%% returns {done,{upload,#upload}} | {done,{error,Reason}} | {cont,#upload}
%%%
%%% Process data_part and data identically
process_part(A, [{part_body, Data}|Tail], State) ->
io:fwrite("upload.yaws:process_part/4 part_body~n"),
process_part(A, [{body, Data}|Tail], State);
%%% Final part list has been processed
process_part(_A, [], State) when State#upload.last==true,State#upload.filename /= undefined ->
io:fwrite("upload.yaws:process_part/4a State=~s~n", [upload_to_string(State)]),
Data = iolist_to_binary(lists:reverse(State#upload.rlist)),
{done, {upload, State#upload{rlist=undefined,data=Data}}};
%%% Final part list has been processed but filename was not processed
process_part(A, [], State) when State#upload.last==true ->
io:fwrite("upload.yaws:process_part/4b State=~s~n", [upload_to_string(State)]),
{done, {error, "Error: did not receive header with upload."}};
%%% Part list was processed
process_part(_A, [], State) ->
io:fwrite("upload.yaws:process_part/4c State=~s~n", [upload_to_string(State)]),
{cont, State};
%%% Process header
process_part(A, [{head, {"file", Opts}}|Tail], State ) ->
io:fwrite("upload.yaws:process_part/4d State=~s~n", [upload_to_string(State)]),
case lists:keysearch(filename, 1, Opts) of
{value, {_, UncheckedFileName}} ->
io:fwrite("upload.yaws:process_part/4d UncheckedFileName=~s~n", [UncheckedFileName]),
FileName = sanitize_filename(UncheckedFileName),
io:fwrite("upload.yaws:process_part/4d FileName=~s~n", [FileName]),
process_part(A, Tail, State#upload{filename=FileName,rlist=[]});
false ->
{done, {error, "Error: filename not found in header."}}
end;
%%% Process data
process_part(A, [{body, Data}|Tail], State) when State#upload.filename /= undefined ->
io:fwrite("upload.yaws:process_part/4e State=~s~n", [upload_to_string(State)]),
NewRList = [list_to_binary(Data) | State#upload.rlist],
io:fwrite("upload.yaws:process_part/4e data part=<<~w bytes>>~n", [length(Data)]),
process_part(A, Tail, State#upload{rlist=NewRList}).
%%% Called by YAWS to generate content for the page
%%% returns Ehtml | {get_more,Continuation,State}
out(A) ->
case (A#arg.req)#http_request.method of
'GET' -> show_form(A, "");
'POST' -> handle_post(A)
end.