66#include < aws/crt/crypto/Hash.h>
77#include < aws/crt/http/HttpConnection.h>
88#include < aws/crt/http/HttpRequestResponse.h>
9+
10+ #include < aws/crt/io/Socks5ProxyOptions.h>
911#include < aws/crt/io/Uri.h>
1012
1113#include < aws/common/command_line_parser.h>
@@ -40,11 +42,52 @@ struct ElasticurlCtx
4042
4143 std::shared_ptr<Io::IStream> InputBody = nullptr ;
4244 std::ofstream Output;
45+
46+ // SOCKS5 proxy support
47+ Aws::Crt::String ProxyHost;
48+ uint16_t ProxyPort = 0 ;
49+ bool UseProxy = false ;
50+ Aws::Crt::Optional<Aws::Crt::Io::Socks5ProxyOptions> Socks5ProxyOptions;
4351};
4452
45- static void s_Usage (int exit_code)
53+ // Parse SOCKS5 proxy URI and fill ElasticurlCtx fields
54+ static bool s_ParseProxyUri (ElasticurlCtx &ctx, const char *proxy_arg)
4655{
56+ if (!proxy_arg || proxy_arg[0 ] == ' \0 ' )
57+ {
58+ std::cerr << " Proxy URI must not be empty" << std::endl;
59+ return false ;
60+ }
61+ ByteCursor uri_cursor = aws_byte_cursor_from_c_str (proxy_arg);
62+ Io::Uri parsed_uri (uri_cursor, ctx.allocator );
63+ if (!parsed_uri)
64+ {
65+ std::cerr << " Failed to parse proxy URI \" " << proxy_arg
66+ << " \" : " << aws_error_debug_str (parsed_uri.LastError ()) << std::endl;
67+ return false ;
68+ }
69+ auto proxyOptions = Io::Socks5ProxyOptions::CreateFromUri (parsed_uri, ctx.ConnectTimeout , ctx.allocator );
70+ if (!proxyOptions)
71+ {
72+ std::cerr << " Failed to create SOCKS5 proxy options from \" " << proxy_arg
73+ << " \" : " << aws_error_debug_str (Aws::Crt::LastError ()) << std::endl;
74+ return false ;
75+ }
76+ ctx.Socks5ProxyOptions = *proxyOptions;
77+ ByteCursor host_cursor = parsed_uri.GetHostName ();
78+ ctx.ProxyHost .assign (reinterpret_cast <const char *>(host_cursor.ptr ), host_cursor.len );
79+ uint32_t port = parsed_uri.GetPort ();
80+ if (port == 0 )
81+ {
82+ port = 1080 ;
83+ }
84+ ctx.ProxyPort = static_cast <uint16_t >(port);
85+ ctx.UseProxy = true ;
86+ return true ;
87+ }
4788
89+ static void s_Usage (int exit_code)
90+ {
4891 std::cerr << " usage: elasticurl [options] url\n " ;
4992 std::cerr << " url: url to make a request to. The default is a GET request.\n " ;
5093 std::cerr << " \n Options:\n\n " ;
@@ -65,6 +108,7 @@ static void s_Usage(int exit_code)
65108 std::cerr << " -o, --output FILE: dumps content-body to FILE instead of stdout.\n " ;
66109 std::cerr << " -t, --trace FILE: dumps logs to FILE instead of stderr.\n " ;
67110 std::cerr << " -v, --verbose: ERROR|INFO|DEBUG|TRACE: log level to configure. Default is none.\n " ;
111+ std::cerr << " --proxy URL: SOCKS5 proxy URI (socks5h://[user[:pass]@]host[:port]).\n " ;
68112 std::cerr << " --version: print the version of elasticurl.\n " ;
69113 std::cerr << " --http2: HTTP/2 connection required\n " ;
70114 std::cerr << " --http1_1: HTTP/1.1 connection required\n " ;
@@ -91,6 +135,7 @@ static struct aws_cli_option s_LongOptions[] = {
91135 {" output" , AWS_CLI_OPTIONS_REQUIRED_ARGUMENT, nullptr , ' o' },
92136 {" trace" , AWS_CLI_OPTIONS_REQUIRED_ARGUMENT, nullptr , ' t' },
93137 {" verbose" , AWS_CLI_OPTIONS_REQUIRED_ARGUMENT, nullptr , ' v' },
138+ {" proxy" , AWS_CLI_OPTIONS_REQUIRED_ARGUMENT, nullptr , ' X' },
94139 {" version" , AWS_CLI_OPTIONS_NO_ARGUMENT, nullptr , ' V' },
95140 {" http2" , AWS_CLI_OPTIONS_NO_ARGUMENT, nullptr , ' w' },
96141 {" http1_1" , AWS_CLI_OPTIONS_NO_ARGUMENT, nullptr , ' W' },
@@ -104,7 +149,7 @@ static void s_ParseOptions(int argc, char **argv, ElasticurlCtx &ctx)
104149 while (true )
105150 {
106151 int option_index = 0 ;
107- int c = aws_cli_getopt_long (argc, argv, " a:b:c:e:f:H:d:g:M:GPHiko:t:v:VwWh " , s_LongOptions, &option_index);
152+ int c = aws_cli_getopt_long (argc, argv, " a:b:c:e:f:H:d:g:M:GPHiko:t:v:VwWhX: " , s_LongOptions, &option_index);
108153 if (c == -1 )
109154 {
110155 /* finished parsing */
@@ -126,6 +171,12 @@ static void s_ParseOptions(int argc, char **argv, ElasticurlCtx &ctx)
126171 s_Usage (1 );
127172 }
128173 break ;
174+ case ' X' :
175+ if (!s_ParseProxyUri (ctx, aws_cli_optarg))
176+ {
177+ s_Usage (1 );
178+ }
179+ break ;
129180 case ' a' :
130181 ctx.CaCert = aws_cli_optarg;
131182 break ;
@@ -375,28 +426,30 @@ int main(int argc, char **argv)
375426 std::promise<void > shutdownPromise;
376427
377428 auto onConnectionSetup =
378- [&appCtx, &connectionPromise](const std::shared_ptr<Http::HttpClientConnection> &newConnection, int errorCode) {
379- if (!errorCode)
429+ [&appCtx, &connectionPromise](const std::shared_ptr<Http::HttpClientConnection> &newConnection, int errorCode)
430+ {
431+ if (!errorCode)
432+ {
433+ if (appCtx.RequiredHttpVersion != Http::HttpVersion::Unknown)
380434 {
381- if (appCtx. RequiredHttpVersion != Http::HttpVersion::Unknown )
435+ if (newConnection-> GetVersion () != appCtx. RequiredHttpVersion )
382436 {
383- if (newConnection->GetVersion () != appCtx.RequiredHttpVersion )
384- {
385- std::cerr << " Error. The requested HTTP version, " << appCtx.Alpn
386- << " , is not supported by the peer." << std::endl;
387- exit (1 );
388- }
437+ std::cerr << " Error. The requested HTTP version, " << appCtx.Alpn
438+ << " , is not supported by the peer." << std::endl;
439+ exit (1 );
389440 }
390441 }
391- else
392- {
393- std::cerr << " Connection failed with error " << aws_error_debug_str (errorCode) << std::endl;
394- exit (1 );
395- }
396- connectionPromise.set_value (newConnection);
397- };
442+ }
443+ else
444+ {
445+ std::cerr << " Connection failed with error " << aws_error_debug_str (errorCode) << std::endl;
446+ exit (1 );
447+ }
448+ connectionPromise.set_value (newConnection);
449+ };
398450
399- auto onConnectionShutdown = [&shutdownPromise](Http::HttpClientConnection &newConnection, int errorCode) {
451+ auto onConnectionShutdown = [&shutdownPromise](Http::HttpClientConnection &newConnection, int errorCode)
452+ {
400453 (void )newConnection;
401454 if (errorCode)
402455 {
@@ -418,6 +471,10 @@ int main(int argc, char **argv)
418471 }
419472 httpClientConnectionOptions.HostName = String ((const char *)hostName.ptr , hostName.len );
420473 httpClientConnectionOptions.Port = port;
474+ if (appCtx.UseProxy && appCtx.Socks5ProxyOptions .has_value ())
475+ {
476+ httpClientConnectionOptions.Socks5ProxyOptions = appCtx.Socks5ProxyOptions .value ();
477+ }
421478
422479 Http::HttpClientConnection::CreateConnection (httpClientConnectionOptions, allocator);
423480
@@ -430,7 +487,8 @@ int main(int argc, char **argv)
430487 requestOptions.request = &request;
431488 std::promise<void > streamCompletePromise;
432489
433- requestOptions.onStreamComplete = [&streamCompletePromise](Http::HttpStream &stream, int errorCode) {
490+ requestOptions.onStreamComplete = [&streamCompletePromise](Http::HttpStream &stream, int errorCode)
491+ {
434492 (void )stream;
435493 if (errorCode)
436494 {
@@ -443,7 +501,8 @@ int main(int argc, char **argv)
443501 requestOptions.onIncomingHeaders = [&](Http::HttpStream &stream,
444502 enum aws_http_header_block header_block,
445503 const Http::HttpHeader *header,
446- std::size_t len) {
504+ std::size_t len)
505+ {
447506 /* Ignore informational headers */
448507 if (header_block == AWS_HTTP_HEADER_BLOCK_INFORMATIONAL)
449508 {
@@ -468,7 +527,8 @@ int main(int argc, char **argv)
468527 }
469528 }
470529 };
471- requestOptions.onIncomingBody = [&appCtx](Http::HttpStream &, const ByteCursor &data) {
530+ requestOptions.onIncomingBody = [&appCtx](Http::HttpStream &, const ByteCursor &data)
531+ {
472532 if (appCtx.Output .is_open ())
473533 {
474534 appCtx.Output .write ((char *)data.ptr , data.len );
@@ -481,9 +541,12 @@ int main(int argc, char **argv)
481541
482542 request.SetMethod (ByteCursorFromCString (appCtx.verb ));
483543 auto pathAndQuery = appCtx.uri .GetPathAndQuery ();
484- if (pathAndQuery.len > 0 ) {
544+ if (pathAndQuery.len > 0 )
545+ {
485546 request.SetPath (pathAndQuery);
486- } else {
547+ }
548+ else
549+ {
487550 request.SetPath (ByteCursorFromCString (" /" ));
488551 }
489552
0 commit comments