aboutsummaryrefslogtreecommitdiff
path: root/src/mod_http_upload.erl
diff options
context:
space:
mode:
Diffstat (limited to 'src/mod_http_upload.erl')
-rw-r--r--src/mod_http_upload.erl206
1 files changed, 117 insertions, 89 deletions
diff --git a/src/mod_http_upload.erl b/src/mod_http_upload.erl
index 8d986d0d3..c3c295f66 100644
--- a/src/mod_http_upload.erl
+++ b/src/mod_http_upload.erl
@@ -107,10 +107,11 @@
get_url :: binary(),
service_url :: binary() | undefined,
thumbnail :: boolean(),
+ custom_headers :: [{binary(), binary()}],
slots = #{} :: map()}).
-record(media_info,
- {type :: binary(),
+ {type :: atom(),
height :: integer(),
width :: integer()}).
@@ -226,6 +227,7 @@ init([ServerHost, Opts]) ->
GetURL = gen_mod:get_opt(get_url, Opts, PutURL),
ServiceURL = gen_mod:get_opt(service_url, Opts),
Thumbnail = gen_mod:get_opt(thumbnail, Opts, true),
+ CustomHeaders = gen_mod:get_opt(custom_headers, Opts, []),
DocRoot1 = expand_home(str:strip(DocRoot, right, $/)),
DocRoot2 = expand_host(DocRoot1, ServerHost),
case DirMode of
@@ -236,12 +238,14 @@ init([ServerHost, Opts]) ->
end,
case Thumbnail of
true ->
- case string:str(os:cmd("identify"), "Magick") of
- 0 ->
- ?ERROR_MSG("Cannot find 'identify' command, please install "
- "ImageMagick or disable thumbnail creation", []);
- _ ->
- ok
+ case misc:have_eimp() of
+ false ->
+ ?ERROR_MSG("ejabberd is built without graphics support, "
+ "please rebuild it with --enable-graphics or "
+ "set 'thumbnail: false' for module '~s' in "
+ "ejabberd.yml", [?MODULE]);
+ _ ->
+ ok
end;
false ->
ok
@@ -258,7 +262,8 @@ init([ServerHost, Opts]) ->
docroot = DocRoot2,
put_url = expand_host(str:strip(PutURL, right, $/), ServerHost),
get_url = expand_host(str:strip(GetURL, right, $/), ServerHost),
- service_url = ServiceURL}}.
+ service_url = ServiceURL,
+ custom_headers = CustomHeaders}}.
-spec handle_call(_, {pid(), _}, state())
-> {reply, {ok, pos_integer(), binary(),
@@ -266,25 +271,30 @@ init([ServerHost, Opts]) ->
pos_integer() | undefined}, state()} |
{reply, {error, atom()}, state()} | {noreply, state()}.
-handle_call({use_slot, Slot, Size}, _From, #state{file_mode = FileMode,
- dir_mode = DirMode,
- get_url = GetPrefix,
- thumbnail = Thumbnail,
- docroot = DocRoot} = State) ->
+handle_call({use_slot, Slot, Size}, _From,
+ #state{file_mode = FileMode,
+ dir_mode = DirMode,
+ get_url = GetPrefix,
+ thumbnail = Thumbnail,
+ custom_headers = CustomHeaders,
+ docroot = DocRoot} = State) ->
case get_slot(Slot, State) of
{ok, {Size, Timer}} ->
timer:cancel(Timer),
NewState = del_slot(Slot, State),
Path = str:join([DocRoot | Slot], <<$/>>),
- {reply, {ok, Path, FileMode, DirMode, GetPrefix, Thumbnail},
+ {reply,
+ {ok, Path, FileMode, DirMode, GetPrefix, Thumbnail, CustomHeaders},
NewState};
{ok, {_WrongSize, _Timer}} ->
{reply, {error, size_mismatch}, State};
error ->
{reply, {error, invalid_slot}, State}
end;
-handle_call(get_docroot, _From, #state{docroot = DocRoot} = State) ->
- {reply, {ok, DocRoot}, State};
+handle_call(get_conf, _From,
+ #state{docroot = DocRoot,
+ custom_headers = CustomHeaders} = State) ->
+ {reply, {ok, DocRoot, CustomHeaders}, State};
handle_call(Request, From, State) ->
?ERROR_MSG("Got unexpected request from ~p: ~p", [From, Request]),
{noreply, State}.
@@ -353,44 +363,44 @@ process(LocalPath, #request{method = Method, host = Host, ip = IP})
Method == 'HEAD' ->
?DEBUG("Rejecting ~s request from ~s for ~s: Too few path components",
[Method, ?ADDR_TO_STR(IP), Host]),
- http_response(Host, 404);
+ http_response(404);
process(_LocalPath, #request{method = 'PUT', host = Host, ip = IP,
data = Data} = Request) ->
{Proc, Slot} = parse_http_request(Request),
case catch gen_server:call(Proc, {use_slot, Slot, byte_size(Data)}) of
- {ok, Path, FileMode, DirMode, GetPrefix, Thumbnail} ->
+ {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,
GetPrefix, Slot, Thumbnail) of
ok ->
- http_response(Host, 201);
+ http_response(201, CustomHeaders);
{ok, Headers, OutData} ->
- http_response(Host, 201, Headers, OutData);
+ http_response(201, Headers ++ CustomHeaders, OutData);
{error, Error} ->
?ERROR_MSG("Cannot store file ~s from ~s for ~s: ~p",
[Path, ?ADDR_TO_STR(IP), Host, ?FORMAT(Error)]),
- http_response(Host, 500)
+ http_response(500)
end;
{error, size_mismatch} ->
?INFO_MSG("Rejecting file from ~s for ~s: Unexpected size (~B)",
[?ADDR_TO_STR(IP), Host, byte_size(Data)]),
- http_response(Host, 413);
+ http_response(413);
{error, invalid_slot} ->
?INFO_MSG("Rejecting file from ~s for ~s: Invalid slot",
[?ADDR_TO_STR(IP), Host]),
- http_response(Host, 403);
+ http_response(403);
Error ->
?ERROR_MSG("Cannot handle PUT request from ~s for ~s: ~p",
[?ADDR_TO_STR(IP), Host, Error]),
- http_response(Host, 500)
+ http_response(500)
end;
process(_LocalPath, #request{method = Method, host = Host, ip = IP} = Request)
when Method == 'GET';
Method == 'HEAD' ->
{Proc, [_UserDir, _RandDir, FileName] = Slot} = parse_http_request(Request),
- case catch gen_server:call(Proc, get_docroot) of
- {ok, DocRoot} ->
+ 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} ->
@@ -405,37 +415,47 @@ process(_LocalPath, #request{method = Method, host = Host, ip = IP} = Request)
$", FileName/binary, $">>}]
end,
Headers2 = [{<<"Content-Type">>, ContentType} | Headers1],
- http_response(Host, 200, Headers2, Data);
+ Headers3 = Headers2 ++ CustomHeaders,
+ http_response(200, Headers3, Data);
{error, eacces} ->
?INFO_MSG("Cannot serve ~s to ~s: Permission denied",
[Path, ?ADDR_TO_STR(IP)]),
- http_response(Host, 403);
+ http_response(403);
{error, enoent} ->
?INFO_MSG("Cannot serve ~s to ~s: No such file",
[Path, ?ADDR_TO_STR(IP)]),
- http_response(Host, 404);
+ http_response(404);
{error, eisdir} ->
?INFO_MSG("Cannot serve ~s to ~s: Is a directory",
[Path, ?ADDR_TO_STR(IP)]),
- http_response(Host, 404);
+ http_response(404);
{error, Error} ->
?INFO_MSG("Cannot serve ~s to ~s: ~s",
[Path, ?ADDR_TO_STR(IP), ?FORMAT(Error)]),
- http_response(Host, 500)
+ http_response(500)
end;
Error ->
?ERROR_MSG("Cannot handle ~s request from ~s for ~s: ~p",
[Method, ?ADDR_TO_STR(IP), Host, Error]),
- http_response(Host, 500)
+ http_response(500)
end;
-process(_LocalPath, #request{method = 'OPTIONS', host = Host, ip = IP}) ->
+process(_LocalPath, #request{method = 'OPTIONS', host = Host,
+ ip = IP} = Request) ->
?DEBUG("Responding to OPTIONS request from ~s for ~s",
[?ADDR_TO_STR(IP), Host]),
- http_response(Host, 200);
+ {Proc, _Slot} = parse_http_request(Request),
+ case catch gen_server:call(Proc, get_conf) of
+ {ok, _DocRoot, CustomHeaders} ->
+ http_response(200, CustomHeaders);
+ Error ->
+ ?ERROR_MSG("Cannot handle OPTIONS request from ~s for ~s: ~p",
+ [?ADDR_TO_STR(IP), Host, Error]),
+ http_response(500)
+ end;
process(_LocalPath, #request{method = Method, host = Host, ip = IP}) ->
?DEBUG("Rejecting ~s request from ~s for ~s",
[Method, ?ADDR_TO_STR(IP), Host]),
- http_response(Host, 405, [{<<"Allow">>, <<"OPTIONS, HEAD, GET, PUT">>}]).
+ http_response(405, [{<<"Allow">>, <<"OPTIONS, HEAD, GET, PUT">>}]).
%%--------------------------------------------------------------------
%% Exported utility functions.
@@ -522,7 +542,7 @@ process_slot_request(#iq{lang = Lang, from = From} = IQ,
deny ->
?DEBUG("Denying HTTP upload slot request from ~s",
[jid:encode(From)]),
- Txt = <<"Denied by ACL">>,
+ Txt = <<"Access denied by service policy">>,
xmpp:make_error(IQ, xmpp:err_forbidden(Txt, Lang))
end.
@@ -726,15 +746,15 @@ parse_http_request(#request{host = Host, path = Path}) ->
store_file(Path, Data, FileMode, DirMode, GetPrefix, Slot, Thumbnail) ->
case do_store_file(Path, Data, FileMode, DirMode) of
ok when Thumbnail ->
- case identify(Path) of
+ case identify(Path, Data) of
{ok, MediaInfo} ->
- case convert(Path, MediaInfo) of
- {ok, OutPath} ->
+ case convert(Path, Data, MediaInfo) of
+ {ok, OutPath, OutMediaInfo} ->
[UserDir, RandDir | _] = Slot,
FileName = filename:basename(OutPath),
URL = str:join([GetPrefix, UserDir,
RandDir, FileName], <<$/>>),
- ThumbEl = thumb_el(OutPath, URL),
+ ThumbEl = thumb_el(OutMediaInfo, URL),
{ok,
[{<<"Content-Type">>,
<<"text/xml; charset=utf-8">>}],
@@ -790,30 +810,29 @@ guess_content_type(FileName) ->
?DEFAULT_CONTENT_TYPE,
?CONTENT_TYPES).
--spec http_response(binary(), 100..599)
+-spec http_response(100..599)
-> {pos_integer(), [{binary(), binary()}], binary()}.
-http_response(Host, Code) ->
- http_response(Host, Code, []).
+http_response(Code) ->
+ http_response(Code, []).
--spec http_response(binary(), 100..599, [{binary(), binary()}])
+-spec http_response(100..599, [{binary(), binary()}])
-> {pos_integer(), [{binary(), binary()}], binary()}.
-http_response(Host, Code, ExtraHeaders) ->
+http_response(Code, ExtraHeaders) ->
Message = <<(code_to_message(Code))/binary, $\n>>,
- http_response(Host, Code, ExtraHeaders, Message).
+ http_response(Code, ExtraHeaders, Message).
--spec http_response(binary(), 100..599, [{binary(), binary()}], binary())
+-spec http_response(100..599, [{binary(), binary()}], binary())
-> {pos_integer(), [{binary(), binary()}], binary()}.
-http_response(Host, Code, ExtraHeaders, Body) ->
- CustomHeaders = gen_mod:get_module_opt(Host, ?MODULE, custom_headers, []),
+http_response(Code, ExtraHeaders, Body) ->
Headers = case proplists:is_defined(<<"Content-Type">>, ExtraHeaders) of
true ->
ExtraHeaders;
false ->
[{<<"Content-Type">>, <<"text/plain">>} | ExtraHeaders]
- end ++ CustomHeaders,
+ end,
{Code, Headers, Body}.
-spec code_to_message(100..599) -> binary().
@@ -830,59 +849,68 @@ code_to_message(_Code) -> <<"">>.
%% Image manipulation stuff.
%%--------------------------------------------------------------------
--spec identify(binary()) -> {ok, media_info()} | pass.
-
-identify(Path) ->
- Cmd = io_lib:format("identify -format 'ok %m %h %w' ~s", [Path]),
- Res = string:strip(os:cmd(Cmd), right, $\n),
- case string:tokens(Res, " ") of
- ["ok", T, H, W] ->
- {ok, #media_info{type = list_to_binary(string:to_lower(T)),
- height = list_to_integer(H),
- width = list_to_integer(W)}};
- _ ->
- ?DEBUG("Cannot identify type of ~s: ~s", [Path, Res]),
+-spec identify(binary(), binary()) -> {ok, media_info()} | pass.
+
+identify(Path, Data) ->
+ case misc:have_eimp() of
+ true ->
+ 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;
+ false ->
pass
end.
--spec convert(binary(), media_info()) -> {ok, binary()} | pass.
+-spec convert(binary(), binary(), media_info()) -> {ok, binary(), media_info()} | pass.
-convert(Path, #media_info{type = T, width = W, height = H}) ->
+convert(Path, Data, #media_info{type = T, width = W, height = H} = Info) ->
if W * H >= 25000000 ->
?DEBUG("The image ~s is more than 25 Mpix", [Path]),
pass;
W =< 300, H =< 300 ->
- {ok, Path};
- T == <<"gif">>; T == <<"jpeg">>; T == <<"png">>; T == <<"webp">> ->
+ {ok, Path, Info};
+ true ->
Dir = filename:dirname(Path),
- FileName = <<(randoms:get_string())/binary, $., T/binary>>,
+ Ext = atom_to_binary(T, latin1),
+ FileName = <<(randoms:get_string())/binary, $., Ext/binary>>,
OutPath = filename:join(Dir, FileName),
- Cmd = io_lib:format("convert -resize 300 ~s ~s", [Path, OutPath]),
- case os:cmd(Cmd) of
- "" ->
- {ok, OutPath};
- Err ->
+ {W1, H1} = if W > H -> {300, round(H*300/W)};
+ H > W -> {round(W*300/H), 300};
+ 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};
+ {error, Why} ->
+ ?ERROR_MSG("Failed to write to ~s: ~s",
+ [OutPath, file:format_error(Why)]),
+ pass
+ end;
+ {error, Why} ->
?ERROR_MSG("Failed to convert ~s to ~s: ~s",
- [Path, OutPath, string:strip(Err, right, $\n)]),
+ [Path, OutPath, eimp:format_error(Why)]),
pass
- end;
- true ->
- ?DEBUG("Won't call 'convert' for unknown type ~s", [T]),
- pass
+ end
end.
--spec thumb_el(binary(), binary()) -> xmlel().
-
-thumb_el(Path, URI) ->
- ContentType = guess_content_type(Path),
- xmpp:encode(
- case identify(Path) of
- {ok, #media_info{height = H, width = W}} ->
- #thumbnail{'media-type' = ContentType, uri = URI,
- height = H, width = W};
- pass ->
- #thumbnail{uri = URI, 'media-type' = ContentType}
- end).
+-spec thumb_el(media_info(), binary()) -> xmlel().
+
+thumb_el(#media_info{type = T, height = H, width = W}, URI) ->
+ MimeType = <<"image/", (atom_to_binary(T, latin1))/binary>>,
+ Thumb = #thumbnail{'media-type' = MimeType, uri = URI,
+ height = H, width = W},
+ xmpp:encode(Thumb).
%%--------------------------------------------------------------------
%% Remove user.