mod_http_upload: Make file/dir modes configurable

The new "file_mode" and "dir_mode" options allow admins to specify the
permission bits of files and directories created by mod_http_upload.
This commit is contained in:
Holger Weiss 2015-08-31 20:06:22 +02:00
parent 12274a2adc
commit d45ec798ee
2 changed files with 54 additions and 10 deletions

View File

@ -77,6 +77,19 @@ The configurable mod_http_upload options are:
mod_http_upload. Otherwise, a SHA-1 hash of the user's bare JID is
included instead.
- file_mode (default: 'undefined')
This option defines the permission bits of uploaded files. The bits are
specified as an octal number (see the chmod(1) manual page) within double
quotes. For example: "0644".
- dir_mode (default: 'undefined')
This option defines the permission bits of the 'docroot' directory and any
directories created during file uploads. The bits are specified as an
octal number (see the chmod(1) manual page) within double quotes. For
example: "0755".
- docroot (default: "@HOME@/upload")
Uploaded files are stored below the directory specified (as an absolute

View File

@ -16,6 +16,7 @@
-define(PROCNAME, ?MODULE).
-define(URL_ENC(URL), binary_to_list(ejabberd_http:url_encode(URL))).
-define(ADDR_TO_STR(IP), ejabberd_config:may_hide_data(jlib:ip_to_list(IP))).
-define(STR_TO_INT(Str, B), jlib:binary_to_integer(iolist_to_binary(Str), B)).
-define(DEFAULT_CONTENT_TYPE, <<"application/octet-stream">>).
-define(CONTENT_TYPES,
[{<<".avi">>, <<"video/avi">>},
@ -78,6 +79,8 @@
max_size :: pos_integer() | infinity,
secret_length :: pos_integer(),
jid_in_url :: sha1 | node,
file_mode :: integer() | undefined,
dir_mode :: integer() | undefined,
docroot :: binary(),
put_url :: binary(),
get_url :: binary(),
@ -164,6 +167,10 @@ mod_opt_type(jid_in_url) ->
fun(sha1) -> sha1;
(node) -> node
end;
mod_opt_type(file_mode) ->
fun(Mode) -> ?STR_TO_INT(Mode, 8) end;
mod_opt_type(dir_mode) ->
fun(Mode) -> ?STR_TO_INT(Mode, 8) end;
mod_opt_type(docroot) ->
fun iolist_to_binary/1;
mod_opt_type(put_url) ->
@ -181,8 +188,8 @@ mod_opt_type(service_url) ->
mod_opt_type(rm_on_unregister) ->
fun(B) when is_boolean(B) -> B end;
mod_opt_type(_) ->
[host, name, access, max_size, secret_length, jid_in_url, docroot,
put_url, get_url, service_url, rm_on_unregister].
[host, name, access, max_size, secret_length, jid_in_url, file_mode,
dir_mode, docroot, put_url, get_url, service_url, rm_on_unregister].
%%--------------------------------------------------------------------
%% gen_server callbacks.
@ -215,6 +222,10 @@ init({ServerHost, Opts}) ->
DocRoot = gen_mod:get_opt(docroot, Opts,
fun iolist_to_binary/1,
<<"@HOME@/upload">>),
FileMode = gen_mod:get_opt(file_mode, Opts,
fun(Mode) -> ?STR_TO_INT(Mode, 8) end),
DirMode = gen_mod:get_opt(dir_mode, Opts,
fun(Mode) -> ?STR_TO_INT(Mode, 8) end),
PutURL = gen_mod:get_opt(put_url, Opts,
fun(<<"http://", _/binary>> = URL) -> URL;
(<<"https://", _/binary>> = URL) -> URL
@ -240,10 +251,17 @@ init({ServerHost, Opts}) ->
application:start(public_key),
application:start(ssl)
end,
case DirMode of
undefined ->
ok;
Mode ->
file:change_mode(DocRoot, Mode)
end,
ejabberd_router:register_route(Host),
{ok, #state{server_host = ServerHost, host = Host, name = Name,
access = Access, max_size = MaxSize,
secret_length = SecretLength, jid_in_url = JIDinURL,
file_mode = FileMode, dir_mode = DirMode,
docroot = expand_home(str:strip(DocRoot, right, $/)),
put_url = expand_host(str:strip(PutURL, right, $/), ServerHost),
get_url = expand_host(str:strip(GetURL, right, $/), ServerHost),
@ -251,13 +269,15 @@ init({ServerHost, Opts}) ->
-spec handle_call(_, {pid(), _}, state()) -> {noreply, state()}.
handle_call({use_slot, Slot}, _From, #state{docroot = DocRoot} = State) ->
handle_call({use_slot, Slot}, _From, #state{file_mode = FileMode,
dir_mode = DirMode,
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, Size, Path}, NewState};
{reply, {ok, Size, Path, FileMode, DirMode}, NewState};
error ->
{reply, {error, <<"Invalid slot">>}, State}
end;
@ -322,10 +342,10 @@ process(LocalPath, #request{method = 'PUT', host = Host, ip = IP,
data = Data}) ->
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
case catch gen_server:call(Proc, {use_slot, LocalPath}) of
{ok, Size, Path} when byte_size(Data) == Size ->
{ok, Size, Path, FileMode, DirMode} when byte_size(Data) == Size ->
?DEBUG("Storing file from ~s for ~s: ~s",
[?ADDR_TO_STR(IP), Host, Path]),
case store_file(Path, Data) of
case store_file(Path, Data, FileMode, DirMode) of
ok ->
http_response(201);
{error, Error} ->
@ -635,15 +655,26 @@ iq_disco_info(Lang, Name) ->
%% HTTP request handling.
-spec store_file(file:filename_all(), binary()) -> ok | {error, term()}.
-spec store_file(file:filename_all(), binary(), integer(), integer())
-> ok | {error, term()}.
store_file(Path, Data) ->
store_file(Path, Data, 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), % Close file even if file:write/2 failed.
ok = Ok % But raise an exception in that case.
ok = file:close(Io),
ok = if is_integer(FileMode) ->
file:change_mode(Path, FileMode);
FileMode == undefined ->
ok
end,
ok = if is_integer(DirMode) ->
file:change_mode(filename:dirname(Path), DirMode);
DirMode == undefined ->
ok
end,
ok = Ok % Raise an exception if file:write/2 failed.
catch
_:{badmatch, {error, Error}} ->
{error, Error};