diff --git a/include/ejabberd_http.hrl b/include/ejabberd_http.hrl index 3c38969ca..e0183a531 100644 --- a/include/ejabberd_http.hrl +++ b/include/ejabberd_http.hrl @@ -31,7 +31,10 @@ port = 5280 :: inet:port_number(), opts = [] :: list(), tp = http :: protocol(), - headers = [] :: [{atom() | binary(), binary()}]}). + headers = [] :: [{atom() | binary(), binary()}], + length = 0 :: non_neg_integer(), + sockmod :: gen_tcp | fast_tls, + socket :: inet:socket() | fast_tls:tls_socket()}). -record(ws, {socket :: inet:socket() | fast_tls:tls_socket(), diff --git a/src/ejabberd_http.erl b/src/ejabberd_http.erl index 40267b5eb..2e73ef1bf 100644 --- a/src/ejabberd_http.erl +++ b/src/ejabberd_http.erl @@ -31,17 +31,16 @@ %% External exports -export([start/2, start_link/2, become_controller/1, - socket_type/0, receive_headers/1, + socket_type/0, receive_headers/1, recv_file/2, transform_listen_option/2, listen_opt_type/1]). -export([init/2, opt_type/1]). -include("ejabberd.hrl"). -include("logger.hrl"). - -include("xmpp.hrl"). - -include("ejabberd_http.hrl"). +-include_lib("kernel/include/file.hrl"). -record(state, {sockmod, socket, @@ -50,7 +49,7 @@ request_path, request_auth, request_keepalive, - request_content_length, + request_content_length = 0, request_lang = <<"en">>, %% XXX bard: request handlers are configured in %% ejabberd.cfg under the HTTP service. For example, @@ -85,6 +84,10 @@ "org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n" "">>). +-define(RECV_BUF, 65536). +-define(SEND_BUF, 65536). +-define(MAX_POST_SIZE, 20971520). %% 20Mb + start(SockData, Opts) -> {ok, proc_lib:spawn(ejabberd_http, init, @@ -113,7 +116,7 @@ init({SockMod, Socket}, Opts) -> end, TLSOpts = [verify_none | TLSOpts3], {SockMod1, Socket1} = if TLSEnabled -> - inet:setopts(Socket, [{recbuf, 8192}]), + inet:setopts(Socket, [{recbuf, ?RECV_BUF}]), {ok, TLSSocket} = fast_tls:tcp_to_tls(Socket, TLSOpts), {fast_tls, TLSSocket}; @@ -168,18 +171,44 @@ become_controller(_Pid) -> socket_type() -> raw. +send_text(_State, none) -> + ok; send_text(State, Text) -> - case catch - (State#state.sockmod):send(State#state.socket, Text) - of - ok -> ok; - {error, timeout} -> - ?INFO_MSG("Timeout on ~p:send", [State#state.sockmod]), - exit(normal); - Error -> - ?DEBUG("Error in ~p:send: ~p", - [State#state.sockmod, Error]), - exit(normal) + case (State#state.sockmod):send(State#state.socket, Text) of + ok -> ok; + {error, timeout} -> + ?INFO_MSG("Timeout on ~p:send", [State#state.sockmod]), + exit(normal); + Error -> + ?DEBUG("Error in ~p:send: ~p", + [State#state.sockmod, Error]), + exit(normal) + end. + +send_file(State, Fd, Size, FileName) -> + try + case State#state.sockmod of + gen_tcp -> + case file:sendfile(Fd, State#state.socket, 0, Size, []) of + {ok, _} -> ok + end; + _ -> + case file:read(Fd, ?SEND_BUF) of + {ok, Data} -> + send_text(State, Data), + send_file(State, Fd, Size, FileName); + eof -> + ok + end + end + catch _:{case_clause, {error, Why}} -> + if Why /= closed -> + ?INFO_MSG("Failed to read ~s: ~s", + [FileName, file_format_error(Why)]), + exit(normal); + true -> + ok + end end. receive_headers(#state{trail = Trail} = State) -> @@ -348,8 +377,8 @@ get_transfer_protocol(RE, SockMod, HostPort) -> %% matches the requested URL path, and pass control to it. If none is %% found, answer with HTTP 404. -process([], _, _, _, _) -> ejabberd_web:error(not_found); -process(Handlers, Request, Socket, SockMod, Trail) -> +process([], _) -> ejabberd_web:error(not_found); +process(Handlers, Request) -> {HandlerPathPrefix, HandlerModule, HandlerOpts, HandlersLeft} = case Handlers of [{Pfx, Mod} | Tail] -> @@ -369,14 +398,14 @@ process(Handlers, Request, Socket, SockMod, Trail) -> LocalPath = lists:nthtail(length(HandlerPathPrefix), Request#request.path), R = try HandlerModule:socket_handoff( - LocalPath, Request, Socket, SockMod, Trail, HandlerOpts) + LocalPath, Request, HandlerOpts) catch error:undef -> HandlerModule:process(LocalPath, Request) end, ejabberd_hooks:run(http_request_debug, [{LocalPath, Request}]), R; false -> - process(HandlersLeft, Request, Socket, SockMod, Trail) + process(HandlersLeft, Request) end. extract_path_query(#state{request_method = Method, @@ -398,24 +427,29 @@ extract_path_query(#state{request_method = Method, extract_path_query(#state{request_method = Method, request_path = {abs_path, Path}, request_content_length = Len, + trail = Trail, sockmod = _SockMod, socket = _Socket} = State) - when (Method =:= 'POST' orelse Method =:= 'PUT') andalso - is_integer(Len) -> - case recv_data(State, Len) of - error -> {State, false}; - {NewState, Data} -> - ?DEBUG("client data: ~p~n", [Data]), + when (Method =:= 'POST' orelse Method =:= 'PUT') andalso Len>0 -> case catch url_decode_q_split(Path) of - {'EXIT', _} -> {NewState, false}; + {'EXIT', _} -> {State, false}; {NPath, _Query} -> - LPath = normalize_path([NPE - || NPE <- str:tokens(path_decode(NPath), <<"/">>)]), - LQuery = case catch parse_urlencoded(Data) of - {'EXIT', _Reason} -> []; - LQ -> LQ - end, - {NewState, {LPath, LQuery, Data}} + LPath = normalize_path( + [NPE || NPE <- str:tokens(path_decode(NPath), <<"/">>)]), + case Method of + 'PUT' -> + {State, {LPath, [], Trail}}; + 'POST' -> + case recv_data(State) of + {ok, Data} -> + LQuery = case catch parse_urlencoded(Data) of + {'EXIT', _Reason} -> []; + LQ -> LQ + end, + {State, {LPath, LQuery, Data}}; + error -> + {State, false} + end end end; extract_path_query(State) -> @@ -434,10 +468,10 @@ process_request(#state{request_method = Method, request_host = Host, request_port = Port, request_tp = TP, + request_content_length = Length, request_headers = RequestHeaders, request_handlers = RequestHandlers, - custom_headers = CustomHeaders, - trail = Trail} = State) -> + custom_headers = CustomHeaders} = State) -> case extract_path_query(State) of {State2, false} -> {State2, make_bad_request(State)}; @@ -459,7 +493,10 @@ process_request(#state{request_method = Method, path = LPath, q = LQuery, auth = Auth, - data = Data, + length = Length, + sockmod = SockMod, + socket = Socket, + data = Data, lang = Lang, host = Host, port = Port, @@ -469,7 +506,7 @@ process_request(#state{request_method = Method, ip = IP}, RequestHandlers1 = ejabberd_hooks:run_fold( http_request_handlers, RequestHandlers, [Host, Request]), - Res = case process(RequestHandlers1, Request, Socket, SockMod, Trail) of + Res = case process(RequestHandlers1, Request) of El when is_record(El, xmlel) -> make_xhtml_output(State, 200, CustomHeaders, El); {Status, Headers, El} @@ -482,6 +519,8 @@ process_request(#state{request_method = Method, when is_binary(Output) or is_list(Output) -> make_text_output(State, Status, Headers ++ CustomHeaders, Output); + {Status, Headers, {file, FileName}} -> + make_file_output(State, Status, Headers, FileName); {Status, Reason, Headers, Output} when is_binary(Output) or is_list(Output) -> make_text_output(State, Status, Reason, @@ -535,114 +574,80 @@ is_ipchain_trusted(UserIPs, Masks) -> end end, UserIPs). -recv_data(State, Len) -> recv_data(State, Len, <<>>). - -recv_data(State, 0, Acc) -> {State, Acc}; -recv_data(#state{trail = Trail} = State, Len, <<>>) when byte_size(Trail) > Len -> - <> = Trail, - {State#state{trail = Rest}, Data}; -recv_data(State, Len, Acc) -> - case State#state.trail of - <<>> -> - case (State#state.sockmod):recv(State#state.socket, - min(Len, 16#4000000), 300000) - of - {ok, Data} -> - recv_data(State, Len - byte_size(Data), <>); - Err -> - ?DEBUG("Cannot receive HTTP data: ~p", [Err]), - error +recv_data(#state{request_content_length = Len}) when Len >= ?MAX_POST_SIZE -> + error; +recv_data(#state{request_content_length = Len, trail = Trail, + sockmod = SockMod, socket = Socket}) -> + NewLen = Len - byte_size(Trail), + if NewLen > 0 -> + case SockMod:recv(Socket, NewLen, 60000) of + {ok, Data} -> {ok, <>}; + {error, _} -> error end; - _ -> - Trail = (State#state.trail), - recv_data(State#state{trail = <<>>}, - Len - byte_size(Trail), <>) + true -> + {ok, Trail} end. -make_xhtml_output(State, Status, Headers, XHTML) -> - Data = case lists:member(html, Headers) of - true -> - iolist_to_binary([?HTML_DOCTYPE, - fxml:element_to_binary(XHTML)]); - _ -> - iolist_to_binary([?XHTML_DOCTYPE, - fxml:element_to_binary(XHTML)]) - end, - Headers1 = case lists:keysearch(<<"Content-Type">>, 1, - Headers) - of - {value, _} -> - [{<<"Content-Length">>, - integer_to_binary(byte_size(Data))} - | Headers]; - _ -> - [{<<"Content-Type">>, <<"text/html; charset=utf-8">>}, - {<<"Content-Length">>, - integer_to_binary(byte_size(Data))} - | Headers] +recv_file(#request{length = Len, data = Trail, + sockmod = SockMod, socket = Socket}, Path) -> + case file:open(Path, [write, exclusive, raw]) of + {ok, Fd} -> + case file:write(Fd, Trail) of + ok -> + NewLen = max(0, Len - byte_size(Trail)), + case do_recv_file(NewLen, SockMod, Socket, Fd) of + ok -> + ok; + {error, _} = Err -> + file:delete(Path), + Err + end; + {error, _} = Err -> + file:delete(Path), + Err + end; + {error, _} = Err -> + Err + end. + +do_recv_file(0, _SockMod, _Socket, Fd) -> + file:close(Fd); +do_recv_file(Len, SockMod, Socket, Fd) -> + ChunkLen = min(Len, ?RECV_BUF), + try + {ok, Data} = SockMod:recv(Socket, ChunkLen, timer:seconds(30)), + ok = file:write(Fd, Data), + do_recv_file(Len-ChunkLen, SockMod, Socket, Fd) + catch _:{badmatch, {error, _} = Err} -> + file:close(Fd), + Err + end. + +make_headers(State, Status, Reason, Headers, Data) -> + Len = if is_integer(Data) -> Data; + true -> iolist_size(Data) + end, + Headers1 = [{<<"Content-Length">>, integer_to_binary(Len)} | Headers], + Headers2 = case lists:keyfind(<<"Content-Type">>, 1, Headers) of + {_, _} -> + Headers1; + false -> + [{<<"Content-Type">>, <<"text/html; charset=utf-8">>} + | Headers1] end, HeadersOut = case {State#state.request_version, - State#state.request_keepalive} - of - {{1, 1}, true} -> Headers1; - {_, true} -> - [{<<"Connection">>, <<"keep-alive">>} | Headers1]; - {_, false} -> - [{<<"Connection">>, <<"close">>} | Headers1] + State#state.request_keepalive} of + {{1, 1}, true} -> Headers2; + {_, true} -> + [{<<"Connection">>, <<"keep-alive">>} | Headers2]; + {_, false} -> + [{<<"Connection">>, <<"close">>} | Headers2] end, Version = case State#state.request_version of - {1, 1} -> <<"HTTP/1.1 ">>; - _ -> <<"HTTP/1.0 ">> + {1, 1} -> <<"HTTP/1.1 ">>; + _ -> <<"HTTP/1.0 ">> end, - H = lists:map(fun ({Attr, Val}) -> - [Attr, <<": ">>, Val, <<"\r\n">>]; - (_) -> [] - end, - HeadersOut), - SL = [Version, - integer_to_binary(Status), <<" ">>, - code_to_phrase(Status), <<"\r\n">>], - Data2 = case State#state.request_method of - 'HEAD' -> <<"">>; - _ -> Data - end, - [SL, H, <<"\r\n">>, Data2]. - -make_text_output(State, Status, Headers, Text) -> - make_text_output(State, Status, <<"">>, Headers, Text). - -make_text_output(State, Status, Reason, Headers, Text) -> - Data = iolist_to_binary(Text), - Headers1 = case lists:keysearch(<<"Content-Type">>, 1, - Headers) - of - {value, _} -> - [{<<"Content-Length">>, - integer_to_binary(byte_size(Data))} - | Headers]; - _ -> - [{<<"Content-Type">>, <<"text/html; charset=utf-8">>}, - {<<"Content-Length">>, - integer_to_binary(byte_size(Data))} - | Headers] - end, - HeadersOut = case {State#state.request_version, - State#state.request_keepalive} - of - {{1, 1}, true} -> Headers1; - {_, true} -> - [{<<"Connection">>, <<"keep-alive">>} | Headers1]; - {_, false} -> - [{<<"Connection">>, <<"close">>} | Headers1] - end, - Version = case State#state.request_version of - {1, 1} -> <<"HTTP/1.1 ">>; - _ -> <<"HTTP/1.0 ">> - end, - H = lists:map(fun ({Attr, Val}) -> - [Attr, <<": ">>, Val, <<"\r\n">>] - end, - HeadersOut), + H = [[Attr, <<": ">>, Val, <<"\r\n">>] || {Attr, Val} <- HeadersOut], NewReason = case Reason of <<"">> -> code_to_phrase(Status); _ -> Reason @@ -650,11 +655,55 @@ make_text_output(State, Status, Reason, Headers, Text) -> SL = [Version, integer_to_binary(Status), <<" ">>, NewReason, <<"\r\n">>], + [SL, H, <<"\r\n">>]. + +make_xhtml_output(State, Status, Headers, XHTML) -> + Data = case State#state.request_method of + 'HEAD' -> <<"">>; + _ -> + DocType = case lists:member(html, Headers) of + true -> ?HTML_DOCTYPE; + false -> ?XHTML_DOCTYPE + end, + iolist_to_binary([DocType, fxml:element_to_binary(XHTML)]) + end, + EncodedHdrs = make_headers(State, Status, <<"">>, Headers, Data), + [EncodedHdrs, Data]. + +make_text_output(State, Status, Headers, Text) -> + make_text_output(State, Status, <<"">>, Headers, Text). + +make_text_output(State, Status, Reason, Headers, Text) -> + Data = iolist_to_binary(Text), Data2 = case State#state.request_method of - 'HEAD' -> <<"">>; - _ -> Data + 'HEAD' -> <<"">>; + _ -> Data end, - [SL, H, <<"\r\n">>, Data2]. + EncodedHdrs = make_headers(State, Status, Reason, Headers, Data2), + [EncodedHdrs, Data2]. + +make_file_output(State, Status, Headers, FileName) -> + case file:read_file_info(FileName) of + {ok, #file_info{size = Size}} when State#state.request_method == 'HEAD' -> + make_headers(State, Status, <<"">>, Headers, Size); + {ok, #file_info{size = Size}} -> + case file:open(FileName, [raw, read]) of + {ok, Fd} -> + EncodedHdrs = make_headers(State, Status, <<"">>, Headers, Size), + send_text(State, EncodedHdrs), + send_file(State, Fd, Size, FileName), + file:close(Fd), + none; + {error, Why} -> + Reason = file_format_error(Why), + ?ERROR_MSG("Failed to open ~s: ~s", [FileName, Reason]), + make_text_output(State, 404, Reason, [], <<>>) + end; + {error, Why} -> + Reason = file_format_error(Why), + ?ERROR_MSG("Failed to read info of ~s: ~s", [FileName, Reason]), + make_text_output(State, 404, Reason, [], <<>>) + end. parse_lang(Langs) -> case str:tokens(Langs, <<",; ">>) of @@ -662,6 +711,12 @@ parse_lang(Langs) -> [] -> <<"en">> end. +file_format_error(Reason) -> + case file:format_error(Reason) of + "unknown POSIX error" -> atom_to_list(Reason); + Text -> Text + end. + % Code below is taken (with some modifications) from the yaws webserver, which % is distributed under the following license: % diff --git a/src/ejabberd_http_ws.erl b/src/ejabberd_http_ws.erl index 0c15ab6c0..b989f3077 100644 --- a/src/ejabberd_http_ws.erl +++ b/src/ejabberd_http_ws.erl @@ -35,7 +35,7 @@ terminate/3, send_xml/2, setopts/2, sockname/1, peername/1, controlling_process/2, become_controller/2, monitor/1, reset_stream/1, close/1, change_shaper/2, - socket_handoff/6, opt_type/1]). + socket_handoff/3, opt_type/1]). -include("ejabberd.hrl"). -include("logger.hrl"). @@ -121,9 +121,8 @@ change_shaper({http_ws, _FsmRef, _IP}, _Shaper) -> %% TODO??? ok. -socket_handoff(LocalPath, Request, Socket, SockMod, Buf, Opts) -> - ejabberd_websocket:socket_handoff(LocalPath, Request, Socket, SockMod, - Buf, Opts, ?MODULE, fun get_human_html_xmlel/0). +socket_handoff(LocalPath, Request, Opts) -> + ejabberd_websocket:socket_handoff(LocalPath, Request, Opts, ?MODULE, fun get_human_html_xmlel/0). %%% Internal diff --git a/src/ejabberd_websocket.erl b/src/ejabberd_websocket.erl index 9e2c5effd..c7d6181f8 100644 --- a/src/ejabberd_websocket.erl +++ b/src/ejabberd_websocket.erl @@ -42,7 +42,7 @@ -author('ecestari@process-one.net'). --export([check/2, socket_handoff/8]). +-export([check/2, socket_handoff/5]). -include("ejabberd.hrl"). -include("logger.hrl"). @@ -89,8 +89,9 @@ check(_Path, Headers) -> socket_handoff(LocalPath, #request{method = 'GET', ip = IP, q = Q, path = Path, headers = Headers, host = Host, port = Port, - opts = HOpts}, - Socket, SockMod, Buf, _Opts, HandlerModule, InfoMsgFun) -> + socket = Socket, sockmod = SockMod, + data = Buf, opts = HOpts}, + _Opts, HandlerModule, InfoMsgFun) -> case check(LocalPath, Headers) of true -> WS = #ws{socket = Socket, @@ -109,11 +110,11 @@ socket_handoff(LocalPath, #request{method = 'GET', ip = IP, q = Q, path = Path, _ -> {200, ?HEADER, InfoMsgFun()} end; -socket_handoff(_, #request{method = 'OPTIONS'}, _, _, _, _, _, _) -> +socket_handoff(_, #request{method = 'OPTIONS'}, _, _, _) -> {200, ?OPTIONS_HEADER, []}; -socket_handoff(_, #request{method = 'HEAD'}, _, _, _, _, _, _) -> +socket_handoff(_, #request{method = 'HEAD'}, _, _, _) -> {200, ?HEADER, []}; -socket_handoff(_, _, _, _, _, _, _, _) -> +socket_handoff(_, _, _, _, _) -> {400, ?HEADER, #xmlel{name = <<"h1">>, children = [{xmlcdata, <<"400 Bad Request">>}]}}. diff --git a/src/mod_http_fileserver.erl b/src/mod_http_fileserver.erl index 4595ac873..0175456d1 100644 --- a/src/mod_http_fileserver.erl +++ b/src/mod_http_fileserver.erl @@ -351,13 +351,12 @@ serve_file(FileInfo, FileName, CustomHeaders, DefaultContentType, ContentTypes) ?DEBUG("Delivering: ~s", [FileName]), ContentType = content_type(FileName, DefaultContentType, ContentTypes), - {ok, FileContents} = file:read_file(FileName), {FileInfo#file_info.size, 200, [{<<"Server">>, <<"ejabberd">>}, {<<"Last-Modified">>, last_modified(FileInfo)}, {<<"Content-Type">>, ContentType} | CustomHeaders], - FileContents}. + {file, FileName}}. %%---------------------------------------------------------------------- %% Log file diff --git a/src/mod_http_upload.erl b/src/mod_http_upload.erl index 7cc4bd56b..1f92672f1 100644 --- a/src/mod_http_upload.erl +++ b/src/mod_http_upload.erl @@ -377,13 +377,13 @@ process(LocalPath, #request{method = Method, host = Host, ip = IP}) [Method, ?ADDR_TO_STR(IP), Host]), http_response(404); process(_LocalPath, #request{method = 'PUT', host = Host, ip = IP, - data = Data} = Request) -> + length = Length} = Request) -> {Proc, Slot} = parse_http_request(Request), - case catch gen_server:call(Proc, {use_slot, Slot, byte_size(Data)}) of + case catch gen_server:call(Proc, {use_slot, Slot, Length}) of {ok, Path, FileMode, DirMode, GetPrefix, Thumbnail, CustomHeaders} -> ?DEBUG("Storing file from ~s for ~s: ~s", [?ADDR_TO_STR(IP), Host, Path]), - case store_file(Path, Data, FileMode, DirMode, + case store_file(Path, Request, FileMode, DirMode, GetPrefix, Slot, Thumbnail) of ok -> http_response(201, CustomHeaders); @@ -396,7 +396,7 @@ process(_LocalPath, #request{method = 'PUT', host = Host, ip = IP, end; {error, size_mismatch} -> ?INFO_MSG("Rejecting file from ~s for ~s: Unexpected size (~B)", - [?ADDR_TO_STR(IP), Host, byte_size(Data)]), + [?ADDR_TO_STR(IP), Host, Length]), http_response(413); {error, invalid_slot} -> ?INFO_MSG("Rejecting file from ~s for ~s: Invalid slot", @@ -414,8 +414,9 @@ process(_LocalPath, #request{method = Method, host = Host, ip = IP} = Request) case catch gen_server:call(Proc, get_conf) of {ok, DocRoot, CustomHeaders} -> Path = str:join([DocRoot | Slot], <<$/>>), - case file:read_file(Path) of - {ok, Data} -> + case file:read(Path, [read]) of + {ok, Fd} -> + file:close(Fd), ?INFO_MSG("Serving ~s to ~s", [Path, ?ADDR_TO_STR(IP)]), ContentType = guess_content_type(FileName), Headers1 = case ContentType of @@ -428,7 +429,7 @@ process(_LocalPath, #request{method = Method, host = Host, ip = IP} = Request) end, Headers2 = [{<<"Content-Type">>, ContentType} | Headers1], Headers3 = Headers2 ++ CustomHeaders, - http_response(200, Headers3, Data); + http_response(200, Headers3, {file, Path}); {error, eacces} -> ?INFO_MSG("Cannot serve ~s to ~s: Permission denied", [Path, ?ADDR_TO_STR(IP)]), @@ -720,17 +721,17 @@ parse_http_request(#request{host = Host, path = Path}) -> end, {gen_mod:get_module_proc(ProcURL, ?MODULE), Slot}. --spec store_file(binary(), binary(), +-spec store_file(binary(), http_request(), integer() | undefined, integer() | undefined, binary(), slot(), boolean()) -> ok | {ok, [{binary(), binary()}], binary()} | {error, term()}. -store_file(Path, Data, FileMode, DirMode, GetPrefix, Slot, Thumbnail) -> - case do_store_file(Path, Data, FileMode, DirMode) of +store_file(Path, Request, FileMode, DirMode, GetPrefix, Slot, Thumbnail) -> + case do_store_file(Path, Request, FileMode, DirMode) of ok when Thumbnail -> - case identify(Path, Data) of + case identify(Path) of {ok, MediaInfo} -> - case convert(Path, Data, MediaInfo) of + case convert(Path, MediaInfo) of {ok, OutPath, OutMediaInfo} -> [UserDir, RandDir | _] = Slot, FileName = filename:basename(OutPath), @@ -753,16 +754,14 @@ store_file(Path, Data, FileMode, DirMode, GetPrefix, Slot, Thumbnail) -> Err end. --spec do_store_file(file:filename_all(), binary(), +-spec do_store_file(file:filename_all(), http_request(), integer() | undefined, integer() | undefined) -> ok | {error, term()}. -do_store_file(Path, Data, FileMode, DirMode) -> +do_store_file(Path, Request, FileMode, DirMode) -> try ok = filelib:ensure_dir(Path), - {ok, Io} = file:open(Path, [write, exclusive, raw]), - Ok = file:write(Io, Data), - ok = file:close(Io), + ok = ejabberd_http:recv_file(Request, Path), if is_integer(FileMode) -> ok = file:change_mode(Path, FileMode); FileMode == undefined -> @@ -775,8 +774,7 @@ do_store_file(Path, Data, FileMode, DirMode) -> ok = file:change_mode(UserDir, DirMode); DirMode == undefined -> ok - end, - ok = Ok % Raise an exception if file:write/2 failed. + end catch _:{badmatch, {error, Error}} -> {error, Error}; @@ -801,7 +799,8 @@ http_response(Code, ExtraHeaders) -> Message = <<(code_to_message(Code))/binary, $\n>>, http_response(Code, ExtraHeaders, Message). --spec http_response(100..599, [{binary(), binary()}], binary()) +-type http_body() :: binary() | {file, file:filename()}. +-spec http_response(100..599, [{binary(), binary()}], http_body()) -> {pos_integer(), [{binary(), binary()}], binary()}. http_response(Code, ExtraHeaders, Body) -> Headers = case proplists:is_defined(<<"Content-Type">>, ExtraHeaders) of @@ -824,22 +823,30 @@ code_to_message(_Code) -> <<"">>. %%-------------------------------------------------------------------- %% Image manipulation stuff. %%-------------------------------------------------------------------- --spec identify(binary(), binary()) -> {ok, media_info()} | pass. -identify(Path, Data) -> - case eimp:identify(Data) of - {ok, Info} -> - {ok, #media_info{ - type = proplists:get_value(type, Info), - width = proplists:get_value(width, Info), - height = proplists:get_value(height, Info)}}; - {error, Why} -> - ?DEBUG("Cannot identify type of ~s: ~s", - [Path, eimp:format_error(Why)]), +-spec identify(binary()) -> {ok, media_info()} | pass. +identify(Path) -> + try + {ok, Fd} = file:open(Path, [read, raw]), + {ok, Data} = file:read(Fd, 1024), + case eimp:identify(Data) of + {ok, Info} -> + {ok, #media_info{ + type = proplists:get_value(type, Info), + width = proplists:get_value(width, Info), + height = proplists:get_value(height, Info)}}; + {error, Why} -> + ?DEBUG("Cannot identify type of ~s: ~s", + [Path, eimp:format_error(Why)]), + pass + end + catch _:{badmatch, {error, Reason}} -> + ?DEBUG("Failed to read file ~s: ~s", + [Path, file:format_error(Reason)]), pass end. --spec convert(binary(), binary(), media_info()) -> {ok, binary(), media_info()} | pass. -convert(Path, Data, #media_info{type = T, width = W, height = H} = Info) -> +-spec convert(binary(), media_info()) -> {ok, binary(), media_info()} | pass. +convert(Path, #media_info{type = T, width = W, height = H} = Info) -> if W * H >= 25000000 -> ?DEBUG("The image ~s is more than 25 Mpix", [Path]), pass; @@ -855,19 +862,26 @@ convert(Path, Data, #media_info{type = T, width = W, height = H} = Info) -> true -> {300, 300} end, OutInfo = #media_info{type = T, width = W1, height = H1}, - case eimp:convert(Data, T, [{scale, {W1, H1}}]) of - {ok, OutData} -> - case file:write_file(OutPath, OutData) of - ok -> - {ok, OutPath, OutInfo}; + case file:read_file(Path) of + {ok, Data} -> + case eimp:convert(Data, T, [{scale, {W1, H1}}]) of + {ok, OutData} -> + case file:write_file(OutPath, OutData) of + ok -> + {ok, OutPath, OutInfo}; + {error, Why} -> + ?ERROR_MSG("Failed to write to ~s: ~s", + [OutPath, file:format_error(Why)]), + pass + end; {error, Why} -> - ?ERROR_MSG("Failed to write to ~s: ~s", - [OutPath, file:format_error(Why)]), + ?ERROR_MSG("Failed to convert ~s to ~s: ~s", + [Path, OutPath, eimp:format_error(Why)]), pass end; {error, Why} -> - ?ERROR_MSG("Failed to convert ~s to ~s: ~s", - [Path, OutPath, eimp:format_error(Why)]), + ?ERROR_MSG("Failed to read file ~s: ~s", + [Path, file:format_error(Why)]), pass end end.