2
2
//! round-tripper that works with the bitcoind RPC server. This can be used
3
3
//! if minimal dependencies are a goal and synchronous communication is ok.
4
4
5
+ #[ cfg( feature = "proxy" ) ]
6
+ use socks:: Socks5Stream ;
5
7
use std:: io:: { BufRead , BufReader , Write } ;
6
- use std:: net:: { TcpStream , ToSocketAddrs } ;
8
+ #[ cfg( not( feature = "proxy" ) ) ]
9
+ use std:: net:: TcpStream ;
10
+ use std:: net:: { SocketAddr , ToSocketAddrs } ;
7
11
use std:: time:: { Duration , Instant } ;
8
12
use std:: { error, fmt, io, net, thread} ;
9
13
@@ -18,6 +22,9 @@ use crate::{Request, Response};
18
22
/// Set to 8332, the default RPC port for bitcoind.
19
23
pub const DEFAULT_PORT : u16 = 8332 ;
20
24
25
+ /// The Default SOCKS5 Port to use for proxy connection.
26
+ pub const DEFAULT_PROXY_PORT : u16 = 9050 ;
27
+
21
28
/// Simple HTTP transport that implements the necessary subset of HTTP for
22
29
/// running a bitcoind RPC client.
23
30
#[ derive( Clone , Debug ) ]
@@ -27,6 +34,10 @@ pub struct SimpleHttpTransport {
27
34
timeout : Duration ,
28
35
/// The value of the `Authorization` HTTP header.
29
36
basic_auth : Option < String > ,
37
+ #[ cfg( feature = "proxy" ) ]
38
+ proxy_addr : net:: SocketAddr ,
39
+ #[ cfg( feature = "proxy" ) ]
40
+ proxy_auth : Option < ( String , String ) > ,
30
41
}
31
42
32
43
impl Default for SimpleHttpTransport {
@@ -39,6 +50,13 @@ impl Default for SimpleHttpTransport {
39
50
path : "/" . to_owned ( ) ,
40
51
timeout : Duration :: from_secs ( 15 ) ,
41
52
basic_auth : None ,
53
+ #[ cfg( feature = "proxy" ) ]
54
+ proxy_addr : net:: SocketAddr :: new (
55
+ net:: IpAddr :: V4 ( net:: Ipv4Addr :: new ( 127 , 0 , 0 , 1 ) ) ,
56
+ DEFAULT_PROXY_PORT ,
57
+ ) ,
58
+ #[ cfg( feature = "proxy" ) ]
59
+ proxy_auth : None ,
42
60
}
43
61
}
44
62
}
@@ -60,6 +78,20 @@ impl SimpleHttpTransport {
60
78
{
61
79
// Open connection
62
80
let request_deadline = Instant :: now ( ) + self . timeout ;
81
+ #[ cfg( feature = "proxy" ) ]
82
+ let mut sock = if let Some ( ( username, password) ) = & self . proxy_auth {
83
+ Socks5Stream :: connect_with_password (
84
+ self . proxy_addr ,
85
+ self . addr ,
86
+ username. as_str ( ) ,
87
+ password. as_str ( ) ,
88
+ ) ?
89
+ . into_inner ( )
90
+ } else {
91
+ Socks5Stream :: connect ( self . proxy_addr , self . addr ) ?. into_inner ( )
92
+ } ;
93
+
94
+ #[ cfg( not( feature = "proxy" ) ) ]
63
95
let mut sock = TcpStream :: connect_timeout ( & self . addr , self . timeout ) ?;
64
96
65
97
sock. set_read_timeout ( Some ( self . timeout ) ) ?;
@@ -246,6 +278,65 @@ fn get_lines<R: BufRead>(reader: &mut R) -> Result<String, Error> {
246
278
Ok ( body)
247
279
}
248
280
281
+ /// Do some very basic manual URL parsing because the uri/url crates
282
+ /// all have unicode-normalization as a dependency and that's broken.
283
+ fn check_url ( url : & str ) -> Result < ( SocketAddr , String ) , Error > {
284
+ // The fallback port in case no port was provided.
285
+ // This changes when the http or https scheme was provided.
286
+ let mut fallback_port = DEFAULT_PORT ;
287
+
288
+ // We need to get the hostname and the port.
289
+ // (1) Split scheme
290
+ let after_scheme = {
291
+ let mut split = url. splitn ( 2 , "://" ) ;
292
+ let s = split. next ( ) . unwrap ( ) ;
293
+ match split. next ( ) {
294
+ None => s, // no scheme present
295
+ Some ( after) => {
296
+ // Check if the scheme is http or https.
297
+ if s == "http" {
298
+ fallback_port = 80 ;
299
+ } else if s == "https" {
300
+ fallback_port = 443 ;
301
+ } else {
302
+ return Err ( Error :: url ( url, "scheme should be http or https" ) ) ;
303
+ }
304
+ after
305
+ }
306
+ }
307
+ } ;
308
+ // (2) split off path
309
+ let ( before_path, path) = {
310
+ if let Some ( slash) = after_scheme. find ( '/' ) {
311
+ ( & after_scheme[ 0 ..slash] , & after_scheme[ slash..] )
312
+ } else {
313
+ ( after_scheme, "/" )
314
+ }
315
+ } ;
316
+ // (3) split off auth part
317
+ let after_auth = {
318
+ let mut split = before_path. splitn ( 2 , '@' ) ;
319
+ let s = split. next ( ) . unwrap ( ) ;
320
+ split. next ( ) . unwrap_or ( s)
321
+ } ;
322
+
323
+ // (4) Parse into socket address.
324
+ // At this point we either have <host_name> or <host_name_>:<port>
325
+ // `std::net::ToSocketAddrs` requires `&str` to have <host_name_>:<port> format.
326
+ let mut addr = match after_auth. to_socket_addrs ( ) {
327
+ Ok ( addr) => addr,
328
+ Err ( _) => {
329
+ // Invalid socket address. Try to add port.
330
+ format ! ( "{}:{}" , after_auth, fallback_port) . to_socket_addrs ( ) ?
331
+ }
332
+ } ;
333
+
334
+ match addr. next ( ) {
335
+ Some ( a) => Ok ( ( a, path. to_owned ( ) ) ) ,
336
+ None => Err ( Error :: url ( url, "invalid hostname: error extracting socket address" ) ) ,
337
+ }
338
+ }
339
+
249
340
impl Transport for SimpleHttpTransport {
250
341
fn send_request ( & self , req : Request ) -> Result < Response , crate :: Error > {
251
342
Ok ( self . request ( req) ?)
@@ -282,66 +373,9 @@ impl Builder {
282
373
283
374
/// Set the URL of the server to the transport.
284
375
pub fn url ( mut self , url : & str ) -> Result < Self , Error > {
285
- // Do some very basic manual URL parsing because the uri/url crates
286
- // all have unicode-normalization as a dependency and that's broken.
287
-
288
- // The fallback port in case no port was provided.
289
- // This changes when the http or https scheme was provided.
290
- let mut fallback_port = DEFAULT_PORT ;
291
-
292
- // We need to get the hostname and the port.
293
- // (1) Split scheme
294
- let after_scheme = {
295
- let mut split = url. splitn ( 2 , "://" ) ;
296
- let s = split. next ( ) . unwrap ( ) ;
297
- match split. next ( ) {
298
- None => s, // no scheme present
299
- Some ( after) => {
300
- // Check if the scheme is http or https.
301
- if s == "http" {
302
- fallback_port = 80 ;
303
- } else if s == "https" {
304
- fallback_port = 443 ;
305
- } else {
306
- return Err ( Error :: url ( url, "scheme schould be http or https" ) ) ;
307
- }
308
- after
309
- }
310
- }
311
- } ;
312
- // (2) split off path
313
- let ( before_path, path) = {
314
- if let Some ( slash) = after_scheme. find ( '/' ) {
315
- ( & after_scheme[ 0 ..slash] , & after_scheme[ slash..] )
316
- } else {
317
- ( after_scheme, "/" )
318
- }
319
- } ;
320
- // (3) split off auth part
321
- let after_auth = {
322
- let mut split = before_path. splitn ( 2 , '@' ) ;
323
- let s = split. next ( ) . unwrap ( ) ;
324
- split. next ( ) . unwrap_or ( s)
325
- } ;
326
-
327
- // (4) Parse into socket address.
328
- // At this point we either have <host_name> or <host_name_>:<port>
329
- // `std::net::ToSocketAddrs` requires `&str` to have <host_name_>:<port> format.
330
- let mut addr = match after_auth. to_socket_addrs ( ) {
331
- Ok ( addr) => addr,
332
- Err ( _) => {
333
- // Invalid socket address. Try to add port.
334
- format ! ( "{}:{}" , after_auth, fallback_port) . to_socket_addrs ( ) ?
335
- }
336
- } ;
337
-
338
- self . tp . addr = match addr. next ( ) {
339
- Some ( a) => a,
340
- None => {
341
- return Err ( Error :: url ( url, "invalid hostname: error extracting socket address" ) )
342
- }
343
- } ;
344
- self . tp . path = path. to_owned ( ) ;
376
+ let url = check_url ( url) ?;
377
+ self . tp . addr = url. 0 ;
378
+ self . tp . path = url. 1 ;
345
379
Ok ( self )
346
380
}
347
381
@@ -362,6 +396,22 @@ impl Builder {
362
396
self
363
397
}
364
398
399
+ #[ cfg( feature = "proxy" ) ]
400
+ /// Add proxy address to the transport for SOCKS5 proxy
401
+ pub fn proxy_addr < S : AsRef < str > > ( mut self , proxy_addr : S ) -> Result < Self , Error > {
402
+ // We don't expect path in proxy address.
403
+ self . tp . proxy_addr = check_url ( proxy_addr. as_ref ( ) ) ?. 0 ;
404
+ Ok ( self )
405
+ }
406
+
407
+ #[ cfg( feature = "proxy" ) ]
408
+ /// Add optional proxy authentication as ('username', 'password')
409
+ pub fn proxy_auth < S : AsRef < str > > ( mut self , user : S , pass : S ) -> Self {
410
+ self . tp . proxy_auth =
411
+ Some ( ( user, pass) ) . map ( |( u, p) | ( u. as_ref ( ) . to_string ( ) , p. as_ref ( ) . to_string ( ) ) ) ;
412
+ self
413
+ }
414
+
365
415
/// Builds the final `SimpleHttpTransport`
366
416
pub fn build ( self ) -> SimpleHttpTransport {
367
417
self . tp
@@ -387,11 +437,34 @@ impl crate::Client {
387
437
}
388
438
Ok ( crate :: Client :: with_transport ( builder. build ( ) ) )
389
439
}
440
+
441
+ #[ cfg( feature = "proxy" ) ]
442
+ /// Create a new JSON_RPC client using a HTTP-Socks5 proxy transport.
443
+ pub fn http_proxy (
444
+ url : & str ,
445
+ user : Option < String > ,
446
+ pass : Option < String > ,
447
+ proxy_addr : & str ,
448
+ proxy_auth : Option < ( & str , & str ) > ,
449
+ ) -> Result < crate :: Client , Error > {
450
+ let mut builder = Builder :: new ( ) . url ( url) ?;
451
+ if let Some ( user) = user {
452
+ builder = builder. auth ( user, pass) ;
453
+ }
454
+ builder = builder. proxy_addr ( proxy_addr) ?;
455
+ if let Some ( ( user, pass) ) = proxy_auth {
456
+ builder = builder. proxy_auth ( user, pass) ;
457
+ }
458
+ let tp = builder. build ( ) ;
459
+ Ok ( crate :: Client :: with_transport ( tp) )
460
+ }
390
461
}
391
462
392
463
#[ cfg( test) ]
393
464
mod tests {
394
465
use std:: net;
466
+ #[ cfg( feature = "proxy" ) ]
467
+ use std:: str:: FromStr ;
395
468
396
469
use super :: * ;
397
470
use crate :: Client ;
@@ -432,7 +505,14 @@ mod tests {
432
505
"http://[2001:0db8:85a3:0000:0000:8a2e:0370:7334]" ,
433
506
] ;
434
507
for u in & valid_urls {
435
- Builder :: new ( ) . url ( * u) . unwrap_or_else ( |_| panic ! ( "error for: {}" , u) ) ;
508
+ let ( addr, path) = check_url ( u) . unwrap ( ) ;
509
+ let builder = Builder :: new ( ) . url ( * u) . unwrap_or_else ( |_| panic ! ( "error for: {}" , u) ) ;
510
+ assert_eq ! ( builder. tp. addr, addr) ;
511
+ assert_eq ! ( builder. tp. path, path) ;
512
+ assert_eq ! ( builder. tp. timeout, Duration :: from_secs( 15 ) ) ;
513
+ assert_eq ! ( builder. tp. basic_auth, None ) ;
514
+ #[ cfg( feature = "proxy" ) ]
515
+ assert_eq ! ( builder. tp. proxy_addr, SocketAddr :: from_str( "127.0.0.1:9050" ) . unwrap( ) ) ;
436
516
}
437
517
438
518
let invalid_urls = [
@@ -462,4 +542,27 @@ mod tests {
462
542
463
543
let _ = Client :: simple_http ( "localhost:22" , None , None ) . unwrap ( ) ;
464
544
}
545
+
546
+ #[ cfg( feature = "proxy" ) ]
547
+ #[ test]
548
+ fn construct_with_proxy ( ) {
549
+ let tp = Builder :: new ( )
550
+ . timeout ( Duration :: from_millis ( 100 ) )
551
+ . url ( "localhost:22" )
552
+ . unwrap ( )
553
+ . auth ( "user" , None )
554
+ . proxy_addr ( "127.0.0.1:9050" )
555
+ . unwrap ( )
556
+ . build ( ) ;
557
+ let _ = Client :: with_transport ( tp) ;
558
+
559
+ let _ = Client :: http_proxy (
560
+ "localhost:22" ,
561
+ None ,
562
+ None ,
563
+ "127.0.0.1:9050" ,
564
+ Some ( ( "user" , "password" ) ) ,
565
+ )
566
+ . unwrap ( ) ;
567
+ }
465
568
}
0 commit comments