Sfoglia il codice sorgente

Add SOCKS5 Support

Supports HTTP and HTTPS destinations and performs all hostname
resolution via the SOCKS proxy, preventing privacy leakage if used
with Tor.

If hostname is a valid IP address then it is used directly, the
hostname is not resolved again via the SOCKS5 proxy (Patch suggested
by Klaus Trainer but implementation here slightly different).
pull/117/head
Robert Newson 10 anni fa
parent
commit
b5b26d5de3
4 ha cambiato i file con 142 aggiunte e 7 eliminazioni
  1. +15
    -0
      README.md
  2. +18
    -7
      src/ibrowse_http_client.erl
  3. +1
    -0
      src/ibrowse_lib.erl
  4. +108
    -0
      src/ibrowse_socks5.erl

+ 15
- 0
README.md Vedi File

@ -27,6 +27,7 @@ ibrowse is a HTTP client written in erlang.
* Asynchronous requests. Responses are streamed to a process
* Basic authentication
* Supports proxy authentication
* Supports socks5
* Can talk to secure webservers using SSL
* *Any other features in the code not listed here :)*
@ -279,3 +280,17 @@ support this. Nor did www.google.com. But good old BBC supports this:
{"Via","1.1 hatproxy01 (NetCache NetApp/5.6.2)"}],
"TRACE / HTTP/1.1\r\nHost: www.bbc.co.uk\r\nConnection: keep-alive\r\nX-Forwarded-For: 172.24.28.29\r\nVia: 1.1 hatproxy01 (NetCache NetApp/5.6.2)\r\nCookie: BBC-UID=7452e...\r\n\r\n"}
```
A `GET` using a socks5:
```erlang
ibrowse:send_req("http://google.com", [], get, [],
[{socks5_host, "127.0.0.1"},
{socks5_port, 5335}]).
ibrowse:send_req("http://google.com", [], get, [],
[{socks5_host, "127.0.0.1"},
{socks5_port, 5335},
{socks5_user, "user4321"},
{socks5_pass, "pass7654"}]).
```

+ 18
- 7
src/ibrowse_http_client.erl Vedi File

@ -504,13 +504,24 @@ 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) ->
gen_tcp:connect(Host, Port, get_sock_options(Host, Options, []), Timeout).
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) ->
Caller_socket_options = get_value(socket_options, Options, []),

+ 1
- 0
src/ibrowse_lib.erl Vedi File

@ -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.

+ 108
- 0
src/ibrowse_socks5.erl Vedi File

@ -0,0 +1,108 @@
% 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).
-define(NO_AUTH, 0).
-define(USERPASS, 2).
-define(UNACCEPTABLE, 16#FF).
-define(RESERVED, 0).
-define(ATYP_IPV4, 1).
-define(ATYP_DOMAINNAME, 3).
-define(ATYP_IPV6, 4).
-define(SUCCEEDED, 0).
-export([connect/5]).
-import(ibrowse_lib, [get_value/2, get_value/3]).
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.
handshake(Socket, Options) when is_port(Socket) ->
{Handshake, Success} = case get_value(socks5_user, Options, <<>>) of
<<>> ->
{<<?VERSION, 1, ?NO_AUTH>>, ?NO_AUTH};
User ->
Password = get_value(socks5_password, Options, <<>>),
{<<?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.
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, <<IP1,IP2,IP3,IP4>>};
{ok, {IP1, IP2, IP3, IP4, IP5, IP6, IP7, IP8}} ->
{?ATYP_IPV6, <<IP1,IP2,IP3,IP4,IP5,IP6,IP7,IP8>>};
_ ->
HostLength = byte_size(Host),
{?ATYP_DOMAINNAME, <<HostLength,Host/binary>>}
end,
ok = gen_tcp:send(Via,
<<?VERSION, ?CONNECT, ?RESERVED,
AddressType, Address/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.
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.

Caricamento…
Annulla
Salva