ibrowse: update to 4.1.0

- for COUCHDB-2041
- ibrowse tagged 4.1.0 sha#7871e2e


Project: http://git-wip-us.apache.org/repos/asf/couchdb/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb/commit/b63f393b
Tree: http://git-wip-us.apache.org/repos/asf/couchdb/tree/b63f393b
Diff: http://git-wip-us.apache.org/repos/asf/couchdb/diff/b63f393b

Branch: refs/heads/2041-update-ibrowse
Commit: b63f393b8fad85bd4fca022a86227e4540c48a6b
Parents: 510070c
Author: Dave Cottlehuber <[email protected]>
Authored: Wed Jan 29 11:05:23 2014 +0100
Committer: Dave Cottlehuber <[email protected]>
Committed: Sun Mar 16 17:52:41 2014 +0100

----------------------------------------------------------------------
 NOTICE                              |   2 +-
 src/ibrowse/ibrowse.app.in          |   2 +-
 src/ibrowse/ibrowse.erl             |  67 ++++--
 src/ibrowse/ibrowse_http_client.erl | 358 +++++++++++++++++++------------
 src/ibrowse/ibrowse_lb.erl          |  28 ++-
 src/ibrowse/ibrowse_lib.erl         |   7 +-
 src/ibrowse/ibrowse_socks5.erl      | 143 ++++--------
 7 files changed, 331 insertions(+), 276 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb/blob/b63f393b/NOTICE
----------------------------------------------------------------------
diff --git a/NOTICE b/NOTICE
index fda3caf..aa39311 100644
--- a/NOTICE
+++ b/NOTICE
@@ -36,7 +36,7 @@ This product also includes the following third-party 
components:
 
  * ibrowse (http://github.com/cmullaparthi/ibrowse/tree/master)
 
-   Copyright 2005-2012, Chandrashekhar Mullaparthi
+   Copyright 2005-2014, Chandrashekhar Mullaparthi
 
  * Erlang OAuth (http://github.com/tim/erlang-oauth)
 

http://git-wip-us.apache.org/repos/asf/couchdb/blob/b63f393b/src/ibrowse/ibrowse.app.in
----------------------------------------------------------------------
diff --git a/src/ibrowse/ibrowse.app.in b/src/ibrowse/ibrowse.app.in
index 1d88084..f318aef 100644
--- a/src/ibrowse/ibrowse.app.in
+++ b/src/ibrowse/ibrowse.app.in
@@ -1,6 +1,6 @@
 {application, ibrowse,
         [{description, "Erlang HTTP client application"},
-         {vsn, "4.0.1"},
+         {vsn, "4.1.0"},
          {registered, [ibrowse_sup, ibrowse]},
          {applications, [kernel,stdlib]},
         {env, []},

http://git-wip-us.apache.org/repos/asf/couchdb/blob/b63f393b/src/ibrowse/ibrowse.erl
----------------------------------------------------------------------
diff --git a/src/ibrowse/ibrowse.erl b/src/ibrowse/ibrowse.erl
index 80a4282..fc90dc6 100644
--- a/src/ibrowse/ibrowse.erl
+++ b/src/ibrowse/ibrowse.erl
@@ -6,7 +6,7 @@
 %%% Created : 11 Oct 2003 by Chandrashekhar Mullaparthi 
<[email protected]>
 %%%-------------------------------------------------------------------
 %% @author Chandrashekhar Mullaparthi <chandrashekhar dot mullaparthi at gmail 
dot com>
-%% @copyright 2005-2012 Chandrashekhar Mullaparthi
+%% @copyright 2005-2014 Chandrashekhar Mullaparthi
 %% @doc The ibrowse application implements an HTTP 1.1 client in erlang. This
 %% module implements the API of the HTTP client. There is one named
 %% process called 'ibrowse' which assists in load balancing and maintaining 
configuration. There is one load balancing process per unique webserver. There 
is
@@ -158,7 +158,7 @@ stop() ->
 %% respHeader() = {headerName(), headerValue()}
 %% headerName() = string()
 %% headerValue() = string()
-%% response() = {ok, Status, ResponseHeaders, ResponseBody} | {ibrowse_req_id, 
req_id() } | {error, Reason}
+%% response() = {ok, Status, ResponseHeaders, ResponseBody} | {ok, Status, 
ResponseHeaders, ResponseBody} | {ibrowse_req_id, req_id() } | {error, Reason}
 %% req_id() = term()
 %% ResponseBody = string() | {file, Filename}
 %% Reason = term()
@@ -175,9 +175,11 @@ send_req(Url, Headers, Method) ->
 send_req(Url, Headers, Method, Body) ->
     send_req(Url, Headers, Method, Body, []).
 
-%% @doc Same as send_req/4. 
-%% For a description of SSL Options, look in the <a 
href="http://www.erlang.org/doc/apps/ssl/index.html";>ssl</a> manpage. If the
-%% HTTP Version to use is not specified, the default is 1.1.
+%% @doc Same as send_req/4.
+
+%% For a description of SSL Options, look in the <a 
href="http://www.erlang.org/doc/apps/ssl/index.html";>ssl</a> manpage.
+%% For a description of Process Options, look in the <a 
href="http://www.erlang.org/doc/man/gen_server.html";>gen_server</a> manpage.
+%% If the HTTP Version to use is not specified, the default is 1.1.
 %% <br/>
 %% <ul>
 %% <li>The <code>host_header</code> option is useful in the case where ibrowse 
is
@@ -254,6 +256,8 @@ send_req(Url, Headers, Method, Body) ->
 %% to receive the raw data stream when the Transfer-Encoding of the server
 %% response is Chunked.
 %% </li>
+%% <li> The <code>return_raw_request</code> option enables the caller to get 
the exact request which was sent by ibrowse to the server, along with the 
response. When this option is used, the response for synchronous requests is a 
5-tuple instead of the usual 4-tuple. For asynchronous requests, the calling 
process gets a message <code>{ibrowse_async_raw_req, Raw_req}</code>. 
+%% </li>
 %% </ul>
 %%
 %% @spec send_req(Url::string(), Headers::headerList(), Method::method(), 
Body::body(), Options::optionList()) -> response()
@@ -286,7 +290,9 @@ send_req(Url, Headers, Method, Body) ->
 %%          {headers_as_is, boolean()}         |
 %%          {give_raw_headers, boolean()}      |
 %%          {preserve_chunked_encoding,boolean()}     |
-%%          {workaround, head_response_with_body}
+%%          {workaround, head_response_with_body}     |
+%%          {worker_process_options, list()} |
+%%          {return_raw_request, true}
 %%
 %% stream_to() = process() | {process(), once}
 %% process() = pid() | atom()
@@ -340,10 +346,12 @@ try_routing_request(Lb_pid, Parsed_url,
                     Max_pipeline_size,
                     {SSLOptions, IsSSL}, 
                     Headers, Method, Body, Options_1, Timeout, Try_count) when 
Try_count < 3 ->
+    ProcessOptions = get_value(worker_process_options, Options_1, []),
     case ibrowse_lb:spawn_connection(Lb_pid, Parsed_url,
                                              Max_sessions, 
                                              Max_pipeline_size,
-                                             {SSLOptions, IsSSL}) of
+                                             {SSLOptions, IsSSL},
+                                             ProcessOptions) of
         {ok, Conn_Pid} ->
             case do_send_req(Conn_Pid, Parsed_url, Headers,
                              Method, Body, Options_1, Timeout) of
@@ -437,7 +445,9 @@ do_send_req(Conn_Pid, Parsed_url, Headers, Method, Body, 
Options, Timeout) ->
         {'EXIT', {noproc, {gen_server, call, [Conn_Pid, _, _]}}} ->
             {error, sel_conn_closed};
         {'EXIT', {normal, _}} ->
-            {error, req_timedout};
+            {error, sel_conn_closed};
+        {'EXIT', {connection_closed, _}} ->
+            {error, sel_conn_closed};
         {error, connection_closed} ->
             {error, sel_conn_closed};
         {'EXIT', Reason} ->
@@ -449,6 +459,13 @@ do_send_req(Conn_Pid, Parsed_url, Headers, Method, Body, 
Options, Timeout) ->
                 binary ->
                     Ret
             end;
+        {ok, St_code, Headers, Body, Req} = Ret when is_binary(Body) ->
+            case get_value(response_format, Options, list) of
+                list ->
+                    {ok, St_code, Headers, binary_to_list(Body), Req};
+                binary ->
+                    Ret
+            end;
         Ret ->
             Ret
     end.
@@ -470,28 +487,32 @@ ensure_bin({Fun, _} = Body) when is_function(Fun) -> Body.
 %% request is sent via any of the send_req_direct/4,5,6,7 functions.<br/>
 %% <b>Note:</b> It is the responsibility of the calling process to control
 %% pipeline size on such connections.
-%%
-%% @spec spawn_worker_process(Url::string()) -> {ok, pid()}
-spawn_worker_process(Url) ->
-    ibrowse_http_client:start(Url).
 
-%% @doc Same as spawn_worker_process/1 but takes as input a Host and Port
-%% instead of a URL.
+%% @spec spawn_worker_process(Url::string() | {Host::string(), 
Port::integer()}) -> {ok, pid()}
+spawn_worker_process(Args) ->
+    spawn_worker_process(Args, []).
+
+%% @doc Same as spawn_worker_process/1 except with Erlang process options.
 %% @spec spawn_worker_process(Host::string(), Port::integer()) -> {ok, pid()}
-spawn_worker_process(Host, Port) ->
-    ibrowse_http_client:start({Host, Port}).
+spawn_worker_process(Host, Port) when is_list(Host), is_integer(Port) ->
+    %% Convert old API calls to new API format.
+    spawn_worker_process({Host, Port}, []);
+spawn_worker_process(Args, Options) ->
+    ibrowse_http_client:start(Args, Options).
 
 %% @doc Same as spawn_worker_process/1 except the the calling process
 %% is linked to the worker process which is spawned.
-%% @spec spawn_link_worker_process(Url::string()) -> {ok, pid()}
-spawn_link_worker_process(Url) ->
-    ibrowse_http_client:start_link(Url).
+%% @spec spawn_link_worker_process(Url::string() | {Host::string(), 
Port::integer()}) -> {ok, pid()}
+spawn_link_worker_process(Args) ->
+    spawn_link_worker_process(Args, []).
 
-%% @doc Same as spawn_worker_process/2 except the the calling process
-%% is linked to the worker process which is spawned.
+%% @doc Same as spawn_link_worker_process/1 except with Erlang process options.
 %% @spec spawn_link_worker_process(Host::string(), Port::integer()) -> {ok, 
pid()}
-spawn_link_worker_process(Host, Port) ->
-    ibrowse_http_client:start_link({Host, Port}).
+spawn_link_worker_process(Host, Port) when is_list(Host), is_integer(Port) ->
+    %% Convert old API calls to new API format.
+    spawn_link_worker_process({Host, Port}, []);
+spawn_link_worker_process(Args, Options) ->
+    ibrowse_http_client:start_link(Args, Options).
 
 %% @doc Terminate a worker process spawned using
 %% spawn_worker_process/2 or spawn_link_worker_process/2. Requests in

http://git-wip-us.apache.org/repos/asf/couchdb/blob/b63f393b/src/ibrowse/ibrowse_http_client.erl
----------------------------------------------------------------------
diff --git a/src/ibrowse/ibrowse_http_client.erl 
b/src/ibrowse/ibrowse_http_client.erl
index a1cf6eb..64fc443 100644
--- a/src/ibrowse/ibrowse_http_client.erl
+++ b/src/ibrowse/ibrowse_http_client.erl
@@ -15,7 +15,9 @@
 %% External exports
 -export([
          start_link/1,
+         start_link/2,
          start/1,
+         start/2,
          stop/1,
          send_req/7
         ]).
@@ -39,8 +41,7 @@
 
 -record(state, {host, port, connect_timeout,
                 inactivity_timer_ref,
-                use_http_proxy = false, http_proxy_auth_digest,
-                socks5_host, socks5_port, socks5_user, socks5_password,
+                use_proxy = false, proxy_auth_digest,
                 ssl_options = [], is_ssl = false, socket,
                 proxy_tunnel_setup = false,
                 tunnel_setup_queue = [],
@@ -62,7 +63,7 @@
                   stream_chunk_size,
                   save_response_to_file = false,
                   tmp_file_name, tmp_file_fd, preserve_chunked_encoding,
-                  response_format, timer_ref}).
+                  response_format, timer_ref, raw_req}).
 
 -import(ibrowse_lib, [
                       get_value/2,
@@ -80,10 +81,16 @@
 %% Description: Starts the server
 %%--------------------------------------------------------------------
 start(Args) ->
-    gen_server:start(?MODULE, Args, []).
+    start(Args, []).
+
+start(Args, Options) ->
+    gen_server:start(?MODULE, Args, Options).
 
 start_link(Args) ->
-    gen_server:start_link(?MODULE, Args, []).
+    start_link(Args, []).
+
+start_link(Args, Options) ->
+    gen_server:start_link(?MODULE, Args, Options).
 
 stop(Conn_pid) ->
     case catch gen_server:call(Conn_pid, stop) of
@@ -187,7 +194,7 @@ handle_info({ssl, _Sock, Data}, State) ->
 
 handle_info({stream_next, Req_id}, #state{socket = Socket,
                                           cur_req = #request{req_id = Req_id}} 
= State) ->
-    do_setopts(Socket, [{active, once}], State),
+    _ = do_setopts(Socket, [{active, once}], State),
     {noreply, set_inac_timer(State)};
 
 handle_info({stream_next, _Req_id}, State) ->
@@ -226,12 +233,13 @@ handle_info({ssl_error, _Sock, Reason}, State) ->
     {stop, normal, State};
 
 handle_info({req_timedout, From}, State) ->
-    case lists:keymember(From, #request.from, queue:to_list(State#state.reqs)) 
of
+    case lists:keysearch(From, #request.from, queue:to_list(State#state.reqs)) 
of
         false ->
             {noreply, State};
-        true ->
+        {value, #request{stream_to = StreamTo, req_id = ReqId}} ->
+            catch StreamTo ! {ibrowse_async_response_timeout, ReqId},
             shutting_down(State),
-%%            do_error_reply(State, req_timedout),
+            do_error_reply(State, req_timedout),
             {stop, normal, State}
     end;
 
@@ -288,12 +296,12 @@ handle_sock_data(Data, #state{status = get_header}=State) 
->
             shutting_down(State),
             {stop, normal, State};
         #state{socket = Socket, status = Status, cur_req = CurReq} = State_1 ->
-            case {Status, CurReq} of
-                {get_header, #request{caller_controls_socket = true}} ->
-                    do_setopts(Socket, [{active, once}], State_1);
-                _ ->
-                    active_once(State_1)
-            end,
+            _ = case {Status, CurReq} of
+                   {get_header, #request{caller_controls_socket = true}} ->
+                       do_setopts(Socket, [{active, once}], State_1);
+                   _ ->
+                       active_once(State_1)
+               end,
             {noreply, set_inac_timer(State_1)}
     end;
 
@@ -312,7 +320,7 @@ handle_sock_data(Data, #state{status           = get_body,
                                             {error, {Reason, {stat_code, 
StatCode}, Headers}}),
                     {stop, normal, State};
                 State_1 ->
-                    active_once(State_1),
+                    _ = active_once(State_1),
                     State_2 = set_inac_timer(State_1),
                     {noreply, State_2}
             end;
@@ -325,14 +333,14 @@ handle_sock_data(Data, #state{status           = get_body,
                     {stop, normal, State};
                 #state{cur_req = #request{caller_controls_socket = Ccs},
                        interim_reply_sent = Irs} = State_1 ->
-                    case Irs of
-                        true ->
-                            active_once(State_1);
-                        false when Ccs == true ->
-                            do_setopts(Socket, [{active, once}], State);
-                        false ->
-                            active_once(State_1)
-                    end,
+                    _ = case Irs of
+                           true ->
+                               active_once(State_1);
+                           false when Ccs == true ->
+                               do_setopts(Socket, [{active, once}], State);
+                           false ->
+                               active_once(State_1)
+                       end,
                     State_2 = State_1#state{interim_reply_sent = false},
                     case Ccs of
                     true ->
@@ -342,7 +350,7 @@ handle_sock_data(Data, #state{status           = get_body,
                         {noreply, set_inac_timer(State_2)}
                     end;
                 State_1 ->
-                    active_once(State_1),
+                    _ = active_once(State_1),
                     State_2 = set_inac_timer(State_1),
                     {noreply, State_2}
             end
@@ -464,7 +472,9 @@ handle_sock_closed(#state{reply_buffer = Buf, reqs = Reqs, 
http_status_code = SC
                          }=State) ->
     #request{from=From, stream_to=StreamTo, req_id=ReqId,
              response_format = Resp_format,
-             options = Options} = CurReq,
+             options = Options,
+             raw_req = Raw_req
+            } = CurReq,
     case IsClosing of
         true ->
             {_, Reqs_1} = queue:out(Reqs),
@@ -475,11 +485,16 @@ handle_sock_closed(#state{reply_buffer = Buf, reqs = 
Reqs, http_status_code = SC
                            ok = file:close(Fd),
                            {file, TmpFilename}
                    end,
+            Give_raw_req = get_value(return_raw_request, Options, false),
             Reply = case get_value(give_raw_headers, Options, false) of
-                          true ->
+                        true when Give_raw_req == false->
                             {ok, Status_line, Raw_headers, Body};
+                        true ->
+                            {ok, Status_line, Raw_headers, Body, Raw_req};
+                        false when Give_raw_req == false ->
+                            {ok, SC, Headers, Buf};
                         false ->
-                            {ok, SC, Headers, Buf}
+                            {ok, SC, Headers, Buf, Raw_req}
                     end,
             State_1 = do_reply(State, From, StreamTo, ReqId, Resp_format, 
Reply),
             ok = do_error_reply(State_1#state{reqs = Reqs_1}, 
connection_closed),
@@ -489,25 +504,19 @@ handle_sock_closed(#state{reply_buffer = Buf, reqs = 
Reqs, http_status_code = SC
             State
     end.
 
-do_connect(Host, Port, Options, #state{socks5_host = SocksHost}=State, Timeout)
-  when SocksHost /= undefined ->
-    ProxyOptions = [
-        {user,     State#state.socks5_user},
-        {password, State#state.socks5_password},
-        {host,     SocksHost},
-        {port,     State#state.socks5_port},
-        {is_ssl,   State#state.is_ssl},
-        {ssl_opts, State#state.ssl_options}],
-    ibrowse_socks5:connect(Host, Port, ProxyOptions,
-                           get_sock_options(SocksHost, Options, []),
-                           Timeout);
-do_connect(Host, Port, Options, #state{is_ssl         = true,
-                                       use_http_proxy = false,
-                                       ssl_options    = SSLOptions},
+do_connect(Host, Port, Options, #state{is_ssl      = true,
+                                       use_proxy   = false,
+                                       ssl_options = SSLOptions},
            Timeout) ->
     ssl:connect(Host, Port, get_sock_options(Host, Options, SSLOptions), 
Timeout);
 do_connect(Host, Port, Options, _State, Timeout) ->
-    gen_tcp:connect(Host, Port, get_sock_options(Host, Options, []), Timeout).
+    Socks5Host = get_value(socks5_host, Options, undefined),
+    case Socks5Host of
+      undefined ->
+        gen_tcp:connect(Host, Port, get_sock_options(Host, Options, []), 
Timeout);
+      _ ->
+        catch ibrowse_socks5:connect(Host, Port, Options)
+    end.
 
 get_sock_options(Host, Options, SSLOptions) ->
     Caller_socket_options = get_value(socket_options, Options, []),
@@ -554,42 +563,68 @@ filter_sock_options(Opts) ->
 
 do_send(Req, #state{socket = Sock,
                     is_ssl = true,
-                    use_http_proxy = true,
+                    use_proxy = true,
                     proxy_tunnel_setup = Pts}) when Pts /= done ->  
gen_tcp:send(Sock, Req);
 do_send(Req, #state{socket = Sock, is_ssl = true})  ->  ssl:send(Sock, Req);
 do_send(Req, #state{socket = Sock, is_ssl = false}) ->  gen_tcp:send(Sock, 
Req).
 
-%% @spec do_send_body(Sock::socket_descriptor(), Source::source_descriptor(), 
IsSSL::boolean()) -> ok | error()
-%% source_descriptor() = fun_arity_0           |
-%%                       {fun_arity_0}         |
-%%                       {fun_arity_1, term()}
-%% error() = term()
 do_send_body(Source, State, TE) when is_function(Source) ->
     do_send_body({Source}, State, TE);
 do_send_body({Source}, State, TE) when is_function(Source) ->
-    do_send_body1(Source, Source(), State, TE);
+    do_send_body_1(generate_body(Source),
+                   State, TE, []);
 do_send_body({Source, Source_state}, State, TE) when is_function(Source) ->
-    do_send_body1(Source, Source(Source_state), State, TE);
+    do_send_body_1(generate_body({Source, Source_state}),
+                   State, TE, []);
 do_send_body(Body, State, _TE) ->
-    do_send(Body, State).
+    case do_send(Body, State) of
+        ok ->
+            {ok, Body};
+        Ret ->
+            Ret
+    end.
 
-do_send_body1(Source, Resp, State, TE) ->
+generate_body({Source, Source_state} = In) when is_function(Source) ->
+    case Source(Source_state) of
+        {ok, Data, Source_state_1} ->
+            {{ok, Data}, {Source, Source_state_1}};
+        Ret ->
+            {Ret, In}
+    end;
+generate_body(Source) when is_function(Source) ->
+    {Source(), Source}.
+
+do_send_body_1({Resp, Source}, State, TE, Acc) when is_function(Source) ->
     case Resp of
-                {ok, Data} when Data == []; Data == <<>> ->
-                        do_send_body({Source}, State, TE);
+        {ok, Data} when Data == []; Data == <<>> ->
+            do_send_body_1(generate_body(Source), State, TE, Acc);
         {ok, Data} ->
-            do_send(maybe_chunked_encode(Data, TE), State),
-            do_send_body({Source}, State, TE);
-                {ok, Data, New_source_state} when Data == []; Data == <<>> ->
-                        do_send_body({Source, New_source_state}, State, TE);
+            Acc_1 = case TE of
+                        true ->
+                            ok = do_send(maybe_chunked_encode(Data, TE), 
State),
+                            Acc;
+                        false ->
+                            [Data | Acc]
+                    end,
+            do_send_body_1(generate_body(Source), State, TE, Acc_1);
+        {ok, Data, New_source_state} when Data == []; Data == <<>> ->
+            do_send_body_1(generate_body({Source, New_source_state}), State, 
TE, Acc);
         {ok, Data, New_source_state} ->
-            do_send(maybe_chunked_encode(Data, TE), State),
-            do_send_body({Source, New_source_state}, State, TE);
+            Acc_1 = case TE of
+                        true ->
+                            ok = do_send(maybe_chunked_encode(Data, TE), 
State),
+                            Acc;
+                        false ->
+                            [Data | Acc]
+                    end,
+            do_send_body_1(generate_body({Source, New_source_state}), State, 
TE, Acc_1);
         eof when TE == true ->
-            do_send(<<"0\r\n\r\n">>, State),
-            ok;
+            ok = do_send(<<"0\r\n\r\n">>, State),
+            {ok, []};
         eof ->
-            ok;
+            Body = list_to_binary(lists:reverse(Acc)),
+            ok = do_send(Body, State),
+            {ok, Body};
         Err ->
             Err
     end.
@@ -602,7 +637,7 @@ maybe_chunked_encode(Data, true) ->
 do_close(#state{socket = undefined})            ->  ok;
 do_close(#state{socket = Sock,
                 is_ssl = true,
-                use_http_proxy = true,
+                use_proxy = true,
                 proxy_tunnel_setup = Pts
                }) when Pts /= done ->  catch gen_tcp:close(Sock);
 do_close(#state{socket = Sock, is_ssl = true})  ->  catch ssl:close(Sock);
@@ -611,11 +646,11 @@ do_close(#state{socket = Sock, is_ssl = false}) ->  catch 
gen_tcp:close(Sock).
 active_once(#state{cur_req = #request{caller_controls_socket = true}}) ->
     ok;
 active_once(#state{socket = Socket} = State) ->
-    do_setopts(Socket, [{active, once}], State).
+    _ = do_setopts(Socket, [{active, once}], State).
 
 do_setopts(_Sock, [],   _)    ->  ok;
 do_setopts(Sock, Opts, #state{is_ssl = true,
-                              use_http_proxy = true,
+                              use_proxy = true,
                               proxy_tunnel_setup = Pts}
                              ) when Pts /= done ->  inet:setopts(Sock, Opts);
 do_setopts(Sock, Opts, #state{is_ssl = true}) -> ssl:setopts(Sock, Opts);
@@ -634,28 +669,17 @@ send_req_1(From,
                 port = Port} = Url,
            Headers, Method, Body, Options, Timeout,
            #state{socket = undefined} = State) ->
-    ProxyHost = get_value(proxy_host, Options, false),
-    ProxyProtocol = get_value(proxy_protocol, Options, http),
     {Host_1, Port_1, State_1} =
-        case {ProxyHost, ProxyProtocol} of
-            {false, _} ->
+        case get_value(proxy_host, Options, false) of
+            false ->
                 {Host, Port, State};
-            {_, http} ->
+            PHost ->
                 ProxyUser     = get_value(proxy_user, Options, []),
                 ProxyPassword = get_value(proxy_password, Options, []),
                 Digest        = http_auth_digest(ProxyUser, ProxyPassword),
-                {ProxyHost, get_value(proxy_port, Options, 80),
-                 State#state{use_http_proxy = true,
-                             http_proxy_auth_digest = Digest}};
-            {_, socks5} ->
-                ProxyUser     = list_to_binary(get_value(proxy_user, Options, 
[])),
-                ProxyPassword = list_to_binary(get_value(proxy_password, 
Options, [])),
-                ProxyPort = get_value(proxy_port, Options, 1080),
-                {Host, Port,
-                 State#state{socks5_host = ProxyHost,
-                             socks5_port = ProxyPort,
-                             socks5_user = ProxyUser,
-                             socks5_password = ProxyPassword}}
+                {PHost, get_value(proxy_port, Options, 80),
+                 State#state{use_proxy = true,
+                             proxy_auth_digest = Digest}}
         end,
     State_2 = check_ssl_options(Options, State_1),
     do_trace("Connecting...~n", []),
@@ -686,7 +710,7 @@ send_req_1(From,
            Headers, Method, Body, Options, Timeout,
            #state{
                   proxy_tunnel_setup = false,
-                  use_http_proxy = true,
+                  use_proxy = true,
                   is_ssl    = true} = State) ->
     Ref = case Timeout of
               infinity ->
@@ -711,9 +735,9 @@ send_req_1(From,
     case do_send(Req, State) of
         ok ->
             case do_send_body(Body_1, State_1, TE) of
-                ok ->
+                {ok, _Sent_body} ->
                     trace_request_body(Body_1),
-                    active_once(State_1),
+                    _ = active_once(State_1),
                     State_1_1 = inc_pipeline_counter(State_1),
                     State_2 = State_1_1#state{status     = get_header,
                                               cur_req    = NewReq,
@@ -775,6 +799,11 @@ send_req_1(From,
               _ ->
                   erlang:send_after(Timeout, self(), {req_timedout, From})
           end,
+    Headers_1 = maybe_modify_headers(Url, Method, Options, Headers, State),
+    {Req, Body_1} = make_request(Method,
+                                 Headers_1,
+                                 AbsPath, RelPath, Body, Options, State,
+                                 ReqId),
     NewReq = #request{url                    = Url,
                       method                 = Method,
                       stream_to              = StreamTo,
@@ -789,26 +818,24 @@ send_req_1(From,
                       preserve_chunked_encoding = 
get_value(preserve_chunked_encoding, Options, false),
                       timer_ref              = Ref
                      },
-    State_1 = State#state{reqs=queue:in(NewReq, State#state.reqs)},
-    Headers_1 = maybe_modify_headers(Url, Method, Options, Headers, State_1),
-    {Req, Body_1} = make_request(Method,
-                                 Headers_1,
-                                 AbsPath, RelPath, Body, Options, State_1,
-                                 ReqId),
     trace_request(Req),
-    do_setopts(Socket, Caller_socket_options, State_1),
+    ok = do_setopts(Socket, Caller_socket_options, State),
     TE = is_chunked_encoding_specified(Options),
-    case do_send(Req, State_1) of
+    case do_send(Req, State) of
         ok ->
-            case do_send_body(Body_1, State_1, TE) of
-                ok ->
-                    trace_request_body(Body_1),
+            case do_send_body(Body_1, State, TE) of
+                {ok, Sent_body} ->
+                    trace_request_body(Sent_body),
+                    Raw_req = list_to_binary([Req, Sent_body]),
+                    NewReq_1 = NewReq#request{raw_req = Raw_req},
+                    State_1 = State#state{reqs=queue:in(NewReq_1, 
State#state.reqs)},
                     State_2 = inc_pipeline_counter(State_1),
-                    active_once(State_2),
+                    _ = active_once(State_2),
                     State_3 = case Status of
                                   idle ->
-                                      State_2#state{status     = get_header,
-                                                    cur_req    = NewReq};
+                                      State_2#state{
+                                        status     = get_header,
+                                        cur_req    = NewReq_1};
                                   _ ->
                                       State_2
                               end,
@@ -816,21 +843,27 @@ send_req_1(From,
                         undefined ->
                             ok;
                         _ ->
-                            gen_server:reply(From, {ibrowse_req_id, ReqId})
+                            gen_server:reply(From, {ibrowse_req_id, ReqId}),
+                            case get_value(return_raw_request, Options, false) 
of
+                                false ->
+                                    ok;
+                                true ->
+                                    catch StreamTo ! {ibrowse_async_raw_req, 
Raw_req}
+                            end
                     end,
                     State_4 = set_inac_timer(State_3),
                     {noreply, State_4};
                 Err ->
-                    shutting_down(State_1),
+                    shutting_down(State),
                     do_trace("Send failed... Reason: ~p~n", [Err]),
                     gen_server:reply(From, {error, {send_failed, Err}}),
-                    {stop, normal, State_1}
+                    {stop, normal, State}
             end;
         Err ->
-            shutting_down(State_1),
+            shutting_down(State),
             do_trace("Send failed... Reason: ~p~n", [Err]),
             gen_server:reply(From, {error, {send_failed, Err}}),
-            {stop, normal, State_1}
+            {stop, normal, State}
     end.
 
 maybe_modify_headers(#url{}, connect, _, Headers, State) ->
@@ -874,11 +907,11 @@ add_auth_headers(#url{username = User,
                 end,
     add_proxy_auth_headers(State, Headers_1).
 
-add_proxy_auth_headers(#state{use_http_proxy = false}, Headers) ->
+add_proxy_auth_headers(#state{use_proxy = false}, Headers) ->
     Headers;
-add_proxy_auth_headers(#state{http_proxy_auth_digest = []}, Headers) ->
+add_proxy_auth_headers(#state{proxy_auth_digest = []}, Headers) ->
     Headers;
-add_proxy_auth_headers(#state{http_proxy_auth_digest = Auth_digest}, Headers) 
->
+add_proxy_auth_headers(#state{proxy_auth_digest = Auth_digest}, Headers) ->
     [{"Proxy-Authorization", ["Basic ", Auth_digest]} | Headers].
 
 http_auth_digest([], []) ->
@@ -887,7 +920,7 @@ http_auth_digest(Username, Password) ->
     ibrowse_lib:encode_base64(Username ++ [$: | Password]).
 
 make_request(Method, Headers, AbsPath, RelPath, Body, Options,
-             #state{use_http_proxy = UseHttpProxy, is_ssl = Is_ssl}, ReqId) ->
+             #state{use_proxy = UseProxy, is_ssl = Is_ssl}, ReqId) ->
     HttpVsn = http_vsn_string(get_value(http_vsn, Options, {1,1})),
     Fun1 = fun({X, Y}) when is_atom(X) ->
                    {to_lower(atom_to_list(X)), X, Y};
@@ -930,7 +963,7 @@ make_request(Method, Headers, AbsPath, RelPath, Body, 
Options,
                         Headers_2
                 end,
     Headers_4 = cons_headers(Headers_3),
-    Uri = case get_value(use_absolute_uri, Options, false) or UseHttpProxy of
+    Uri = case get_value(use_absolute_uri, Options, false) or UseProxy of
               true ->
                   case Is_ssl of
                       true ->
@@ -1028,7 +1061,8 @@ parse_response(Data, #state{reply_buffer = Acc, reqs = 
Reqs,
                             cur_req = CurReq} = State) ->
     #request{from=From, stream_to=StreamTo, req_id=ReqId,
              method=Method, response_format = Resp_format,
-             options = Options, timer_ref = T_ref
+             options = Options, timer_ref = T_ref,
+             raw_req = Raw_req
             } = CurReq,
     MaxHeaderSize = ibrowse:get_config_value(max_headers_size, infinity),
     case scan_header(Acc, Data) of
@@ -1048,6 +1082,7 @@ parse_response(Data, #state{reply_buffer = Acc, reqs = 
Reqs,
                               State
                       end,
             Give_raw_headers = get_value(give_raw_headers, Options, false),
+            Give_raw_req = get_value(return_raw_request, Options, false),
             State_1 = case Give_raw_headers of
                           true ->
                               State_0#state{recvd_headers=Headers_1, 
status=get_body,
@@ -1061,7 +1096,9 @@ parse_response(Data, #state{reply_buffer = Acc, reqs = 
Reqs,
                                             http_status_code=StatCode}
                       end,
             put(conn_close, ConnClose),
-            TransferEncoding = to_lower(get_value("transfer-encoding", 
LCHeaders, "false")),
+            TransferEncodings = to_lower(get_value("transfer-encoding", 
LCHeaders, "false")),
+            IsChunked = lists:any(fun(Enc) -> string:strip(Enc) =:= "chunked" 
end,
+                                  string:tokens(TransferEncodings, ",")),
             Head_response_with_body = lists:member({workaround, 
head_response_with_body}, Options),
             case get_value("content-length", LCHeaders, undefined) of
                 _ when Method == connect,
@@ -1086,8 +1123,13 @@ parse_response(Data, #state{reply_buffer = Acc, reqs = 
Reqs,
                     %% there was still a body.  Issue #67 on Github
                     {_, Reqs_1} = queue:out(Reqs),
                     send_async_headers(ReqId, StreamTo, Give_raw_headers, 
State_1),
-                    State_1_1 = do_reply(State_1, From, StreamTo, ReqId, 
Resp_format,
-                                         {ok, StatCode, Headers_1, []}),
+                    Reply = case Give_raw_req of
+                                false ->
+                                    {ok, StatCode, Headers_1, []};
+                                true ->
+                                    {ok, StatCode, Headers_1, [], Raw_req}
+                            end,
+                    State_1_1 = do_reply(State_1, From, StreamTo, ReqId, 
Resp_format, Reply),
                     cancel_timer(T_ref, {eat_message, {req_timedout, From}}),
                     State_2 = reset_state(State_1_1),
                     State_3 = set_cur_request(State_2#state{reqs = Reqs_1}),
@@ -1106,13 +1148,18 @@ parse_response(Data, #state{reply_buffer = Acc, reqs = 
Reqs,
                     %% RFC2616 - Sec 4.4
                     {_, Reqs_1} = queue:out(Reqs),
                     send_async_headers(ReqId, StreamTo, Give_raw_headers, 
State_1),
-                    State_1_1 = do_reply(State_1, From, StreamTo, ReqId, 
Resp_format,
-                                         {ok, StatCode, Headers_1, []}),
+                    Reply = case Give_raw_req of
+                                true ->
+                                    {ok, StatCode, Headers_1, []};
+                                false ->
+                                    {ok, StatCode, Headers_1, [], Raw_req}
+                            end,
+                    State_1_1 = do_reply(State_1, From, StreamTo, ReqId, 
Resp_format, Reply),
                     cancel_timer(T_ref, {eat_message, {req_timedout, From}}),
                     State_2 = reset_state(State_1_1),
                     State_3 = set_cur_request(State_2#state{reqs = Reqs_1}),
                     parse_response(Data_1, State_3);
-                _ when TransferEncoding =:= "chunked" ->
+                _ when IsChunked ->
                     do_trace("Chunked encoding detected...~n",[]),
                     send_async_headers(ReqId, StreamTo, Give_raw_headers, 
State_1),
                     case parse_11_response(Data_1, 
State_1#state{transfer_encoding=chunked,
@@ -1130,6 +1177,25 @@ parse_response(Data, #state{reply_buffer = Acc, reqs = 
Reqs,
                                ConnClose =:= "close" ->
                     send_async_headers(ReqId, StreamTo, Give_raw_headers, 
State_1),
                     State_1#state{reply_buffer = Data_1};
+                undefined when StatCode =:= "303" ->
+                    %% Some servers send 303 requests without a body.
+                    %% RFC2616 says that they SHOULD, but they dont.
+                    case ibrowse:get_config_value(allow_303_with_no_body, 
false) of
+                      false -> 
+                        fail_pipelined_requests(State_1,
+                                            {error, {content_length_undefined,
+                                                     {stat_code, StatCode}, 
Headers}}),
+                       {error, content_length_undefined};
+                      true -> 
+                        {_, Reqs_1} = queue:out(Reqs),
+                        send_async_headers(ReqId, StreamTo, Give_raw_headers, 
State_1),
+                        State_1_1 = do_reply(State_1, From, StreamTo, ReqId, 
Resp_format,
+                                             {ok, StatCode, Headers_1, []}),
+                        cancel_timer(T_ref, {eat_message, {req_timedout, 
From}}),
+                        State_2 = reset_state(State_1_1),
+                        State_3 = set_cur_request(State_2#state{reqs = 
Reqs_1}),
+                        parse_response(Data_1, State_3)
+                    end;
                 undefined ->
                     fail_pipelined_requests(State_1,
                                             {error, {content_length_undefined,
@@ -1335,7 +1401,8 @@ handle_response(#request{from=From, stream_to=StreamTo, 
req_id=ReqId,
                          tmp_file_name = TmpFilename,
                          tmp_file_fd = Fd,
                          options       = Options,
-                         timer_ref     = ReqTimer
+                         timer_ref     = ReqTimer,
+                         raw_req       = Raw_req
                         },
                 #state{http_status_code = SCode,
                        status_line   = Status_line,
@@ -1356,18 +1423,25 @@ handle_response(#request{from=From, stream_to=StreamTo, 
req_id=ReqId,
                            {file, TmpFilename}
                    end,
     {Resp_headers_1, Raw_headers_1} = maybe_add_custom_headers(RespHeaders, 
Raw_headers, Options),
+    Give_raw_req = get_value(return_raw_request, Options, false),
     Reply = case get_value(give_raw_headers, Options, false) of
-                true ->
+                true when Give_raw_req == false ->
                     {ok, Status_line, Raw_headers_1, ResponseBody};
+                true ->
+                    {ok, Status_line, Raw_headers_1, ResponseBody, Raw_req};
+                false when Give_raw_req == false ->
+                    {ok, SCode, Resp_headers_1, ResponseBody};
                 false ->
-                    {ok, SCode, Resp_headers_1, ResponseBody}
+                    {ok, SCode, Resp_headers_1, ResponseBody, Raw_req}
             end,
     State_1 = do_reply(State, From, StreamTo, ReqId, Resp_format, Reply),
     cancel_timer(ReqTimer, {eat_message, {req_timedout, From}}),
     set_cur_request(State_1);
 handle_response(#request{from=From, stream_to=StreamTo, req_id=ReqId,
                          response_format = Resp_format,
-                         options = Options, timer_ref = ReqTimer},
+                         options = Options, timer_ref = ReqTimer,
+                         raw_req  = Raw_req
+                        },
                 #state{http_status_code = SCode,
                        status_line      = Status_line,
                        raw_headers      = Raw_headers,
@@ -1376,11 +1450,16 @@ handle_response(#request{from=From, stream_to=StreamTo, 
req_id=ReqId,
                       } = State) ->
     Body = RepBuf,
     {Resp_headers_1, Raw_headers_1} = maybe_add_custom_headers(Resp_headers, 
Raw_headers, Options),
+    Give_raw_req = get_value(return_raw_request, Options, false),
     Reply = case get_value(give_raw_headers, Options, false) of
-                true ->
+                true when Give_raw_req == false ->
                     {ok, Status_line, Raw_headers_1, Body};
+                true ->
+                    {ok, Status_line, Raw_headers_1, Body, Raw_req};
+                false when Give_raw_req == false ->
+                    {ok, SCode, Resp_headers_1, Body};
                 false ->
-                    {ok, SCode, Resp_headers_1, Body}
+                    {ok, SCode, Resp_headers_1, Body, Raw_req}
             end,
     State_1 = do_reply(State, From, StreamTo, ReqId, Resp_format, Reply),
     cancel_timer(ReqTimer, {eat_message, {req_timedout, From}}),
@@ -1407,12 +1486,8 @@ set_cur_request(#state{reqs = Reqs, socket = Socket} = 
State) ->
         empty ->
             State#state{cur_req = undefined};
         {value, #request{caller_controls_socket = Ccs} = NextReq} ->
-            case Ccs of
-                true ->
-                    do_setopts(Socket, [{active, once}], State);
-                _ ->
-                    ok
-            end,
+            _ = Ccs =:= true
+               andalso do_setopts(Socket, [{active, once}], State),
             State#state{cur_req = NextReq}
     end.
 
@@ -1586,6 +1661,7 @@ get_crlf_pos(<<>>, _)                     -> no.
 fmt_val(L) when is_list(L)    -> L;
 fmt_val(I) when is_integer(I) -> integer_to_list(I);
 fmt_val(A) when is_atom(A)    -> atom_to_list(A);
+fmt_val(B) when is_binary(B)    -> B;
 fmt_val(Term)                 -> io_lib:format("~p", [Term]).
 
 crnl() -> "\r\n".
@@ -1863,13 +1939,13 @@ dec_pipeline_counter(#state{lb_ets_tid = undefined} = 
State) ->
     State;
 dec_pipeline_counter(#state{cur_pipeline_size = Pipe_sz,
                             lb_ets_tid = Tid} = State) ->
-    try
-        update_counter(Tid, self(), {2,-1,0,0}),
-        update_counter(Tid, self(), {3,-1,0,0})
-    catch
-        _:_ ->
-            ok
-    end,
+    _ = try
+           update_counter(Tid, self(), {2,-1,0,0}),
+           update_counter(Tid, self(), {3,-1,0,0})
+       catch
+           _:_ ->
+               ok
+       end,
     State#state{cur_pipeline_size = Pipe_sz - 1}.
 
 flatten([H | _] = L) when is_integer(H) ->
@@ -1941,5 +2017,7 @@ trace_request_body(Body) ->
             ok
     end.
 
-to_binary(X) when is_list(X)   -> list_to_binary(X);
-to_binary(X) when is_binary(X) -> X.
+to_binary({X, _}) when is_function(X) -> to_binary(X);
+to_binary(X) when is_function(X)      -> <<"body generated by function">>;
+to_binary(X) when is_list(X)          -> list_to_binary(X);
+to_binary(X) when is_binary(X)        -> X.

http://git-wip-us.apache.org/repos/asf/couchdb/blob/b63f393b/src/ibrowse/ibrowse_lb.erl
----------------------------------------------------------------------
diff --git a/src/ibrowse/ibrowse_lb.erl b/src/ibrowse/ibrowse_lb.erl
index d98cf32..f5a9aef 100644
--- a/src/ibrowse/ibrowse_lb.erl
+++ b/src/ibrowse/ibrowse_lb.erl
@@ -16,7 +16,7 @@
 %% External exports
 -export([
         start_link/1,
-        spawn_connection/5,
+        spawn_connection/6,
          stop/1
        ]).
 
@@ -81,13 +81,14 @@ init([Host, Port]) ->
 spawn_connection(Lb_pid, Url,
                 Max_sessions,
                 Max_pipeline_size,
-                SSL_options)
+                SSL_options,
+                Process_options)
   when is_pid(Lb_pid),
        is_record(Url, url),
        is_integer(Max_pipeline_size),
        is_integer(Max_sessions) ->
     gen_server:call(Lb_pid,
-                   {spawn_connection, Url, Max_sessions, Max_pipeline_size, 
SSL_options}).
+                   {spawn_connection, Url, Max_sessions, Max_pipeline_size, 
SSL_options, Process_options}).
 
 stop(Lb_pid) ->
     case catch gen_server:call(Lb_pid, stop) of
@@ -123,19 +124,19 @@ handle_call(_, _From, #state{proc_state = shutting_down} 
= State) ->
     {reply, {error, shutting_down}, State};
 
 %% Update max_sessions in #state with supplied value
-handle_call({spawn_connection, _Url, Max_sess, Max_pipe, _}, _From,
-           #state{num_cur_sessions = Num} = State) 
+handle_call({spawn_connection, _Url, Max_sess, Max_pipe, _, _}, _From,
+           #state{num_cur_sessions = Num} = State)
     when Num >= Max_sess ->
     State_1 = maybe_create_ets(State),
     Reply = find_best_connection(State_1#state.ets_tid, Max_pipe),
     {reply, Reply, State_1#state{max_sessions = Max_sess,
                                  max_pipeline_size = Max_pipe}};
 
-handle_call({spawn_connection, Url, Max_sess, Max_pipe, SSL_options}, _From,
+handle_call({spawn_connection, Url, Max_sess, Max_pipe, SSL_options, 
Process_options}, _From,
            #state{num_cur_sessions = Cur} = State) ->
     State_1 = maybe_create_ets(State),
     Tid = State_1#state.ets_tid,
-    {ok, Pid} = ibrowse_http_client:start_link({Tid, Url, SSL_options}),
+    {ok, Pid} = ibrowse_http_client:start_link({Tid, Url, SSL_options}, 
Process_options),
     ets:insert(Tid, {Pid, 0, 0}),
     {reply, {ok, Pid}, State_1#state{num_cur_sessions = Cur + 1,
                                      max_sessions = Max_sess,
@@ -230,7 +231,9 @@ code_change(_OldVsn, State, _Extra) ->
 %%% Internal functions
 %%--------------------------------------------------------------------
 find_best_connection(Tid, Max_pipe) ->
+    ets:safe_fixtable(Tid, true),
     Res = find_best_connection(ets:first(Tid), Tid, Max_pipe),
+    ets:safe_fixtable(Tid, false),
     Res.
 
 find_best_connection('$end_of_table', _, _) ->
@@ -239,9 +242,14 @@ find_best_connection(Pid, Tid, Max_pipe) ->
     case ets:lookup(Tid, Pid) of
         [{Pid, Cur_sz, Speculative_sz}] when Cur_sz < Max_pipe,
                                              Speculative_sz < Max_pipe ->
-            ets:update_counter(Tid, Pid, {3, 1, 9999999, 9999999}),
-            {ok, Pid};
-        _ ->
+            case catch ets:update_counter(Tid, Pid, {3, 1, 9999999, 9999999}) 
of
+                {'EXIT', _} ->
+                    %% The selected process has shutdown
+                    find_best_connection(ets:next(Tid, Pid), Tid, Max_pipe);
+                _ ->
+                    {ok, Pid}
+            end;
+         _ ->
             find_best_connection(ets:next(Tid, Pid), Tid, Max_pipe)
     end.
 

http://git-wip-us.apache.org/repos/asf/couchdb/blob/b63f393b/src/ibrowse/ibrowse_lib.erl
----------------------------------------------------------------------
diff --git a/src/ibrowse/ibrowse_lib.erl b/src/ibrowse/ibrowse_lib.erl
index 7b12cb3..1ce6bd4 100644
--- a/src/ibrowse/ibrowse_lib.erl
+++ b/src/ibrowse/ibrowse_lib.erl
@@ -362,10 +362,9 @@ parse_url([], get_password, Url, TmpAcc) ->
 parse_url([], State, Url, TmpAcc) ->
     {invalid_uri_2, State, Url, TmpAcc}.
 
-default_port(socks5) -> 1080;
-default_port(http)   -> 80;
-default_port(https)  -> 443;
-default_port(ftp)    -> 21.
+default_port(http)  -> 80;
+default_port(https) -> 443;
+default_port(ftp)   -> 21.
 
 printable_date() ->
     {{Y,Mo,D},{H, M, S}} = calendar:local_time(),

http://git-wip-us.apache.org/repos/asf/couchdb/blob/b63f393b/src/ibrowse/ibrowse_socks5.erl
----------------------------------------------------------------------
diff --git a/src/ibrowse/ibrowse_socks5.erl b/src/ibrowse/ibrowse_socks5.erl
index d00df44..e6d8913 100644
--- a/src/ibrowse/ibrowse_socks5.erl
+++ b/src/ibrowse/ibrowse_socks5.erl
@@ -1,109 +1,58 @@
-% Licensed under the Apache License, Version 2.0 (the "License"); you may not
-% use this file except in compliance with the License. You may obtain a copy of
-% the License at
-%
-%   http://www.apache.org/licenses/LICENSE-2.0
-%
-% Unless required by applicable law or agreed to in writing, software
-% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-% License for the specific language governing permissions and limitations under
-% the License.
-
 -module(ibrowse_socks5).
 
--define(VERSION, 5).
--define(CONNECT, 1).
+-include_lib("kernel/src/inet_dns.hrl").
 
--define(NO_AUTH, 0).
--define(USERPASS, 2).
--define(UNACCEPTABLE, 16#FF).
--define(RESERVED, 0).
+-export([connect/3]).
 
--define(ATYP_IPV4, 1).
--define(ATYP_DOMAINNAME, 3).
--define(ATYP_IPV6, 4).
+-define(TIMEOUT, 2000).
 
--define(SUCCEEDED, 0).
+-define(SOCKS5, 5).
+-define(AUTH_METHOD_NO, 0).
+-define(AUTH_METHOD_USERPASS, 2).
+-define(ADDRESS_TYPE_IP4, 1).
+-define(COMMAND_TYPE_TCPIP_STREAM, 1).
+-define(RESERVER, 0).
+-define(STATUS_GRANTED, 0).
 
--export([connect/5]).
+-define(DNS_IP, {8,8,8,8}).
 
--import(ibrowse_lib, [get_value/2, get_value/3]).
+connect(Host, Port, Options) ->
+    Socks5Host = proplists:get_value(socks5_host, Options),
+    Socks5Port = proplists:get_value(socks5_port, Options),
 
-connect(TargetHost, TargetPort, ProxyOptions, Options, Timeout) ->
-    case gen_tcp:connect(get_value(host, ProxyOptions),
-                         get_value(port, ProxyOptions),
-                         Options, Timeout) of
-        {ok, Socket} ->
-            case handshake(Socket, Options) of
-                ok ->
-                    case connect(TargetHost, TargetPort, Socket) of
-                        ok ->
-                            maybe_ssl(Socket, ProxyOptions, Timeout);
-                        Else ->
-                            gen_tcp:close(Socket),
-                            Else
-                    end;
-                Else ->
-                    gen_tcp:close(Socket),
-                    Else
-            end;
-        Else ->
-            Else
-    end.
+    {ok, Socket} = gen_tcp:connect(Socks5Host, Socks5Port, [binary, {packet, 
0}, {keepalive, true}, {active, false}]),
 
-handshake(Socket, ProxyOptions) when is_port(Socket) ->
-    {Handshake, Success} = case get_value(user, ProxyOptions, <<>>) of
-        <<>> ->
-            {<<?VERSION, 1, ?NO_AUTH>>, ?NO_AUTH};
-        User ->
-            Password = get_value(password, ProxyOptions, <<>>),
-            {<<?VERSION, 1, ?USERPASS, (byte_size(User)), User,
-               (byte_size(Password)), Password>>, ?USERPASS}
-    end,
-    ok = gen_tcp:send(Socket, Handshake),
-    case gen_tcp:recv(Socket, 0) of
-        {ok, <<?VERSION, Success>>} ->
-            ok;
-        {ok, <<?VERSION, ?UNACCEPTABLE>>} ->
-            {error, unacceptable};
-        {error, Reason} ->
-            {error, Reason}
-    end.
+    {ok, _Bin} =
+    case proplists:get_value(socks5_user, Options, undefined) of
+        undefined ->
+            ok = gen_tcp:send(Socket, <<?SOCKS5, 1, ?AUTH_METHOD_NO>>),
+            {ok, <<?SOCKS5, ?AUTH_METHOD_NO>>} = gen_tcp:recv(Socket, 2, 
?TIMEOUT);
+        _Else ->
+            Socks5User = list_to_binary(proplists:get_value(socks5_user, 
Options)),
+            Socks5Pass = list_to_binary(proplists:get_value(socks5_pass, 
Options)),
+
+            ok = gen_tcp:send(Socket, <<?SOCKS5, 1, ?AUTH_METHOD_USERPASS>>),
+            {ok, <<?SOCKS5, ?AUTH_METHOD_USERPASS>>} = gen_tcp:recv(Socket, 2, 
?TIMEOUT),
 
-connect(Host, Port, Via) when is_list(Host) ->
-    connect(list_to_binary(Host), Port, Via);
-connect(Host, Port, Via) when is_binary(Host), is_integer(Port),
-                              is_port(Via) ->
-    ok = gen_tcp:send(Via,
-        <<?VERSION, ?CONNECT, ?RESERVED, ?ATYP_DOMAINNAME,
-          (byte_size(Host)), Host/binary,
-          (Port):16>>),
-    case gen_tcp:recv(Via, 0) of
-        {ok, <<?VERSION, ?SUCCEEDED, ?RESERVED, _/binary>>} ->
-            ok;
-        {ok, <<?VERSION, Rep, ?RESERVED, _/binary>>} ->
-            {error, rep(Rep)};
-        {error, Reason} ->
-            {error, Reason}
-    end.
+            UserLength = byte_size(Socks5User),
 
-maybe_ssl(Socket, ProxyOptions, Timeout) ->
-    IsSsl = get_value(is_ssl, ProxyOptions, false),
-    SslOpts = get_value(ssl_opts, ProxyOptions, []),
-    case IsSsl of
-        false ->
-            {ok, Socket};
-        true ->
-            ssl:connect(Socket, SslOpts, Timeout)
-    end.
+            ok = gen_tcp:send(Socket, << 1, UserLength >>),
+            ok = gen_tcp:send(Socket, Socks5User),
+            PassLength = byte_size(Socks5Pass),
+            ok = gen_tcp:send(Socket, << PassLength >>),
+            ok = gen_tcp:send(Socket, Socks5Pass),
+            {ok, <<1, 0>>} = gen_tcp:recv(Socket, 2, ?TIMEOUT)
+    end,
+
+    {IP1,IP2,IP3,IP4} = case inet_parse:address(Host) of
+        {ok, IP} ->
+            IP;
+        _Other ->
+            {ok, NsData} = inet_res:nslookup(Host, in, a, [{?DNS_IP, 53}]),
+            [Addr | _NewAnList] = [D || #dns_rr{data=D, type=a} <- 
NsData#dns_rec.anlist],
+            Addr
+    end,
 
-rep(0) -> succeeded;
-rep(1) -> server_fail;
-rep(2) -> disallowed_by_ruleset;
-rep(3) -> network_unreachable;
-rep(4) -> host_unreachable;
-rep(5) -> connection_refused;
-rep(6) -> ttl_expired;
-rep(7) -> command_not_supported;
-rep(8) -> address_type_not_supported.
+    ok = gen_tcp:send(Socket, <<?SOCKS5, ?COMMAND_TYPE_TCPIP_STREAM, 
?RESERVER, ?ADDRESS_TYPE_IP4, IP1, IP2, IP3, IP4, Port:16>>),
+    {ok, << ?SOCKS5, ?STATUS_GRANTED, ?RESERVER, ?ADDRESS_TYPE_IP4, IP1, IP2, 
IP3, IP4, Port:16 >>} = gen_tcp:recv(Socket, 10, ?TIMEOUT),
+    {ok, Socket}.

Reply via email to