diff --git a/src/ibrowse_http_client.erl b/src/ibrowse_http_client.erl index eef8b9f..ad24351 100644 --- a/src/ibrowse_http_client.erl +++ b/src/ibrowse_http_client.erl @@ -504,18 +504,23 @@ handle_sock_closed(#state{reply_buffer = Buf, reqs = Reqs, http_status_code = SC State end. -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) -> - 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) +do_connect(Host, Port, Options, State, Timeout) -> + SockOptions = get_sock_options(Host, Options, State#state.ssl_options), + case {get_value(socks5_host, Options, undefined), State#state.is_ssl} of + {undefined, true} -> + ssl:connect(Host, Port, SockOptions, Timeout); + {undefined, false} -> + gen_tcp:connect(Host, Port, SockOptions, Timeout); + {_, _} -> + case {ibrowse_socks5:connect(Host, Port, Options, SockOptions, Timeout), + State#state.is_ssl} of + {{ok, Socket}, true} -> + ssl:connect(Socket, SockOptions, Timeout); + {{ok, Socket}, false} -> + {ok, Socket}; + {Else, _} -> + Else + end end. get_sock_options(Host, Options, SSLOptions) -> diff --git a/src/ibrowse_lib.erl b/src/ibrowse_lib.erl index 1ce6bd4..3362b39 100644 --- a/src/ibrowse_lib.erl +++ b/src/ibrowse_lib.erl @@ -362,6 +362,7 @@ 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. diff --git a/src/ibrowse_socks5.erl b/src/ibrowse_socks5.erl index 41d57f2..417f595 100644 --- a/src/ibrowse_socks5.erl +++ b/src/ibrowse_socks5.erl @@ -1,47 +1,108 @@ --module(ibrowse_socks5). +% 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. --export([connect/3]). +-module(ibrowse_socks5). --define(TIMEOUT, 2000). +-define(VERSION, 5). +-define(CONNECT, 1). --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). +-define(NO_AUTH, 0). +-define(USERPASS, 2). +-define(UNACCEPTABLE, 16#FF). +-define(RESERVED, 0). -connect(Host, Port, Options) -> - Socks5Host = proplists:get_value(socks5_host, Options), - Socks5Port = proplists:get_value(socks5_port, Options), +-define(ATYP_IPV4, 1). +-define(ATYP_DOMAINNAME, 3). +-define(ATYP_IPV6, 4). - {ok, Socket} = gen_tcp:connect(Socks5Host, Socks5Port, [binary, {packet, 0}, {keepalive, true}, {active, false}]), +-define(SUCCEEDED, 0). - {ok, _Bin} = - case proplists:get_value(socks5_user, Options, undefined) of - undefined -> - ok = gen_tcp:send(Socket, <>), - {ok, <>} = 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)), +-export([connect/5]). - ok = gen_tcp:send(Socket, <>), - {ok, <>} = gen_tcp:recv(Socket, 2, ?TIMEOUT), +-import(ibrowse_lib, [get_value/2, get_value/3]). - UserLength = byte_size(Socks5User), +connect(Host, Port, Options, SockOptions, Timeout) -> + Socks5Host = get_value(socks5_host, Options), + Socks5Port = get_value(socks5_port, Options), + case gen_tcp:connect(Socks5Host, Socks5Port, SockOptions, Timeout) of + {ok, Socket} -> + case handshake(Socket, Options) of + ok -> + case connect(Host, Port, Socket) of + ok -> + {ok, Socket}; + Else -> + gen_tcp:close(Socket), + Else + end; + Else -> + gen_tcp:close(Socket), + Else + end; + Else -> + Else + 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) +handshake(Socket, Options) when is_port(Socket) -> + {Handshake, Success} = case get_value(socks5_user, Options, <<>>) of + <<>> -> + {<>, ?NO_AUTH}; + User -> + Password = get_value(socks5_password, Options, <<>>), + {<>, ?USERPASS} end, + ok = gen_tcp:send(Socket, Handshake), + case gen_tcp:recv(Socket, 0) of + {ok, <>} -> + ok; + {ok, <>} -> + {error, unacceptable}; + {error, Reason} -> + {error, Reason} + end. - {ok, {IP1,IP2,IP3,IP4}} = inet:getaddr(Host, inet), +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) -> + {AddressType, Address} = case inet:parse_address(binary_to_list(Host)) of + {ok, {IP1, IP2, IP3, IP4}} -> + {?ATYP_IPV4, <>}; + {ok, {IP1, IP2, IP3, IP4, IP5, IP6, IP7, IP8}} -> + {?ATYP_IPV6, <>}; + _ -> + HostLength = byte_size(Host), + {?ATYP_DOMAINNAME, <>} + end, + ok = gen_tcp:send(Via, + <>), + case gen_tcp:recv(Via, 0) of + {ok, <>} -> + ok; + {ok, <>} -> + {error, rep(Rep)}; + {error, Reason} -> + {error, Reason} + end. - ok = gen_tcp:send(Socket, <>), - {ok, << ?SOCKS5, ?STATUS_GRANTED, ?RESERVER, ?ADDRESS_TYPE_IP4, IP1, IP2, IP3, IP4, Port:16 >>} = gen_tcp:recv(Socket, 10, ?TIMEOUT), - {ok, Socket}. +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.