Skip to content

Commit

Permalink
Update WebSocket to use Fetch's WebSocket alterations
Browse files Browse the repository at this point in the history
This makes the following changes to the WebSocket API:

* Moves Mixed Content, HSTS, and cookie handling to the network layer.
* Makes sure that other things handled by the network layer now also apply to WebSocket, such as
  upgrading of insecure requests.
* Removes the unused "extensions in use" concept.
* Removes the "client-specified protocols" concept since that is entirely handled by the Web Socket
  Protocol specification (and still is with the Fetch alterations).
* Inlines the parsing of URLs since it's a lot less involved now. Also uses the URL parser rather
  than the "parse a URL" construct since there's no base URL.

Fixes #180.
  • Loading branch information
annevk committed Mar 9, 2016
1 parent 0dd609f commit b122a17
Showing 1 changed file with 46 additions and 168 deletions.
214 changes: 46 additions & 168 deletions source
Expand Up @@ -3536,18 +3536,21 @@ a.setAttribute('href', 'http://example.com/'); // change the content attribute d
<dt>The WebSocket protocol</dt>

<dd>
<p>The following terms are defined in the WHATWG Fetch standard: <ref spec=FETCH></p>

<ul class="brief">
<li><dfn data-noexport="" data-x="concept-websocket-establish"
data-x-href="https://fetch.spec.whatwg.org/#concept-websocket-establish">establish a WebSocket
connection</dfn>
</ul>

<p>The following terms are defined in the WebSocket protocol specification: <ref spec=WSP></p>

<ul class="brief">

<li><dfn data-noexport="" data-x="concept-websocket-establish">establish a WebSocket connection</dfn>
<li><dfn data-noexport="" data-x="concept-websocket-established">the WebSocket connection is established</dfn>
<li><dfn data-noexport="" data-x="concept-websocket-validate">validate the server's response</dfn>
<li><dfn data-noexport="" data-x="concept-websockets-active-extensions">extensions in use</dfn>
<li><dfn data-noexport="" data-x="concept-websocket-subprotocol">subprotocol in use</dfn>
<li><dfn data-noexport="" data-x="concept-websocket-cookie-headers">headers to send appropriate cookies</dfn>
<li><dfn data-noexport="" data-x="concept-websocket-handshake-cookies">cookies set during the server's opening handshake</dfn>
<li><dfn data-noexport="" data-x="concept-websocket-message-received">a WebSocket message has been received</dfn>
<li><dfn data-noexport="" data-x="concept-websocket-send">send a WebSocket Message</dfn>
<li><dfn data-noexport="" data-x="concept-websocket-fail">fail the WebSocket connection</dfn>
Expand Down Expand Up @@ -91660,9 +91663,9 @@ data:&nbsp;test
<h4>The <code>WebSocket</code> interface</h4>

<pre class="idl">enum <dfn>BinaryType</dfn> { "<span data-x="dom-BinaryType-blob">blob</span>", "<span data-x="dom-BinaryType-arraybuffer">arraybuffer</span>" };
[<span data-x="dom-WebSocket">Constructor</span>(DOMString url, optional (DOMString or sequence&lt;DOMString&gt;) protocols), Exposed=(Window,Worker)]
[<span data-x="dom-WebSocket">Constructor</span>(USVString url, optional (DOMString or sequence&lt;DOMString&gt;) protocols = []), Exposed=(Window,Worker)]
interface <dfn>WebSocket</dfn> : <span>EventTarget</span> {
readonly attribute DOMString <span data-x="dom-WebSocket-url">url</span>;
readonly attribute USVString <span data-x="dom-WebSocket-url">url</span>;

// ready state
const unsigned short <span data-x="dom-WebSocket-CONNECTING">CONNECTING</span> = 0;
Expand All @@ -91689,6 +91692,9 @@ interface <dfn>WebSocket</dfn> : <span>EventTarget</span> {
void <span data-x="dom-WebSocket-send">send</span>(<span data-x="idl-ArrayBufferView">ArrayBufferView</span> data);
};</pre>

<p>Each <code>WebSocket</code> object has an associated <dfn
data-x="concept-websocket-url">url</dfn> (a <span>URL record</span>).

<p>The <dfn><code data-x="dom-WebSocket">WebSocket(<var>url</var>,
<var>protocols</var>)</code></dfn> constructor takes one or two arguments. The first argument,
<var>url</var>, specifies the <span>URL</span> to which to connect. The second,
Expand All @@ -91700,115 +91706,71 @@ interface <dfn>WebSocket</dfn> : <span>EventTarget</span> {
<code data-x="http-sec-websocket-protocol">Sec-WebSocket-Protocol</code> fields as defined by the
WebSocket protocol specification. <ref spec=WSP></p>

<p>When the <code data-x="dom-WebSocket">WebSocket()</code> constructor is invoked, the UA must
run these steps:</p>
<p>The <code data-x="dom-WebSocket">WebSocket(<var>url</var>, <var>protocols</var>)</code>
constructor, when invoked, must run these steps:</p>

<ol>
<li><p>Let <var>urlRecord</var> be the result of applying the <span>URL parser</span> to
<var>url</var>.</p></li>

<li><p><i data-x="parse a WebSocket URL's components">Parse a WebSocket URL's components</i> from
the <var>url</var> argument, to obtain <var>host</var>, <var>port</var>, <var>resource
name</var>, and <var>secure</var>. If this fails, throw a <code>SyntaxError</code> exception and
abort these steps. <ref spec=WSP></p></li>

<li><p>If <var>secure</var> is false but the <span>origin</span> specified by the <span>entry
settings object</span> has a scheme component that is itself a secure protocol, e.g. HTTPS, then
throw a <code>SecurityError</code> exception and abort these steps.</p></li>

<li>
<p>If <var>secure</var> is false, <var>host</var> is a <span
data-x="concept-domain">domain</span>, and matching <var>host</var> per <a
href="https://tools.ietf.org/html/rfc6797#section-8.2">Known HSTS Host Domain Name Matching</a>
results in either a superdomain match with an asserted <code data-x="">includeSubDomains</code>
directive or a congruent match (with or without an asserted <code
data-x="">includeSubDomains</code> directive), then run these substeps: <ref spec=HSTS></p>
<li><p>If <var>urlRecord</var> is failure, then throw a <code>SyntaxError</code>
exception.</p></li>

<ol>
<li><p>Set <var>secure</var> to true.</p></li>
<li><p>If <var>urlRecord</var>'s <span data-x="concept-url-scheme">scheme</span> is not "<code
data-x="">ws</code>" or "<code data-x="">wss</code>", then throw a <code>SyntaxError</code>
exception.</p></li>

<li><p>If <var>port</var> is 80, set <var>port</var> to 443.</p></li>
</ol>
<li><p>If <var>urlRecord</var>'s <span data-x="concept-url-fragment">fragment</span> is non-null,
then throw a <code>SyntaxError</code> exception.</p></li>

<li>

<p>If <var>port</var> is a port to which the user agent is configured to block access, then
throw a <code>SecurityError</code> exception and abort these steps. (User agents typically block
access to well-known ports like SMTP.)</p>
<p>If <var>urlRecord</var>'s <span data-x="concept-url-port">port</span> is a port to which the
user agent is configured to block access, then throw a <code>SecurityError</code> exception and
abort these steps. (User agents typically block access to well-known ports like SMTP.)</p>
<!-- e.g. http://www.mozilla.org/projects/netlib/PortBanning.html -->

<p>Access to ports 80 and 443 should not be blocked, including the unlikely cases when
<var>secure</var> is false but <var>port</var> is 443 or <var>secure</var> is true but
<var>port</var> is 80.</p>
<var>urlRecord</var>'s <span data-x="concept-url-scheme">scheme</span> is "<code
data-x="">ws</code>" but its <span data-x="concept-url-port">port</span> is 443 or its <span
data-x="concept-url-scheme">scheme</span> is "<code data-x="">wss</code>" but its <span
data-x="concept-url-port">port</span> is 80.</p>
<!-- paragraph requested by zcorpan -->

</li>

<li>

<p>If <var>protocols</var> is absent, let <var>protocols</var> be an empty array.</p>

<p>Otherwise, if <var>protocols</var> is present and a string, let <var>protocols</var> instead
be an array consisting of just that string.</p>

</li>
<li><p>If <var>protocols</var> is a string, set <var>protocols</var> to an an array consisting of
just that string.</p></li>

<li><p>If any of the values in <var>protocols</var> occur more than once or otherwise fail to
match the requirements for elements that comprise the value of <code
data-x="http-sec-websocket-protocol">Sec-WebSocket-Protocol</code> fields as defined by the
WebSocket protocol specification, then throw a <code>SyntaxError</code> exception and abort these
steps. <ref spec=WSP></p></li>

<li><p>Let <var>origin</var> be the <span data-x="ASCII serialisation of an origin">ASCII
serialisation</span> of the <span>origin</span> specified by the <span>entry settings
object</span>, <span>converted to ASCII lowercase</span>.</p></li>

<li><p>Return a new <code>WebSocket</code> object, but continue these steps
<li><p>Return a new <code>WebSocket</code> object whose <span
data-x="concept-websocket-url">url</span> is <var>urlRecord</var>, but continue these steps
<span>in parallel</span>.</p></li>

<li><p>Let the new object's <dfn>client-specified protocols</dfn> be the values (if any) given in
<var>protocols</var>.</p></li>

<li>
<p><span data-x="concept-websocket-establish">Establish a WebSocket connection</span> given
<var>urlRecord</var>, <var>protocols</var>, and the <span>entry settings object</span>. <ref
spec=FETCH></p>

<p><i data-x="concept-websocket-establish">Establish a WebSocket connection</i> given the set
(<var>host</var>, <var>port</var>, <var>resource name</var>, <var>secure</var>), along with the
<var>protocols</var> list, an empty list for the extensions, and <var>origin</var>. The
<i data-x="concept-websocket-cookie-headers">headers to send appropriate cookies</i> must be a
`<code data-x="http-cookie">Cookie</code>` header whose value is the <span>cookie-string</span>
computed from the user's cookie store and the URL <var>url</var>; for these purposes this is
<em>not</em> a "non-HTTP" API. <ref spec=WSP> <ref spec=COOKIES></p>

<p>When the user agent <i data-x="concept-websocket-validate">validates the server's
response</i> during the "<i data-x="concept-websocket-establish">establish a WebSocket
connection</i>" algorithm, if the status code received from the server is not 101 (e.g. it is a
redirect), the user agent must <i data-x="concept-websocket-fail">fail the WebSocket
connection</i>.</p>

<p class="warning">Following HTTP procedures here could introduce serious security problems in a
Web browser context. For example, consider a host with a WebSocket server at one path and an
open HTTP redirector at another. Suddenly, any script that can be given a particular WebSocket
URL can be tricked into communicating to (and potentially sharing secrets with) any host on the
Internet, even if the script checks that the URL has the right hostname.</p> <!--
https://www.ietf.org/mail-archive/web/hybi/current/msg06951.html -->

<p class="note">If the <i data-x="concept-websocket-establish">establish a WebSocket
connection</i> algorithm fails, it triggers the <i data-x="concept-websocket-fail">fail the
<p class="note">If the <span data-x="concept-websocket-establish">establish a WebSocket
connection</span> algorithm fails, it triggers the <i data-x="concept-websocket-fail">fail the
WebSocket connection</i> algorithm, which then invokes the <i
data-x="concept-websocket-close">close the WebSocket connection</i> algorithm, which then
establishes that <i data-x="concept-websocket-closed">the WebSocket connection is closed</i>,
which fires the <code data-x="event-close">close</code> event <a href="#closeWebSocket">as
described below</a>.</p>

</li>

</ol>

<hr>

<p>The <dfn><code data-x="dom-WebSocket-url">url</code></dfn> attribute must return the
<span>resulting URL string</span> of <span data-x="parse a url">parsing</span> the
<span>URL</span> that was passed to the constructor, with the URL character encoding set to UTF-8.
(It doesn't matter what it is parsed relative to, since we already know it is an <span>absolute
URL</span>.)</p>
<p>The <dfn><code data-x="dom-WebSocket-url">url</code></dfn> attribute must return this
<code>WebSocket</code> object's <span data-x="concept-websocket-url">url</span>, <span
data-x="concept-url-serialiser">serialised</span>.</p>

<p>The <dfn><code data-x="dom-WebSocket-readyState">readyState</code></dfn> attribute represents
the state of the connection. It can have the following values:</p>
Expand Down Expand Up @@ -91838,13 +91800,11 @@ interface <dfn>WebSocket</dfn> : <span>EventTarget</span> {
<p>When the object is created its <code data-x="dom-WebSocket-readyState">readyState</code> must be
set to <code data-x="dom-WebSocket-CONNECTING">CONNECTING</code> (0).</p>

<p>The <dfn><code data-x="dom-WebSocket-extensions">extensions</code></dfn> attribute must
initially return the empty string. After <i data-x="concept-websocket-established">the WebSocket
connection is established</i>, its value might change, as defined below.</p>
<p>The <dfn><code data-x="dom-WebSocket-extensions">extensions</code></dfn> attribute must return
the empty string.

<p class="note">The <code data-x="dom-WebSocket-extensions">extensions</code> attribute returns
the extensions selected by the server, if any. (Currently this will only ever be the empty
string.)</p>
<p class="note">The <code data-x="dom-WebSocket-extensions">extensions</code> attribute might in
the future return extensions selected by the server, if any.</p>

<p>The <dfn><code data-x="dom-WebSocket-protocol">protocol</code></dfn> attribute must initially
return the empty string. After <i>the WebSocket connection is established</i>, its value might
Expand Down Expand Up @@ -92142,30 +92102,13 @@ socket.onopen = function () {

<ol>

<li><p>If the <code>WebSocket</code> object's <span>client-specified protocols</span> was not an
empty list, but the <i data-x="concept-websocket-subprotocol">subprotocol in use</i> is the null
value, then <i data-x="concept-websocket-fail">fail the WebSocket connection</i>, set the <code
data-x="dom-WebSocket-readyState">readyState</code> attribute's value to <code
data-x="dom-WebSocket-CLOSING">CLOSING</code> (2), and abort these steps. <ref spec=WSP></p></li>

<li><p>Change the <code data-x="dom-WebSocket-readyState">readyState</code> attribute's value to
<code data-x="dom-WebSocket-OPEN">OPEN</code> (1).</p></li>

<li><p>Change the <code data-x="dom-WebSocket-extensions">extensions</code> attribute's value to
the <i data-x="concept-websockets-active-extensions">extensions in use</i>, if is not the null
value. <ref spec=WSP></p></li>

<li><p>Change the <code data-x="dom-WebSocket-protocol">protocol</code> attribute's value to the
<i data-x="concept-websocket-subprotocol">subprotocol in use</i>, if is not the null value. <ref
spec=WSP></p></li>

<li><p>Act as if the user agent had <span data-x="receives a set-cookie-string">received a
set-cookie-string</span> consisting of the <span
data-x="concept-websocket-handshake-cookies">cookies set during the server's opening
handshake</span>, for the URL <var>url</var> given to the <code
data-x="dom-WebSocket">WebSocket()</code> constructor. <ref spec=COOKIES> <ref spec=ENCODING>
<ref spec=WSP></p></li>

<li><p><span>Fire a simple event</span> named <code data-x="event-open">open</code> at the
<code>WebSocket</code> object.</p>

Expand Down Expand Up @@ -92360,71 +92303,6 @@ socket.onopen = function () {
https://www.w3.org/Bugs/Public/show_bug.cgi?id=17264 -->


<h4>Parsing WebSocket URLs</h4>

<p>The steps to <dfn>parse a WebSocket URL's components</dfn> from a string <var>url</var> are as
follows. These steps return either a <var>host</var>, a <var>port</var>, a <var>resource
name</var>, and a <var>secure</var> flag, or they fail.</p>

<ol>

<li><p>If the <var>url</var> string is not an <span>absolute URL</span>, then fail this
algorithm.</p></li>

<li>

<p><span data-x="parse a url">Parse</span> the <var>url</var> string, with the URL character
encoding set to UTF-8. <ref spec=ENCODING></p> <!-- the URL character encoding is used to escape
the query component -->

<p class="note">Since we already know the <var>url</var> string is an <span>absolute URL</span>,
<span data-x="parse a url">parsing</span> it will never return an error, and it doesn't
matter what it is resolved relative to.</p>

</li>

<li><p>If the <span>resulting URL record</span> does not have a <span
data-x="concept-url-scheme">scheme</span> component whose value is either "<code
data-x="">ws</code>" or "<code data-x="">wss</code>", then fail this algorithm.</p></li>

<li><p>If the <span>resulting URL record</span> has a non-null <span
data-x="concept-url-fragment">fragment</span> component, then fail this algorithm.</p></li>

<li><p>If the <span data-x="concept-url-scheme">scheme</span> component of the <span>resulting
URL record</span> is "<code data-x="">ws</code>", set <var>secure</var> to false;
otherwise, the <span data-x="concept-url-scheme">scheme</span> component is "<code
data-x="">wss</code>", set <var>secure</var> to true.</p></li>

<li><p>Let <var>host</var> be the value of the <span>resulting URL record</span>'s <span
data-x="concept-url-host">host</span> component.</p></li> <!-- at this point this is
Punycode-encoded and lowercased already -->

<li><p>If the <span>resulting URL record</span> has a <span data-x="concept-url-port">port</span>
component that is not the empty string, then let <var>port</var> be that component's value;
otherwise, there is no explicit <var>port</var>.</p></li>

<li><p>If there is no explicit <var>port</var>, then: if <var>secure</var> is false, let
<var>port</var> be 80, otherwise let <var>port</var> be 443.</p></li>

<li><p>Let <var>resource name</var> be the value of the <span>resulting URL record</span>'s <span
data-x="concept-url-path">path</span> component (which might be empty).</p></li> <!-- at this
point this is UTF-8 encoded and percent encoded -->

<li><p>If <var>resource name</var> is the empty string, set it to a single character U+002F
SOLIDUS (/).</p></li>

<li><p>If the <span>resulting URL record</span> has a non-null <span
data-x="concept-url-query">query</span> component, then append a single U+003F QUESTION MARK
character (?) to <var>resource name</var>, followed by the value of the <span
data-x="concept-url-query">query</span> component.</p></li> <!-- at this point this is UTF-8
encoded and percent encoded -->

<li><p>Return <var>host</var>, <var>port</var>, <var>resource name</var>, and
<var>secure</var>.</p></li>

</ol>


<h4>The <code>CloseEvent</code> interfaces</h4>

<pre class="idl">[Constructor(DOMString type, optional <span>CloseEventInit</span> eventInitDict), Exposed=(Window,Worker)]
Expand Down

0 comments on commit b122a17

Please sign in to comment.