mirror of
https://github.com/processone/ejabberd.git
synced 2024-11-22 16:20:52 +01:00
Cancel revision 798
SVN Revision: 799
This commit is contained in:
parent
9cd3a6db07
commit
2e00142b38
@ -20,7 +20,6 @@ OBJS = \
|
|||||||
$(OUTDIR)/mod_proxy65_service.beam \
|
$(OUTDIR)/mod_proxy65_service.beam \
|
||||||
$(OUTDIR)/mod_proxy65_sm.beam \
|
$(OUTDIR)/mod_proxy65_sm.beam \
|
||||||
$(OUTDIR)/mod_proxy65_stream.beam \
|
$(OUTDIR)/mod_proxy65_stream.beam \
|
||||||
$(OUTDIR)/mod_proxy65_http.beam \
|
|
||||||
$(OUTDIR)/mod_proxy65_lib.beam
|
$(OUTDIR)/mod_proxy65_lib.beam
|
||||||
|
|
||||||
all: $(OBJS)
|
all: $(OBJS)
|
||||||
@ -36,3 +35,4 @@ distclean: clean
|
|||||||
|
|
||||||
TAGS:
|
TAGS:
|
||||||
etags *.erl
|
etags *.erl
|
||||||
|
|
||||||
|
@ -1,17 +0,0 @@
|
|||||||
|
|
||||||
|
|
||||||
HTTP transfer
|
|
||||||
-------------
|
|
||||||
|
|
||||||
* stream POST to GET in real time, whithout storing the file
|
|
||||||
* handle acl
|
|
||||||
* get base URL from ejabberd_http config
|
|
||||||
* add parameters:
|
|
||||||
- store directory
|
|
||||||
- URL of upload
|
|
||||||
* virtual host handling
|
|
||||||
* check security stuffs:
|
|
||||||
- limit file size at upload time
|
|
||||||
- acl
|
|
||||||
* transfer socks5 -> HTTP
|
|
||||||
* transfer HTTP -> socks5
|
|
@ -50,14 +50,8 @@ init([Host, Opts]) ->
|
|||||||
[gen_mod:get_module_proc(Host, ejabberd_mod_proxy65_sup),
|
[gen_mod:get_module_proc(Host, ejabberd_mod_proxy65_sup),
|
||||||
mod_proxy65_stream]},
|
mod_proxy65_stream]},
|
||||||
transient, infinity, supervisor, [ejabberd_tmp_sup]},
|
transient, infinity, supervisor, [ejabberd_tmp_sup]},
|
||||||
HttpStreamSupervisor =
|
|
||||||
{ejabberd_mod_proxy65_http_sup,
|
|
||||||
{ejabberd_tmp_sup, start_link,
|
|
||||||
[gen_mod:get_module_proc(Host, ejabberd_mod_proxy65_http_sup),
|
|
||||||
mod_proxy65_http]},
|
|
||||||
transient, infinity, supervisor, [ejabberd_tmp_sup]},
|
|
||||||
StreamManager =
|
StreamManager =
|
||||||
{mod_proxy65_sm, {mod_proxy65_sm, start_link, [Host, Opts]},
|
{mod_proxy65_sm, {mod_proxy65_sm, start_link, [Host, Opts]},
|
||||||
transient, 5000, worker, [mod_proxy65_sm]},
|
transient, 5000, worker, [mod_proxy65_sm]},
|
||||||
{ok, {{one_for_one, 10, 1},
|
{ok, {{one_for_one, 10, 1},
|
||||||
[StreamManager, StreamSupervisor, HttpStreamSupervisor, Service]}}.
|
[StreamManager, StreamSupervisor, Service]}}.
|
||||||
|
@ -27,37 +27,20 @@
|
|||||||
|
|
||||||
%% RFC 1928 replies
|
%% RFC 1928 replies
|
||||||
-define(SUCCESS, 0).
|
-define(SUCCESS, 0).
|
||||||
-define(SOCKS5_ERR_GENERAL_FAILURE, 1).
|
-define(ERR_GENERAL_FAILURE, 1).
|
||||||
-define(SOCKS5_ERR_NOT_ALLOWED, 2).
|
-define(ERR_NOT_ALLOWED, 2).
|
||||||
-define(SOCKS5_ERR_NETWORK_UNREACHABLE, 3).
|
-define(ERR_NETWORK_UNREACHABLE, 3).
|
||||||
-define(SOCKS5_ERR_HOST_UNREACHABLE, 4).
|
-define(ERR_HOST_UNREACHABLE, 4).
|
||||||
-define(SOCKS5_ERR_CONNECTION_REFUSED, 5).
|
-define(ERR_CONNECTION_REFUSED, 5).
|
||||||
-define(SOCKS5_ERR_TTL_EXPIRED, 6).
|
-define(ERR_TTL_EXPIRED, 6).
|
||||||
-define(SOCKS5_ERR_COMMAND_NOT_SUPPORTED, 7).
|
-define(ERR_COMMAND_NOT_SUPPORTED, 7).
|
||||||
-define(SOCKS5_ERR_ADDRESS_TYPE_NOT_SUPPORTED, 8).
|
-define(ERR_ADDRESS_TYPE_NOT_SUPPORTED, 8).
|
||||||
|
|
||||||
%% RFC 1928 defined timeout.
|
%% RFC 1928 defined timeout.
|
||||||
-define(SOCKS5_REPLY_TIMEOUT, 10000).
|
-define(SOCKS5_REPLY_TIMEOUT, 10000).
|
||||||
|
|
||||||
-record(bytestream, {
|
|
||||||
sha1, %% SHA1 key
|
|
||||||
target, %% Target Pid
|
|
||||||
initiator, %% Initiator Pid
|
|
||||||
active = false, %% Activity flag
|
|
||||||
jid_i, %% Initiator's JID
|
|
||||||
jid_t, %% Target's JID (for http file transfert)
|
|
||||||
file, %% store status of file (for http file transfert)
|
|
||||||
myhost %% proxy's jid
|
|
||||||
}).
|
|
||||||
|
|
||||||
-record(s5_request, {
|
-record(s5_request, {
|
||||||
rsv = 0,
|
rsv = 0,
|
||||||
cmd,
|
cmd,
|
||||||
sha1
|
sha1
|
||||||
}).
|
}).
|
||||||
|
|
||||||
% For http transfer
|
|
||||||
-define(NS_HTTP_BYTESTREAMS, "http://oneteam.im/bs-proxy").
|
|
||||||
-define(DEFAULT_HTTP_BASE_PATH, "/proxy").
|
|
||||||
-define(DEFAULT_HTTP_UPLOAD_PATH, "/upload").
|
|
||||||
-define(DEFAULT_STORE_PATH, "/tmp").
|
|
||||||
|
@ -1,156 +0,0 @@
|
|||||||
%%%----------------------------------------------------------------------
|
|
||||||
%%% File : mod_proxy65_http
|
|
||||||
%%% Author : Jérôme Sautret <jerome.sautret@process-one.net>
|
|
||||||
%%% Purpose : HTTP bytestreams proxy for oneteam file transfert
|
|
||||||
%%% Created : 28 may 2007
|
|
||||||
%%% Id : $Id: mod_last.erl 370 2005-06-20 03:18:13Z alexey $
|
|
||||||
%%%----------------------------------------------------------------------
|
|
||||||
|
|
||||||
-module(mod_proxy65_http).
|
|
||||||
-author('jerome.sautret@process-one.net').
|
|
||||||
|
|
||||||
-behaviour(gen_mod).
|
|
||||||
|
|
||||||
%% gen_mod callbacks.
|
|
||||||
-export([
|
|
||||||
start/2,
|
|
||||||
stop/1
|
|
||||||
]).
|
|
||||||
%% mod_proxy65 api
|
|
||||||
-export([
|
|
||||||
activate/2
|
|
||||||
]).
|
|
||||||
%% ejabberd_http callback.
|
|
||||||
-export([
|
|
||||||
process/2
|
|
||||||
]).
|
|
||||||
|
|
||||||
-include("mod_proxy65.hrl").
|
|
||||||
-include("../ejabberd.hrl").
|
|
||||||
-include("../jlib.hrl").
|
|
||||||
-include("../web/ejabberd_http.hrl").
|
|
||||||
|
|
||||||
%%%----------------------------------------------------------------------
|
|
||||||
%%% gen_mod Callbacks
|
|
||||||
%%%----------------------------------------------------------------------
|
|
||||||
|
|
||||||
start(_Host, _Opts) ->
|
|
||||||
ok.
|
|
||||||
|
|
||||||
stop(_Host) ->
|
|
||||||
ok.
|
|
||||||
|
|
||||||
activate({_IPid, _IJid}, {_TPid, _TJid}) ->
|
|
||||||
ok.
|
|
||||||
|
|
||||||
%%%----------------------------------------------------------------------
|
|
||||||
%%% ejabberd_http Callbacks
|
|
||||||
%%%----------------------------------------------------------------------
|
|
||||||
|
|
||||||
% Receive File
|
|
||||||
% XXX TODO: limit file size
|
|
||||||
process(["upload"], #request{method='POST',
|
|
||||||
content_type = "multipart/form-data;"++_Boundary = ContentType,
|
|
||||||
data=Data} = _Request) ->
|
|
||||||
io:format("POST~n", []),
|
|
||||||
DataParts = ejabberd_http:parse_data(Data, ContentType),
|
|
||||||
{SHA1, {Filename, FileContentType, FileContent}} = parse_upload_data(DataParts),
|
|
||||||
case mnesia:dirty_read(bytestream, SHA1) of
|
|
||||||
[#bytestream{jid_t = TargetJID, file = BaseURL, myhost = MyHost}] when is_list(BaseURL) ->
|
|
||||||
Path = store_file(Filename, FileContent),
|
|
||||||
F = fun() ->
|
|
||||||
mnesia:write(#bytestream{sha1 = SHA1,
|
|
||||||
file = {path, Path},
|
|
||||||
target = FileContentType})
|
|
||||||
end,
|
|
||||||
mnesia:transaction(F),
|
|
||||||
URL = BaseURL ++ "/" ++ filename:join(Path),
|
|
||||||
send_activated(TargetJID, MyHost, SHA1, URL),
|
|
||||||
Result = "ok",
|
|
||||||
ejabberd_web:make_xhtml([{xmlcdata, Result}]);
|
|
||||||
_Other ->
|
|
||||||
?ERROR_MSG("Upload ~p not activated~n", [SHA1]),
|
|
||||||
ejabberd_web:error(bad_request)
|
|
||||||
end;
|
|
||||||
|
|
||||||
process([_UID, _Filename] = Path, #request{method='GET'} = _Request) ->
|
|
||||||
io:format("GET~n~p~n", [Path]),
|
|
||||||
case mnesia:dirty_index_read(bytestream, {path, Path}, #bytestream.file) of
|
|
||||||
[#bytestream{sha1=SHA1, target=ContentType}|_Tail] ->
|
|
||||||
mnesia:dirty_delete({bytestream, SHA1}),
|
|
||||||
serve_file(Path, ContentType);
|
|
||||||
_ ->
|
|
||||||
?ERROR_MSG("Bad request, GET ~p~n", [Path]),
|
|
||||||
ejabberd_web:error(bad_request)
|
|
||||||
end.
|
|
||||||
|
|
||||||
|
|
||||||
% store the data transfered in a file and return the URL of the file
|
|
||||||
store_file(Filename, FileContent) ->
|
|
||||||
HASH = sha:sha(FileContent),
|
|
||||||
% TODO store dir from a parameter
|
|
||||||
Path = ?DEFAULT_STORE_PATH ++ "/" ++ HASH ++ "/",
|
|
||||||
io:format("Path ~p~n", [Path]),
|
|
||||||
ok = filelib:ensure_dir(Path),
|
|
||||||
FilePath = Path ++ "/" ++ Filename,
|
|
||||||
%io:format("FilePath ~p~n~p", [FilePath, FileContent]),
|
|
||||||
ok = file:write_file(FilePath, list_to_binary(FileContent)),
|
|
||||||
[HASH, Filename].
|
|
||||||
|
|
||||||
% send the <activated> connexion to the target.
|
|
||||||
send_activated(TargetJID, MyJID, SHA1, URL) ->
|
|
||||||
IQ = #iq{type=set,
|
|
||||||
sub_el=[{xmlelement, "activated",
|
|
||||||
[{"xmlns", ?NS_HTTP_BYTESTREAMS}, {"sidhash", SHA1}, {"url", URL}], []}]},
|
|
||||||
ejabberd_router:route(jlib:string_to_jid(MyJID), TargetJID, jlib:iq_to_xml(IQ)).
|
|
||||||
|
|
||||||
|
|
||||||
parse_upload_data(Data) ->
|
|
||||||
parse_upload_data(Data, {undefined, undefined, undefined}).
|
|
||||||
parse_upload_data([], Result) ->
|
|
||||||
Result;
|
|
||||||
parse_upload_data([#http_data{content_disposition="form-data",
|
|
||||||
content_type=ContentType, args=Args, data=Data} | Tail],
|
|
||||||
Result) ->
|
|
||||||
Result2 = case lists:keysearch("name", 1, Args) of
|
|
||||||
{value, {"name", "SIDHASH"}} ->
|
|
||||||
{remove_cr(Data), element(2, Result)};
|
|
||||||
{value, {"name", "FILE"}} ->
|
|
||||||
case lists:keysearch("filename", 1, Args) of
|
|
||||||
{value, {"filename", Filename}} ->
|
|
||||||
{element(1, Result), {Filename, ContentType, remove_cr(Data)}}
|
|
||||||
end;
|
|
||||||
_ ->
|
|
||||||
Result
|
|
||||||
end,
|
|
||||||
parse_upload_data(Tail, Result2).
|
|
||||||
|
|
||||||
|
|
||||||
% remove last trailling carriage return
|
|
||||||
remove_cr(String) ->
|
|
||||||
lists:reverse(remove_leading_cr(lists:reverse(String))).
|
|
||||||
% remove fisrt leading inversed carriage return
|
|
||||||
remove_leading_cr([$\n, $\r|String]) ->
|
|
||||||
String;
|
|
||||||
remove_leading_cr(String) ->
|
|
||||||
String.
|
|
||||||
|
|
||||||
|
|
||||||
serve_file(Path, ContentType) ->
|
|
||||||
FileName = filename:join([?DEFAULT_STORE_PATH | Path]),
|
|
||||||
case file:read_file(FileName) of
|
|
||||||
{ok, FileContents} ->
|
|
||||||
?DEBUG("Delivering content.", []),
|
|
||||||
{200,
|
|
||||||
[{"Server", "ejabberd"},
|
|
||||||
{"Content-type", ContentType},
|
|
||||||
{"Content-disposition", "attachment; filename="++filename:basename(FileName)}],
|
|
||||||
FileContents};
|
|
||||||
{error, Error} ->
|
|
||||||
?DEBUG("Delivering error: ~p", [Error]),
|
|
||||||
case Error of
|
|
||||||
eacces -> {403, [], "Forbidden"};
|
|
||||||
enoent -> {404, [], "Not found"};
|
|
||||||
_Else -> {500, [], atom_to_list(Error)}
|
|
||||||
end
|
|
||||||
end.
|
|
@ -53,7 +53,7 @@ make_init_reply(Method) ->
|
|||||||
[?VERSION_5, Method].
|
[?VERSION_5, Method].
|
||||||
|
|
||||||
make_auth_reply(true) -> [1, ?SUCCESS];
|
make_auth_reply(true) -> [1, ?SUCCESS];
|
||||||
make_auth_reply(false) -> [1, ?SOCKS5_ERR_NOT_ALLOWED].
|
make_auth_reply(false) -> [1, ?ERR_NOT_ALLOWED].
|
||||||
|
|
||||||
%% WARNING: According to SOCKS5 RFC, this reply is _incorrect_, but
|
%% WARNING: According to SOCKS5 RFC, this reply is _incorrect_, but
|
||||||
%% Psi writes junk to the beginning of the file on correct reply.
|
%% Psi writes junk to the beginning of the file on correct reply.
|
||||||
@ -63,7 +63,7 @@ make_reply() ->
|
|||||||
[?VERSION_5, ?SUCCESS, 0, 0, 0, 0].
|
[?VERSION_5, ?SUCCESS, 0, 0, 0, 0].
|
||||||
|
|
||||||
make_error_reply(Request) ->
|
make_error_reply(Request) ->
|
||||||
make_error_reply(Request, ?SOCKS5_ERR_NOT_ALLOWED).
|
make_error_reply(Request, ?ERR_NOT_ALLOWED).
|
||||||
|
|
||||||
make_error_reply(#s5_request{rsv = RSV, sha1 = SHA1}, Reason) ->
|
make_error_reply(#s5_request{rsv = RSV, sha1 = SHA1}, Reason) ->
|
||||||
[?VERSION_5, Reason, RSV, ?ATYP_DOMAINNAME, length(SHA1), SHA1, 0,0].
|
[?VERSION_5, Reason, RSV, ?ATYP_DOMAINNAME, length(SHA1), SHA1, 0,0].
|
||||||
|
@ -23,7 +23,6 @@
|
|||||||
%% API.
|
%% API.
|
||||||
-export([start_link/2]).
|
-export([start_link/2]).
|
||||||
|
|
||||||
-include("mod_proxy65.hrl").
|
|
||||||
-include("../ejabberd.hrl").
|
-include("../ejabberd.hrl").
|
||||||
-include("../jlib.hrl").
|
-include("../jlib.hrl").
|
||||||
|
|
||||||
@ -35,8 +34,6 @@
|
|||||||
name,
|
name,
|
||||||
stream_addr,
|
stream_addr,
|
||||||
port,
|
port,
|
||||||
http_port,
|
|
||||||
http_base_path,
|
|
||||||
acl
|
acl
|
||||||
}).
|
}).
|
||||||
|
|
||||||
@ -87,14 +84,12 @@ handle_info(_Info, State) ->
|
|||||||
%%%------------------------
|
%%%------------------------
|
||||||
|
|
||||||
%% disco#info request
|
%% disco#info request
|
||||||
process_iq(_, #iq{type = get, xmlns = ?NS_DISCO_INFO} = IQ, #state{name=Name, http_port=HTTP_Port}) ->
|
process_iq(_, #iq{type = get, xmlns = ?NS_DISCO_INFO, lang = Lang} = IQ, #state{name=Name}) ->
|
||||||
io:format("~p~n", [IQ]),
|
|
||||||
IQ#iq{type = result, sub_el =
|
IQ#iq{type = result, sub_el =
|
||||||
[{xmlelement, "query", [{"xmlns", ?NS_DISCO_INFO}], iq_disco_info(Name, HTTP_Port)}]};
|
[{xmlelement, "query", [{"xmlns", ?NS_DISCO_INFO}], iq_disco_info(Lang, Name)}]};
|
||||||
|
|
||||||
%% disco#items request
|
%% disco#items request
|
||||||
process_iq(_, #iq{type = get, xmlns = ?NS_DISCO_ITEMS} = IQ, _) ->
|
process_iq(_, #iq{type = get, xmlns = ?NS_DISCO_ITEMS} = IQ, _) ->
|
||||||
io:format("~p~n", [IQ]),
|
|
||||||
IQ#iq{type = result, sub_el =
|
IQ#iq{type = result, sub_el =
|
||||||
[{xmlelement, "query", [{"xmlns", ?NS_DISCO_ITEMS}], []}]};
|
[{xmlelement, "query", [{"xmlns", ?NS_DISCO_ITEMS}], []}]};
|
||||||
|
|
||||||
@ -106,7 +101,6 @@ process_iq(_, #iq{type = get, xmlns = ?NS_VCARD, lang = Lang} = IQ, _) ->
|
|||||||
%% bytestreams info request
|
%% bytestreams info request
|
||||||
process_iq(JID, #iq{type = get, sub_el = SubEl, xmlns = ?NS_BYTESTREAMS} = IQ,
|
process_iq(JID, #iq{type = get, sub_el = SubEl, xmlns = ?NS_BYTESTREAMS} = IQ,
|
||||||
#state{acl = ACL, stream_addr = StreamAddr, serverhost = ServerHost}) ->
|
#state{acl = ACL, stream_addr = StreamAddr, serverhost = ServerHost}) ->
|
||||||
io:format("~p~n", [IQ]),
|
|
||||||
case acl:match_rule(ServerHost, ACL, JID) of
|
case acl:match_rule(ServerHost, ACL, JID) of
|
||||||
allow ->
|
allow ->
|
||||||
StreamHostEl = [{xmlelement, "streamhost", StreamAddr, []}],
|
StreamHostEl = [{xmlelement, "streamhost", StreamAddr, []}],
|
||||||
@ -116,39 +110,9 @@ process_iq(JID, #iq{type = get, sub_el = SubEl, xmlns = ?NS_BYTESTREAMS} = IQ,
|
|||||||
IQ#iq{type = error, sub_el = [SubEl, ?ERR_FORBIDDEN]}
|
IQ#iq{type = error, sub_el = [SubEl, ?ERR_FORBIDDEN]}
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
|
||||||
%% bytestream target fake connection (for later http connection)
|
|
||||||
process_iq(TargetJID, #iq{type = set, sub_el = SubEl, xmlns = ?NS_HTTP_BYTESTREAMS} = IQ,
|
|
||||||
#state{acl = _ACL, myhost = MyHost,
|
|
||||||
http_port=HTTP_Port, http_base_path=HTTP_Base_Path}) ->
|
|
||||||
% XXX TODO: acl
|
|
||||||
SID = xml:get_tag_attr_s("sid", SubEl),
|
|
||||||
case catch jlib:string_to_jid(xml:get_tag_attr_s("jid", SubEl)) of
|
|
||||||
InitiatorJID when is_record(InitiatorJID, jid), SID /= "",
|
|
||||||
length(SID) =< 128, TargetJID /= InitiatorJID ->
|
|
||||||
Target = jlib:jid_to_string(jlib:jid_tolower(TargetJID)),
|
|
||||||
Initiator = jlib:jid_to_string(jlib:jid_tolower(InitiatorJID)),
|
|
||||||
SHA1 = sha:sha(SID ++ Initiator ++ Target),
|
|
||||||
URL = "http://" ++ MyHost ++ ":"++HTTP_Port++ HTTP_Base_Path,
|
|
||||||
case catch mod_proxy65_sm:register_stream(SHA1, TargetJID, URL, MyHost, self()) of
|
|
||||||
{atomic, ok} ->
|
|
||||||
IQ#iq{type = result, sub_el =
|
|
||||||
[{xmlelement, "connected",
|
|
||||||
[{"xmlns", ?NS_HTTP_BYTESTREAMS}, {"jid", MyHost}], []}]};
|
|
||||||
_Reason ->
|
|
||||||
?ERROR_MSG("process IQ set ~p:~n~p~n", [?NS_HTTP_BYTESTREAMS, _Reason]),
|
|
||||||
IQ#iq{type = error, sub_el = [SubEl, ?ERR_INTERNAL_SERVER_ERROR]}
|
|
||||||
end;
|
|
||||||
_Reason ->
|
|
||||||
?ERROR_MSG("process IQ set ~p:~n~p~n", [?NS_HTTP_BYTESTREAMS, _Reason]),
|
|
||||||
IQ#iq{type = error, sub_el = [SubEl, ?ERR_BAD_REQUEST]}
|
|
||||||
end;
|
|
||||||
|
|
||||||
|
|
||||||
%% bytestream activation request
|
%% bytestream activation request
|
||||||
process_iq(InitiatorJID, #iq{type = set, sub_el = SubEl, xmlns = ?NS_BYTESTREAMS} = IQ,
|
process_iq(InitiatorJID, #iq{type = set, sub_el = SubEl, xmlns = ?NS_BYTESTREAMS} = IQ,
|
||||||
#state{acl = ACL, serverhost = ServerHost, myhost = MyHost,
|
#state{acl = ACL, serverhost = ServerHost}) ->
|
||||||
http_port=HTTP_Port, http_base_path=HTTP_Base_Path}) ->
|
|
||||||
case acl:match_rule(ServerHost, ACL, InitiatorJID) of
|
case acl:match_rule(ServerHost, ACL, InitiatorJID) of
|
||||||
allow ->
|
allow ->
|
||||||
ActivateEl = xml:get_path_s(SubEl, [{elem, "activate"}]),
|
ActivateEl = xml:get_path_s(SubEl, [{elem, "activate"}]),
|
||||||
@ -159,21 +123,9 @@ process_iq(InitiatorJID, #iq{type = set, sub_el = SubEl, xmlns = ?NS_BYTESTREAMS
|
|||||||
Target = jlib:jid_to_string(jlib:jid_tolower(TargetJID)),
|
Target = jlib:jid_to_string(jlib:jid_tolower(TargetJID)),
|
||||||
Initiator = jlib:jid_to_string(jlib:jid_tolower(InitiatorJID)),
|
Initiator = jlib:jid_to_string(jlib:jid_tolower(InitiatorJID)),
|
||||||
SHA1 = sha:sha(SID ++ Initiator ++ Target),
|
SHA1 = sha:sha(SID ++ Initiator ++ Target),
|
||||||
{Module, Activated} =
|
case mod_proxy65_sm:activate_stream(SHA1, InitiatorJID, TargetJID, ServerHost) of
|
||||||
case xml:get_path_s(SubEl, [{elem, "x"}]) of
|
|
||||||
{xmlelement, "x", [{"xmlns", ?NS_HTTP_BYTESTREAMS}], _} ->
|
|
||||||
{mod_proxy65_http,
|
|
||||||
[{xmlelement, "activated",
|
|
||||||
[{"xmlns", ?NS_HTTP_BYTESTREAMS},
|
|
||||||
{"url", "http://" ++ MyHost ++ ":"++HTTP_Port++
|
|
||||||
HTTP_Base_Path ++ ?DEFAULT_HTTP_UPLOAD_PATH}], []}]};
|
|
||||||
_ ->
|
|
||||||
{mod_proxy65_sm, []}
|
|
||||||
end,
|
|
||||||
case mod_proxy65_sm:activate_stream(SHA1, InitiatorJID, TargetJID,
|
|
||||||
ServerHost, Module) of
|
|
||||||
ok ->
|
ok ->
|
||||||
IQ#iq{type = result, sub_el = Activated};
|
IQ#iq{type = result, sub_el = []};
|
||||||
false ->
|
false ->
|
||||||
IQ#iq{type = error, sub_el = [SubEl, ?ERR_ITEM_NOT_FOUND]};
|
IQ#iq{type = error, sub_el = [SubEl, ?ERR_ITEM_NOT_FOUND]};
|
||||||
limit ->
|
limit ->
|
||||||
@ -183,8 +135,7 @@ process_iq(InitiatorJID, #iq{type = set, sub_el = SubEl, xmlns = ?NS_BYTESTREAMS
|
|||||||
_ ->
|
_ ->
|
||||||
IQ#iq{type = error, sub_el = [SubEl, ?ERR_INTERNAL_SERVER_ERROR]}
|
IQ#iq{type = error, sub_el = [SubEl, ?ERR_INTERNAL_SERVER_ERROR]}
|
||||||
end;
|
end;
|
||||||
_Reason ->
|
_ ->
|
||||||
?ERROR_MSG("process IQ set ~p:~n~p~n", [?NS_BYTESTREAMS, _Reason]),
|
|
||||||
IQ#iq{type = error, sub_el = [SubEl, ?ERR_BAD_REQUEST]}
|
IQ#iq{type = error, sub_el = [SubEl, ?ERR_BAD_REQUEST]}
|
||||||
end;
|
end;
|
||||||
deny ->
|
deny ->
|
||||||
@ -204,21 +155,15 @@ process_iq(_, _, _) ->
|
|||||||
%%%-------------------------
|
%%%-------------------------
|
||||||
-define(FEATURE(Feat), {xmlelement,"feature",[{"var", Feat}],[]}).
|
-define(FEATURE(Feat), {xmlelement,"feature",[{"var", Feat}],[]}).
|
||||||
|
|
||||||
iq_disco_info(Name, HTTP_Port) ->
|
iq_disco_info(Lang, Name) ->
|
||||||
HTTP_Bytestreams = case HTTP_Port of
|
|
||||||
"0" ->
|
|
||||||
[];
|
|
||||||
_ ->
|
|
||||||
[?FEATURE(?NS_HTTP_BYTESTREAMS)]
|
|
||||||
end,
|
|
||||||
[{xmlelement, "identity",
|
[{xmlelement, "identity",
|
||||||
[{"category", "proxy"},
|
[{"category", "proxy"},
|
||||||
{"type", "bytestreams"},
|
{"type", "bytestreams"},
|
||||||
{"name", Name}], []},
|
{"name", translate:translate(Lang, Name)}], []},
|
||||||
?FEATURE(?NS_DISCO_INFO),
|
?FEATURE(?NS_DISCO_INFO),
|
||||||
?FEATURE(?NS_DISCO_ITEMS),
|
?FEATURE(?NS_DISCO_ITEMS),
|
||||||
?FEATURE(?NS_VCARD),
|
?FEATURE(?NS_VCARD),
|
||||||
?FEATURE(?NS_BYTESTREAMS)] ++ HTTP_Bytestreams.
|
?FEATURE(?NS_BYTESTREAMS)].
|
||||||
|
|
||||||
iq_vcard(Lang) ->
|
iq_vcard(Lang) ->
|
||||||
[{xmlelement, "FN", [],
|
[{xmlelement, "FN", [],
|
||||||
@ -232,8 +177,6 @@ iq_vcard(Lang) ->
|
|||||||
parse_options(ServerHost, Opts) ->
|
parse_options(ServerHost, Opts) ->
|
||||||
MyHost = gen_mod:get_opt(host, Opts, "proxy." ++ ServerHost),
|
MyHost = gen_mod:get_opt(host, Opts, "proxy." ++ ServerHost),
|
||||||
Port = gen_mod:get_opt(port, Opts, 7777),
|
Port = gen_mod:get_opt(port, Opts, 7777),
|
||||||
HTTP_Port = integer_to_list(gen_mod:get_opt(http_port, Opts, 0)),
|
|
||||||
HTTP_Base_Path = gen_mod:get_opt(http_base_path, Opts, ?DEFAULT_HTTP_BASE_PATH),
|
|
||||||
ACL = gen_mod:get_opt(access, Opts, all),
|
ACL = gen_mod:get_opt(access, Opts, all),
|
||||||
Name = gen_mod:get_opt(name, Opts, "SOCKS5 Bytestreams"),
|
Name = gen_mod:get_opt(name, Opts, "SOCKS5 Bytestreams"),
|
||||||
IP = case gen_mod:get_opt(ip, Opts, none) of
|
IP = case gen_mod:get_opt(ip, Opts, none) of
|
||||||
@ -246,8 +189,6 @@ parse_options(ServerHost, Opts) ->
|
|||||||
serverhost = ServerHost,
|
serverhost = ServerHost,
|
||||||
name = Name,
|
name = Name,
|
||||||
port = Port,
|
port = Port,
|
||||||
http_port = HTTP_Port,
|
|
||||||
http_base_path = HTTP_Base_Path,
|
|
||||||
stream_addr = StreamAddr,
|
stream_addr = StreamAddr,
|
||||||
acl = ACL}}.
|
acl = ACL}}.
|
||||||
|
|
||||||
|
@ -24,14 +24,18 @@
|
|||||||
-export([
|
-export([
|
||||||
start_link/2,
|
start_link/2,
|
||||||
register_stream/1,
|
register_stream/1,
|
||||||
register_stream/5,
|
|
||||||
unregister_stream/1,
|
unregister_stream/1,
|
||||||
activate_stream/5
|
activate_stream/4
|
||||||
]).
|
]).
|
||||||
|
|
||||||
-include("mod_proxy65.hrl").
|
|
||||||
|
|
||||||
-record(state, {max_connections}).
|
-record(state, {max_connections}).
|
||||||
|
-record(bytestream, {
|
||||||
|
sha1, %% SHA1 key
|
||||||
|
target, %% Target Pid
|
||||||
|
initiator, %% Initiator Pid
|
||||||
|
active = false, %% Activity flag
|
||||||
|
jid_i %% Initiator's JID
|
||||||
|
}).
|
||||||
|
|
||||||
-define(PROCNAME, ejabberd_mod_proxy65_sm).
|
-define(PROCNAME, ejabberd_mod_proxy65_sm).
|
||||||
|
|
||||||
@ -51,7 +55,6 @@ start_link(Host, Opts) ->
|
|||||||
init([Opts]) ->
|
init([Opts]) ->
|
||||||
mnesia:create_table(bytestream, [{ram_copies, [node()]},
|
mnesia:create_table(bytestream, [{ram_copies, [node()]},
|
||||||
{attributes, record_info(fields, bytestream)}]),
|
{attributes, record_info(fields, bytestream)}]),
|
||||||
mnesia:add_table_index(bytestream, file),
|
|
||||||
mnesia:add_table_copy(bytestream, node(), ram_copies),
|
mnesia:add_table_copy(bytestream, node(), ram_copies),
|
||||||
MaxConnections = gen_mod:get_opt(max_connections, Opts, infinity),
|
MaxConnections = gen_mod:get_opt(max_connections, Opts, infinity),
|
||||||
{ok, #state{max_connections=MaxConnections}}.
|
{ok, #state{max_connections=MaxConnections}}.
|
||||||
@ -64,7 +67,7 @@ handle_call({activate, SHA1, IJid}, _From, State) ->
|
|||||||
F = fun() ->
|
F = fun() ->
|
||||||
case mnesia:read(bytestream, SHA1, write) of
|
case mnesia:read(bytestream, SHA1, write) of
|
||||||
[#bytestream{target = TPid, initiator = IPid} = ByteStream]
|
[#bytestream{target = TPid, initiator = IPid} = ByteStream]
|
||||||
when is_pid(TPid), is_pid(IPid) ->
|
when is_pid(TPid), is_pid(IPid) ->
|
||||||
ActiveFlag = ByteStream#bytestream.active,
|
ActiveFlag = ByteStream#bytestream.active,
|
||||||
if
|
if
|
||||||
ActiveFlag == false ->
|
ActiveFlag == false ->
|
||||||
@ -106,20 +109,13 @@ handle_call(_Request, _From, State) ->
|
|||||||
%%% transaction abort
|
%%% transaction abort
|
||||||
%%% SHA1 = string()
|
%%% SHA1 = string()
|
||||||
%%%---------------------------------------------------
|
%%%---------------------------------------------------
|
||||||
register_stream(SHA1) ->
|
register_stream(SHA1) when is_list(SHA1) ->
|
||||||
register_stream(SHA1, undefined, undefined, undefined, undefined).
|
|
||||||
register_stream(SHA1, JID, URL, MyHost, InitiatorPID) when is_list(SHA1) ->
|
|
||||||
% PIDs are not used for http, we set it for compatibilty with plain socks5
|
|
||||||
StreamPid = self(),
|
StreamPid = self(),
|
||||||
F = fun() ->
|
F = fun() ->
|
||||||
case mnesia:read(bytestream, SHA1, write) of
|
case mnesia:read(bytestream, SHA1, write) of
|
||||||
[] ->
|
[] ->
|
||||||
mnesia:write(#bytestream{sha1 = SHA1,
|
mnesia:write(#bytestream{sha1 = SHA1,
|
||||||
target = StreamPid,
|
target = StreamPid});
|
||||||
initiator = InitiatorPID,
|
|
||||||
jid_t = JID,
|
|
||||||
file = URL,
|
|
||||||
myhost = MyHost});
|
|
||||||
[#bytestream{target = Pid,
|
[#bytestream{target = Pid,
|
||||||
initiator = undefined} = ByteStream]
|
initiator = undefined} = ByteStream]
|
||||||
when is_pid(Pid), Pid /= StreamPid ->
|
when is_pid(Pid), Pid /= StreamPid ->
|
||||||
@ -149,14 +145,14 @@ unregister_stream(SHA1) when is_list(SHA1) ->
|
|||||||
%%% IJid = TJid = jid()
|
%%% IJid = TJid = jid()
|
||||||
%%% Host = string()
|
%%% Host = string()
|
||||||
%%%--------------------------------------------------------
|
%%%--------------------------------------------------------
|
||||||
activate_stream(SHA1, IJid, TJid, Host, Module) when is_list(SHA1) ->
|
activate_stream(SHA1, IJid, TJid, Host) when is_list(SHA1) ->
|
||||||
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
|
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
|
||||||
case catch gen_server:call(Proc, {activate, SHA1, IJid}) of
|
case catch gen_server:call(Proc, {activate, SHA1, IJid}) of
|
||||||
{atomic, {ok, IPid, TPid}} ->
|
{atomic, {ok, IPid, TPid}} ->
|
||||||
Module:activate({IPid, IJid}, {TPid, TJid});
|
mod_proxy65_stream:activate({IPid, IJid}, {TPid, TJid});
|
||||||
{atomic, {limit, IPid, TPid}} ->
|
{atomic, {limit, IPid, TPid}} ->
|
||||||
Module:stop(IPid),
|
mod_proxy65_stream:stop(IPid),
|
||||||
Module:stop(TPid),
|
mod_proxy65_stream:stop(TPid),
|
||||||
limit;
|
limit;
|
||||||
{atomic, conflict} ->
|
{atomic, conflict} ->
|
||||||
conflict;
|
conflict;
|
||||||
|
@ -163,7 +163,7 @@ wait_for_request(Packet, #state{socket=Socket} = StateData) ->
|
|||||||
{stop, normal, StateData}
|
{stop, normal, StateData}
|
||||||
end;
|
end;
|
||||||
#s5_request{cmd=udp} ->
|
#s5_request{cmd=udp} ->
|
||||||
Err = mod_proxy65_lib:make_error_reply(Request, ?SOCKS5_ERR_COMMAND_NOT_SUPPORTED),
|
Err = mod_proxy65_lib:make_error_reply(Request, ?ERR_COMMAND_NOT_SUPPORTED),
|
||||||
gen_tcp:send(Socket, Err),
|
gen_tcp:send(Socket, Err),
|
||||||
{stop, normal, StateData};
|
{stop, normal, StateData};
|
||||||
_ ->
|
_ ->
|
||||||
|
@ -16,10 +16,7 @@
|
|||||||
become_controller/1,
|
become_controller/1,
|
||||||
socket_type/0,
|
socket_type/0,
|
||||||
receive_headers/1,
|
receive_headers/1,
|
||||||
url_encode/1,
|
url_encode/1]).
|
||||||
test/0, get_line/1, data/0,
|
|
||||||
parse_data/2
|
|
||||||
]).
|
|
||||||
|
|
||||||
-include("ejabberd.hrl").
|
-include("ejabberd.hrl").
|
||||||
-include("jlib.hrl").
|
-include("jlib.hrl").
|
||||||
@ -33,7 +30,6 @@
|
|||||||
request_auth,
|
request_auth,
|
||||||
request_keepalive,
|
request_keepalive,
|
||||||
request_content_length,
|
request_content_length,
|
||||||
request_content_type,
|
|
||||||
request_lang = "en",
|
request_lang = "en",
|
||||||
%% XXX bard: request handlers are configured in
|
%% XXX bard: request handlers are configured in
|
||||||
%% ejabberd.cfg under the HTTP service. For example,
|
%% ejabberd.cfg under the HTTP service. For example,
|
||||||
@ -198,8 +194,6 @@ process_header(State, Data) ->
|
|||||||
_ ->
|
_ ->
|
||||||
State
|
State
|
||||||
end;
|
end;
|
||||||
{ok, {http_header, _, 'Content-Type', _, ContentType}} ->
|
|
||||||
State#state{request_content_type = ContentType};
|
|
||||||
{ok, {http_header, _, 'Accept-Language', _, Langs}} ->
|
{ok, {http_header, _, 'Accept-Language', _, Langs}} ->
|
||||||
State#state{request_lang = parse_lang(Langs)};
|
State#state{request_lang = parse_lang(Langs)};
|
||||||
{ok, {http_header, _, _, _, _}} ->
|
{ok, {http_header, _, _, _, _}} ->
|
||||||
@ -308,7 +302,6 @@ process_request(#state{request_method = 'POST',
|
|||||||
request_auth = Auth,
|
request_auth = Auth,
|
||||||
request_content_length = Len,
|
request_content_length = Len,
|
||||||
request_lang = Lang,
|
request_lang = Lang,
|
||||||
request_content_type = ContentType,
|
|
||||||
sockmod = SockMod,
|
sockmod = SockMod,
|
||||||
socket = Socket,
|
socket = Socket,
|
||||||
request_handlers = RequestHandlers} = State)
|
request_handlers = RequestHandlers} = State)
|
||||||
@ -324,7 +317,7 @@ process_request(#state{request_method = 'POST',
|
|||||||
case (catch url_decode_q_split(Path)) of
|
case (catch url_decode_q_split(Path)) of
|
||||||
{'EXIT', _} ->
|
{'EXIT', _} ->
|
||||||
process_request(false);
|
process_request(false);
|
||||||
{NPath, _Query} ->
|
{NPath, Query} ->
|
||||||
LPath = string:tokens(NPath, "/"),
|
LPath = string:tokens(NPath, "/"),
|
||||||
LQuery = case (catch parse_urlencoded(Data)) of
|
LQuery = case (catch parse_urlencoded(Data)) of
|
||||||
{'EXIT', _Reason} ->
|
{'EXIT', _Reason} ->
|
||||||
@ -336,7 +329,6 @@ process_request(#state{request_method = 'POST',
|
|||||||
path = LPath,
|
path = LPath,
|
||||||
q = LQuery,
|
q = LQuery,
|
||||||
auth = Auth,
|
auth = Auth,
|
||||||
content_type = ContentType,
|
|
||||||
data = Data,
|
data = Data,
|
||||||
lang = Lang},
|
lang = Lang},
|
||||||
case process(RequestHandlers, Request) of
|
case process(RequestHandlers, Request) of
|
||||||
@ -363,7 +355,7 @@ process_request(State) ->
|
|||||||
recv_data(State, Len) ->
|
recv_data(State, Len) ->
|
||||||
recv_data(State, Len, []).
|
recv_data(State, Len, []).
|
||||||
|
|
||||||
recv_data(_State, 0, Acc) ->
|
recv_data(State, 0, Acc) ->
|
||||||
binary_to_list(list_to_binary(Acc));
|
binary_to_list(list_to_binary(Acc));
|
||||||
recv_data(State, Len, Acc) ->
|
recv_data(State, Len, Acc) ->
|
||||||
case State#state.trail of
|
case State#state.trail of
|
||||||
@ -623,7 +615,7 @@ code_to_phrase(504) -> "Gateway Timeout";
|
|||||||
code_to_phrase(505) -> "HTTP Version Not Supported".
|
code_to_phrase(505) -> "HTTP Version Not Supported".
|
||||||
|
|
||||||
|
|
||||||
parse_auth(_Orig = "Basic " ++ Auth64) ->
|
parse_auth(Orig = "Basic " ++ Auth64) ->
|
||||||
case decode_base64(Auth64) of
|
case decode_base64(Auth64) of
|
||||||
{error, _Err} ->
|
{error, _Err} ->
|
||||||
undefined;
|
undefined;
|
||||||
@ -980,102 +972,3 @@ get_line("\r\n" ++ Tail, Cur) ->
|
|||||||
end;
|
end;
|
||||||
get_line([H|T], Cur) ->
|
get_line([H|T], Cur) ->
|
||||||
get_line(T, [H|Cur]).
|
get_line(T, [H|Cur]).
|
||||||
|
|
||||||
% return {Value, [{Arg, ArgValue}]}
|
|
||||||
% example, for
|
|
||||||
% String = "form-data; name=\"FILE\"; filename=\"TEST\""
|
|
||||||
% return
|
|
||||||
% {"form-data", [{"name", "FILE"}, {"filename", "TEST"}]}
|
|
||||||
% XXX TODO: don't work if an arg value contains a semicolon ;
|
|
||||||
parse_header(String) ->
|
|
||||||
[Value | Args] = string:tokens(String, ";"),
|
|
||||||
catch {string:strip(Value),
|
|
||||||
lists:map(fun(Arg) ->
|
|
||||||
[ArgName, ArgValue] = string:tokens(string:strip(Arg), "="),
|
|
||||||
{string:strip(ArgName), string:strip(string:strip(ArgValue), both, $")}
|
|
||||||
end, Args)}.
|
|
||||||
|
|
||||||
% return [#http_data]
|
|
||||||
parse_data(Data, ContentType) ->
|
|
||||||
case parse_header(ContentType) of
|
|
||||||
{"multipart/form-data", Args} ->
|
|
||||||
case lists:keysearch("boundary", 1, Args) of
|
|
||||||
{value, {"boundary", Boundary}} ->
|
|
||||||
parse_multipart_data(Data, "--"++Boundary);
|
|
||||||
false ->
|
|
||||||
{error, "no boundary for multipart/form-data Content-Type"}
|
|
||||||
end;
|
|
||||||
{'EXIT', _} ->
|
|
||||||
{error, "malformed Content-Type"};
|
|
||||||
_ ->
|
|
||||||
[#http_data{content_type=ContentType, data=Data}]
|
|
||||||
end.
|
|
||||||
|
|
||||||
parse_multipart_data(Data, Boundary)->
|
|
||||||
case catch parse_multipart_data([], Data, Boundary) of
|
|
||||||
{'EXIT', _Reason} ->
|
|
||||||
{error, "malformed multipart/form-data body"};
|
|
||||||
List ->
|
|
||||||
List
|
|
||||||
end.
|
|
||||||
|
|
||||||
parse_multipart_data(Acc, Tail, Boundary) ->
|
|
||||||
BoundaryEnd = Boundary++"--\r\n",
|
|
||||||
case get_line(Tail) of
|
|
||||||
{incomplete, BoundaryEnd} ->
|
|
||||||
lists:reverse(Acc);
|
|
||||||
{line, Boundary, Tail2} ->
|
|
||||||
parse_multipart_headers([#http_data{data=[]} | Acc], Tail2, Boundary);
|
|
||||||
{line, Line, Tail2} ->
|
|
||||||
[#http_data{data=Data} = Cur | T] = Acc,
|
|
||||||
parse_multipart_data([Cur#http_data{data=Data++Line++"\r\n"} | T], Tail2, Boundary);
|
|
||||||
{lastline, Line, Tail2} ->
|
|
||||||
[#http_data{data=Data} = Cur | T] = Acc,
|
|
||||||
parse_multipart_data([Cur#http_data{data=Data++Line++"\r\n\r\n"} | T], Tail2, Boundary)
|
|
||||||
end.
|
|
||||||
parse_multipart_headers([#http_data{args=Args} = Cur | T], Tail, Boundary) ->
|
|
||||||
case get_line(Tail) of
|
|
||||||
{LineType, Line, Tail2} when Line /= Boundary ->
|
|
||||||
NewCur = case Line of
|
|
||||||
"Content-Type:"++Value ->
|
|
||||||
{Header, NewArgs} = parse_header(Value),
|
|
||||||
Cur#http_data{content_type=Header,
|
|
||||||
args=Args++NewArgs};
|
|
||||||
"Content-Disposition:"++Value ->
|
|
||||||
{Header, NewArgs} = parse_header(Value),
|
|
||||||
Cur#http_data{content_disposition=Header,
|
|
||||||
args=Args++NewArgs};
|
|
||||||
_ ->
|
|
||||||
Cur
|
|
||||||
end,
|
|
||||||
case LineType of
|
|
||||||
line ->
|
|
||||||
parse_multipart_headers([NewCur | T], Tail2, Boundary);
|
|
||||||
lastline ->
|
|
||||||
parse_multipart_data([NewCur | T], Tail2, Boundary)
|
|
||||||
end
|
|
||||||
end.
|
|
||||||
|
|
||||||
|
|
||||||
data() ->
|
|
||||||
S="-----------------------------7148830871206398517200280906
|
|
||||||
Content-Type: text/plain; charset=ISO-8859-2
|
|
||||||
Content-Disposition: form-data; name=\"SIDHASH\"
|
|
||||||
|
|
||||||
SIDHASH
|
|
||||||
-----------------------------7148830871206398517200280906
|
|
||||||
Content-Disposition: form-data; name=\"FILE\"; filename=\"TEST\"
|
|
||||||
Content-Type: application/octet-stream
|
|
||||||
|
|
||||||
TEST
|
|
||||||
|
|
||||||
-----------------------------7148830871206398517200280906--
|
|
||||||
",
|
|
||||||
lists:flatten(lists:map( fun($\n) ->
|
|
||||||
"\r\n";
|
|
||||||
(C) ->
|
|
||||||
C
|
|
||||||
end, S)).
|
|
||||||
|
|
||||||
test() ->
|
|
||||||
parse_data(data(), "multipart/form-data; boundary=---------------------------7148830871206398517200280906").
|
|
||||||
|
@ -12,13 +12,6 @@
|
|||||||
us,
|
us,
|
||||||
auth,
|
auth,
|
||||||
lang = "",
|
lang = "",
|
||||||
content_type,
|
|
||||||
data = "",
|
data = "",
|
||||||
ip
|
ip
|
||||||
}).
|
}).
|
||||||
|
|
||||||
-record(http_data, {content_type,
|
|
||||||
content_disposition,
|
|
||||||
args=[],
|
|
||||||
data
|
|
||||||
}).
|
|
||||||
|
@ -53,11 +53,7 @@ make_xhtml(Els) ->
|
|||||||
{"name", Name},
|
{"name", Name},
|
||||||
{"value", Value}])).
|
{"value", Value}])).
|
||||||
|
|
||||||
error(bad_request) ->
|
|
||||||
{400, [], make_xhtml([?XC("h1", "400 Bad Request")])};
|
|
||||||
error(not_allowed) ->
|
|
||||||
{401, [], make_xhtml([?XC("h1", "401 Unauthorized")])};
|
|
||||||
error(not_found) ->
|
error(not_found) ->
|
||||||
{404, [], make_xhtml([?XC("h1", "404 Not Found")])};
|
{404, [], make_xhtml([?XC("h1", "404 Not Found")])};
|
||||||
error(internal) ->
|
error(not_allowed) ->
|
||||||
{500, [], make_xhtml([?XC("h1", "500 Internal Error")])}.
|
{401, [], make_xhtml([?XC("h1", "401 Unauthorized")])}.
|
||||||
|
Loading…
Reference in New Issue
Block a user