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:
parent
12274a2adc
commit
d45ec798ee
|
@ -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
|
mod_http_upload. Otherwise, a SHA-1 hash of the user's bare JID is
|
||||||
included instead.
|
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")
|
- docroot (default: "@HOME@/upload")
|
||||||
|
|
||||||
Uploaded files are stored below the directory specified (as an absolute
|
Uploaded files are stored below the directory specified (as an absolute
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
-define(PROCNAME, ?MODULE).
|
-define(PROCNAME, ?MODULE).
|
||||||
-define(URL_ENC(URL), binary_to_list(ejabberd_http:url_encode(URL))).
|
-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(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(DEFAULT_CONTENT_TYPE, <<"application/octet-stream">>).
|
||||||
-define(CONTENT_TYPES,
|
-define(CONTENT_TYPES,
|
||||||
[{<<".avi">>, <<"video/avi">>},
|
[{<<".avi">>, <<"video/avi">>},
|
||||||
|
@ -78,6 +79,8 @@
|
||||||
max_size :: pos_integer() | infinity,
|
max_size :: pos_integer() | infinity,
|
||||||
secret_length :: pos_integer(),
|
secret_length :: pos_integer(),
|
||||||
jid_in_url :: sha1 | node,
|
jid_in_url :: sha1 | node,
|
||||||
|
file_mode :: integer() | undefined,
|
||||||
|
dir_mode :: integer() | undefined,
|
||||||
docroot :: binary(),
|
docroot :: binary(),
|
||||||
put_url :: binary(),
|
put_url :: binary(),
|
||||||
get_url :: binary(),
|
get_url :: binary(),
|
||||||
|
@ -164,6 +167,10 @@ mod_opt_type(jid_in_url) ->
|
||||||
fun(sha1) -> sha1;
|
fun(sha1) -> sha1;
|
||||||
(node) -> node
|
(node) -> node
|
||||||
end;
|
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) ->
|
mod_opt_type(docroot) ->
|
||||||
fun iolist_to_binary/1;
|
fun iolist_to_binary/1;
|
||||||
mod_opt_type(put_url) ->
|
mod_opt_type(put_url) ->
|
||||||
|
@ -181,8 +188,8 @@ mod_opt_type(service_url) ->
|
||||||
mod_opt_type(rm_on_unregister) ->
|
mod_opt_type(rm_on_unregister) ->
|
||||||
fun(B) when is_boolean(B) -> B end;
|
fun(B) when is_boolean(B) -> B end;
|
||||||
mod_opt_type(_) ->
|
mod_opt_type(_) ->
|
||||||
[host, name, access, max_size, secret_length, jid_in_url, docroot,
|
[host, name, access, max_size, secret_length, jid_in_url, file_mode,
|
||||||
put_url, get_url, service_url, rm_on_unregister].
|
dir_mode, docroot, put_url, get_url, service_url, rm_on_unregister].
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% gen_server callbacks.
|
%% gen_server callbacks.
|
||||||
|
@ -215,6 +222,10 @@ init({ServerHost, Opts}) ->
|
||||||
DocRoot = gen_mod:get_opt(docroot, Opts,
|
DocRoot = gen_mod:get_opt(docroot, Opts,
|
||||||
fun iolist_to_binary/1,
|
fun iolist_to_binary/1,
|
||||||
<<"@HOME@/upload">>),
|
<<"@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,
|
PutURL = gen_mod:get_opt(put_url, Opts,
|
||||||
fun(<<"http://", _/binary>> = URL) -> URL;
|
fun(<<"http://", _/binary>> = URL) -> URL;
|
||||||
(<<"https://", _/binary>> = URL) -> URL
|
(<<"https://", _/binary>> = URL) -> URL
|
||||||
|
@ -240,10 +251,17 @@ init({ServerHost, Opts}) ->
|
||||||
application:start(public_key),
|
application:start(public_key),
|
||||||
application:start(ssl)
|
application:start(ssl)
|
||||||
end,
|
end,
|
||||||
|
case DirMode of
|
||||||
|
undefined ->
|
||||||
|
ok;
|
||||||
|
Mode ->
|
||||||
|
file:change_mode(DocRoot, Mode)
|
||||||
|
end,
|
||||||
ejabberd_router:register_route(Host),
|
ejabberd_router:register_route(Host),
|
||||||
{ok, #state{server_host = ServerHost, host = Host, name = Name,
|
{ok, #state{server_host = ServerHost, host = Host, name = Name,
|
||||||
access = Access, max_size = MaxSize,
|
access = Access, max_size = MaxSize,
|
||||||
secret_length = SecretLength, jid_in_url = JIDinURL,
|
secret_length = SecretLength, jid_in_url = JIDinURL,
|
||||||
|
file_mode = FileMode, dir_mode = DirMode,
|
||||||
docroot = expand_home(str:strip(DocRoot, right, $/)),
|
docroot = expand_home(str:strip(DocRoot, right, $/)),
|
||||||
put_url = expand_host(str:strip(PutURL, right, $/), ServerHost),
|
put_url = expand_host(str:strip(PutURL, right, $/), ServerHost),
|
||||||
get_url = expand_host(str:strip(GetURL, 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()}.
|
-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
|
case get_slot(Slot, State) of
|
||||||
{ok, {Size, Timer}} ->
|
{ok, {Size, Timer}} ->
|
||||||
timer:cancel(Timer),
|
timer:cancel(Timer),
|
||||||
NewState = del_slot(Slot, State),
|
NewState = del_slot(Slot, State),
|
||||||
Path = str:join([DocRoot | Slot], <<$/>>),
|
Path = str:join([DocRoot | Slot], <<$/>>),
|
||||||
{reply, {ok, Size, Path}, NewState};
|
{reply, {ok, Size, Path, FileMode, DirMode}, NewState};
|
||||||
error ->
|
error ->
|
||||||
{reply, {error, <<"Invalid slot">>}, State}
|
{reply, {error, <<"Invalid slot">>}, State}
|
||||||
end;
|
end;
|
||||||
|
@ -322,10 +342,10 @@ process(LocalPath, #request{method = 'PUT', host = Host, ip = IP,
|
||||||
data = Data}) ->
|
data = Data}) ->
|
||||||
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
|
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
|
||||||
case catch gen_server:call(Proc, {use_slot, LocalPath}) of
|
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",
|
?DEBUG("Storing file from ~s for ~s: ~s",
|
||||||
[?ADDR_TO_STR(IP), Host, Path]),
|
[?ADDR_TO_STR(IP), Host, Path]),
|
||||||
case store_file(Path, Data) of
|
case store_file(Path, Data, FileMode, DirMode) of
|
||||||
ok ->
|
ok ->
|
||||||
http_response(201);
|
http_response(201);
|
||||||
{error, Error} ->
|
{error, Error} ->
|
||||||
|
@ -635,15 +655,26 @@ iq_disco_info(Lang, Name) ->
|
||||||
|
|
||||||
%% HTTP request handling.
|
%% 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
|
try
|
||||||
ok = filelib:ensure_dir(Path),
|
ok = filelib:ensure_dir(Path),
|
||||||
{ok, Io} = file:open(Path, [write, exclusive, raw]),
|
{ok, Io} = file:open(Path, [write, exclusive, raw]),
|
||||||
Ok = file:write(Io, Data),
|
Ok = file:write(Io, Data),
|
||||||
ok = file:close(Io), % Close file even if file:write/2 failed.
|
ok = file:close(Io),
|
||||||
ok = Ok % But raise an exception in that case.
|
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
|
catch
|
||||||
_:{badmatch, {error, Error}} ->
|
_:{badmatch, {error, Error}} ->
|
||||||
{error, Error};
|
{error, Error};
|
||||||
|
|
Loading…
Reference in New Issue