@@ -720,6 +720,174 @@ class message
720
720
}
721
721
};
722
722
723
+ asio::awaitable<void >
724
+ connect_socks5_proxy (
725
+ asio::ip::tcp::socket& stream,
726
+ urls::url_view url,
727
+ urls::url_view proxy)
728
+ {
729
+ auto executor = co_await asio::this_coro::executor;
730
+ auto resolver = asio::ip::tcp::resolver{ executor };
731
+ auto rresults = co_await resolver.async_resolve (
732
+ proxy.host (), effective_port (proxy));
733
+
734
+ // Connect to the proxy server
735
+ co_await asio::async_connect (stream, rresults);
736
+
737
+ // Greeting request
738
+ if (proxy.has_userinfo ())
739
+ {
740
+ std::uint8_t greeting_req[4 ] = { 0x05 , 0x02 , 0x00 , 0x02 };
741
+ co_await asio::async_write (stream, asio::buffer (greeting_req));
742
+ }
743
+ else
744
+ {
745
+ std::uint8_t greeting_req[3 ] = { 0x05 , 0x01 , 0x00 };
746
+ co_await asio::async_write (stream, asio::buffer (greeting_req));
747
+ }
748
+
749
+ // Greeting response
750
+ std::uint8_t greeting_resp[2 ];
751
+ co_await asio::async_read (stream, asio::buffer (greeting_resp));
752
+
753
+ if (greeting_resp[0 ] != 0x05 )
754
+ throw std::runtime_error{ " SOCKS5 invalid version" };
755
+
756
+ switch (greeting_resp[1 ])
757
+ {
758
+ case 0x00 : // No Authentication
759
+ break ;
760
+ case 0x02 : // Username/password
761
+ {
762
+ // Authentication request
763
+ auto auth_req = std::string{ 0x01 };
764
+
765
+ auto user = proxy.encoded_user ();
766
+ auth_req.push_back (static_cast <std::uint8_t >(user.decoded_size ()));
767
+ user.decode ({}, urls::string_token::append_to (auth_req));
768
+
769
+ auto pass = proxy.encoded_password ();
770
+ auth_req.push_back (static_cast <std::uint8_t >(pass.decoded_size ()));
771
+ pass.decode ({}, urls::string_token::append_to (auth_req));
772
+
773
+ co_await asio::async_write (stream, asio::buffer (auth_req));
774
+
775
+ // Authentication response
776
+ std::uint8_t greeting_resp[2 ];
777
+ co_await asio::async_read (stream, asio::buffer (greeting_resp));
778
+
779
+ if (greeting_resp[1 ] != 0x00 )
780
+ throw std::runtime_error{
781
+ " SOCKS5 authentication failed" };
782
+ break ;
783
+ }
784
+ default :
785
+ throw std::runtime_error{
786
+ " SOCKS5 no acceptable authentication method"
787
+ };
788
+ }
789
+
790
+ // Connection request
791
+ auto conn_req = std::string{ 0x05 , 0x01 , 0x00 , 0x03 };
792
+ auto host = url.encoded_host ();
793
+ conn_req.push_back (static_cast <std::uint8_t >(host.decoded_size ()));
794
+ host.decode ({}, urls::string_token::append_to (conn_req));
795
+
796
+ std::uint16_t port = std::stoi (effective_port (url));
797
+ conn_req.push_back (static_cast <std::uint8_t >((port >> 8 ) & 0xFF ));
798
+ conn_req.push_back (static_cast <std::uint8_t >(port & 0xFF ));
799
+
800
+ co_await asio::async_write (stream, asio::buffer (conn_req));
801
+
802
+ // Connection response
803
+ std::uint8_t conn_resp_head[5 ];
804
+ co_await asio::async_read (stream, asio::buffer (conn_resp_head));
805
+
806
+ if (conn_resp_head[1 ] != 0x00 )
807
+ throw std::runtime_error{
808
+ " SOCKS5 connection request failed" };
809
+
810
+ std::string conn_resp_tail;
811
+ conn_resp_tail.resize (
812
+ [&]()
813
+ {
814
+ // subtract 1 because we have pre-read one byte
815
+ switch (conn_resp_head[3 ])
816
+ {
817
+ case 0x01 :
818
+ return 4 + 2 - 1 ; // ipv4 + port
819
+ case 0x03 :
820
+ return conn_resp_head[4 ] + 2 - 1 ; // domain name + port
821
+ case 0x04 :
822
+ return 16 + 2 - 1 ; // ipv6 + port
823
+ default :
824
+ throw std::runtime_error{
825
+ " SOCKS5 invalid address type" };
826
+ }
827
+ }());
828
+ co_await asio::async_read (stream, asio::buffer (conn_resp_tail));
829
+ }
830
+
831
+ asio::awaitable<void >
832
+ connect_http_proxy (
833
+ const po::variables_map& vm,
834
+ http_proto::context& http_proto_ctx,
835
+ asio::ip::tcp::socket& stream,
836
+ urls::url_view url,
837
+ urls::url_view proxy)
838
+ {
839
+ auto executor = co_await asio::this_coro::executor;
840
+ auto resolver = asio::ip::tcp::resolver{ executor };
841
+ auto rresults = co_await resolver.async_resolve (
842
+ proxy.host (), effective_port (proxy));
843
+
844
+ // Connect to the proxy server
845
+ co_await asio::async_connect (stream, rresults);
846
+
847
+ using http_proto::field;
848
+ auto request = http_proto::request{};
849
+ auto host_port = [&]()
850
+ {
851
+ auto rs = url.encoded_host ().decode ();
852
+ rs.push_back (' :' );
853
+ rs.append (effective_port (url));
854
+ return rs;
855
+ }();
856
+
857
+ request.set_method (http_proto::method::connect);
858
+ request.set_target (host_port);
859
+ request.set (field::host, host_port);
860
+ request.set (field::proxy_connection, " keep-alive" );
861
+
862
+ if (vm.count (" user-agent" ))
863
+ {
864
+ request.set (
865
+ field::user_agent,
866
+ vm.at (" user-agent" ).as <std::string>());
867
+ }
868
+ else
869
+ {
870
+ request.set (field::user_agent, " Boost.Http.Io" );
871
+ }
872
+
873
+ // TODO
874
+ // request.set(field::proxy_authorization, "");
875
+
876
+ auto serializer = http_proto::serializer{ http_proto_ctx };
877
+ auto parser = http_proto::response_parser{ http_proto_ctx };
878
+
879
+ serializer.start (request);
880
+ co_await http_io::async_write (stream, serializer);
881
+
882
+ parser.reset ();
883
+ parser.start ();
884
+ co_await http_io::async_read_header (stream, parser);
885
+
886
+ if (parser.get ().status () != http_proto::status::ok)
887
+ throw std::runtime_error{
888
+ " Proxy server rejected the connection" };
889
+ }
890
+
723
891
asio::awaitable<any_stream>
724
892
connect (
725
893
const po::variables_map& vm,
@@ -728,7 +896,6 @@ connect(
728
896
urls::url_view url)
729
897
{
730
898
auto executor = co_await asio::this_coro::executor;
731
- auto resolver = asio::ip::tcp::resolver{ executor };
732
899
auto stream = asio::ip::tcp::socket{ executor };
733
900
734
901
if (vm.count (" proxy" ))
@@ -738,63 +905,31 @@ connect(
738
905
if (proxy_url.has_error ())
739
906
throw system_error{ proxy_url.error (), " Failed to parse proxy" };
740
907
741
- if (proxy_url->scheme () != " http" )
742
- throw std::runtime_error{ " only HTTP proxies are supported" };
743
-
744
- // Connect to the HTTP proxy server
745
- auto rr = co_await resolver.async_resolve (
746
- proxy_url->host (), effective_port (proxy_url.value ()));
747
- co_await asio::async_connect (stream, rr);
748
-
908
+ if (proxy_url->scheme () == " http" )
749
909
{
750
- using http_proto::field;
751
- auto request = http_proto::request{};
752
- auto host = std::string{ url.encoded_host () };
753
-
754
- host.push_back (' :' );
755
- host.append (effective_port (url));
756
-
757
- request.set_method (http_proto::method::connect);
758
- request.set_target (host);
759
- request.set (field::host, host);
760
- request.set (field::proxy_connection, " keep-alive" );
761
-
762
- if (vm.count (" user-agent" ))
763
- {
764
- request.set (
765
- field::user_agent,
766
- vm.at (" user-agent" ).as <std::string>());
767
- }
768
- else
769
- {
770
- request.set (field::user_agent, " Boost.Http.Io" );
771
- }
772
-
773
- // TODO
774
- // request.set(field::proxy_authorization, "");
775
-
776
- auto serializer = http_proto::serializer{ http_proto_ctx };
777
- serializer.start (request);
778
- co_await http_io::async_write (stream, serializer);
910
+ co_await connect_http_proxy (
911
+ vm, http_proto_ctx, stream, url, proxy_url.value ());
779
912
}
780
-
913
+ else if (proxy_url-> scheme () == " socks5 " )
781
914
{
782
- auto parser = http_proto::response_parser{ http_proto_ctx };
783
- parser. reset ( );
784
- parser. start ();
785
- co_await http_io::async_read_header (stream, parser);
786
- if (parser. get (). status () != http_proto::status::ok)
787
- throw std::runtime_error{
788
- " Proxy server rejected the connection " };
915
+ co_await connect_socks5_proxy (
916
+ stream, url, proxy_url. value () );
917
+ }
918
+ else
919
+ {
920
+ throw std::runtime_error{
921
+ " only HTTP and SOCKS5 proxies are supported " };
789
922
}
790
923
}
791
924
else // no proxy
792
925
{
793
- auto rr = co_await resolver.async_resolve (
926
+ auto resolver = asio::ip::tcp::resolver{ executor };
927
+ auto rresults = co_await resolver.async_resolve (
794
928
url.host (), effective_port (url));
795
- co_await asio::async_connect (stream, rr );
929
+ co_await asio::async_connect (stream, rresults );
796
930
}
797
931
932
+ // TLS handshake
798
933
if (url.scheme () == " https" )
799
934
{
800
935
auto ssl_stream = ssl::stream<asio::ip::tcp::socket>{
0 commit comments