Fetching https
This commit is contained in:
		
							parent
							
								
									1c19a807d9
								
							
						
					
					
						commit
						25fc919913
					
				
							
								
								
									
										18
									
								
								util/https-fetch/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								util/https-fetch/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,18 @@ | ||||
| [package] | ||||
| description = "HTTPS fetching library" | ||||
| homepage = "http://ethcore.io" | ||||
| license = "GPL-3.0" | ||||
| name = "https-fetch" | ||||
| version = "0.1.0" | ||||
| authors = ["Ethcore <admin@ethcore.io>"] | ||||
| 
 | ||||
| [dependencies] | ||||
| log = "0.3" | ||||
| mio = { git = "https://github.com/ethcore/mio", branch = "v0.5.x" } | ||||
| rustls = { git = "https://github.com/ctz/rustls" } | ||||
| clippy = { version = "0.0.85", optional = true} | ||||
| 
 | ||||
| [features] | ||||
| default = [] | ||||
| ca-github-only = [] | ||||
| dev = ["clippy"] | ||||
							
								
								
									
										14
									
								
								util/https-fetch/examples/fetch.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								util/https-fetch/examples/fetch.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,14 @@ | ||||
| extern crate https_fetch; | ||||
| 
 | ||||
| use std::io; | ||||
| use https_fetch::*; | ||||
| 
 | ||||
| fn main() { | ||||
| 	let client = Client::new().unwrap(); | ||||
| 
 | ||||
| 	let rx = client.fetch(Url::new("github.com", 443, "/").unwrap(), Box::new(io::stdout())).unwrap(); | ||||
| 
 | ||||
| 	let result = rx.recv().unwrap(); | ||||
| 
 | ||||
| 	assert!(result.is_ok()); | ||||
| } | ||||
							
								
								
									
										4496
									
								
								util/https-fetch/src/ca-certificates.crt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4496
									
								
								util/https-fetch/src/ca-certificates.crt
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										93
									
								
								util/https-fetch/src/ca-github.crt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										93
									
								
								util/https-fetch/src/ca-github.crt
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,93 @@ | ||||
| -----BEGIN CERTIFICATE----- | ||||
| MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBs | ||||
| MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 | ||||
| d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j | ||||
| ZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTMxMTExMDAwMDAwMFowbDEL | ||||
| MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3 | ||||
| LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug | ||||
| RVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm | ||||
| +9S75S0tMqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTW | ||||
| PNt0OKRKzE0lgvdKpVMSOO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEM | ||||
| xChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFB | ||||
| Ik5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQNAQTXKFx01p8VdteZOE3 | ||||
| hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUeh10aUAsg | ||||
| EsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQF | ||||
| MAMBAf8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaA | ||||
| FLE+w2kD+L9HAdSYJhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3Nec | ||||
| nzyIZgYIVyHbIUf4KmeqvxgydkAQV8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6z | ||||
| eM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFpmyPInngiK3BD41VHMWEZ71jF | ||||
| hS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkKmNEVX58Svnw2 | ||||
| Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe | ||||
| vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep | ||||
| +OkuE6N36B9K | ||||
| -----END CERTIFICATE----- | ||||
| -----BEGIN CERTIFICATE----- | ||||
| MIIEtjCCA56gAwIBAgIQDHmpRLCMEZUgkmFf4msdgzANBgkqhkiG9w0BAQsFADBs | ||||
| MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 | ||||
| d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j | ||||
| ZSBFViBSb290IENBMB4XDTEzMTAyMjEyMDAwMFoXDTI4MTAyMjEyMDAwMFowdTEL | ||||
| MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3 | ||||
| LmRpZ2ljZXJ0LmNvbTE0MDIGA1UEAxMrRGlnaUNlcnQgU0hBMiBFeHRlbmRlZCBW | ||||
| YWxpZGF0aW9uIFNlcnZlciBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC | ||||
| ggEBANdTpARR+JmmFkhLZyeqk0nQOe0MsLAAh/FnKIaFjI5j2ryxQDji0/XspQUY | ||||
| uD0+xZkXMuwYjPrxDKZkIYXLBxA0sFKIKx9om9KxjxKws9LniB8f7zh3VFNfgHk/ | ||||
| LhqqqB5LKw2rt2O5Nbd9FLxZS99RStKh4gzikIKHaq7q12TWmFXo/a8aUGxUvBHy | ||||
| /Urynbt/DvTVvo4WiRJV2MBxNO723C3sxIclho3YIeSwTQyJ3DkmF93215SF2AQh | ||||
| cJ1vb/9cuhnhRctWVyh+HA1BV6q3uCe7seT6Ku8hI3UarS2bhjWMnHe1c63YlC3k | ||||
| 8wyd7sFOYn4XwHGeLN7x+RAoGTMCAwEAAaOCAUkwggFFMBIGA1UdEwEB/wQIMAYB | ||||
| Af8CAQAwDgYDVR0PAQH/BAQDAgGGMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEF | ||||
| BQcDAjA0BggrBgEFBQcBAQQoMCYwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRp | ||||
| Z2ljZXJ0LmNvbTBLBgNVHR8ERDBCMECgPqA8hjpodHRwOi8vY3JsNC5kaWdpY2Vy | ||||
| dC5jb20vRGlnaUNlcnRIaWdoQXNzdXJhbmNlRVZSb290Q0EuY3JsMD0GA1UdIAQ2 | ||||
| MDQwMgYEVR0gADAqMCgGCCsGAQUFBwIBFhxodHRwczovL3d3dy5kaWdpY2VydC5j | ||||
| b20vQ1BTMB0GA1UdDgQWBBQ901Cl1qCt7vNKYApl0yHU+PjWDzAfBgNVHSMEGDAW | ||||
| gBSxPsNpA/i/RwHUmCYaCALvY2QrwzANBgkqhkiG9w0BAQsFAAOCAQEAnbbQkIbh | ||||
| hgLtxaDwNBx0wY12zIYKqPBKikLWP8ipTa18CK3mtlC4ohpNiAexKSHc59rGPCHg | ||||
| 4xFJcKx6HQGkyhE6V6t9VypAdP3THYUYUN9XR3WhfVUgLkc3UHKMf4Ib0mKPLQNa | ||||
| 2sPIoc4sUqIAY+tzunHISScjl2SFnjgOrWNoPLpSgVh5oywM395t6zHyuqB8bPEs | ||||
| 1OG9d4Q3A84ytciagRpKkk47RpqF/oOi+Z6Mo8wNXrM9zwR4jxQUezKcxwCmXMS1 | ||||
| oVWNWlZopCJwqjyBcdmdqEU79OX2olHdx3ti6G8MdOu42vi/hw15UJGQmxg7kVkn | ||||
| 8TUoE6smftX3eg== | ||||
| -----END CERTIFICATE----- | ||||
| -----BEGIN CERTIFICATE----- | ||||
| MIIHeTCCBmGgAwIBAgIQC/20CQrXteZAwwsWyVKaJzANBgkqhkiG9w0BAQsFADB1 | ||||
| MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 | ||||
| d3cuZGlnaWNlcnQuY29tMTQwMgYDVQQDEytEaWdpQ2VydCBTSEEyIEV4dGVuZGVk | ||||
| IFZhbGlkYXRpb24gU2VydmVyIENBMB4XDTE2MDMxMDAwMDAwMFoXDTE4MDUxNzEy | ||||
| MDAwMFowgf0xHTAbBgNVBA8MFFByaXZhdGUgT3JnYW5pemF0aW9uMRMwEQYLKwYB | ||||
| BAGCNzwCAQMTAlVTMRkwFwYLKwYBBAGCNzwCAQITCERlbGF3YXJlMRAwDgYDVQQF | ||||
| Ewc1MTU3NTUwMSQwIgYDVQQJExs4OCBDb2xpbiBQIEtlbGx5LCBKciBTdHJlZXQx | ||||
| DjAMBgNVBBETBTk0MTA3MQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5p | ||||
| YTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNjbzEVMBMGA1UEChMMR2l0SHViLCBJbmMu | ||||
| MRMwEQYDVQQDEwpnaXRodWIuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB | ||||
| CgKCAQEA54hc8pZclxgcupjiA/F/OZGRwm/ZlucoQGTNTKmBEgNsrn/mxhngWmPw | ||||
| bAvUaLP//T79Jc+1WXMpxMiz9PK6yZRRFuIo0d2bx423NA6hOL2RTtbnfs+y0PFS | ||||
| /YTpQSelTuq+Fuwts5v6aAweNyMcYD0HBybkkdosFoDccBNzJ92Ac8I5EVDUc3Or | ||||
| /4jSyZwzxu9kdmBlBzeHMvsqdH8SX9mNahXtXxRpwZnBiUjw36PgN+s9GLWGrafd | ||||
| 02T0ux9Yzd5ezkMxukqEAQ7AKIIijvaWPAJbK/52XLhIy2vpGNylyni/DQD18bBP | ||||
| T+ZG1uv0QQP9LuY/joO+FKDOTler4wIDAQABo4IDejCCA3YwHwYDVR0jBBgwFoAU | ||||
| PdNQpdagre7zSmAKZdMh1Pj41g8wHQYDVR0OBBYEFIhcSGcZzKB2WS0RecO+oqyH | ||||
| IidbMCUGA1UdEQQeMByCCmdpdGh1Yi5jb22CDnd3dy5naXRodWIuY29tMA4GA1Ud | ||||
| DwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwdQYDVR0f | ||||
| BG4wbDA0oDKgMIYuaHR0cDovL2NybDMuZGlnaWNlcnQuY29tL3NoYTItZXYtc2Vy | ||||
| dmVyLWcxLmNybDA0oDKgMIYuaHR0cDovL2NybDQuZGlnaWNlcnQuY29tL3NoYTIt | ||||
| ZXYtc2VydmVyLWcxLmNybDBLBgNVHSAERDBCMDcGCWCGSAGG/WwCATAqMCgGCCsG | ||||
| AQUFBwIBFhxodHRwczovL3d3dy5kaWdpY2VydC5jb20vQ1BTMAcGBWeBDAEBMIGI | ||||
| BggrBgEFBQcBAQR8MHowJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0 | ||||
| LmNvbTBSBggrBgEFBQcwAoZGaHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0Rp | ||||
| Z2lDZXJ0U0hBMkV4dGVuZGVkVmFsaWRhdGlvblNlcnZlckNBLmNydDAMBgNVHRMB | ||||
| Af8EAjAAMIIBfwYKKwYBBAHWeQIEAgSCAW8EggFrAWkAdgCkuQmQtBhYFIe7E6LM | ||||
| Z3AKPDWYBPkb37jjd80OyA3cEAAAAVNhieoeAAAEAwBHMEUCIQCHHSEY/ROK2/sO | ||||
| ljbKaNEcKWz6BxHJNPOtjSyuVnSn4QIgJ6RqvYbSX1vKLeX7vpnOfCAfS2Y8lB5R | ||||
| NMwk6us2QiAAdgBo9pj4H2SCvjqM7rkoHUz8cVFdZ5PURNEKZ6y7T0/7xAAAAVNh | ||||
| iennAAAEAwBHMEUCIQDZpd5S+3to8k7lcDeWBhiJASiYTk2rNAT26lVaM3xhWwIg | ||||
| NUqrkIODZpRg+khhp8ag65B8mu0p4JUAmkRDbiYnRvYAdwBWFAaaL9fC7NP14b1E | ||||
| sj7HRna5vJkRXMDvlJhV1onQ3QAAAVNhieqZAAAEAwBIMEYCIQDnm3WStlvE99GC | ||||
| izSx+UGtGmQk2WTokoPgo1hfiv8zIAIhAPrYeXrBgseA9jUWWoB4IvmcZtshjXso | ||||
| nT8MIG1u1zF8MA0GCSqGSIb3DQEBCwUAA4IBAQCLbNtkxuspqycq8h1EpbmAX0wM | ||||
| 5DoW7hM/FVdz4LJ3Kmftyk1yd8j/PSxRrAQN2Mr/frKeK8NE1cMji32mJbBqpWtK | ||||
| /+wC+avPplBUbNpzP53cuTMF/QssxItPGNP5/OT9Aj1BxA/NofWZKh4ufV7cz3pY | ||||
| RDS4BF+EEFQ4l5GY+yp4WJA/xSvYsTHWeWxRD1/nl62/Rd9FN2NkacRVozCxRVle | ||||
| FrBHTFxqIP6kDnxiLElBrZngtY07ietaYZVLQN/ETyqLQftsf8TecwTklbjvm8NT | ||||
| JqbaIVifYwqwNN+4lRxS3F5lNlA/il12IOgbRioLI62o8G0DaEUQgHNf8vSG | ||||
| -----END CERTIFICATE----- | ||||
							
								
								
									
										185
									
								
								util/https-fetch/src/client.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										185
									
								
								util/https-fetch/src/client.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,185 @@ | ||||
| // Copyright 2015, 2016 Ethcore (UK) Ltd.
 | ||||
| // This file is part of Parity.
 | ||||
| 
 | ||||
| // Parity is free software: you can redistribute it and/or modify
 | ||||
| // it under the terms of the GNU General Public License as published by
 | ||||
| // the Free Software Foundation, either version 3 of the License, or
 | ||||
| // (at your option) any later version.
 | ||||
| 
 | ||||
| // Parity is distributed in the hope that it will be useful,
 | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of
 | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | ||||
| // GNU General Public License for more details.
 | ||||
| 
 | ||||
| // You should have received a copy of the GNU General Public License
 | ||||
| // along with Parity.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| use std::str; | ||||
| use std::thread; | ||||
| use std::sync::mpsc; | ||||
| use std::io::{self, Write}; | ||||
| use std::collections::HashMap; | ||||
| 
 | ||||
| use mio; | ||||
| use tlsclient::{TlsClient, TlsClientError}; | ||||
| 
 | ||||
| use url::Url; | ||||
| 
 | ||||
| #[derive(Debug)] | ||||
| pub enum FetchError { | ||||
| 	InvalidAddress, | ||||
| 	ReadingCaCertificates, | ||||
| 	CaCertificates(io::Error), | ||||
| 	Io(io::Error), | ||||
| 	Notify(mio::NotifyError<ClientMessage>), | ||||
| 	Client(TlsClientError), | ||||
| } | ||||
| 
 | ||||
| impl From<io::Error> for FetchError { | ||||
| 	fn from(e: io::Error) -> Self { | ||||
| 		FetchError::Io(e) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| impl From<mio::NotifyError<ClientMessage>> for FetchError { | ||||
| 	fn from(e: mio::NotifyError<ClientMessage>) -> Self { | ||||
| 		FetchError::Notify(e) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| impl From<TlsClientError> for FetchError { | ||||
| 	fn from(e: TlsClientError) -> Self { | ||||
| 		FetchError::Client(e) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| pub type FetchResult = Result<(), FetchError>; | ||||
| 
 | ||||
| pub enum ClientMessage { | ||||
| 	Fetch(Url, Box<io::Write + Send>, mpsc::Sender<FetchResult>), | ||||
| 	Shutdown, | ||||
| } | ||||
| 
 | ||||
| pub struct Client { | ||||
| 	channel: mio::Sender<ClientMessage>, | ||||
| 	thread: Option<thread::JoinHandle<()>>, | ||||
| } | ||||
| 
 | ||||
| impl Drop for Client { | ||||
| 	fn drop(&mut self) { | ||||
| 		if let Err(e) = self.channel.send(ClientMessage::Shutdown) { | ||||
| 			warn!("Error while closing client: {:?}. Already stopped?", e); | ||||
| 		} | ||||
| 		if let Some(thread) = self.thread.take() { | ||||
| 			thread.join().expect("Clean shutdown."); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| impl Client { | ||||
| 	pub fn new() -> Result<Self, FetchError> { | ||||
| 		let mut event_loop = try!(mio::EventLoop::new()); | ||||
| 		let channel = event_loop.channel(); | ||||
| 
 | ||||
| 		let thread = thread::spawn(move || { | ||||
| 			let mut client = ClientLoop { | ||||
| 				next_token: 0, | ||||
| 				sessions: HashMap::new(), | ||||
| 			}; | ||||
| 			event_loop.run(&mut client).unwrap(); | ||||
| 		}); | ||||
| 
 | ||||
| 		Ok(Client { | ||||
| 			channel: channel, | ||||
| 			thread: Some(thread), | ||||
| 		}) | ||||
| 	} | ||||
| 
 | ||||
| 	pub fn fetch(&self, url: Url, writer: Box<io::Write + Send>) -> Result<mpsc::Receiver<FetchResult>, FetchError> { | ||||
| 		let (tx, rx) = mpsc::channel(); | ||||
| 		try!(self.channel.send(ClientMessage::Fetch(url, writer, tx))); | ||||
| 		Ok(rx) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| pub struct ClientLoop { | ||||
| 	next_token: usize, | ||||
| 	sessions: HashMap<usize, TlsClient>, | ||||
| } | ||||
| 
 | ||||
| impl mio::Handler for ClientLoop { | ||||
| 	type Timeout = (); | ||||
| 	type Message = ClientMessage; | ||||
| 
 | ||||
| 	fn ready(&mut self, event_loop: &mut mio::EventLoop<ClientLoop>, token: mio::Token, events: mio::EventSet) { | ||||
| 		let utoken = token.as_usize(); | ||||
| 		let remove = if let Some(mut tlsclient) = self.sessions.get_mut(&utoken) { | ||||
| 			tlsclient.ready(event_loop, token, events) | ||||
| 		} else { | ||||
| 			false | ||||
| 		}; | ||||
| 
 | ||||
| 		if remove { | ||||
| 			self.sessions.remove(&utoken); | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	fn notify(&mut self, event_loop: &mut mio::EventLoop<Self>, msg: Self::Message) { | ||||
| 		match msg { | ||||
| 			ClientMessage::Shutdown => event_loop.shutdown(), | ||||
| 			ClientMessage::Fetch(url, writer, sender) => { | ||||
| 				let token = self.next_token; | ||||
| 				self.next_token += 1; | ||||
| 
 | ||||
| 				if let Ok(mut tlsclient) = TlsClient::new(mio::Token(token), &url, writer, sender) { | ||||
| 					let httpreq = format!( | ||||
| 						"GET {} HTTP/1.1\r\nHost: {}\r\nConnection: close\r\nAccept-Encoding: identity\r\n\r\n", | ||||
| 						url.path(), | ||||
| 						url.hostname() | ||||
| 					); | ||||
| 					let _ = tlsclient.write(httpreq.as_bytes()); | ||||
| 					tlsclient.register(event_loop); | ||||
| 
 | ||||
| 					self.sessions.insert(token, tlsclient); | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| #[test] | ||||
| fn should_successfuly_fetch_a_page() { | ||||
| 	use std::io::{self, Cursor}; | ||||
| 	use std::sync::Arc; | ||||
| 	use std::sync::atomic::{AtomicUsize, Ordering}; | ||||
| 
 | ||||
| 	struct Writer { | ||||
| 		wrote: Arc<AtomicUsize>, | ||||
| 		data: Cursor<Vec<u8>>, | ||||
| 	} | ||||
| 
 | ||||
| 	impl io::Write for Writer { | ||||
| 		fn write(&mut self, buf: &[u8]) -> io::Result<usize> { | ||||
| 			let res = self.data.write(buf); | ||||
| 			if let Ok(count) = res { | ||||
| 				self.wrote.fetch_add(count, Ordering::Relaxed); | ||||
| 			} | ||||
| 			res | ||||
| 		} | ||||
| 		fn flush(&mut self) -> io::Result<()> { Ok(()) } | ||||
| 	} | ||||
| 
 | ||||
| 	let client = Client::new().unwrap(); | ||||
| 
 | ||||
| 	let wrote = Arc::new(AtomicUsize::new(0)); | ||||
| 	let writer = Writer { | ||||
| 		wrote: wrote.clone(), | ||||
| 		data: Cursor::new(Vec::new()), | ||||
| 	}; | ||||
| 	let rx = client.fetch(Url::new("github.com", 443, "/").unwrap(), Box::new(writer)).unwrap(); | ||||
| 
 | ||||
| 	let result = rx.recv().unwrap(); | ||||
| 
 | ||||
| 	assert!(result.is_ok()); | ||||
| 	assert!(wrote.load(Ordering::Relaxed) > 0); | ||||
| } | ||||
							
								
								
									
										27
									
								
								util/https-fetch/src/lib.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								util/https-fetch/src/lib.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,27 @@ | ||||
| // Copyright 2015, 2016 Ethcore (UK) Ltd.
 | ||||
| // This file is part of Parity.
 | ||||
| 
 | ||||
| // Parity is free software: you can redistribute it and/or modify
 | ||||
| // it under the terms of the GNU General Public License as published by
 | ||||
| // the Free Software Foundation, either version 3 of the License, or
 | ||||
| // (at your option) any later version.
 | ||||
| 
 | ||||
| // Parity is distributed in the hope that it will be useful,
 | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of
 | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | ||||
| // GNU General Public License for more details.
 | ||||
| 
 | ||||
| // You should have received a copy of the GNU General Public License
 | ||||
| // along with Parity.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| extern crate rustls; | ||||
| extern crate mio; | ||||
| #[macro_use] extern crate log; | ||||
| 
 | ||||
| mod tlsclient; | ||||
| mod client; | ||||
| mod url; | ||||
| 
 | ||||
| pub use self::client::{Client, FetchError, FetchResult}; | ||||
| pub use self::url::{Url, UrlError}; | ||||
| 
 | ||||
							
								
								
									
										231
									
								
								util/https-fetch/src/tlsclient.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										231
									
								
								util/https-fetch/src/tlsclient.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,231 @@ | ||||
| // Copyright 2015, 2016 Ethcore (UK) Ltd.
 | ||||
| // This file is part of Parity.
 | ||||
| 
 | ||||
| // Parity is free software: you can redistribute it and/or modify
 | ||||
| // it under the terms of the GNU General Public License as published by
 | ||||
| // the Free Software Foundation, either version 3 of the License, or
 | ||||
| // (at your option) any later version.
 | ||||
| 
 | ||||
| // Parity is distributed in the hope that it will be useful,
 | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of
 | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | ||||
| // GNU General Public License for more details.
 | ||||
| 
 | ||||
| // You should have received a copy of the GNU General Public License
 | ||||
| // along with Parity.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| use std::str; | ||||
| use std::sync::{mpsc, Arc}; | ||||
| use std::io::{self, Read, Cursor, BufReader}; | ||||
| 
 | ||||
| use mio; | ||||
| use mio::tcp::TcpStream; | ||||
| use rustls::{self, Session}; | ||||
| 
 | ||||
| use client::{FetchError, ClientLoop, FetchResult}; | ||||
| use url::Url; | ||||
| 
 | ||||
| #[derive(Debug)] | ||||
| pub enum TlsClientError { | ||||
| 	Initialization, | ||||
| 	UnexpectedEof, | ||||
| 	Connection(io::Error), | ||||
| 	Writer(io::Error), | ||||
| 	Tls(rustls::TLSError), | ||||
| } | ||||
| 
 | ||||
| /// This encapsulates the TCP-level connection, some connection
 | ||||
| /// state, and the underlying TLS-level session.
 | ||||
| pub struct TlsClient { | ||||
| 	token: mio::Token, | ||||
| 	socket: TcpStream, | ||||
| 	tls_session: rustls::ClientSession, | ||||
| 	writer: Box<io::Write>, | ||||
| 	error: Option<TlsClientError>, | ||||
| 	closing: bool, | ||||
| 	listener: mpsc::Sender<FetchResult>, | ||||
| } | ||||
| 
 | ||||
| impl io::Write for TlsClient { | ||||
| 	fn write(&mut self, bytes: &[u8]) -> io::Result<usize> { | ||||
| 		self.tls_session.write(bytes) | ||||
| 	} | ||||
| 
 | ||||
| 	fn flush(&mut self) -> io::Result<()> { | ||||
| 		self.tls_session.flush() | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| impl io::Read for TlsClient { | ||||
| 	fn read(&mut self, bytes: &mut [u8]) -> io::Result<usize> { | ||||
| 		self.tls_session.read(bytes) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| impl TlsClient { | ||||
| 	pub fn make_config() -> Result<Arc<rustls::ClientConfig>, FetchError> { | ||||
| 		let mut config = rustls::ClientConfig::new(); | ||||
| 		// TODO [ToDr] Windows / MacOs support!
 | ||||
| 		let mut cursor = Cursor::new(if cfg!(feature = "ca-github-only") { | ||||
| 			include_bytes!("./ca-github.crt").to_vec() | ||||
| 		} else { | ||||
| 			include_bytes!("./ca-certificates.crt").to_vec() | ||||
| 		}); | ||||
| 		let mut reader = BufReader::new(&mut cursor); | ||||
| 		try!(config.root_store.add_pem_file(&mut reader).map_err(|_| FetchError::ReadingCaCertificates)); | ||||
| 		// TODO [ToDr] client certificate?
 | ||||
| 		Ok(Arc::new(config)) | ||||
| 	} | ||||
| 
 | ||||
| 	pub fn new( | ||||
| 		token: mio::Token, | ||||
| 		url: &Url, | ||||
| 		writer: Box<io::Write + Send>, | ||||
| 		sender: mpsc::Sender<FetchResult>, | ||||
| 		) -> Result<Self, FetchError> { | ||||
| 			let res = TlsClient::make_config().and_then(|cfg| { | ||||
| 				TcpStream::connect(url.address()).map(|sock| { | ||||
| 					(cfg, sock) | ||||
| 				}).map_err(Into::into) | ||||
| 			}); | ||||
| 
 | ||||
| 			match res { | ||||
| 				Ok((cfg, sock)) => Ok(TlsClient { | ||||
| 					token: token, | ||||
| 					writer: writer, | ||||
| 					socket: sock, | ||||
| 					closing: false, | ||||
| 					error: None, | ||||
| 					tls_session: rustls::ClientSession::new(&cfg, url.hostname()), | ||||
| 					listener: sender, | ||||
| 				}), | ||||
| 				Err(e) => { | ||||
| 					sender.send(Err(e)).unwrap_or_else(|e| warn!("Client initialization error: {:?}", e)); | ||||
| 					Err(FetchError::Client(TlsClientError::Initialization)) | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 	/// Called by mio each time events we register() for happen.
 | ||||
| 	/// Return false if reregistering again.
 | ||||
| 	pub fn ready(&mut self, event_loop: &mut mio::EventLoop<ClientLoop>, token: mio::Token, events: mio::EventSet) -> bool { | ||||
| 		assert_eq!(token, self.token); | ||||
| 
 | ||||
| 		if events.is_readable() { | ||||
| 			self.do_read(); | ||||
| 		} | ||||
| 
 | ||||
| 		if events.is_writable() { | ||||
| 			self.do_write(); | ||||
| 		} | ||||
| 
 | ||||
| 		if self.is_closed() { | ||||
| 			trace!("Connection closed"); | ||||
| 			let res = self.listener.send(match self.error.take() { | ||||
| 				Some(err) => Err(err.into()), | ||||
| 				None => Ok(()), | ||||
| 			}); | ||||
| 
 | ||||
| 			if let Err(e) = res { | ||||
| 				warn!("Finished fetching but listener is not available: {:?}", e); | ||||
| 			} | ||||
| 			return true; | ||||
| 		} | ||||
| 
 | ||||
| 		self.reregister(event_loop); | ||||
| 		false | ||||
| 	} | ||||
| 
 | ||||
| 	pub fn register(&mut self, event_loop: &mut mio::EventLoop<ClientLoop>) { | ||||
| 		event_loop.register( | ||||
| 			&self.socket, | ||||
| 			self.token, | ||||
| 			self.event_set(), | ||||
| 			mio::PollOpt::level() | mio::PollOpt::oneshot() | ||||
| 			).unwrap_or_else(|e| self.error = Some(TlsClientError::Connection(e))); | ||||
| 	} | ||||
| 
 | ||||
| 	fn reregister(&mut self, event_loop: &mut mio::EventLoop<ClientLoop>) { | ||||
| 		event_loop.reregister( | ||||
| 			&self.socket, | ||||
| 			self.token, | ||||
| 			self.event_set(), | ||||
| 			mio::PollOpt::level() | mio::PollOpt::oneshot() | ||||
| 			).unwrap_or_else(|e| self.error = Some(TlsClientError::Connection(e))); | ||||
| 	} | ||||
| 
 | ||||
| 	/// We're ready to do a read.
 | ||||
| 	fn do_read(&mut self) { | ||||
| 		// Read TLS data.  This fails if the underlying TCP connection is broken.
 | ||||
| 		let rc = self.tls_session.read_tls(&mut self.socket); | ||||
| 		if let Err(e) = rc { | ||||
| 			trace!("TLS read error: {:?}", e); | ||||
| 			self.closing = true; | ||||
| 			self.error = Some(TlsClientError::Connection(e)); | ||||
| 			return; | ||||
| 		} | ||||
| 
 | ||||
| 		// If we're ready but there's no data: EOF.
 | ||||
| 		if rc.unwrap() == 0 { | ||||
| 			trace!("Unexpected EOF"); | ||||
| 			self.error = Some(TlsClientError::UnexpectedEof); | ||||
| 			self.closing = true; | ||||
| 			return; | ||||
| 		} | ||||
| 
 | ||||
| 		// Reading some TLS data might have yielded new TLS messages to process.
 | ||||
| 		// Errors from this indicate TLS protocol problems and are fatal.
 | ||||
| 		let processed = self.tls_session.process_new_packets(); | ||||
| 		if let Err(e) = processed { | ||||
| 			trace!("TLS error: {:?}", e); | ||||
| 			self.error = Some(TlsClientError::Tls(e)); | ||||
| 			self.closing = true; | ||||
| 			return; | ||||
| 		} | ||||
| 
 | ||||
| 		// Having read some TLS data, and processed any new messages, we might have new plaintext as a result.
 | ||||
| 		// Read it and then write it to stdout.
 | ||||
| 		let mut plaintext = Vec::new(); | ||||
| 		let rc = self.tls_session.read_to_end(&mut plaintext); | ||||
| 		if !plaintext.is_empty() { | ||||
| 			self.writer.write(&plaintext).unwrap_or_else(|e| { | ||||
| 				trace!("Write error: {:?}", e); | ||||
| 				self.error = Some(TlsClientError::Writer(e)); | ||||
| 				0 | ||||
| 			}); | ||||
| 		} | ||||
| 
 | ||||
| 		// If that fails, the peer might have started a clean TLS-level session closure.
 | ||||
| 		if let Err(err) = rc { | ||||
| 			if err.kind() != io::ErrorKind::ConnectionAborted { | ||||
| 				self.error = Some(TlsClientError::Connection(err)); | ||||
| 			} | ||||
| 			self.closing = true; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	fn do_write(&mut self) { | ||||
| 		self.tls_session.write_tls(&mut self.socket).unwrap_or_else(|e| { | ||||
| 			warn!("TLS write error: {:?}", e); | ||||
| 		}); | ||||
| 	} | ||||
| 
 | ||||
| 	// Use wants_read/wants_write to register for different mio-level IO readiness events.
 | ||||
| 	fn event_set(&self) -> mio::EventSet { | ||||
| 		let rd = self.tls_session.wants_read(); | ||||
| 		let wr = self.tls_session.wants_write(); | ||||
| 
 | ||||
| 		if rd && wr { | ||||
| 			mio::EventSet::readable() | mio::EventSet::writable() | ||||
| 		} else if wr { | ||||
| 			mio::EventSet::writable() | ||||
| 		} else { | ||||
| 			mio::EventSet::readable() | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	fn is_closed(&self) -> bool { | ||||
| 		self.closing | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
							
								
								
									
										81
									
								
								util/https-fetch/src/url.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								util/https-fetch/src/url.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,81 @@ | ||||
| // Copyright 2015, 2016 Ethcore (UK) Ltd.
 | ||||
| // This file is part of Parity.
 | ||||
| 
 | ||||
| // Parity is free software: you can redistribute it and/or modify
 | ||||
| // it under the terms of the GNU General Public License as published by
 | ||||
| // the Free Software Foundation, either version 3 of the License, or
 | ||||
| // (at your option) any later version.
 | ||||
| 
 | ||||
| // Parity is distributed in the hope that it will be useful,
 | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of
 | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | ||||
| // GNU General Public License for more details.
 | ||||
| 
 | ||||
| // You should have received a copy of the GNU General Public License
 | ||||
| // along with Parity.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| use std::net::SocketAddr; | ||||
| 
 | ||||
| #[derive(Debug)] | ||||
| pub enum UrlError { | ||||
| 	InvalidAddress | ||||
| } | ||||
| 
 | ||||
| /// Build a ClientConfig from our arguments
 | ||||
| pub struct Url { | ||||
| 	address: SocketAddr, | ||||
| 	hostname: String, | ||||
| 	port: u16, | ||||
| 	path: String, | ||||
| } | ||||
| 
 | ||||
| impl Url { | ||||
| 	pub fn new(hostname: &str, port: u16, path: &str) -> Result<Self, UrlError> { | ||||
| 		let addr = try!(Self::lookup_ipv4(hostname, port)); | ||||
| 		Ok(Url { | ||||
| 			address: addr, | ||||
| 			hostname: hostname.into(), | ||||
| 			port: port, | ||||
| 			path: path.into(), | ||||
| 		}) | ||||
| 	} | ||||
| 
 | ||||
| 	fn lookup_ipv4(host: &str, port: u16) -> Result<SocketAddr, UrlError> { | ||||
| 		use std::net::ToSocketAddrs; | ||||
| 
 | ||||
| 		let addrs = try!((host, port).to_socket_addrs().map_err(|_| UrlError::InvalidAddress)); | ||||
| 		for addr in addrs { | ||||
| 			if let SocketAddr::V4(_) = addr { | ||||
| 				return Ok(addr.clone()); | ||||
| 			} | ||||
| 		} | ||||
| 		Err(UrlError::InvalidAddress) | ||||
| 	} | ||||
| 
 | ||||
| 	pub fn address(&self) -> &SocketAddr { | ||||
| 		&self.address | ||||
| 	} | ||||
| 
 | ||||
| 	pub fn hostname(&self) -> &str { | ||||
| 		&self.hostname | ||||
| 	} | ||||
| 
 | ||||
| 	pub fn port(&self) -> u16 { | ||||
| 		self.port | ||||
| 	} | ||||
| 
 | ||||
| 	pub fn path(&self) -> &str { | ||||
| 		&self.path | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| #[cfg(test)] | ||||
| #[test] | ||||
| fn should_parse_url() { | ||||
| 	// given
 | ||||
| 	let url = Url::new("github.com", 443, "/").unwrap(); | ||||
| 
 | ||||
| 	assert_eq!(url.hostname(), "github.com"); | ||||
| 	assert_eq!(url.port(), 443); | ||||
| 	assert_eq!(url.path(), "/"); | ||||
| } | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user