]> granicus.if.org Git - ejabberd/commitdiff
Unsafe working version of http file transfer
authorJérôme Sautret <jerome.sautret@process-one.net>
Wed, 27 Jun 2007 10:01:39 +0000 (10:01 +0000)
committerJérôme Sautret <jerome.sautret@process-one.net>
Wed, 27 Jun 2007 10:01:39 +0000 (10:01 +0000)
SVN Revision: 798

12 files changed:
src/mod_proxy65/Makefile.in
src/mod_proxy65/TODO [new file with mode: 0644]
src/mod_proxy65/mod_proxy65.erl
src/mod_proxy65/mod_proxy65.hrl
src/mod_proxy65/mod_proxy65_http.erl [new file with mode: 0644]
src/mod_proxy65/mod_proxy65_lib.erl
src/mod_proxy65/mod_proxy65_service.erl
src/mod_proxy65/mod_proxy65_sm.erl
src/mod_proxy65/mod_proxy65_stream.erl
src/web/ejabberd_http.erl
src/web/ejabberd_http.hrl
src/web/ejabberd_web.erl

index bb70ac5cb19368f6ad1ea93a3213c36b60378eaf..0cd3e06390f7b2886b9d00f4317b05bf2284debc 100644 (file)
@@ -1,12 +1,12 @@
 # $Id$
 
-CC = @CC@ 
+CC = @CC@
 CFLAGS = @CFLAGS@ @ERLANG_CFLAGS@
 CPPFLAGS = @CPPFLAGS@
 LDFLAGS = @LDFLAGS@
 LIBS = @LIBS@ @ERLANG_LIBS@
 
-SUBDIRS = 
+SUBDIRS =
 
 OUTDIR = ..
 EFLAGS = -I .. -pz ..
@@ -20,6 +20,7 @@ OBJS   = \
        $(OUTDIR)/mod_proxy65_service.beam \
        $(OUTDIR)/mod_proxy65_sm.beam \
        $(OUTDIR)/mod_proxy65_stream.beam \
+       $(OUTDIR)/mod_proxy65_http.beam \
        $(OUTDIR)/mod_proxy65_lib.beam
 
 all:    $(OBJS)
@@ -35,4 +36,3 @@ distclean: clean
 
 TAGS:
        etags *.erl
-
diff --git a/src/mod_proxy65/TODO b/src/mod_proxy65/TODO
new file mode 100644 (file)
index 0000000..7414ff8
--- /dev/null
@@ -0,0 +1,17 @@
+
+
+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
\ No newline at end of file
index 65719241eb31544e2e0fdfa7510e11bda8fa9624..dfa5b89cbf1fc4db698e05d8c43f7e2737deb32c 100644 (file)
@@ -50,8 +50,14 @@ init([Host, Opts]) ->
          [gen_mod:get_module_proc(Host, ejabberd_mod_proxy65_sup),
           mod_proxy65_stream]},
       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 =
        {mod_proxy65_sm, {mod_proxy65_sm, start_link, [Host, Opts]},
         transient, 5000, worker, [mod_proxy65_sm]},
     {ok, {{one_for_one, 10, 1},
-         [StreamManager, StreamSupervisor, Service]}}.
+         [StreamManager, StreamSupervisor, HttpStreamSupervisor, Service]}}.
index eeab6804d8416ef5617ac80ca63793999543dd6d..a26c04c4a8dc0b0a0ee8bb332f401f9c18344082 100644 (file)
 
 %% RFC 1928 replies
 -define(SUCCESS, 0).
--define(ERR_GENERAL_FAILURE, 1).
--define(ERR_NOT_ALLOWED, 2).
--define(ERR_NETWORK_UNREACHABLE, 3).
--define(ERR_HOST_UNREACHABLE, 4).
--define(ERR_CONNECTION_REFUSED, 5).
--define(ERR_TTL_EXPIRED, 6).
--define(ERR_COMMAND_NOT_SUPPORTED, 7).
--define(ERR_ADDRESS_TYPE_NOT_SUPPORTED, 8).
+-define(SOCKS5_ERR_GENERAL_FAILURE, 1).
+-define(SOCKS5_ERR_NOT_ALLOWED, 2).
+-define(SOCKS5_ERR_NETWORK_UNREACHABLE, 3).
+-define(SOCKS5_ERR_HOST_UNREACHABLE, 4).
+-define(SOCKS5_ERR_CONNECTION_REFUSED, 5).
+-define(SOCKS5_ERR_TTL_EXPIRED, 6).
+-define(SOCKS5_ERR_COMMAND_NOT_SUPPORTED, 7).
+-define(SOCKS5_ERR_ADDRESS_TYPE_NOT_SUPPORTED, 8).
 
 %% RFC 1928 defined timeout.
 -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, {
          rsv = 0,
          cmd,
          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").
diff --git a/src/mod_proxy65/mod_proxy65_http.erl b/src/mod_proxy65/mod_proxy65_http.erl
new file mode 100644 (file)
index 0000000..f3d6abb
--- /dev/null
@@ -0,0 +1,156 @@
+%%%----------------------------------------------------------------------
+%%% 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.
index 09ee6b981b90617b2e356b2e76194b8c16cb6fc1..02d4ecbe8c66725518fabeb15446f05bf8ee3888 100644 (file)
@@ -53,7 +53,7 @@ make_init_reply(Method) ->
     [?VERSION_5, Method].
 
 make_auth_reply(true) -> [1, ?SUCCESS];
-make_auth_reply(false) -> [1, ?ERR_NOT_ALLOWED].
+make_auth_reply(false) -> [1, ?SOCKS5_ERR_NOT_ALLOWED].
 
 %% WARNING: According to SOCKS5 RFC, this reply is _incorrect_, but
 %% 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].
 
 make_error_reply(Request) ->
-    make_error_reply(Request, ?ERR_NOT_ALLOWED).
+    make_error_reply(Request, ?SOCKS5_ERR_NOT_ALLOWED).
 
 make_error_reply(#s5_request{rsv = RSV, sha1 = SHA1}, Reason) ->
     [?VERSION_5, Reason, RSV, ?ATYP_DOMAINNAME, length(SHA1), SHA1, 0,0].
index f9f3bf12d8dbe513405211ccbd5059937459770b..c590f22f716b917072ca02ea5da4dac99a879ea0 100644 (file)
@@ -23,6 +23,7 @@
 %% API.
 -export([start_link/2]).
 
+-include("mod_proxy65.hrl").
 -include("../ejabberd.hrl").
 -include("../jlib.hrl").
 
@@ -34,6 +35,8 @@
          name,
          stream_addr,
          port,
+         http_port,
+         http_base_path,
          acl
         }).
 
@@ -84,12 +87,14 @@ handle_info(_Info, State) ->
 %%%------------------------
 
 %% disco#info request
-process_iq(_, #iq{type = get, xmlns = ?NS_DISCO_INFO, lang = Lang} = IQ, #state{name=Name}) ->
+process_iq(_, #iq{type = get, xmlns = ?NS_DISCO_INFO} = IQ, #state{name=Name, http_port=HTTP_Port}) ->
+    io:format("~p~n", [IQ]),
     IQ#iq{type = result, sub_el =
-         [{xmlelement, "query", [{"xmlns", ?NS_DISCO_INFO}], iq_disco_info(Lang, Name)}]};
+         [{xmlelement, "query", [{"xmlns", ?NS_DISCO_INFO}], iq_disco_info(Name, HTTP_Port)}]};
 
 %% disco#items request
 process_iq(_, #iq{type = get, xmlns = ?NS_DISCO_ITEMS} = IQ, _) ->
+    io:format("~p~n", [IQ]),
     IQ#iq{type = result, sub_el =
          [{xmlelement, "query", [{"xmlns", ?NS_DISCO_ITEMS}], []}]};
 
@@ -101,6 +106,7 @@ process_iq(_, #iq{type = get, xmlns = ?NS_VCARD, lang = Lang} = IQ, _) ->
 %% bytestreams info request
 process_iq(JID, #iq{type = get, sub_el = SubEl, xmlns = ?NS_BYTESTREAMS} = IQ,
           #state{acl = ACL, stream_addr = StreamAddr, serverhost = ServerHost}) ->
+    io:format("~p~n", [IQ]),
     case acl:match_rule(ServerHost, ACL, JID) of
        allow ->
            StreamHostEl = [{xmlelement, "streamhost", StreamAddr, []}],
@@ -110,9 +116,39 @@ process_iq(JID, #iq{type = get, sub_el = SubEl, xmlns = ?NS_BYTESTREAMS} = IQ,
            IQ#iq{type = error, sub_el = [SubEl, ?ERR_FORBIDDEN]}
     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
 process_iq(InitiatorJID, #iq{type = set, sub_el = SubEl, xmlns = ?NS_BYTESTREAMS} = IQ,
-          #state{acl = ACL, serverhost = ServerHost}) ->
+          #state{acl = ACL, serverhost = ServerHost, myhost = MyHost,
+                 http_port=HTTP_Port, http_base_path=HTTP_Base_Path}) ->
     case acl:match_rule(ServerHost, ACL, InitiatorJID) of
        allow ->
            ActivateEl = xml:get_path_s(SubEl, [{elem, "activate"}]),
@@ -123,9 +159,21 @@ process_iq(InitiatorJID, #iq{type = set, sub_el = SubEl, xmlns = ?NS_BYTESTREAMS
                    Target = jlib:jid_to_string(jlib:jid_tolower(TargetJID)),
                    Initiator = jlib:jid_to_string(jlib:jid_tolower(InitiatorJID)),
                    SHA1 = sha:sha(SID ++ Initiator ++ Target),
-                   case mod_proxy65_sm:activate_stream(SHA1, InitiatorJID, TargetJID, ServerHost) of
+                   {Module, Activated} =
+                       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 ->
-                           IQ#iq{type = result, sub_el = []};
+                           IQ#iq{type = result, sub_el = Activated};
                        false ->
                            IQ#iq{type = error, sub_el = [SubEl, ?ERR_ITEM_NOT_FOUND]};
                        limit ->
@@ -135,7 +183,8 @@ process_iq(InitiatorJID, #iq{type = set, sub_el = SubEl, xmlns = ?NS_BYTESTREAMS
                        _ ->
                            IQ#iq{type = error, sub_el = [SubEl, ?ERR_INTERNAL_SERVER_ERROR]}
                    end;
-               _ ->
+               _Reason ->
+                   ?ERROR_MSG("process IQ set ~p:~n~p~n", [?NS_BYTESTREAMS, _Reason]),
                    IQ#iq{type = error, sub_el = [SubEl, ?ERR_BAD_REQUEST]}
            end;
        deny ->
@@ -155,15 +204,21 @@ process_iq(_, _, _) ->
 %%%-------------------------
 -define(FEATURE(Feat), {xmlelement,"feature",[{"var", Feat}],[]}).
 
-iq_disco_info(Lang, Name) ->
+iq_disco_info(Name, HTTP_Port) ->
+    HTTP_Bytestreams = case HTTP_Port of
+                          "0" ->
+                              [];
+                          _ ->
+                             [?FEATURE(?NS_HTTP_BYTESTREAMS)]
+                      end,
     [{xmlelement, "identity",
       [{"category", "proxy"},
        {"type", "bytestreams"},
-       {"name", translate:translate(Lang, Name)}], []},
+       {"name", Name}], []},
      ?FEATURE(?NS_DISCO_INFO),
      ?FEATURE(?NS_DISCO_ITEMS),
      ?FEATURE(?NS_VCARD),
-     ?FEATURE(?NS_BYTESTREAMS)].
+     ?FEATURE(?NS_BYTESTREAMS)] ++ HTTP_Bytestreams.
 
 iq_vcard(Lang) ->
     [{xmlelement, "FN", [],
@@ -177,6 +232,8 @@ iq_vcard(Lang) ->
 parse_options(ServerHost, Opts) ->
     MyHost = gen_mod:get_opt(host, Opts, "proxy." ++ ServerHost),
     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),
     Name = gen_mod:get_opt(name, Opts, "SOCKS5 Bytestreams"),
     IP = case gen_mod:get_opt(ip, Opts, none) of
@@ -189,7 +246,9 @@ parse_options(ServerHost, Opts) ->
                serverhost  = ServerHost,
                name        = Name,
                port        = Port,
-               stream_addr = StreamAddr, 
+               http_port   = HTTP_Port,
+               http_base_path = HTTP_Base_Path,
+               stream_addr = StreamAddr,
                acl         = ACL}}.
 
 %% Return the IP of the proxy host, or if not found, the ip of the xmpp domain
@@ -201,4 +260,4 @@ get_proxy_or_domainip(ServerHost, MyHost) ->
                 {ok, Addr} -> Addr;
                 {error, _} -> {127,0,0,1}
             end
-    end.
\ No newline at end of file
+    end.
index 7ddfd2c898603323830b5bd73cbcda26c0909355..32e89fac4ae5a5d11ed7bb7de298f1a8b338da64 100644 (file)
 -export([
         start_link/2,
         register_stream/1,
+        register_stream/5,
         unregister_stream/1,
-        activate_stream/4
+        activate_stream/5
        ]).
 
+-include("mod_proxy65.hrl").
+
 -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).
 
@@ -55,6 +51,7 @@ start_link(Host, Opts) ->
 init([Opts]) ->
     mnesia:create_table(bytestream, [{ram_copies, [node()]},
                                     {attributes, record_info(fields, bytestream)}]),
+    mnesia:add_table_index(bytestream, file),
     mnesia:add_table_copy(bytestream, node(), ram_copies),
     MaxConnections = gen_mod:get_opt(max_connections, Opts, infinity),
     {ok, #state{max_connections=MaxConnections}}.
@@ -67,7 +64,7 @@ handle_call({activate, SHA1, IJid}, _From, State) ->
     F = fun() ->
                case mnesia:read(bytestream, SHA1, write) of
                    [#bytestream{target = TPid, initiator = IPid} = ByteStream]
-                   when is_pid(TPid), is_pid(IPid) ->
+                   when is_pid(TPid), is_pid(IPid)  ->
                        ActiveFlag = ByteStream#bytestream.active,
                        if
                            ActiveFlag == false ->
@@ -109,13 +106,20 @@ handle_call(_Request, _From, State) ->
 %%%                          transaction abort
 %%% SHA1 = string()
 %%%---------------------------------------------------
-register_stream(SHA1) when is_list(SHA1) ->
+register_stream(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(),
     F = fun() ->
                case mnesia:read(bytestream, SHA1, write) of
                    [] ->
                        mnesia:write(#bytestream{sha1 = SHA1,
-                                                target = StreamPid});
+                                                target = StreamPid,
+                                                initiator = InitiatorPID,
+                                                jid_t = JID,
+                                                file = URL,
+                                                myhost = MyHost});
                    [#bytestream{target = Pid,
                                 initiator = undefined} = ByteStream]
                    when is_pid(Pid), Pid /= StreamPid ->
@@ -145,14 +149,14 @@ unregister_stream(SHA1) when is_list(SHA1) ->
 %%% IJid = TJid = jid()
 %%% Host = string()
 %%%--------------------------------------------------------
-activate_stream(SHA1, IJid, TJid, Host) when is_list(SHA1) ->
+activate_stream(SHA1, IJid, TJid, Host, Module) when is_list(SHA1) ->
     Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
     case catch gen_server:call(Proc, {activate, SHA1, IJid}) of
        {atomic, {ok, IPid, TPid}} ->
-           mod_proxy65_stream:activate({IPid, IJid}, {TPid, TJid});
+           Module:activate({IPid, IJid}, {TPid, TJid});
        {atomic, {limit, IPid, TPid}} ->
-           mod_proxy65_stream:stop(IPid),
-           mod_proxy65_stream:stop(TPid),
+           Module:stop(IPid),
+           Module:stop(TPid),
            limit;
        {atomic, conflict} ->
            conflict;
index 1a15033844dcea6d14acf7896881f82c9c7c3948..88ad1eb29fd45f19241ad06c2079b1a554799713 100644 (file)
@@ -163,7 +163,7 @@ wait_for_request(Packet, #state{socket=Socket} = StateData) ->
                    {stop, normal, StateData}
            end;
        #s5_request{cmd=udp} ->
-           Err = mod_proxy65_lib:make_error_reply(Request, ?ERR_COMMAND_NOT_SUPPORTED),
+           Err = mod_proxy65_lib:make_error_reply(Request, ?SOCKS5_ERR_COMMAND_NOT_SUPPORTED),
            gen_tcp:send(Socket, Err),
            {stop, normal, StateData};
        _ ->
index baa7f861c373729c83e79658b370f2903a8934b0..fbc4a8118e99b60c8f87cced3b86c8703f8c6c52 100644 (file)
         become_controller/1,
         socket_type/0,
         receive_headers/1,
-        url_encode/1]).
+        url_encode/1,
+        test/0, get_line/1, data/0,
+        parse_data/2
+       ]).
 
 -include("ejabberd.hrl").
 -include("jlib.hrl").
@@ -30,6 +33,7 @@
                request_auth,
                request_keepalive,
                request_content_length,
+               request_content_type,
                request_lang = "en",
                %% XXX bard: request handlers are configured in
                %% ejabberd.cfg under the HTTP service.  For example,
@@ -194,6 +198,8 @@ process_header(State, Data) ->
                _ ->
                    State
            end;
+       {ok, {http_header, _, 'Content-Type', _, ContentType}} ->
+           State#state{request_content_type = ContentType};
        {ok, {http_header, _, 'Accept-Language', _, Langs}} ->
            State#state{request_lang = parse_lang(Langs)};
        {ok, {http_header, _, _, _, _}} ->
@@ -302,6 +308,7 @@ process_request(#state{request_method = 'POST',
                       request_auth = Auth,
                       request_content_length = Len,
                       request_lang = Lang,
+                      request_content_type = ContentType,
                       sockmod = SockMod,
                       socket = Socket,
                       request_handlers = RequestHandlers} = State)
@@ -317,7 +324,7 @@ process_request(#state{request_method = 'POST',
     case (catch url_decode_q_split(Path)) of
        {'EXIT', _} ->
            process_request(false);
-       {NPath, Query} ->
+       {NPath, _Query} ->
            LPath = string:tokens(NPath, "/"),
            LQuery = case (catch parse_urlencoded(Data)) of
                         {'EXIT', _Reason} ->
@@ -329,6 +336,7 @@ process_request(#state{request_method = 'POST',
                               path = LPath,
                               q = LQuery,
                               auth = Auth,
+                              content_type = ContentType,
                               data = Data,
                               lang = Lang},
            case process(RequestHandlers, Request) of
@@ -355,7 +363,7 @@ process_request(State) ->
 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));
 recv_data(State, Len, Acc) ->
     case State#state.trail of
@@ -615,7 +623,7 @@ code_to_phrase(504) -> "Gateway Timeout";
 code_to_phrase(505) -> "HTTP Version Not Supported".
 
 
-parse_auth(Orig = "Basic " ++ Auth64) ->
+parse_auth(_Orig = "Basic " ++ Auth64) ->
     case decode_base64(Auth64) of
        {error, _Err} ->
            undefined;
@@ -972,3 +980,102 @@ get_line("\r\n" ++ Tail, Cur) ->
     end;
 get_line([H|T], 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").
index 7f94c5e24e114be902ac6bdb62d79102121d38d1..fa5f6135fbddf257b7d3fa8cd098cff4d0d2f7f3 100644 (file)
                  us,
                  auth,
                  lang = "",
+                 content_type,
                  data = "",
                  ip
                 }).
+
+-record(http_data, {content_type,
+                   content_disposition,
+                   args=[],
+                   data
+                  }).
index a1d6b6592f773d601f1383d8f7af9e7fec8f838a..d0e037d66c08bda77727b969d29679f3ae56fac1 100644 (file)
@@ -1,7 +1,7 @@
 %%%----------------------------------------------------------------------
 %%% File    : ejabberd_web.erl
 %%% Author  : Alexey Shchepin <alexey@sevcom.net>
-%%% Purpose : 
+%%% Purpose :
 %%% Created : 28 Feb 2004 by Alexey Shchepin <alexey@sevcom.net>
 %%% Id      : $Id$
 %%%----------------------------------------------------------------------
@@ -53,7 +53,11 @@ make_xhtml(Els) ->
                      {"name", Name},
                      {"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) ->
     {404, [], make_xhtml([?XC("h1", "404 Not Found")])};
-error(not_allowed) ->
-    {401, [], make_xhtml([?XC("h1", "401 Unauthorized")])}.
+error(internal) ->
+    {500, [], make_xhtml([?XC("h1", "500 Internal Error")])}.