servo: Merge #16099 - Properly follow the spec in WebSocket::Constructor (from nox:tungstenite); r=jdm

Source-Repo: https://github.com/servo/servo
Source-Revision: 189b0d9094a8448aedd778ae2aac538a3a9e4871

--HG--
extra : subtree_source : https%3A//hg.mozilla.org/projects/converted-servo-linear
extra : subtree_revision : 380ec561f5d4fac6d1f0aa7d7f57dc88867c42bb
This commit is contained in:
Anthony Ramine 2017-03-24 01:46:14 -07:00
Родитель 26a234de5d
Коммит cb26da19d2
9 изменённых файлов: 172 добавлений и 204 удалений

Просмотреть файл

@ -92,7 +92,7 @@ impl FontTemplateData {
.expect("No URL for Core Text font!")
.get_string()
.to_string()).expect("Couldn't parse Core Text font URL!")
.as_url().unwrap().to_file_path()
.as_url().to_file_path()
.expect("Core Text font didn't name a path!");
let mut bytes = Vec::new();
File::open(path).expect("Couldn't open font file!").read_to_end(&mut bytes).unwrap();

Просмотреть файл

@ -20,6 +20,7 @@ use net_traits::{FetchTaskTarget, NetworkError, ReferrerPolicy};
use net_traits::request::{Referrer, Request, RequestMode, ResponseTainting};
use net_traits::request::{Type, Origin, Window};
use net_traits::response::{Response, ResponseBody, ResponseType};
use servo_url::ServoUrl;
use std::borrow::Cow;
use std::fmt;
use std::fs::File;
@ -148,17 +149,8 @@ pub fn main_fetch(request: Rc<Request>,
// Step 5
// TODO this step (CSP port/content blocking)
if let Some(port) = request.url().port() {
let is_ftp = request.url().scheme() == "ftp" && (port == 20 || port == 21);
static BAD_PORTS: [u16; 64] = [1, 7, 9, 11, 13, 15, 17, 19, 20, 21, 22, 23, 25, 37, 42,
43, 53, 77, 79, 87, 95, 101, 102, 103, 104, 109, 110, 111,
113, 115, 117, 119, 123, 135, 139, 143, 179, 389, 465, 512,
513, 514, 515, 526, 530, 531, 532, 540, 556, 563, 587, 601,
636, 993, 995, 2049, 3659, 4045, 6000, 6665, 6666, 6667,
6668, 6669];
if !is_ftp && BAD_PORTS.binary_search(&port).is_ok() {
response = Some(Response::network_error(NetworkError::Internal("Request attempted on bad port".into())));
}
if should_be_blocked_due_to_bad_port(&request.url()) {
response = Some(Response::network_error(NetworkError::Internal("Request attempted on bad port".into())));
}
// Step 6
@ -623,3 +615,50 @@ fn should_block_nosniff(request: &Request, response: &Response) -> bool {
_ => false
};
}
/// https://fetch.spec.whatwg.org/#block-bad-port
pub fn should_be_blocked_due_to_bad_port(url: &ServoUrl) -> bool {
// Step 1 is not applicable, this function just takes the URL directly.
// Step 2.
let scheme = url.scheme();
// Step 3.
// If there is no explicit port, this means the default one is used for
// the given scheme, and thus this means the request should not be blocked
// due to a bad port.
let port = if let Some(port) = url.port() { port } else { return false };
// Step 4.
if scheme == "ftp" && (port == 20 || port == 21) {
return false;
}
// Step 5.
if is_network_scheme(scheme) && is_bad_port(port) {
return true;
}
// Step 6.
false
}
/// https://fetch.spec.whatwg.org/#network-scheme
fn is_network_scheme(scheme: &str) -> bool {
scheme == "ftp" || scheme == "http" || scheme == "https"
}
/// https://fetch.spec.whatwg.org/#bad-port
fn is_bad_port(port: u16) -> bool {
static BAD_PORTS: [u16; 64] = [
1, 7, 9, 11, 13, 15, 17, 19, 20, 21, 22, 23, 25, 37, 42,
43, 53, 77, 79, 87, 95, 101, 102, 103, 104, 109, 110, 111,
113, 115, 117, 119, 123, 135, 139, 143, 179, 389, 465, 512,
513, 514, 515, 526, 530, 531, 532, 540, 556, 563, 587, 601,
636, 993, 995, 2049, 3659, 4045, 6000, 6665, 6666, 6667,
6668, 6669
];
BAD_PORTS.binary_search(&port).is_ok()
}

Просмотреть файл

@ -2,11 +2,13 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use cookie::Cookie;
use cookie_storage::CookieStorage;
use fetch::methods::should_be_blocked_due_to_bad_port;
use http_loader;
use hyper::header::Host;
use net_traits::{WebSocketCommunicate, WebSocketConnectData, WebSocketDomAction, WebSocketNetworkEvent};
use net_traits::MessageData;
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::unwrap_websocket_protocol;
use servo_url::ServoUrl;
@ -23,72 +25,90 @@ use websocket::sender::Sender;
use websocket::stream::WebSocketStream;
use websocket::ws::receiver::Receiver as WSReceiver;
use websocket::ws::sender::Sender as Sender_Object;
use websocket::ws::util::url::parse_url;
/// *Establish a WebSocket Connection* as defined in RFC 6455.
fn establish_a_websocket_connection(resource_url: &ServoUrl, net_url: (Host, String, bool),
origin: String, protocols: Vec<String>,
// 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<(Headers, Sender<WebSocketStream>, Receiver<WebSocketStream>)> {
let host = Host {
hostname: resource_url.host_str().unwrap().to_owned(),
port: resource_url.port_or_known_default(),
};
-> WebSocketResult<(Headers, 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.
let mut request = try!(Client::connect(net_url));
request.headers.set(Origin(origin));
request.headers.set(host);
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());
{
let protocol_in_use = unwrap_websocket_protocol(response.protocol());
if let Some(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"));
};
// Step 13 and transitive subset of step 14.
// See step 6 of http://tools.ietf.org/html/rfc6455#section-4.1.
if let Some(protocol_name) = unwrap_websocket_protocol(response.protocol()) {
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 headers = response.headers.clone();
let (sender, receiver) = response.begin().split();
Ok((headers, 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 || {
// Step 8: Protocols.
// Step 9.
// URL that we actually fetch from the network, after applying the replacements
// specified in the hosts file.
let net_url_result = parse_url(replace_hosts(&connect_data.resource_url).as_url().unwrap());
let net_url = match net_url_result {
Ok(net_url) => net_url,
Err(e) => {
debug!("Failed to establish a WebSocket connection: {:?}", e);
let _ = connect.event_sender.send(WebSocketNetworkEvent::Fail);
return;
}
};
let channel = establish_a_websocket_connection(&connect_data.resource_url,
net_url,
connect_data.origin,
connect_data.protocols.clone(),
connect_data.protocols,
cookie_jar);
let (_, ws_sender, mut receiver) = match channel {
Ok(channel) => {
let _ = connect.event_sender.send(WebSocketNetworkEvent::ConnectionEstablished(channel.0.clone(),
connect_data.protocols));
channel
let (ws_sender, mut receiver) = match channel {
Ok((headers, sender, receiver)) => {
let _ = connect.event_sender.send(WebSocketNetworkEvent::ConnectionEstablished(headers));
(sender, receiver)
},
Err(e) => {
debug!("Failed to establish a WebSocket connection: {:?}", e);

Просмотреть файл

@ -350,8 +350,7 @@ pub enum WebSocketDomAction {
pub enum WebSocketNetworkEvent {
ConnectionEstablished(#[serde(deserialize_with = "::hyper_serde::deserialize",
serialize_with = "::hyper_serde::serialize")]
header::Headers,
Vec<String>),
header::Headers),
MessageReceived(MessageData),
Close(Option<u16>, String),
Fail,

Просмотреть файл

@ -48,7 +48,7 @@ impl URL {
}
pub fn query_pairs(&self) -> Vec<(String, String)> {
self.url.borrow().as_url().unwrap().query_pairs().into_owned().collect()
self.url.borrow().as_url().query_pairs().into_owned().collect()
}
pub fn set_query_pairs(&self, pairs: &[(String, String)]) {

Просмотреть файл

@ -12,37 +12,37 @@ pub struct UrlHelper;
impl UrlHelper {
pub fn Origin(url: &ServoUrl) -> USVString {
USVString(quirks::origin(url.as_url().unwrap()).to_owned())
USVString(quirks::origin(url.as_url()).to_owned())
}
pub fn Href(url: &ServoUrl) -> USVString {
USVString(quirks::href(url.as_url().unwrap()).to_owned())
USVString(quirks::href(url.as_url()).to_owned())
}
pub fn Hash(url: &ServoUrl) -> USVString {
USVString(quirks::hash(url.as_url().unwrap()).to_owned())
USVString(quirks::hash(url.as_url()).to_owned())
}
pub fn Host(url: &ServoUrl) -> USVString {
USVString(quirks::host(url.as_url().unwrap()).to_owned())
USVString(quirks::host(url.as_url()).to_owned())
}
pub fn Port(url: &ServoUrl) -> USVString {
USVString(quirks::port(url.as_url().unwrap()).to_owned())
USVString(quirks::port(url.as_url()).to_owned())
}
pub fn Search(url: &ServoUrl) -> USVString {
USVString(quirks::search(url.as_url().unwrap()).to_owned())
USVString(quirks::search(url.as_url()).to_owned())
}
pub fn Hostname(url: &ServoUrl) -> USVString {
USVString(quirks::hostname(url.as_url().unwrap()).to_owned())
USVString(quirks::hostname(url.as_url()).to_owned())
}
pub fn Password(url: &ServoUrl) -> USVString {
USVString(quirks::password(url.as_url().unwrap()).to_owned())
USVString(quirks::password(url.as_url()).to_owned())
}
pub fn Pathname(url: &ServoUrl) -> USVString {
USVString(quirks::pathname(url.as_url().unwrap()).to_owned())
USVString(quirks::pathname(url.as_url()).to_owned())
}
pub fn Protocol(url: &ServoUrl) -> USVString {
USVString(quirks::protocol(url.as_url().unwrap()).to_owned())
USVString(quirks::protocol(url.as_url()).to_owned())
}
pub fn Username(url: &ServoUrl) -> USVString {
USVString(quirks::username(url.as_url().unwrap()).to_owned())
USVString(quirks::username(url.as_url()).to_owned())
}
pub fn SetHash(url: &mut ServoUrl, value: USVString) {
if let Some(ref mut url) = url.as_mut_url() {

Просмотреть файл

@ -23,17 +23,13 @@ use dom::globalscope::GlobalScope;
use dom::messageevent::MessageEvent;
use dom::urlhelper::UrlHelper;
use dom_struct::dom_struct;
use hyper;
use hyper_serde::Serde;
use ipc_channel::ipc::{self, IpcReceiver, IpcSender};
use js::jsapi::JSAutoCompartment;
use js::jsval::UndefinedValue;
use js::typedarray::{ArrayBuffer, CreateWith};
use net_traits::{WebSocketCommunicate, WebSocketConnectData, WebSocketDomAction, WebSocketNetworkEvent};
use net_traits::CookieSource::HTTP;
use net_traits::CoreResourceMsg::{SetCookiesForUrl, WebsocketConnect};
use net_traits::CoreResourceMsg::WebsocketConnect;
use net_traits::MessageData;
use net_traits::hosts::replace_hosts;
use net_traits::unwrap_websocket_protocol;
use script_runtime::CommonScriptMsg;
use script_runtime::ScriptThreadEventCategory::WebSocketEvent;
@ -47,7 +43,6 @@ use std::thread;
use task_source::TaskSource;
use task_source::networking::NetworkingTaskSource;
use websocket::header::{Headers, WebSocketProtocol};
use websocket::ws::util::url::parse_url;
#[derive(JSTraceable, PartialEq, Copy, Clone, Debug, HeapSizeOf)]
enum WebSocketRequestState {
@ -57,75 +52,6 @@ enum WebSocketRequestState {
Closed = 3,
}
// list of bad ports according to
// https://fetch.spec.whatwg.org/#port-blocking
const BLOCKED_PORTS_LIST: &'static [u16] = &[
1, // tcpmux
7, // echo
9, // discard
11, // systat
13, // daytime
15, // netstat
17, // qotd
19, // chargen
20, // ftp-data
21, // ftp
22, // ssh
23, // telnet
25, // smtp
37, // time
42, // name
43, // nicname
53, // domain
77, // priv-rjs
79, // finger
87, // ttylink
95, // supdup
101, // hostriame
102, // iso-tsap
103, // gppitnp
104, // acr-nema
109, // pop2
110, // pop3
111, // sunrpc
113, // auth
115, // sftp
117, // uucp-path
119, // nntp
123, // ntp
135, // loc-srv / epmap
139, // netbios
143, // imap2
179, // bgp
389, // ldap
465, // smtp+ssl
512, // print / exec
513, // login
514, // shell
515, // printer
526, // tempo
530, // courier
531, // chat
532, // netnews
540, // uucp
556, // remotefs
563, // nntp+ssl
587, // smtp
601, // syslog-conn
636, // ldap+ssl
993, // imap+ssl
995, // pop3+ssl
2049, // nfs
3659, // apple-sasl
4045, // lockd
6000, // x11
6665, // irc (alternate)
6666, // irc (alternate)
6667, // irc (default)
6668, // irc (alternate)
6669, // irc (alternate)
];
// Close codes defined in https://tools.ietf.org/html/rfc6455#section-7.4.1
// Names are from https://github.com/mozilla/gecko-dev/blob/master/netwerk/protocol/websocket/nsIWebSocketChannel.idl
#[allow(dead_code)]
@ -202,34 +128,36 @@ impl WebSocket {
global, WebSocketBinding::Wrap)
}
/// https://html.spec.whatwg.org/multipage/#dom-websocket
pub fn Constructor(global: &GlobalScope,
url: DOMString,
protocols: Option<StringOrStringSequence>)
-> Fallible<Root<WebSocket>> {
// Step 1.
let resource_url = try!(ServoUrl::parse(&url).map_err(|_| Error::Syntax));
// Although we do this replace and parse operation again in the resource thread,
// we try here to be able to immediately throw a syntax error on failure.
let _ = try!(parse_url(&replace_hosts(&resource_url).as_url().unwrap()).map_err(|_| Error::Syntax));
// Step 2: Disallow https -> ws connections.
// Steps 1-2.
let url_record = ServoUrl::parse(&url).or(Err(Error::Syntax))?;
// Step 3: Potentially block access to some ports.
let port: u16 = resource_url.port_or_known_default().unwrap();
if BLOCKED_PORTS_LIST.iter().any(|&p| p == port) {
return Err(Error::Security);
// Step 3.
match url_record.scheme() {
"ws" | "wss" => {},
_ => return Err(Error::Syntax),
}
// Step 4.
let protocols = match protocols {
Some(StringOrStringSequence::String(string)) => vec![String::from(string)],
Some(StringOrStringSequence::StringSequence(sequence)) => {
sequence.into_iter().map(String::from).collect()
},
_ => Vec::new(),
};
if url_record.fragment().is_some() {
return Err(Error::Syntax);
}
// Step 5.
let protocols = protocols.map_or(vec![], |p| {
match p {
StringOrStringSequence::String(string) => vec![string.into()],
StringOrStringSequence::StringSequence(seq) => {
seq.into_iter().map(String::from).collect()
},
}
});
// Step 6.
for (i, protocol) in protocols.iter().enumerate() {
// https://tools.ietf.org/html/rfc6455#section-4.1
// Handshake requirements, step 10
@ -244,16 +172,12 @@ impl WebSocket {
}
}
// Step 6: Origin.
let origin = UrlHelper::Origin(&global.get_url()).0;
// Step 7.
let ws = WebSocket::new(global, resource_url.clone());
let ws = WebSocket::new(global, url_record.clone());
let address = Trusted::new(&*ws);
let connect_data = WebSocketConnectData {
resource_url: resource_url.clone(),
origin: origin,
resource_url: url_record,
origin: UrlHelper::Origin(&global.get_url()).0,
protocols: protocols,
};
@ -270,42 +194,42 @@ impl WebSocket {
action_receiver: resource_action_receiver,
};
// Step 8.
let _ = global.core_resource_thread().send(WebsocketConnect(connect, connect_data));
*ws.sender.borrow_mut() = Some(dom_action_sender);
let moved_address = address.clone();
let task_source = global.networking_task_source();
let wrapper = global.get_runnable_wrapper();
thread::spawn(move || {
while let Ok(event) = dom_event_receiver.recv() {
match event {
WebSocketNetworkEvent::ConnectionEstablished(headers, protocols) => {
WebSocketNetworkEvent::ConnectionEstablished(headers) => {
let open_thread = box ConnectionEstablishedTask {
address: moved_address.clone(),
address: address.clone(),
headers: headers,
protocols: protocols,
};
task_source.queue_with_wrapper(open_thread, &wrapper).unwrap();
},
WebSocketNetworkEvent::MessageReceived(message) => {
let message_thread = box MessageReceivedTask {
address: moved_address.clone(),
address: address.clone(),
message: message,
};
task_source.queue_with_wrapper(message_thread, &wrapper).unwrap();
},
WebSocketNetworkEvent::Fail => {
fail_the_websocket_connection(moved_address.clone(),
fail_the_websocket_connection(address.clone(),
&task_source, &wrapper);
},
WebSocketNetworkEvent::Close(code, reason) => {
close_the_websocket_connection(moved_address.clone(),
close_the_websocket_connection(address.clone(),
&task_source, &wrapper, code, reason);
},
}
}
});
// Step 7.
Ok(ws)
}
@ -466,45 +390,31 @@ impl WebSocketMethods for WebSocket {
/// Task queued when *the WebSocket connection is established*.
/// https://html.spec.whatwg.org/multipage/#feedback-from-the-protocol:concept-websocket-established
struct ConnectionEstablishedTask {
address: Trusted<WebSocket>,
protocols: Vec<String>,
headers: Headers,
}
impl Runnable for ConnectionEstablishedTask {
fn name(&self) -> &'static str { "ConnectionEstablishedTask" }
/// https://html.spec.whatwg.org/multipage/#feedback-from-the-protocol:concept-websocket-established
fn handler(self: Box<Self>) {
let ws = self.address.root();
// Step 1: Protocols.
if !self.protocols.is_empty() && self.headers.get::<WebSocketProtocol>().is_none() {
let task_source = ws.global().networking_task_source();
fail_the_websocket_connection(self.address, &task_source, &ws.global().get_runnable_wrapper());
return;
}
// Step 2.
// Step 1.
ws.ready_state.set(WebSocketRequestState::Open);
// Step 3: Extensions.
//TODO: Set extensions to extensions in use
// Step 2: Extensions.
// TODO: Set extensions to extensions in use.
// Step 4: Protocols.
let protocol_in_use = unwrap_websocket_protocol(self.headers.get::<WebSocketProtocol>());
if let Some(protocol_name) = protocol_in_use {
// Step 3.
if let Some(protocol_name) = unwrap_websocket_protocol(self.headers.get::<WebSocketProtocol>()) {
*ws.protocol.borrow_mut() = protocol_name.to_owned();
};
// Step 5: Cookies.
if let Some(cookies) = self.headers.get::<hyper::header::SetCookie>() {
let cookies = cookies.iter().map(|c| Serde(c.clone())).collect();
let _ = ws.global().core_resource_thread().send(
SetCookiesForUrl(ws.url.clone(), cookies, HTTP));
}
// Step 6.
// Step 4.
ws.upcast().fire_event(atom!("open"));
}
}

Просмотреть файл

@ -1496,8 +1496,8 @@ impl Window {
let referrer_policy = referrer_policy.or(doc.get_referrer_policy());
// https://html.spec.whatwg.org/multipage/#navigating-across-documents
if !force_reload && url.as_url().unwrap()[..Position::AfterQuery] ==
doc.url().as_url().unwrap()[..Position::AfterQuery] {
if !force_reload && url.as_url()[..Position::AfterQuery] ==
doc.url().as_url()[..Position::AfterQuery] {
// Step 5
if let Some(fragment) = url.fragment() {
doc.check_and_scroll_fragment(fragment);

Просмотреть файл

@ -53,8 +53,8 @@ impl ServoUrl {
Some(Arc::try_unwrap(self.0).unwrap_or_else(|s| (*s).clone()))
}
pub fn as_url(&self) -> Option<&Arc<Url>> {
Some(&self.0)
pub fn as_url(&self) -> &Url {
&self.0
}
pub fn parse(input: &str) -> Result<Self, url::ParseError> {