Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use NetworkConnector directly to account for replaced hosts #16131

Merged
merged 5 commits into from Mar 26, 2017
@@ -192,7 +192,7 @@ pub fn main_fetch(request: Rc<Request>,
.read()
.unwrap()
.is_host_secure(request.current_url().domain().unwrap()) {
request.url_list.borrow_mut().last_mut().unwrap().as_mut_url().unwrap().set_scheme("https").unwrap();
request.url_list.borrow_mut().last_mut().unwrap().as_mut_url().set_scheme("https").unwrap();
}
}

@@ -15,6 +15,7 @@ use hsts::HstsList;
use hyper::Error as HttpError;
use hyper::LanguageTag;
use hyper::client::{Pool, Request as HyperRequest, Response as HyperResponse};
use hyper::client::pool::PooledStream;
use hyper::header::{AcceptEncoding, AcceptLanguage, AccessControlAllowCredentials};
use hyper::header::{AccessControlAllowOrigin, AccessControlAllowHeaders, AccessControlAllowMethods};
use hyper::header::{AccessControlRequestHeaders, AccessControlMaxAge, AccessControlRequestMethod};
@@ -24,17 +25,18 @@ use hyper::header::{IfUnmodifiedSince, IfModifiedSince, IfNoneMatch, Location, P
use hyper::header::{QualityItem, Referer, SetCookie, UserAgent, qitem};
use hyper::header::Origin as HyperOrigin;
use hyper::method::Method;
use hyper::net::Fresh;
use hyper::net::{Fresh, HttpStream, HttpsStream, NetworkConnector};
use hyper::status::StatusCode;
use hyper_serde::Serde;
use log;
use msg::constellation_msg::PipelineId;
use net_traits::{CookieSource, FetchMetadata, NetworkError, ReferrerPolicy};
use net_traits::hosts::replace_hosts;
use net_traits::hosts::replace_host;
use net_traits::request::{CacheMode, CredentialsMode, Destination, Origin};
use net_traits::request::{RedirectMode, Referrer, Request, RequestMode, ResponseTainting};
use net_traits::response::{HttpsState, Response, ResponseBody, ResponseType};
use openssl;
use openssl::ssl::SslStream;
use openssl::ssl::error::{OpensslError, SslError};
use resource_thread::AuthCache;
use servo_url::{ImmutableOrigin, ServoUrl};
@@ -125,12 +127,18 @@ struct NetworkHttpRequestFactory {
pub connector: Arc<Pool<Connector>>,
}

impl NetworkConnector for NetworkHttpRequestFactory {
type Stream = PooledStream<HttpsStream<SslStream<HttpStream>>>;

fn connect(&self, host: &str, port: u16, scheme: &str) -> Result<Self::Stream, HttpError> {
self.connector.connect(&replace_host(host), port, scheme)
}
}

impl NetworkHttpRequestFactory {
fn create(&self, url: ServoUrl, method: Method, headers: Headers)
-> Result<HyperRequest<Fresh>, NetworkError> {
let connection = HyperRequest::with_connector(method,
url.clone().into_url().unwrap(),
&*self.connector);
let connection = HyperRequest::with_connector(method, url.clone().into_url(), self);

if let Err(HttpError::Ssl(ref error)) = connection {
let error: &(Error + Send + 'static) = &**error;
@@ -222,7 +230,7 @@ fn strict_origin_when_cross_origin(referrer_url: ServoUrl, url: ServoUrl) -> Opt
fn strip_url(mut referrer_url: ServoUrl, origin_only: bool) -> Option<ServoUrl> {
if referrer_url.scheme() == "https" || referrer_url.scheme() == "http" {
{
let referrer = referrer_url.as_mut_url().unwrap();
let referrer = referrer_url.as_mut_url();
referrer.set_username("").unwrap();
referrer.set_password(None).unwrap();
referrer.set_fragment(None);
@@ -408,7 +416,6 @@ fn obtain_response(request_factory: &NetworkHttpRequestFactory,
is_xhr: bool)
-> Result<(WrappedHttpResponse, Option<ChromeToDevtoolsControlMsg>), NetworkError> {
let null_data = None;
let connection_url = replace_hosts(&url);

// loop trying connections in connection pool
// they may have grown stale (disconnected), in which case we'll get
@@ -439,7 +446,7 @@ fn obtain_response(request_factory: &NetworkHttpRequestFactory,
}

if log_enabled!(log::LogLevel::Info) {
info!("{} {}", method, connection_url);
info!("{} {}", method, url);
for header in headers.iter() {
info!(" - {}", header);
}
@@ -448,7 +455,7 @@ fn obtain_response(request_factory: &NetworkHttpRequestFactory,

let connect_start = precise_time_ms();

let request = try!(request_factory.create(connection_url.clone(), method.clone(),
let request = try!(request_factory.create(url.clone(), method.clone(),
headers.clone()));

let connect_end = precise_time_ms();
@@ -900,7 +907,7 @@ fn http_network_or_cache_fetch(request: Rc<Request>,
let headers = &mut *http_request.headers.borrow_mut();
let host = Host {
hostname: current_url.host_str().unwrap().to_owned(),
port: current_url.port_or_known_default()
port: current_url.port()
};
headers.set(host);
// unlike http_loader, we should not set the accept header
@@ -9,7 +9,7 @@ use http_loader;
use hyper::header::{Host, SetCookie};
use net_traits::{CookieSource, MessageData, WebSocketCommunicate};
use net_traits::{WebSocketConnectData, WebSocketDomAction, WebSocketNetworkEvent};
use net_traits::hosts::replace_hosts;
use net_traits::hosts::replace_host_in_url;
use servo_url::ServoUrl;
use std::ascii::AsciiExt;
use std::sync::{Arc, Mutex, RwLock};
@@ -25,84 +25,6 @@ use websocket::stream::WebSocketStream;
use websocket::ws::receiver::Receiver as WSReceiver;
use websocket::ws::sender::Sender as Sender_Object;

// https://fetch.spec.whatwg.org/#concept-websocket-establish
fn establish_a_websocket_connection(resource_url: &ServoUrl,
origin: String,
protocols: Vec<String>,
cookie_jar: Arc<RwLock<CookieStorage>>)
-> WebSocketResult<(Option<String>,
Sender<WebSocketStream>,
Receiver<WebSocketStream>)> {
// Steps 1-2 are not really applicable here, given we don't exactly go
// through the same infrastructure as the Fetch spec.

if should_be_blocked_due_to_bad_port(resource_url) {
// Subset of steps 11-12, we inline the bad port check here from the
// main fetch algorithm for the same reason steps 1-2 are not
// applicable.
return Err(WebSocketError::RequestError("Request should be blocked due to bad port."));
}

// Steps 3-7.
let net_url = replace_hosts(resource_url);
let mut request = try!(Client::connect(net_url.as_url()));

// Client::connect sets the Host header to the host of the URL that is
// passed to it, so we need to reset it afterwards to the correct one.
request.headers.set(Host {
hostname: resource_url.host_str().unwrap().to_owned(),
port: resource_url.port(),
});

// Step 8.
if !protocols.is_empty() {
request.headers.set(WebSocketProtocol(protocols.clone()));
}

// Steps 9-10.
// TODO: support for permessage-deflate extension.

// Subset of step 11.
// See step 2 of https://fetch.spec.whatwg.org/#concept-fetch.
request.headers.set(Origin(origin));

// Transitive subset of step 11.
// See step 17.1 of https://fetch.spec.whatwg.org/#concept-http-network-or-cache-fetch.
http_loader::set_request_cookies(&resource_url, &mut request.headers, &cookie_jar);

// Step 11, somewhat.
let response = try!(request.send());

// Step 12, 14.
try!(response.validate());

// Step 13 and transitive subset of step 14.
// See step 6 of http://tools.ietf.org/html/rfc6455#section-4.1.
let protocol_in_use = response.protocol().and_then(|header| {
// https://github.com/whatwg/fetch/issues/515
header.first().cloned()
});
if let Some(ref protocol_name) = protocol_in_use {
if !protocols.is_empty() && !protocols.iter().any(|p| (&**p).eq_ignore_ascii_case(protocol_name)) {
return Err(WebSocketError::ProtocolError("Protocol in Use not in client-supplied protocol list"));
};
};

// Transitive subset of step 11.
// See step 15 of https://fetch.spec.whatwg.org/#http-network-fetch.
if let Some(cookies) = response.headers.get::<SetCookie>() {
let mut jar = cookie_jar.write().unwrap();
for cookie in &**cookies {
if let Some(cookie) = Cookie::new_wrapped(cookie.clone(), resource_url, CookieSource::HTTP) {
jar.push(cookie, resource_url, CookieSource::HTTP);
}
}
}

let (sender, receiver) = response.begin().split();
Ok((protocol_in_use, sender, receiver))
}

pub fn init(connect: WebSocketCommunicate, connect_data: WebSocketConnectData, cookie_jar: Arc<RwLock<CookieStorage>>) {
thread::Builder::new().name(format!("WebSocket connection to {}", connect_data.resource_url)).spawn(move || {
let channel = establish_a_websocket_connection(&connect_data.resource_url,
@@ -182,3 +104,81 @@ pub fn init(connect: WebSocketCommunicate, connect_data: WebSocketConnectData, c
}
}).expect("Thread spawning failed");
}

// https://fetch.spec.whatwg.org/#concept-websocket-establish
fn establish_a_websocket_connection(resource_url: &ServoUrl,
origin: String,
protocols: Vec<String>,
cookie_jar: Arc<RwLock<CookieStorage>>)
-> WebSocketResult<(Option<String>,
Sender<WebSocketStream>,
Receiver<WebSocketStream>)> {
// Steps 1-2 are not really applicable here, given we don't exactly go
// through the same infrastructure as the Fetch spec.

if should_be_blocked_due_to_bad_port(resource_url) {
// Subset of steps 11-12, we inline the bad port check here from the
// main fetch algorithm for the same reason steps 1-2 are not
// applicable.
return Err(WebSocketError::RequestError("Request should be blocked due to bad port."));
}

// Steps 3-7.
let net_url = replace_host_in_url(resource_url.clone());
let mut request = try!(Client::connect(net_url.as_url()));

// Client::connect sets the Host header to the host of the URL that is
// passed to it, so we need to reset it afterwards to the correct one.
request.headers.set(Host {
hostname: resource_url.host_str().unwrap().to_owned(),
port: resource_url.port(),
});

// Step 8.
if !protocols.is_empty() {
request.headers.set(WebSocketProtocol(protocols.clone()));
}

// Steps 9-10.
// TODO: support for permessage-deflate extension.

// Subset of step 11.
// See step 2 of https://fetch.spec.whatwg.org/#concept-fetch.
request.headers.set(Origin(origin));

// Transitive subset of step 11.
// See step 17.1 of https://fetch.spec.whatwg.org/#concept-http-network-or-cache-fetch.
http_loader::set_request_cookies(&resource_url, &mut request.headers, &cookie_jar);

// Step 11, somewhat.
let response = try!(request.send());

// Step 12, 14.
try!(response.validate());

// Step 13 and transitive subset of step 14.
// See step 6 of http://tools.ietf.org/html/rfc6455#section-4.1.
let protocol_in_use = response.protocol().and_then(|header| {
// https://github.com/whatwg/fetch/issues/515
header.first().cloned()
});
if let Some(ref protocol_name) = protocol_in_use {
if !protocols.is_empty() && !protocols.iter().any(|p| (&**p).eq_ignore_ascii_case(protocol_name)) {
return Err(WebSocketError::ProtocolError("Protocol in Use not in client-supplied protocol list"));
};
};

// Transitive subset of step 11.
// See step 15 of https://fetch.spec.whatwg.org/#http-network-fetch.
if let Some(cookies) = response.headers.get::<SetCookie>() {
let mut jar = cookie_jar.write().unwrap();
for cookie in &**cookies {
if let Some(cookie) = Cookie::new_wrapped(cookie.clone(), resource_url, CookieSource::HTTP) {
jar.push(cookie, resource_url, CookieSource::HTTP);
}
}
}

let (sender, receiver) = response.begin().split();
Ok((protocol_in_use, sender, receiver))
}
@@ -4,6 +4,7 @@

use parse_hosts::HostsFile;
use servo_url::ServoUrl;
use std::borrow::Cow;
use std::collections::HashMap;
use std::env;
use std::fs::File;
@@ -56,19 +57,24 @@ pub fn parse_hostsfile(hostsfile_content: &str) -> HashMap<String, IpAddr> {
host_table
}

pub fn replace_hosts(url: &ServoUrl) -> ServoUrl {
HOST_TABLE.lock().unwrap().as_ref().map_or_else(|| url.clone(),
|host_table| host_replacement(host_table, url))
pub fn replace_host(host: &str) -> Cow<str> {
HOST_TABLE.lock().unwrap().as_ref()
.and_then(|table| table.get(host))
.map_or(host.into(), |replaced_host| replaced_host.to_string().into())
}

pub fn host_replacement(host_table: &HashMap<String, IpAddr>, url: &ServoUrl) -> ServoUrl {
url.domain()
.and_then(|domain| {
host_table.get(domain).map(|ip| {
let mut new_url = url.clone();
new_url.set_ip_host(*ip).unwrap();
new_url
})
})
.unwrap_or_else(|| url.clone())
pub fn replace_host_in_url(url: ServoUrl) -> ServoUrl {
if let Some(table) = HOST_TABLE.lock().unwrap().as_ref() {
host_replacement(table, url)
} else {
url
}
}

pub fn host_replacement(host_table: &HashMap<String, IpAddr>, mut url: ServoUrl) -> ServoUrl {
let replacement = url.domain().and_then(|domain| host_table.get(domain));
if let Some(ip) = replacement {
url.set_ip_host(*ip).unwrap();
}
url
}
@@ -384,12 +384,12 @@ impl HTMLFormElement {
fn mutate_action_url(&self, form_data: &mut Vec<FormDatum>, mut load_data: LoadData, encoding: EncodingRef) {
let charset = &*encoding.whatwg_name().unwrap();

if let Some(ref mut url) = load_data.url.as_mut_url() {
url.query_pairs_mut().clear()
.encoding_override(Some(self.pick_encoding()))
.extend_pairs(form_data.into_iter()
.map(|field| (field.name.clone(), field.replace_value(charset))));
}
load_data.url
.as_mut_url()
.query_pairs_mut().clear()
.encoding_override(Some(self.pick_encoding()))
.extend_pairs(form_data.into_iter()
.map(|field| (field.name.clone(), field.replace_value(charset))));

self.plan_to_navigate(load_data);
}
@@ -403,13 +403,12 @@ impl HTMLFormElement {
let charset = &*encoding.whatwg_name().unwrap();
load_data.headers.set(ContentType::form_url_encoded());


if let Some(ref mut url) = load_data.url.as_mut_url() {
url.query_pairs_mut().clear()
.encoding_override(Some(self.pick_encoding()))
.extend_pairs(form_data.into_iter()
.map(|field| (field.name.clone(), field.replace_value(charset))));
}
load_data.url
.as_mut_url()
.query_pairs_mut().clear()
.encoding_override(Some(self.pick_encoding()))
.extend_pairs(form_data.into_iter()
.map(|field| (field.name.clone(), field.replace_value(charset))));

load_data.url.query().unwrap_or("").to_string().into_bytes()
}
@@ -52,9 +52,8 @@ impl URL {
}

pub fn set_query_pairs(&self, pairs: &[(String, String)]) {
if let Some(ref mut url) = self.url.borrow_mut().as_mut_url() {
url.query_pairs_mut().clear().extend_pairs(pairs);
}
let mut url = self.url.borrow_mut();
url.as_mut_url().query_pairs_mut().clear().extend_pairs(pairs);
}
}

ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.