Auto merge of #16180 - nox:tungstenite, r=jdm

Make the WebSocket handshake ourselves 🍷

HYPE

<!-- Reviewable:start -->
---
This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/servo/16180)
<!-- Reviewable:end -->
This commit is contained in:
bors-servo 2017-03-29 12:55:38 -05:00 committed by GitHub
commit 76a2c9705a
7 changed files with 632 additions and 131 deletions

View file

@ -527,7 +527,7 @@ fn is_null_body_status(status: &Option<StatusCode>) -> bool {
}
/// https://fetch.spec.whatwg.org/#should-response-to-request-be-blocked-due-to-nosniff?
fn should_be_blocked_due_to_nosniff(request_type: Type, response_headers: &Headers) -> bool {
pub fn should_be_blocked_due_to_nosniff(request_type: Type, response_headers: &Headers) -> bool {
/// https://fetch.spec.whatwg.org/#x-content-type-options-header
/// This is needed to parse `X-Content-Type-Options` according to spec,
/// which requires that we inspect only the first value.

View file

@ -35,9 +35,7 @@ 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};
use std::collections::HashSet;
@ -140,34 +138,7 @@ 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(), self);
if let Err(HttpError::Ssl(ref error)) = connection {
let error: &(Error + Send + 'static) = &**error;
if let Some(&SslError::OpenSslErrors(ref errors)) = error.downcast_ref::<SslError>() {
if errors.iter().any(is_cert_verify_error) {
let mut error_report = vec![format!("ssl error ({}):", openssl::version::version())];
let mut suggestion = None;
for err in errors {
if is_unknown_message_digest_err(err) {
suggestion = Some("<b>Servo recommends upgrading to a newer OpenSSL version.</b>");
}
error_report.push(format_ssl_error(err));
}
if let Some(suggestion) = suggestion {
error_report.push(suggestion.to_owned());
}
let error_report = error_report.join("<br>\n");
return Err(NetworkError::SslValidation(url, error_report));
}
}
}
let mut request = match connection {
Ok(req) => req,
Err(e) => return Err(NetworkError::Internal(e.description().to_owned())),
};
let mut request = connection.map_err(|e| NetworkError::from_hyper_error(&url, e))?;
*request.headers_mut() = headers;
Ok(request)
@ -505,35 +476,6 @@ fn obtain_response(request_factory: &NetworkHttpRequestFactory,
}
}
// FIXME: This incredibly hacky. Make it more robust, and at least test it.
fn is_cert_verify_error(error: &OpensslError) -> bool {
match error {
&OpensslError::UnknownError { ref library, ref function, ref reason } => {
library == "SSL routines" &&
function.to_uppercase() == "SSL3_GET_SERVER_CERTIFICATE" &&
reason == "certificate verify failed"
}
}
}
fn is_unknown_message_digest_err(error: &OpensslError) -> bool {
match error {
&OpensslError::UnknownError { ref library, ref function, ref reason } => {
library == "asn1 encoding routines" &&
function == "ASN1_item_verify" &&
reason == "unknown message digest algorithm"
}
}
}
fn format_ssl_error(error: &OpensslError) -> String {
match error {
&OpensslError::UnknownError { ref library, ref function, ref reason } => {
format!("{}: {} - {}", library, function, reason)
}
}
}
/// [HTTP fetch](https://fetch.spec.whatwg.org#http-fetch)
pub fn http_fetch(request: Rc<Request>,
cache: &mut CorsCache,
@ -1417,7 +1359,7 @@ fn response_needs_revalidation(_response: &Response) -> bool {
}
/// https://fetch.spec.whatwg.org/#redirect-status
fn is_redirect_status(status: StatusCode) -> bool {
pub fn is_redirect_status(status: StatusCode) -> bool {
match status {
StatusCode::MovedPermanently |
StatusCode::Found |

View file

@ -355,6 +355,9 @@ impl CoreResourceManager {
connect: WebSocketCommunicate,
connect_data: WebSocketConnectData,
resource_grp: &ResourceGroup) {
websocket_loader::init(connect, connect_data, resource_grp.cookie_jar.clone());
websocket_loader::init(connect,
connect_data,
resource_grp.cookie_jar.clone(),
resource_grp.ssl_context.clone());
}
}

View file

@ -4,33 +4,45 @@
use cookie::Cookie;
use cookie_storage::CookieStorage;
use fetch::methods::should_be_blocked_due_to_bad_port;
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_host_in_url;
use fetch::methods::{should_be_blocked_due_to_bad_port, should_be_blocked_due_to_nosniff};
use http_loader::{is_redirect_status, set_request_cookies};
use hyper::buffer::BufReader;
use hyper::header::{Accept, CacheControl, CacheDirective, Connection, ConnectionOption};
use hyper::header::{Headers, Host, SetCookie, Pragma, Protocol, ProtocolName, Upgrade};
use hyper::http::h1::{LINE_ENDING, parse_response};
use hyper::method::Method;
use hyper::net::{HttpStream, HttpsStream};
use hyper::status::StatusCode;
use hyper::version::HttpVersion;
use net_traits::{CookieSource, MessageData, NetworkError, WebSocketCommunicate, WebSocketConnectData};
use net_traits::{WebSocketDomAction, WebSocketNetworkEvent};
use net_traits::hosts::replace_host;
use net_traits::request::Type;
use openssl::ssl::{SslContext, SslStream};
use servo_url::ServoUrl;
use std::ascii::AsciiExt;
use std::io::{self, Write};
use std::net::TcpStream;
use std::sync::{Arc, Mutex, RwLock};
use std::sync::atomic::{AtomicBool, Ordering};
use std::thread;
use websocket::{Client, Message};
use websocket::header::{Origin, WebSocketProtocol};
use websocket::message::Type;
use url::Position;
use websocket::{Message, Receiver as WSReceiver, Sender as WSSender};
use websocket::header::{Origin, WebSocketAccept, WebSocketKey, WebSocketProtocol, WebSocketVersion};
use websocket::message::Type as MessageType;
use websocket::receiver::Receiver;
use websocket::result::{WebSocketError, WebSocketResult};
use websocket::sender::Sender;
use websocket::stream::WebSocketStream;
use websocket::ws::receiver::Receiver as WSReceiver;
use websocket::ws::sender::Sender as Sender_Object;
pub fn init(connect: WebSocketCommunicate, connect_data: WebSocketConnectData, cookie_jar: Arc<RwLock<CookieStorage>>) {
pub fn init(connect: WebSocketCommunicate,
connect_data: WebSocketConnectData,
cookie_jar: Arc<RwLock<CookieStorage>>,
ssl_context: Arc<SslContext>) {
thread::Builder::new().name(format!("WebSocket connection to {}", connect_data.resource_url)).spawn(move || {
let channel = establish_a_websocket_connection(&connect_data.resource_url,
connect_data.origin,
connect_data.protocols,
cookie_jar);
cookie_jar,
ssl_context);
let (ws_sender, mut receiver) = match channel {
Ok((protocol_in_use, sender, receiver)) => {
let _ = connect.event_sender.send(WebSocketNetworkEvent::ConnectionEstablished { protocol_in_use });
@ -61,15 +73,15 @@ pub fn init(connect: WebSocketCommunicate, connect_data: WebSocketConnectData, c
}
};
let message = match message.opcode {
Type::Text => MessageData::Text(String::from_utf8_lossy(&message.payload).into_owned()),
Type::Binary => MessageData::Binary(message.payload.into_owned()),
Type::Ping => {
MessageType::Text => MessageData::Text(String::from_utf8_lossy(&message.payload).into_owned()),
MessageType::Binary => MessageData::Binary(message.payload.into_owned()),
MessageType::Ping => {
let pong = Message::pong(message.payload);
ws_sender_incoming.lock().unwrap().send_message(&pong).unwrap();
continue;
},
Type::Pong => continue,
Type::Close => {
MessageType::Pong => continue,
MessageType::Close => {
if !initiated_close_incoming.fetch_or(true, Ordering::SeqCst) {
ws_sender_incoming.lock().unwrap().send_message(&message).unwrap();
}
@ -105,80 +117,555 @@ pub fn init(connect: WebSocketCommunicate, connect_data: WebSocketConnectData, c
}).expect("Thread spawning failed");
}
type Stream = HttpsStream<SslStream<HttpStream>>;
// https://fetch.spec.whatwg.org/#concept-websocket-connection-obtain
fn obtain_a_websocket_connection(url: &ServoUrl, ssl_context: Arc<SslContext>)
-> Result<Stream, NetworkError> {
// Step 1.
let host = url.host_str().unwrap();
// Step 2.
let port = url.port_or_known_default().unwrap();
// Step 3.
// We did not replace the scheme by "http" or "https" in step 1 of
// establish_a_websocket_connection.
let secure = match url.scheme() {
"ws" => false,
"wss" => true,
_ => panic!("URL's scheme should be ws or wss"),
};
// Steps 4-5.
let host = replace_host(host);
let tcp_stream = TcpStream::connect((&*host, port)).map_err(|e| {
NetworkError::Internal(format!("Could not connect to host: {}", e))
})?;
let http_stream = HttpStream(tcp_stream);
if !secure {
return Ok(HttpsStream::Http(http_stream));
}
let ssl_stream = SslStream::connect(&*ssl_context, http_stream).map_err(|e| {
NetworkError::from_ssl_error(url, &e)
})?;
Ok(HttpsStream::Https(ssl_stream))
}
// 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
cookie_jar: Arc<RwLock<CookieStorage>>,
ssl_context: Arc<SslContext>)
-> Result<(Option<String>,
Sender<Stream>,
Receiver<Stream>),
NetworkError> {
// Steps 1 is 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."));
}
// Step 2, slimmed down because we don't go through the whole Fetch infra.
let mut headers = Headers::new();
// Steps 3-7.
let net_url = replace_host_in_url(resource_url.clone());
let mut request = try!(Client::connect(net_url.as_url()));
// Step 3.
headers.set(Upgrade(vec![Protocol::new(ProtocolName::WebSocket, None)]));
// 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 4.
headers.set(Connection(vec![ConnectionOption::ConnectionHeader("upgrade".into())]));
// Step 5.
let key_value = WebSocketKey::new();
// Step 6.
headers.set(key_value);
// Step 7.
headers.set(WebSocketVersion::WebSocket13);
// Step 8.
if !protocols.is_empty() {
request.headers.set(WebSocketProtocol(protocols.clone()));
headers.set(WebSocketProtocol(protocols.clone()));
}
// Steps 9-10.
// TODO: support for permessage-deflate extension.
// TODO: handle permessage-deflate extension.
// Subset of step 11.
// See step 2 of https://fetch.spec.whatwg.org/#concept-fetch.
request.headers.set(Origin(origin));
// Step 11 and network error check from step 12.
let response = fetch(resource_url, origin, headers, cookie_jar, ssl_context)?;
// 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 12, the status code check.
if response.status != StatusCode::SwitchingProtocols {
return Err(NetworkError::Internal("Response's status should be 101.".into()));
}
// Step 11, somewhat.
let response = try!(request.send());
// Step 13.
if !protocols.is_empty() {
if response.headers.get::<WebSocketProtocol>().map_or(true, |protocols| protocols.is_empty()) {
return Err(NetworkError::Internal(
"Response's Sec-WebSocket-Protocol header is missing, malformed or empty.".into()));
}
}
// Step 12, 14.
try!(response.validate());
// Step 14.2.
let upgrade_header = response.headers.get::<Upgrade>().ok_or_else(|| {
NetworkError::Internal("Response should have an Upgrade header.".into())
})?;
if upgrade_header.len() != 1 {
return Err(NetworkError::Internal("Response's Upgrade header should have only one value.".into()));
}
if upgrade_header[0].name != ProtocolName::WebSocket {
return Err(NetworkError::Internal("Response's Upgrade header value should be \"websocket\".".into()));
}
// 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()
// Step 14.3.
let connection_header = response.headers.get::<Connection>().ok_or_else(|| {
NetworkError::Internal("Response should have a Connection header.".into())
})?;
let connection_includes_upgrade = connection_header.iter().any(|option| {
match *option {
ConnectionOption::ConnectionHeader(ref option) => *option == "upgrade",
_ => false,
}
});
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"));
};
if !connection_includes_upgrade {
return Err(NetworkError::Internal("Response's Connection header value should include \"upgrade\".".into()));
}
// Step 14.4.
let accept_header = response.headers.get::<WebSocketAccept>().ok_or_else(|| {
NetworkError::Internal("Response should have a Sec-Websocket-Accept header.".into())
})?;
if *accept_header != WebSocketAccept::new(&key_value) {
return Err(NetworkError::Internal(
"Response's Sec-WebSocket-Accept header value did not match the sent key.".into()));
}
// Step 14.5.
// TODO: handle permessage-deflate extension.
// We don't support any extension, so we fail at the mere presence of
// a Sec-WebSocket-Extensions header.
if response.headers.get_raw("Sec-WebSocket-Extensions").is_some() {
return Err(NetworkError::Internal(
"Response's Sec-WebSocket-Extensions header value included unsupported extensions.".into()));
}
// Step 14.6.
let protocol_in_use = if let Some(response_protocols) = response.headers.get::<WebSocketProtocol>() {
for replied in &**response_protocols {
if !protocols.iter().any(|requested| requested.eq_ignore_ascii_case(replied)) {
return Err(NetworkError::Internal(
"Response's Sec-WebSocket-Protocols contain values that were not requested.".into()));
}
}
response_protocols.first().cloned()
} else {
None
};
// Transitive subset of step 11.
// See step 15 of https://fetch.spec.whatwg.org/#http-network-fetch.
let sender = Sender::new(response.writer, true);
let receiver = Receiver::new(response.reader, false);
Ok((protocol_in_use, sender, receiver))
}
struct Response {
status: StatusCode,
headers: Headers,
reader: BufReader<Stream>,
writer: Stream,
}
// https://fetch.spec.whatwg.org/#concept-fetch
fn fetch(url: &ServoUrl,
origin: String,
mut headers: Headers,
cookie_jar: Arc<RwLock<CookieStorage>>,
ssl_context: Arc<SslContext>)
-> Result<Response, NetworkError> {
// Step 1.
// TODO: handle request's window.
// Step 2.
// TODO: handle request's origin.
// Step 3.
// We know there is no `Accept` header in `headers`.
{
// Step 3.1.
let value = Accept::star();
// Step 3.2.
// Not applicable: not a navigation request.
// Step 3.3.
// Not applicable: request's type is the empty string.
// Step 3.4.
headers.set(value);
}
// Step 4.
// TODO: handle `Accept-Language`.
// Step 5.
// TODO: handle request's priority.
// Step 6.
// Not applicable: not a navigation request.
// Step 7.
// We know this is a subresource request.
{
// Step 7.1.
// Not applicable: client hints list is empty.
// Steps 7.2-3.
// TODO: handle fetch groups.
}
// Step 8.
main_fetch(url, origin, headers, cookie_jar, ssl_context)
}
// https://fetch.spec.whatwg.org/#concept-main-fetch
fn main_fetch(url: &ServoUrl,
origin: String,
mut headers: Headers,
cookie_jar: Arc<RwLock<CookieStorage>>,
ssl_context: Arc<SslContext>)
-> Result<Response, NetworkError> {
// Step 1.
let mut response = None;
// Step 2.
// Not applicable: requests local-URLs-only flag is unset.
// Step 3.
// TODO: handle content security policy violations.
// Step 4.
// TODO: handle upgrade to a potentially secure URL.
// Step 5.
if should_be_blocked_due_to_bad_port(url) {
response = Some(Err(NetworkError::Internal("Request should be blocked due to bad port.".into())));
}
// TODO: handle blocking as mixed content.
// TODO: handle blocking by content security policy.
// Steps 6-8.
// TODO: handle request's referrer policy.
// Step 9.
// Not applicable: request's current URL's scheme is not "ftp".
// Step 10.
// TODO: handle known HSTS host domain.
// Step 11.
// Not applicable: request's synchronous flag is set.
// Step 12.
let mut response = response.unwrap_or_else(|| {
// We must run the first sequence of substeps, given request's mode
// is "websocket".
// Step 12.1.
// Not applicable: the response is never exposed to the Web so it
// doesn't need to be filtered at all.
// Step 12.2.
basic_fetch(url, origin, &mut headers, cookie_jar, ssl_context)
});
// Step 13.
// Not applicable: recursive flag is unset.
// Step 14.
// Not applicable: the response is never exposed to the Web so it doesn't
// need to be filtered at all.
// Steps 15-16.
// Not applicable: no need to maintain an internal response.
// Step 17.
if response.is_ok() {
// TODO: handle blocking as mixed content.
// TODO: handle blocking by content security policy.
// Not applicable: blocking due to MIME type matters only for scripts.
if should_be_blocked_due_to_nosniff(Type::None, &headers) {
response = Err(NetworkError::Internal("Request should be blocked due to nosniff.".into()));
}
}
// Step 18.
// Not applicable: we don't care about the body at all.
// Step 19.
// Not applicable: request's integrity metadata is the empty string.
// Step 20.
// TODO: wait for response's body here, maybe?
response
}
// https://fetch.spec.whatwg.org/#concept-basic-fetch
fn basic_fetch(url: &ServoUrl,
origin: String,
headers: &mut Headers,
cookie_jar: Arc<RwLock<CookieStorage>>,
ssl_context: Arc<SslContext>)
-> Result<Response, NetworkError> {
// In the case of a WebSocket request, HTTP fetch is always used.
http_fetch(url, origin, headers, cookie_jar, ssl_context)
}
// https://fetch.spec.whatwg.org/#concept-http-fetch
fn http_fetch(url: &ServoUrl,
origin: String,
headers: &mut Headers,
cookie_jar: Arc<RwLock<CookieStorage>>,
ssl_context: Arc<SslContext>)
-> Result<Response, NetworkError> {
// Step 1.
// Not applicable: with step 3 being useless here, this one is too.
// Step 2.
// Not applicable: we don't need to maintain an internal response.
// Step 3.
// Not applicable: request's service-workers mode is "none".
// Step 4.
// There cannot be a response yet at this point.
let mut response = {
// Step 4.1.
// Not applicable: CORS-preflight flag is unset.
// Step 4.2.
// Not applicable: request's redirect mode is "error".
// Step 4.3.
let response = http_network_or_cache_fetch(url, origin, headers, cookie_jar, ssl_context);
// Step 4.4.
// Not applicable: CORS flag is unset.
response
};
// Step 5.
if response.as_ref().ok().map_or(false, |response| is_redirect_status(response.status)) {
// Step 5.1.
// Not applicable: the connection does not use HTTP/2.
// Steps 5.2-4.
// Not applicable: matters only if request's redirect mode is not "error".
// Step 5.5.
// Request's redirect mode is "error".
response = Err(NetworkError::Internal("Response should not be a redirection.".into()));
}
// Step 6.
response
}
// https://fetch.spec.whatwg.org/#concept-http-network-or-cache-fetch
fn http_network_or_cache_fetch(url: &ServoUrl,
origin: String,
headers: &mut Headers,
cookie_jar: Arc<RwLock<CookieStorage>>,
ssl_context: Arc<SslContext>)
-> Result<Response, NetworkError> {
// Steps 1-3.
// Not applicable: we don't even have a request yet, and there is no body
// in a WebSocket request.
// Step 4.
// Not applicable: credentials flag is always set
// because credentials mode is "include."
// Steps 5-9.
// Not applicable: there is no body in a WebSocket request.
// Step 10.
// TODO: handle header Referer.
// Step 11.
// Request's mode is "websocket".
headers.set(Origin(origin));
// Step 12.
// TODO: handle header User-Agent.
// Steps 13-14.
// Not applicable: request's cache mode is "no-store".
// Step 15.
{
// Step 15.1.
// We know there is no Pragma header yet.
headers.set(Pragma::NoCache);
// Step 15.2.
// We know there is no Cache-Control header yet.
headers.set(CacheControl(vec![CacheDirective::NoCache]));
}
// Step 16.
// TODO: handle Accept-Encoding.
// Not applicable: Connection header is already present.
// TODO: handle DNT.
headers.set(Host {
hostname: url.host_str().unwrap().to_owned(),
port: url.port(),
});
// Step 17.
// Credentials flag is set.
{
// Step 17.1.
// TODO: handle user agent configured to block cookies.
set_request_cookies(&url, headers, &cookie_jar);
// Steps 17.2-6.
// Not applicable: request has no Authorization header.
}
// Step 18.
// TODO: proxy-authentication entry.
// Step 19.
// Not applicable: with step 21 being useless, this one is too.
// Step 20.
// Not applicable: revalidatingFlag is only useful if step 21 is.
// Step 21.
// Not applicable: cache mode is "no-store".
// Step 22.
// There is no response yet.
let response = {
// Step 22.1.
// Not applicable: cache mode is "no-store".
// Step 22.2.
let forward_response = http_network_fetch(url, headers, cookie_jar, ssl_context);
// Step 22.3.
// Not applicable: request's method is not unsafe.
// Step 22.4.
// Not applicable: revalidatingFlag is unset.
// Step 22.5.
// There is no response yet and the response should not be cached.
forward_response
};
// Step 23.
// TODO: handle 401 status when request's window is not "no-window".
// Step 24.
// TODO: handle 407 status when request's window is not "no-window".
// Step 25.
// Not applicable: authentication-fetch flag is unset.
// Step 26.
response
}
// https://fetch.spec.whatwg.org/#concept-http-network-fetch
fn http_network_fetch(url: &ServoUrl,
headers: &Headers,
cookie_jar: Arc<RwLock<CookieStorage>>,
ssl_context: Arc<SslContext>)
-> Result<Response, NetworkError> {
// Step 1.
// Not applicable: credentials flag is set.
// Steps 2-3.
// Request's mode is "websocket".
let connection = obtain_a_websocket_connection(url, ssl_context)?;
// Step 4.
// Not applicable: requests body is null.
// Step 5.
let response = make_request(connection, url, headers)?;
// Steps 6-12.
// Not applicable: correct WebSocket responses don't have a body.
// Step 13.
// TODO: handle response's CSP list.
// Step 14.
// Not applicable: request's cache mode is "no-store".
// Step 15.
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);
if let Some(cookie) = Cookie::new_wrapped(cookie.clone(), url, CookieSource::HTTP) {
jar.push(cookie, url, CookieSource::HTTP);
}
}
}
let (sender, receiver) = response.begin().split();
Ok((protocol_in_use, sender, receiver))
// Step 16.
// Not applicable: correct WebSocket responses don't have a body.
// Step 17.
Ok(response)
}
fn make_request(mut stream: Stream,
url: &ServoUrl,
headers: &Headers)
-> Result<Response, NetworkError> {
write_request(&mut stream, url, headers).map_err(|e| {
NetworkError::Internal(format!("Request could not be sent: {}", e))
})?;
// FIXME: Stream isn't supposed to be cloned.
let writer = stream.clone();
// FIXME: BufReader from hyper isn't supposed to be used.
let mut reader = BufReader::new(stream);
let head = parse_response(&mut reader).map_err(|e| {
NetworkError::Internal(format!("Response could not be read: {}", e))
})?;
// This isn't in the spec, but this is the correct thing to do for WebSocket requests.
if head.version != HttpVersion::Http11 {
return Err(NetworkError::Internal("Response's HTTP version should be HTTP/1.1.".into()));
}
// FIXME: StatusCode::from_u16 isn't supposed to be used.
let status = StatusCode::from_u16(head.subject.0);
Ok(Response {
status: status,
headers: head.headers,
reader: reader,
writer: writer,
})
}
fn write_request(stream: &mut Stream,
url: &ServoUrl,
headers: &Headers)
-> io::Result<()> {
// Write "GET /foo/bar HTTP/1.1\r\n".
let method = Method::Get;
let request_uri = &url.as_url()[Position::BeforePath..Position::AfterQuery];
let version = HttpVersion::Http11;
write!(stream, "{} {} {}{}", method, request_uri, version, LINE_ENDING)?;
// Write the headers.
write!(stream, "{}{}", headers, LINE_ENDING)
}

View file

@ -22,6 +22,7 @@ lazy_static = "0.2"
log = "0.3.5"
msg = {path = "../msg"}
num-traits = "0.1.32"
openssl = "0.7.6"
parse-hosts = "0.3.0"
serde = "0.9"
serde_derive = "0.9"

View file

@ -22,6 +22,7 @@ extern crate lazy_static;
extern crate log;
extern crate msg;
extern crate num_traits;
extern crate openssl;
extern crate parse_hosts;
extern crate serde;
#[macro_use]
@ -35,16 +36,19 @@ extern crate webrender_traits;
use cookie_rs::Cookie;
use filemanager_thread::FileManagerThreadMsg;
use heapsize::HeapSizeOf;
use hyper::Error as HyperError;
use hyper::header::{ContentType, Headers, ReferrerPolicy as ReferrerPolicyHeader};
use hyper::http::RawStatus;
use hyper::mime::{Attr, Mime};
use hyper_serde::Serde;
use ipc_channel::Error;
use ipc_channel::Error as IpcError;
use ipc_channel::ipc::{self, IpcReceiver, IpcSender};
use ipc_channel::router::ROUTER;
use openssl::ssl::error::{OpensslError, SslError};
use request::{Request, RequestInit};
use response::{HttpsState, Response};
use servo_url::ServoUrl;
use std::error::Error;
use storage_thread::StorageThreadMsg;
pub mod blob_url_store;
@ -265,7 +269,7 @@ impl<T: FetchResponseListener> Action<T> for FetchResponseMsg {
/// Handle to a resource thread
pub type CoreResourceThread = IpcSender<CoreResourceMsg>;
pub type IpcSendResult = Result<(), Error>;
pub type IpcSendResult = Result<(), IpcError>;
/// Abstraction of the ability to send a particular type of message,
/// used by net_traits::ResourceThreads to ease the use its IpcSender sub-fields
@ -526,6 +530,69 @@ pub enum NetworkError {
SslValidation(ServoUrl, String),
}
impl NetworkError {
pub fn from_hyper_error(url: &ServoUrl, error: HyperError) -> Self {
if let HyperError::Ssl(ref ssl_error) = error {
if let Some(ssl_error) = ssl_error.downcast_ref::<SslError>() {
return NetworkError::from_ssl_error(url, ssl_error);
}
}
NetworkError::Internal(error.description().to_owned())
}
pub fn from_ssl_error(url: &ServoUrl, error: &SslError) -> Self {
if let SslError::OpenSslErrors(ref errors) = *error {
if errors.iter().any(is_cert_verify_error) {
let mut error_report = vec![format!("ssl error ({}):", openssl::version::version())];
let mut suggestion = None;
for err in errors {
if is_unknown_message_digest_err(err) {
suggestion = Some("<b>Servo recommends upgrading to a newer OpenSSL version.</b>");
}
error_report.push(format_ssl_error(err));
}
if let Some(suggestion) = suggestion {
error_report.push(suggestion.to_owned());
}
let error_report = error_report.join("<br>\n");
return NetworkError::SslValidation(url.clone(), error_report);
}
}
NetworkError::Internal(error.description().to_owned())
}
}
fn format_ssl_error(error: &OpensslError) -> String {
match error {
&OpensslError::UnknownError { ref library, ref function, ref reason } => {
format!("{}: {} - {}", library, function, reason)
}
}
}
// FIXME: This incredibly hacky. Make it more robust, and at least test it.
fn is_cert_verify_error(error: &OpensslError) -> bool {
match error {
&OpensslError::UnknownError { ref library, ref function, ref reason } => {
library == "SSL routines" &&
function.to_uppercase() == "SSL3_GET_SERVER_CERTIFICATE" &&
reason == "certificate verify failed"
}
}
}
fn is_unknown_message_digest_err(error: &OpensslError) -> bool {
match error {
&OpensslError::UnknownError { ref library, ref function, ref reason } => {
library == "asn1 encoding routines" &&
function == "ASN1_item_verify" &&
reason == "unknown message digest algorithm"
}
}
}
/// Normalize `slice`, as defined by
/// [the Fetch Spec](https://fetch.spec.whatwg.org/#concept-header-value-normalize).
pub fn trim_http_whitespace(mut slice: &[u8]) -> &[u8] {