mod_http_upload: Add slot request hook

Let mod_http_upload run an 'http_upload_slot_request' hook.  If 'deny'
or an error element is returned, the request is rejected; if 'allow' is
returned, it is accepted.
This commit is contained in:
Holger Weiss 2015-10-22 18:29:19 +02:00
parent 14c3b13a11
commit 8a849069ec
1 changed files with 51 additions and 37 deletions

View File

@ -439,17 +439,16 @@ process_iq(_From,
sub_el = [#xmlel{name = <<"query">>, sub_el = [#xmlel{name = <<"query">>,
attrs = [{<<"xmlns">>, ?NS_DISCO_INFO}], attrs = [{<<"xmlns">>, ?NS_DISCO_INFO}],
children = iq_disco_info(Lang, Name) ++ AddInfo}]}; children = iq_disco_info(Lang, Name) ++ AddInfo}]};
process_iq(#jid{luser = LUser, lserver = LServer} = From, process_iq(From,
#iq{type = get, xmlns = XMLNS, lang = Lang, sub_el = SubEl} = IQ, #iq{type = get, xmlns = XMLNS, lang = Lang, sub_el = SubEl} = IQ,
#state{server_host = ServerHost, access = Access} = State) #state{server_host = ServerHost, access = Access} = State)
when XMLNS == ?NS_HTTP_UPLOAD; when XMLNS == ?NS_HTTP_UPLOAD;
XMLNS == ?NS_HTTP_UPLOAD_OLD -> XMLNS == ?NS_HTTP_UPLOAD_OLD ->
User = <<LUser/binary, $@, LServer/binary>>,
case acl:match_rule(ServerHost, Access, From) of case acl:match_rule(ServerHost, Access, From) of
allow -> allow ->
case parse_request(SubEl, Lang) of case parse_request(SubEl, Lang) of
{ok, File, Size, ContentType} -> {ok, File, Size, ContentType} ->
case create_slot(State, User, File, Size, ContentType, Lang) of case create_slot(State, From, File, Size, ContentType, Lang) of
{ok, Slot} -> {ok, Slot} ->
{ok, Timer} = timer:send_after(?SLOT_TIMEOUT, {ok, Timer} = timer:send_after(?SLOT_TIMEOUT,
{slot_timed_out, Slot}), {slot_timed_out, Slot}),
@ -463,11 +462,13 @@ process_iq(#jid{luser = LUser, lserver = LServer} = From,
IQ#iq{type = error, sub_el = [SubEl, Error]} IQ#iq{type = error, sub_el = [SubEl, Error]}
end; end;
{error, Error} -> {error, Error} ->
?DEBUG("Cannot parse request from ~s", [User]), ?DEBUG("Cannot parse request from ~s",
[jlib:jid_to_string(From)]),
IQ#iq{type = error, sub_el = [SubEl, Error]} IQ#iq{type = error, sub_el = [SubEl, Error]}
end; end;
deny -> deny ->
?DEBUG("Denying HTTP upload slot request from ~s", [User]), ?DEBUG("Denying HTTP upload slot request from ~s",
[jlib:jid_to_string(From)]),
IQ#iq{type = error, sub_el = [SubEl, ?ERR_FORBIDDEN]} IQ#iq{type = error, sub_el = [SubEl, ?ERR_FORBIDDEN]}
end; end;
process_iq(_From, #iq{sub_el = SubEl} = IQ, _State) -> process_iq(_From, #iq{sub_el = SubEl} = IQ, _State) ->
@ -504,34 +505,46 @@ parse_request(#xmlel{name = <<"request">>, attrs = Attrs} = Request, Lang) ->
end; end;
parse_request(_El, _Lang) -> {error, ?ERR_BAD_REQUEST}. parse_request(_El, _Lang) -> {error, ?ERR_BAD_REQUEST}.
-spec create_slot(state(), binary(), binary(), pos_integer(), binary(), -spec create_slot(state(), jid(), binary(), pos_integer(), binary(), binary())
binary())
-> {ok, slot()} | {ok, binary(), binary()} | {error, xmlel()}. -> {ok, slot()} | {ok, binary(), binary()} | {error, xmlel()}.
create_slot(#state{service_url = undefined, max_size = MaxSize}, create_slot(#state{service_url = undefined, max_size = MaxSize},
User, File, Size, _ContentType, Lang) when MaxSize /= infinity, JID, File, Size, _ContentType, Lang) when MaxSize /= infinity,
Size > MaxSize -> Size > MaxSize ->
Text = <<"File larger than ", (jlib:integer_to_binary(MaxSize))/binary, Text = <<"File larger than ", (jlib:integer_to_binary(MaxSize))/binary,
" Bytes.">>, " Bytes.">>,
?INFO_MSG("Rejecting file ~s from ~s (too large: ~B bytes)", ?INFO_MSG("Rejecting file ~s from ~s (too large: ~B bytes)",
[File, User, Size]), [File, jlib:jid_to_string(JID), Size]),
{error, ?ERRT_NOT_ACCEPTABLE(Lang, Text)}; {error, ?ERRT_NOT_ACCEPTABLE(Lang, Text)};
create_slot(#state{service_url = undefined, create_slot(#state{service_url = undefined,
jid_in_url = JIDinURL, jid_in_url = JIDinURL,
secret_length = SecretLength}, secret_length = SecretLength,
User, File, _Size, _ContentType, _Lang) -> server_host = ServerHost,
UserStr = make_user_string(User, JIDinURL), docroot = DocRoot},
RandStr = make_rand_string(SecretLength), JID, File, Size, _ContentType, Lang) ->
FileStr = make_file_string(File), UserStr = make_user_string(JID, JIDinURL),
?INFO_MSG("Got HTTP upload slot for ~s (file: ~s)", [User, File]), UserDir = <<DocRoot/binary, $/, UserStr/binary>>,
{ok, [UserStr, RandStr, FileStr]}; case ejabberd_hooks:run_fold(http_upload_slot_request, ServerHost, allow,
create_slot(#state{service_url = ServiceURL}, User, File, Size, ContentType, [JID, UserDir, Size, Lang]) of
allow ->
RandStr = make_rand_string(SecretLength),
FileStr = make_file_string(File),
?INFO_MSG("Got HTTP upload slot for ~s (file: ~s)",
[jlib:jid_to_string(JID), File]),
{ok, [UserStr, RandStr, FileStr]};
deny ->
{error, ?ERR_SERVICE_UNAVAILABLE};
#xmlel{} = Error ->
{error, Error}
end;
create_slot(#state{service_url = ServiceURL},
#jid{luser = U, lserver = S} = JID, File, Size, ContentType,
_Lang) -> _Lang) ->
Options = [{body_format, binary}, {full_result, false}], Options = [{body_format, binary}, {full_result, false}],
HttpOptions = [{timeout, ?SERVICE_REQUEST_TIMEOUT}], HttpOptions = [{timeout, ?SERVICE_REQUEST_TIMEOUT}],
SizeStr = jlib:integer_to_binary(Size), SizeStr = jlib:integer_to_binary(Size),
GetRequest = binary_to_list(ServiceURL) ++ GetRequest = binary_to_list(ServiceURL) ++
"?jid=" ++ ?URL_ENC(User) ++ "?jid=" ++ ?URL_ENC(jlib:jid_to_string({U, S, <<"">>})) ++
"&name=" ++ ?URL_ENC(File) ++ "&name=" ++ ?URL_ENC(File) ++
"&size=" ++ ?URL_ENC(SizeStr) ++ "&size=" ++ ?URL_ENC(SizeStr) ++
"&content_type=" ++ ?URL_ENC(ContentType), "&content_type=" ++ ?URL_ENC(ContentType),
@ -540,29 +553,32 @@ create_slot(#state{service_url = ServiceURL}, User, File, Size, ContentType,
case binary:split(Body, <<$\n>>, [global, trim]) of case binary:split(Body, <<$\n>>, [global, trim]) of
[<<"http", _/binary>> = PutURL, <<"http", _/binary>> = GetURL] -> [<<"http", _/binary>> = PutURL, <<"http", _/binary>> = GetURL] ->
?INFO_MSG("Got HTTP upload slot for ~s (file: ~s)", ?INFO_MSG("Got HTTP upload slot for ~s (file: ~s)",
[User, File]), [jlib:jid_to_string(JID), File]),
{ok, PutURL, GetURL}; {ok, PutURL, GetURL};
Lines -> Lines ->
?ERROR_MSG("Cannot parse data received for ~s from <~s>: ~p", ?ERROR_MSG("Cannot parse data received for ~s from <~s>: ~p",
[User, ServiceURL, Lines]), [jlib:jid_to_string(JID), ServiceURL, Lines]),
{error, ?ERR_SERVICE_UNAVAILABLE} {error, ?ERR_SERVICE_UNAVAILABLE}
end; end;
{ok, {402, _Body}} -> {ok, {402, _Body}} ->
?INFO_MSG("Got status code 402 for ~s from <~s>", [User, ServiceURL]), ?INFO_MSG("Got status code 402 for ~s from <~s>",
[jlib:jid_to_string(JID), ServiceURL]),
{error, ?ERR_RESOURCE_CONSTRAINT}; {error, ?ERR_RESOURCE_CONSTRAINT};
{ok, {403, _Body}} -> {ok, {403, _Body}} ->
?INFO_MSG("Got status code 403 for ~s from <~s>", [User, ServiceURL]), ?INFO_MSG("Got status code 403 for ~s from <~s>",
[jlib:jid_to_string(JID), ServiceURL]),
{error, ?ERR_NOT_ALLOWED}; {error, ?ERR_NOT_ALLOWED};
{ok, {413, _Body}} -> {ok, {413, _Body}} ->
?INFO_MSG("Got status code 413 for ~s from <~s>", [User, ServiceURL]), ?INFO_MSG("Got status code 413 for ~s from <~s>",
[jlib:jid_to_string(JID), ServiceURL]),
{error, ?ERR_NOT_ACCEPTABLE}; {error, ?ERR_NOT_ACCEPTABLE};
{ok, {Code, _Body}} -> {ok, {Code, _Body}} ->
?ERROR_MSG("Got unexpected status code for ~s from <~s>: ~B", ?ERROR_MSG("Got unexpected status code for ~s from <~s>: ~B",
[User, ServiceURL, Code]), [jlib:jid_to_string(JID), ServiceURL, Code]),
{error, ?ERR_SERVICE_UNAVAILABLE}; {error, ?ERR_SERVICE_UNAVAILABLE};
{error, Reason} -> {error, Reason} ->
?ERROR_MSG("Error requesting upload slot for ~s from <~s>: ~p", ?ERROR_MSG("Error requesting upload slot for ~s from <~s>: ~p",
[User, ServiceURL, Reason]), [jlib:jid_to_string(JID), ServiceURL, Reason]),
{error, ?ERR_SERVICE_UNAVAILABLE} {error, ?ERR_SERVICE_UNAVAILABLE}
end. end.
@ -597,13 +613,12 @@ slot_el(PutURL, GetURL, XMLNS) ->
#xmlel{name = <<"get">>, #xmlel{name = <<"get">>,
children = [{xmlcdata, GetURL}]}]}. children = [{xmlcdata, GetURL}]}]}.
-spec make_user_string(binary(), sha1 | node) -> binary(). -spec make_user_string(jid(), sha1 | node) -> binary().
make_user_string(User, sha1) -> make_user_string(#jid{luser = U, lserver = S}, sha1) ->
p1_sha:sha(User); p1_sha:sha(<<U/binary, $@, S/binary>>);
make_user_string(User, node) -> make_user_string(#jid{luser = U}, node) ->
[Node, _Domain] = binary:split(User, <<$@>>), re:replace(U, <<"[^a-zA-Z0-9_.-]">>, <<$_>>, [global, {return, binary}]).
re:replace(Node, <<"[^a-zA-Z0-9_.-]">>, <<$_>>, [global, {return, binary}]).
-spec make_file_string(binary()) -> binary(). -spec make_file_string(binary()) -> binary().
@ -769,17 +784,16 @@ get_proc_name(ServerHost) ->
-spec remove_user(binary(), binary()) -> ok. -spec remove_user(binary(), binary()) -> ok.
remove_user(User, Server) -> remove_user(User, Server) ->
LUser = jlib:nodeprep(User), ServerHost = jlib:nameprep(Server),
LServer = jlib:nameprep(Server), DocRoot = gen_mod:get_module_opt(ServerHost, ?MODULE, docroot,
DocRoot = gen_mod:get_module_opt(LServer, ?MODULE, docroot,
fun iolist_to_binary/1, fun iolist_to_binary/1,
<<"@HOME@/upload">>), <<"@HOME@/upload">>),
JIDinURL = gen_mod:get_module_opt(LServer, ?MODULE, jid_in_url, JIDinURL = gen_mod:get_module_opt(ServerHost, ?MODULE, jid_in_url,
fun(sha1) -> sha1; fun(sha1) -> sha1;
(node) -> node (node) -> node
end, end,
sha1), sha1),
UserStr = make_user_string(<<LUser/binary, $@, LServer/binary>>, JIDinURL), UserStr = make_user_string(jlib:make_jid(User, Server, <<"">>), JIDinURL),
UserDir = str:join([expand_home(DocRoot), UserStr], <<$/>>), UserDir = str:join([expand_home(DocRoot), UserStr], <<$/>>),
case del_tree(UserDir) of case del_tree(UserDir) of
ok -> ok ->