author | Nick Hurley <hurley@todesschaf.org> |
Wed, 25 Apr 2012 20:12:33 -0400 | |
changeset 92451 | 301bf79e90299c12975096e22d0f891c2e64f304 |
parent 92450 | adea606b56942b2a7d5ad218daaa31ed75ee72e5 |
child 92452 | d0d3c5935c19370409814184981c948d013a102f |
push id | 8694 |
push user | ryanvm@gmail.com |
push date | Thu, 26 Apr 2012 00:12:38 +0000 |
treeherder | mozilla-inbound@81736e2ab216 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | mcmanus, ted |
bugs | 719609 |
milestone | 15.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
new file mode 100644 --- /dev/null +++ b/netwerk/test/unit/test_spdy.js @@ -0,0 +1,344 @@ +var Ci = Components.interfaces; +var Cc = Components.classes; + +// Generate a small and a large post with known pre-calculated md5 sums +function generateContent(size) { + var content = ""; + for (var i = 0; i < size; i++) { + content += "0"; + } + return content; +} + +var posts = []; +posts.push(generateContent(10)); +posts.push(generateContent(128 * 1024)); + +// pre-calculated md5sums (in hex) of the above posts +var md5s = ['f1b708bba17f1ce948dc979f4d7092bc', + '8f607cfdd2c87d6a7eedb657dafbd836']; + +function checkIsSpdy(request) { + try { + if (request.getResponseHeader("X-Firefox-Spdy") == "1") { + if (request.getResponseHeader("X-Connection-Spdy") == "yes") { + return true; + } + return false; // Weird case, but the server disagrees with us + } + } catch (e) { + // Nothing to do here + } + return false; +} + +var SpdyCheckListener = function() {}; + +SpdyCheckListener.prototype = { + onStartRequestFired: false, + onDataAvailableFired: false, + isSpdyConnection: false, + + onStartRequest: function testOnStartRequest(request, ctx) { + this.onStartRequestFired = true; + + if (!Components.isSuccessCode(request.status)) + do_throw("Channel should have a success code! (" + request.status + ")"); + if (!(request instanceof Components.interfaces.nsIHttpChannel)) + do_throw("Expecting an HTTP channel"); + + do_check_eq(request.responseStatus, 200); + do_check_eq(request.requestSucceeded, true); + }, + + onDataAvailable: function testOnDataAvailable(request, ctx, stream, off, cnt) { + this.onDataAvailableFired = true; + this.isSpdyConnection = checkIsSpdy(request); + + read_stream(stream, cnt); + }, + + onStopRequest: function testOnStopRequest(request, ctx, status) { + do_check_true(this.onStartRequestFired); + do_check_true(this.onDataAvailableFired); + do_check_true(this.isSpdyConnection); + + run_next_test(); + do_test_finished(); + } +}; + +/* + * Support for testing valid multiplexing of streams + */ + +var multiplexContent = generateContent(30*1024); +var completed_channels = []; +function register_completed_channel(listener) { + completed_channels.push(listener); + if (completed_channels.length == 2) { + do_check_neq(completed_channels[0].streamID, completed_channels[1].streamID); + run_next_test(); + do_test_finished(); + } +} + +/* Listener class to control the testing of multiplexing */ +var SpdyMultiplexListener = function() {}; + +SpdyMultiplexListener.prototype = new SpdyCheckListener(); + +SpdyMultiplexListener.prototype.streamID = 0; +SpdyMultiplexListener.prototype.buffer = ""; + +SpdyMultiplexListener.prototype.onDataAvailable = function(request, ctx, stream, off, cnt) { + this.onDataAvailableFired = true; + this.isSpdyConnection = checkIsSpdy(request); + this.streamID = parseInt(request.getResponseHeader("X-Spdy-StreamID")); + var data = read_stream(stream, cnt); + this.buffer = this.buffer.concat(data); +}; + +SpdyMultiplexListener.prototype.onStopRequest = function(request, ctx, status) { + do_check_true(this.onStartRequestFired); + do_check_true(this.onDataAvailableFired); + do_check_true(this.isSpdyConnection); + do_check_true(this.buffer == multiplexContent); + + // This is what does most of the hard work for us + register_completed_channel(this); +}; + +// Does the appropriate checks for header gatewaying +var SpdyHeaderListener = function(value) { + this.value = value +}; + +SpdyHeaderListener.prototype = new SpdyCheckListener(); +SpdyHeaderListener.prototype.value = ""; + +SpdyHeaderListener.prototype.onDataAvailable = function(request, ctx, stream, off, cnt) { + this.onDataAvailableFired = true; + this.isSpdyConnection = checkIsSpdy(request); + do_check_eq(request.getResponseHeader("X-Received-Test-Header"), this.value); + read_stream(stream, cnt); +}; + +// Does the appropriate checks for a large GET response +var SpdyBigListener = function() {}; + +SpdyBigListener.prototype = new SpdyCheckListener(); +SpdyBigListener.prototype.buffer = ""; + +SpdyBigListener.prototype.onDataAvailable = function(request, ctx, stream, off, cnt) { + this.onDataAvailableFired = true; + this.isSpdyConnection = checkIsSpdy(request); + this.buffer = this.buffer.concat(read_stream(stream, cnt)); + // We know the server should send us the same data as our big post will be, + // so the md5 should be the same + do_check_eq(md5s[1], request.getResponseHeader("X-Expected-MD5")); +}; + +SpdyBigListener.prototype.onStopRequest = function(request, ctx, status) { + do_check_true(this.onStartRequestFired); + do_check_true(this.onDataAvailableFired); + do_check_true(this.isSpdyConnection); + + // Don't want to flood output, so don't use do_check_eq + do_check_true(this.buffer == posts[1]); + + run_next_test(); + do_test_finished(); +}; + +// Does the appropriate checks for POSTs +var SpdyPostListener = function(expected_md5) { + this.expected_md5 = expected_md5; +}; + +SpdyPostListener.prototype = new SpdyCheckListener(); +SpdyPostListener.prototype.expected_md5 = ""; + +SpdyPostListener.prototype.onDataAvailable = function(request, ctx, stream, off, cnt) { + this.onDataAvailableFired = true; + this.isSpdyConnection = checkIsSpdy(request); + read_stream(stream, cnt); + do_check_eq(this.expected_md5, request.getResponseHeader("X-Calculated-MD5")); +}; + +function makeChan(url) { + var ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService); + var chan = ios.newChannel(url, null, null).QueryInterface(Ci.nsIHttpChannel); + + return chan; +} + +// Make sure we make a spdy connection and both us and the server mark it as such +function test_spdy_basic() { + var chan = makeChan("https://localhost:4443/"); + var listener = new SpdyCheckListener(); + chan.asyncOpen(listener, null); +} + +// Support for making sure XHR works over SPDY +function checkXhr(xhr) { + if (xhr.readyState != 4) { + return; + } + + do_check_eq(xhr.status, 200); + do_check_eq(checkIsSpdy(xhr), true); + run_next_test(); + do_test_finished(); +} + +// Fires off an XHR request over SPDY +function test_spdy_xhr() { + var req = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"] + .createInstance(Ci.nsIXMLHttpRequest); + req.open("GET", "https://localhost:4443/", true); + req.addEventListener("readystatechange", function (evt) { checkXhr(req); }, + false); + req.send(null); +} + +// Test to make sure we get multiplexing right +function test_spdy_multiplex() { + var chan1 = makeChan("https://localhost:4443/multiplex1"); + var chan2 = makeChan("https://localhost:4443/multiplex2"); + var listener1 = new SpdyMultiplexListener(); + var listener2 = new SpdyMultiplexListener(); + chan1.asyncOpen(listener1, null); + chan2.asyncOpen(listener2, null); +} + +// Test to make sure we gateway non-standard headers properly +function test_spdy_header() { + var chan = makeChan("https://localhost:4443/header"); + var hvalue = "Headers are fun"; + var listener = new SpdyHeaderListener(hvalue); + chan.setRequestHeader("X-Test-Header", hvalue, false); + chan.asyncOpen(listener, null); +} + +// Make sure we handle GETs that cover more than 2 frames properly +function test_spdy_big() { + var chan = makeChan("https://localhost:4443/big"); + var listener = new SpdyBigListener(); + chan.asyncOpen(listener, null); +} + +// Support for doing a POST +function do_post(content, chan, listener) { + var stream = Cc["@mozilla.org/io/string-input-stream;1"] + .createInstance(Ci.nsIStringInputStream); + stream.data = content; + + var uchan = chan.QueryInterface(Ci.nsIUploadChannel); + uchan.setUploadStream(stream, "text/plain", stream.available()); + + chan.requestMethod = "POST"; + + chan.asyncOpen(listener, null); +} + +// Make sure we can do a simple POST +function test_spdy_post() { + var chan = makeChan("https://localhost:4443/post"); + var listener = new SpdyPostListener(md5s[0]); + do_post(posts[0], chan, listener); +} + +// Make sure we can do a POST that covers more than 2 frames +function test_spdy_post_big() { + var chan = makeChan("https://localhost:4443/post"); + var listener = new SpdyPostListener(md5s[1]); + do_post(posts[1], chan, listener); +} + +var tests = [ test_spdy_basic + , test_spdy_xhr + , test_spdy_multiplex + , test_spdy_header + , test_spdy_big + , test_spdy_post + , test_spdy_post_big + ]; +var current_test = 0; + +function run_next_test() { + if (current_test < tests.length) { + tests[current_test](); + current_test++; + do_test_pending(); + } +} + +// Support for making sure we can talk to the invalid cert the server presents +var CertOverrideListener = function(host, port, bits) { + this.host = host; + if (port) { + this.port = port; + } + this.bits = bits; +}; + +CertOverrideListener.prototype = { + host: null, + port: -1, + bits: null, + + getInterface: function(aIID) { + return this.QueryInterface(aIID); + }, + + QueryInterface: function(aIID) { + if (aIID.equals(Ci.nsIBadCertListener2) || + aIID.equals(Ci.nsIInterfaceRequestor) || + aIID.equals(Ci.nsISupports)) + return this; + throw Components.results.NS_ERROR_NO_INTERFACE; + }, + + notifyCertProblem: function(socketInfo, sslStatus, targetHost) { + var cert = sslStatus.QueryInterface(Ci.nsISSLStatus).serverCert; + var cos = Cc["@mozilla.org/security/certoverride;1"]. + getService(Ci.nsICertOverrideService); + cos.rememberValidityOverride(this.host, this.port, cert, this.bits, false); + return true; + }, +}; + +function addCertOverride(host, port, bits) { + var req = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"] + .createInstance(Ci.nsIXMLHttpRequest); + try { + var url; + if (port) { + url = "https://" + host + ":" + port + "/"; + } else { + url = "https://" + host + "/"; + } + req.open("GET", url, false); + req.channel.notificationCallbacks = new CertOverrideListener(host, port, bits); + req.send(null); + } catch (e) { + // This will fail since the server is not trusted yet + } +} + +function run_test() { + // Set to allow the cert presented by our SPDY server + do_get_profile(); + addCertOverride("localhost", 4443, + Ci.nsICertOverrideService.ERROR_UNTRUSTED | + Ci.nsICertOverrideService.ERROR_MISMATCH | + Ci.nsICertOverrideService.ERROR_TIME); + + // Make sure spdy is enabled + var prefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch); + prefs.setBoolPref("network.http.spdy.enabled", true); + + // And make go! + run_next_test(); +}
--- a/netwerk/test/unit/xpcshell.ini +++ b/netwerk/test/unit/xpcshell.ini @@ -169,16 +169,19 @@ skip-if = os == "win" [test_resumable_channel.js] [test_resumable_truncate.js] [test_safeoutputstream.js] [test_simple.js] [test_sockettransportsvc_available.js] [test_socks.js] # Bug 675039: test hangs consistently on Android skip-if = os == "android" +[test_spdy.js] +# Bug 719609: this test has particular requirements +skip-if = true [test_speculative_connect.js] [test_standardurl.js] [test_standardurl_port.js] [test_streamcopier.js] [test_traceable_channel.js] [test_unescapestring.js] [test_xmlhttprequest.js] [test_XHR_redirects.js]
--- a/testing/xpcshell/Makefile.in +++ b/testing/xpcshell/Makefile.in @@ -51,16 +51,18 @@ TEST_DIRS += example include $(topsrcdir)/config/rules.mk # Harness files from the srcdir TEST_HARNESS_FILES := \ runxpcshelltests.py \ remotexpcshelltests.py \ head.js \ + node-spdy \ + moz-spdy \ $(NULL) # Extra files needed from $(topsrcdir)/build EXTRA_BUILD_FILES := \ automationutils.py \ manifestparser.py \ $(NULL)
new file mode 100644 --- /dev/null +++ b/testing/xpcshell/moz-spdy/README.txt @@ -0,0 +1,14 @@ +Test server for SPDY unit tests. To run it, you need node >= 0.7.0 (not provided) +and node-spdy (provided). Just run + +node /path/to/moz-spdy.js + +And you will get a SPDY server listening on port 4443, then you can run the +xpcshell unit tests in netwerk/test/unit/test_spdy.js + +*** A NOTE ON TLS CERTIFICATES *** + +The certificates used for this test (*.pem in this directory) are the ones +provided as examples by node-spdy, and are copied directly from keys/ under +its top-level source directory (slightly renamed to match the option names +in the options dictionary passed to spdy.createServer).
new file mode 100644 --- /dev/null +++ b/testing/xpcshell/moz-spdy/moz-spdy.js @@ -0,0 +1,145 @@ +var spdy = require('../node-spdy/lib/spdy.js'); +var fs = require('fs'); +var url = require('url'); +var crypto = require('crypto'); + +function getHttpContent(path) { + var content = '<!doctype html>' + + '<html>' + + '<head><title>HOORAY!</title></head>' + + '<body>You Win! (by requesting' + path + ')</body>' + + '</html>'; + return content; +} + +function getHugeContent(size) { + var content = ''; + + for (var i = 0; i < size; i++) { + content += '0'; + } + + return content; +} + +/* This takes care of responding to the multiplexed request for us */ +var Multiplex = function() {}; + +Multiplex.prototype = { + mp1res: null, + mp2res: null, + buf: null, + mp1start: 0, + mp2start: 0, + + checkReady: function() { + if (this.mp1res != null && this.mp2res != null) { + this.buf = getHugeContent(30*1024); + this.mp1start = 0; + this.mp2start = 0; + this.send(this.mp1res, 0); + setTimeout(function() { this.send(this.mp2res, 0); }.bind(this), 5); + } + }, + + send: function(res, start) { + var end = start + 1024; + if (end > this.buf.length) + end = this.buf.length; + var content = this.buf.substring(start, end); + if (end < this.buf.length) { + res.write(content); + setTimeout(function() { this.send(res, end); }.bind(this), 10); + } else { + res.end(content); + } + }, +}; + +var m = new Multiplex(); + +var post_hash = null; +function receivePostData(chunk) { + post_hash.update(chunk.toString()); +} + +function finishPost(res, content) { + var md5 = post_hash.digest('hex'); + res.setHeader('X-Calculated-MD5', md5); + res.writeHead(200); + res.end(content); +} + +function handleRequest(req, res) { + var u = url.parse(req.url); + var content = getHttpContent(u.pathname); + + if (req.streamID) { + res.setHeader('X-Connection-Spdy', 'yes'); + res.setHeader('X-Spdy-StreamId', '' + req.streamID); + } else { + res.setHeader('X-Connection-Spdy', 'no'); + } + + if (u.pathname == '/exit') { + res.setHeader('Content-Type', 'text/plain'); + res.writeHead(200); + res.end('ok'); + process.exit(); + } else if (u.pathname == '/multiplex1' && req.streamID) { + res.setHeader('Content-Type', 'text/plain'); + res.writeHead(200); + m.mp1res = res; + m.checkReady(); + return; + } else if (u.pathname == '/multiplex2' && req.streamID) { + res.setHeader('Content-Type', 'text/plain'); + res.writeHead(200); + m.mp2res = res; + m.checkReady(); + return; + } else if (u.pathname == "/header") { + var val = req.headers["x-test-header"]; + if (val) { + res.setHeader("X-Received-Test-Header", val); + } + } else if (u.pathname == "/big") { + content = getHugeContent(128 * 1024); + var hash = crypto.createHash('md5'); + hash.update(content); + var md5 = hash.digest('hex'); + res.setHeader("X-Expected-MD5", md5); + } else if (u.pathname == "/post") { + if (req.method != "POST") { + res.writeHead(405); + res.end('Unexpected method: ' + req.method); + return; + } + + post_hash = crypto.createHash('md5'); + req.on('data', receivePostData); + req.on('end', function () { finishPost(res, content); }); + + return; + } + + res.setHeader('Content-Type', 'text/html'); + res.writeHead(200); + res.end(content); +} + +// Set up the SSL certs for our server +var options = { + key: fs.readFileSync(__dirname + '/spdy-key.pem'), + cert: fs.readFileSync(__dirname + '/spdy-cert.pem'), + ca: fs.readFileSync(__dirname + '/spdy-ca.pem'), +}; + +spdy.createServer(options, handleRequest).listen(4443); +console.log('SPDY server listening on port 4443'); + +// Set up to exit when the user finishes our stdin +process.stdin.resume(); +process.stdin.on('end', function () { + process.exit(); +});
new file mode 100644 --- /dev/null +++ b/testing/xpcshell/moz-spdy/spdy-ca.pem @@ -0,0 +1,11 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIBkzCB/QIBADBUMQswCQYDVQQGEwJSVTETMBEGA1UECBMKU29tZS1TdGF0ZTEN +MAsGA1UEBxMET21zazEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRk +MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDVufbmw+S/jrCXvQF9/Gtp2WRF +3/Gnld/wcOE9J/M01y0RFiyVdPZ9fsOZ93nDVdNWqoaoniLOnlV7ChU4cDy5q+je +i+mA1ZqyKXnMANZozVPSedXRGwVtlbM5OabVTcPjwrytbcXmQ5np/jJGr1BPWAX+ +A3vk+3j3hjJjIm79rwIDAQABoAAwDQYJKoZIhvcNAQEFBQADgYEAiNWhz6EppIVa +FfUaB3sLeqfamb9tg9kBHtvqj/FJni0snqms0kPWaTySEPHZF0irIb7VVdq/sVCb +3gseMVSyoDvPJ4lHC3PXqGQ7kM1mIPhDnR/4HDA3BhlGhTXSDIHgZnvI+HMBdsyC +hC3dz5odyKqe4nmoofomALkBL9t4H8s= +-----END CERTIFICATE REQUEST-----
new file mode 100644 --- /dev/null +++ b/testing/xpcshell/moz-spdy/spdy-cert.pem @@ -0,0 +1,14 @@ +-----BEGIN CERTIFICATE----- +MIICHzCCAYgCCQCPPSUAa8QZojANBgkqhkiG9w0BAQUFADBUMQswCQYDVQQGEwJS +VTETMBEGA1UECBMKU29tZS1TdGF0ZTENMAsGA1UEBxMET21zazEhMB8GA1UEChMY +SW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMB4XDTExMDQwOTEwMDY0NVoXDTExMDUw +OTEwMDY0NVowVDELMAkGA1UEBhMCUlUxEzARBgNVBAgTClNvbWUtU3RhdGUxDTAL +BgNVBAcTBE9tc2sxITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDCB +nzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA1bn25sPkv46wl70BffxradlkRd/x +p5Xf8HDhPSfzNNctERYslXT2fX7Dmfd5w1XTVqqGqJ4izp5VewoVOHA8uavo3ovp +gNWasil5zADWaM1T0nnV0RsFbZWzOTmm1U3D48K8rW3F5kOZ6f4yRq9QT1gF/gN7 +5Pt494YyYyJu/a8CAwEAATANBgkqhkiG9w0BAQUFAAOBgQBuRZisIViI2G/R+w79 +vk21TzC/cJ+O7tKsseDqotXYTH8SuimEH5IWcXNgnWhNzczwN8s2362NixyvCipV +yd4wzMpPbjIhnWGM0hluWZiK2RxfcqimIBjDParTv6CMUIuwGQ257THKY8hXGg7j +Uws6Lif3P9UbsuRiYPxMgg98wg== +-----END CERTIFICATE-----
new file mode 100644 --- /dev/null +++ b/testing/xpcshell/moz-spdy/spdy-key.pem @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXAIBAAKBgQDVufbmw+S/jrCXvQF9/Gtp2WRF3/Gnld/wcOE9J/M01y0RFiyV +dPZ9fsOZ93nDVdNWqoaoniLOnlV7ChU4cDy5q+jei+mA1ZqyKXnMANZozVPSedXR +GwVtlbM5OabVTcPjwrytbcXmQ5np/jJGr1BPWAX+A3vk+3j3hjJjIm79rwIDAQAB +AoGAAv2QI9h32epQND9TxwSCKD//dC7W/cZOFNovfKCTeZjNK6EIzKqPTGA6smvR +C1enFl5adf+IcyWqAoe4lkqTvurIj+2EhtXdQ8DBlVuXKr3xvEFdYxXPautdTCF6 +KbXEyS/s1TZCRFjYftvCrXxc3pK45AQX/wg7z1K+YB5pyIECQQD0OJvLoxLYoXAc +FZraIOZiDsEbGuSHqoCReFXH75EC3+XGYkH2bQ/nSIZ0h1buuwQ/ylKXOlTPT3Qt +Xm1OQEBvAkEA4AjWsIO/rRpOm/Q2aCrynWMpoUXTZSbL2yGf8pxp/+8r2br5ier0 +M1LeBb/OPY1+k39NWLXxQoo64xoSFYk2wQJAd2wDCwX4HkR7HNCXw1hZL9QFK6rv +20NN0VSlpboJD/3KT0MW/FiCcVduoCbaJK0Au+zEjDyy4hj5N4I4Mw6KMwJAXVAx +I+psTsxzS4/njXG+BgIEl/C+gRYsuMQDnAi8OebDq/et8l0Tg8ETSu++FnM18neG +ntmBeMacinUUbTXuwQJBAJp/onZdsMzeVulsGrqR1uS+Lpjc5Q1gt5ttt2cxj91D +rio48C/ZvWuKNE8EYj2ALtghcVKRvgaWfOxt2GPguGg= +-----END RSA PRIVATE KEY-----
new file mode 100644 --- /dev/null +++ b/testing/xpcshell/node-spdy/README.md @@ -0,0 +1,132 @@ +# SPDY Server for node.js [](http://travis-ci.org/indutny/node-spdy) + +With this module you can create [SPDY](http://www.chromium.org/spdy) servers +in node.js with natural http module interface and fallback to regular https +(for browsers that doesn't support SPDY yet). + +## Node+OpenSSL building + +At the moment node-spdy requires zlib dictionary support, which will come to +node.js only in 0.7.x version. To build 0.7.x version follow instructions below: + +```bash +git clone git://github.com/joyent/node.git +cd node +./configure --prefix=$HOME/.node/dev # <- or any other dir + +make install -j4 # in -jN, N is number of CPU cores on your machine + +# Add node's bin to PATH env variable +echo 'export PATH=$HOME/.node/dev/bin:$PATH' >> ~/.bashrc + +# +# You have working node 0.7.x + NPN now !!! +# +``` + +## Usage + +```javascript +var spdy = require('spdy'); + +var options = { + key: fs.readFileSync(__dirname + '/keys/spdy-key.pem'), + cert: fs.readFileSync(__dirname + '/keys/spdy-cert.pem'), + ca: fs.readFileSync(__dirname + '/keys/spdy-csr.pem') +}; + +spdy.createServer(options, function(req, res) { + res.writeHead(200); + res.end('hello world!'); +}); + +spdy.listen(443); +``` + +## API + +API is compatible with `http` and `https` module, but you can use another +function as base class for SPDYServer. For example, +`require('express').HTTPSServer` given that as base class you'll get a server +compatible with [express](https://github.com/visionmedia/express) API. + +```javascript +spdy.createServer( + [base class constructor, i.e. https.Server or express.HTTPSServer], + { /* keys and options */ }, // <- the only one required argument + [request listener] +).listen([port], [host], [callback]); +``` + +Request listener will receive two arguments: `request` and `response`. They're +both instances of `http`'s `IncomingMessage` and `OutgoingMessage`. But two +custom properties are added to both of them: `streamID` and `isSpdy`. The first +one indicates on which spdy stream are sitting request and response. Latter one +is always true and can be checked to ensure that incoming request wasn't +received by HTTPS callback. + +### Push streams + +It's possible to initiate 'push' stream to send content to client, before one'll +request it. + +```javascript +spdy.createServer(options, function(req, res) { + var headers = { 'content-type': 'application/javascript' }; + res.send('/main.js', headers, function(err, stream) { + if (err) return; + + stream.end('alert("hello from push stream!");'); + }); + + res.end('<script src="/main.js"></script>'); +}).listen(443); +``` + +`.push('full or relative url', { ... headers ... }, callback)` + +You can use either full ( `http://host/path` ) or relative ( `/path` ) urls with +`.push()`. `headers` are the same as for regular response object. `callback` +will receive two arguments: `err` (if any error is happened) and `stream` +(stream object have API compatible with a +[net.Socket](http://nodejs.org/docs/latest/api/net.html#net.Socket) ). + +### Options + +All options supported by +[tls](http://nodejs.org/docs/latest/api/tls.html#tls.createServer) are working +with node-spdy. In addition, `maxStreams` options is available. it allows you +controlling [maximum concurrent streams][http://www.chromium.org/spdy/spdy-protocol/spdy-protocol-draft2#TOC-SETTINGS] +protocol option (if client will start more streams than that limit, RST_STREAM +will be sent for each additional stream). + +#### Contributors + +* [Fedor Indutny](https://github.com/indutny) +* [Chris Storm](https://github.com/eee-c) +* [François de Metz](https://github.com/francois2metz) + +#### LICENSE + +This software is licensed under the MIT License. + +Copyright Fedor Indutny, 2012. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to permit +persons to whom the Software is furnished to do so, subject to the +following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE.
new file mode 100644 --- /dev/null +++ b/testing/xpcshell/node-spdy/examples/twitlog/app.js @@ -0,0 +1,64 @@ +/** + * Module dependencies. + */ + +var fs = require('fs'), + io = require('socket.io'), + express = require('express'), + spdy = require('../../'); + routes = require('./routes'); + realtime = require('./realtime'); + +var options = { + key: fs.readFileSync(__dirname + '/keys/twitlog-key.pem'), + cert: fs.readFileSync(__dirname + '/keys/twitlog-cert.pem'), + ca: fs.readFileSync(__dirname + '/keys/twitlog-csr.pem'), + ciphers: '!aNULL:!ADH:!eNull:!LOW:!EXP:RC4+RSA:MEDIUM:HIGH', + maxStreams: 15 +}; + +var app = module.exports = spdy.createServer(express.HTTPSServer, options); + +io = io.listen(app, { log: false }); +io.set('transports', ['xhr-polling', 'jsonp-polling']); + +// Configuration + +app.configure(function(){ + app.set('views', __dirname + '/views'); + app.set('view engine', 'jade'); + app.use(express.bodyParser()); + app.use(express.methodOverride()); + app.use(app.router); + app.use(express.staticCache()); + app.use(express.static(__dirname + '/public')); +}); + +app.configure('development', function(){ + app.use(express.errorHandler({ dumpExceptions: true, showStack: true })); +}); + +app.configure('production', function(){ + app.use(express.errorHandler()); +}); + +// Routes + +app.get('/', routes.index); + +// Socket.io + +realtime.init(io); + +app.listen(8081); +console.log( + 'Express server listening on port %d in %s mode', + app.address().port, + app.settings.env +); + +// Do not fail on exceptions + +process.on('uncaughtException', function(err) { + console.error(err.stack); +});
new file mode 100644 --- /dev/null +++ b/testing/xpcshell/node-spdy/examples/twitlog/autorestart.js @@ -0,0 +1,15 @@ +var path = require('path'), + spawn = require('child_process').spawn; + +function start() { + var child = spawn(process.execPath, [path.resolve(__dirname, 'app.js')]); + process.stderr.write('started child with pid: ' + child.pid + '\n'); + + child.on('exit', function(code) { + process.stderr.write('child exit: ' + code + '!\n'); + setTimeout(start, 100); + }); + child.stdout.pipe(process.stdout); + child.stderr.pipe(process.stderr); +} +start();
new file mode 100644 --- /dev/null +++ b/testing/xpcshell/node-spdy/examples/twitlog/keys/twitlog-cert.pem @@ -0,0 +1,41 @@ +-----BEGIN CERTIFICATE----- +MIIHNDCCBhygAwIBAgIDBRVaMA0GCSqGSIb3DQEBBQUAMIGMMQswCQYDVQQGEwJJ +TDEWMBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0 +YWwgQ2VydGlmaWNhdGUgU2lnbmluZzE4MDYGA1UEAxMvU3RhcnRDb20gQ2xhc3Mg +MSBQcmltYXJ5IEludGVybWVkaWF0ZSBTZXJ2ZXIgQ0EwHhcNMTIwMTA2MTAyMDQz +WhcNMTMwMTA2MDc1NzQwWjBzMRkwFwYDVQQNExBqdDVKVUE2NHlXaTg4T2k3MQsw +CQYDVQQGEwJSVTEhMB8GA1UEAxMYc3BkeS10d2l0bG9nLmluZHV0bnkuY29tMSYw +JAYJKoZIhvcNAQkBFhdmZWRvci5pbmR1dG55QGdtYWlsLmNvbTCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBANgiMg1hIHoxk1AuMouaERtSv8g/J+JbTgAe +lD5kF7hGpQOB+qGLuCU+7QovsjftKee7aXXNduQDkzkCD7wg7C9m3QHJ+YXDk7vE +IycgK6XIK7G1kAfMXfJm6D2gRD3JjM4fWRY/MYTxSvLnqDoAgHIrCBrwauNNuHdl +4x5LclRKAWAl1sixQgdyw/CzLjuFJFgmRn1Oc1T/lwgzwcXcw0tBvTQtTdvaogvJ ++cqT+TrHVm+w+BRfp+CT9slZp+4b9JQTD5u4/0k3RoxbUBHvKRJ9FKOsq/GFNdlj +KWMIfQ8ZdHJDKWVX8zlDydhpMTpofaynvdmSPurMcFNGGmYEmkUCAwEAAaOCA7Uw +ggOxMAkGA1UdEwQCMAAwCwYDVR0PBAQDAgOoMBMGA1UdJQQMMAoGCCsGAQUFBwMB +MB0GA1UdDgQWBBTy7FYuO1wZ1yws0eNEAI2CVcS5yzAfBgNVHSMEGDAWgBTrQjTQ +mLCrn/Qbawj3zGQu7w4sRTAwBgNVHREEKTAnghhzcGR5LXR3aXRsb2cuaW5kdXRu +eS5jb22CC2luZHV0bnkuY29tMIICIQYDVR0gBIICGDCCAhQwggIQBgsrBgEEAYG1 +NwECAjCCAf8wLgYIKwYBBQUHAgEWImh0dHA6Ly93d3cuc3RhcnRzc2wuY29tL3Bv +bGljeS5wZGYwNAYIKwYBBQUHAgEWKGh0dHA6Ly93d3cuc3RhcnRzc2wuY29tL2lu +dGVybWVkaWF0ZS5wZGYwgfcGCCsGAQUFBwICMIHqMCcWIFN0YXJ0Q29tIENlcnRp +ZmljYXRpb24gQXV0aG9yaXR5MAMCAQEagb5UaGlzIGNlcnRpZmljYXRlIHdhcyBp +c3N1ZWQgYWNjb3JkaW5nIHRvIHRoZSBDbGFzcyAxIFZhbGlkYXRpb24gcmVxdWly +ZW1lbnRzIG9mIHRoZSBTdGFydENvbSBDQSBwb2xpY3ksIHJlbGlhbmNlIG9ubHkg +Zm9yIHRoZSBpbnRlbmRlZCBwdXJwb3NlIGluIGNvbXBsaWFuY2Ugb2YgdGhlIHJl +bHlpbmcgcGFydHkgb2JsaWdhdGlvbnMuMIGcBggrBgEFBQcCAjCBjzAnFiBTdGFy +dENvbSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTADAgECGmRMaWFiaWxpdHkgYW5k +IHdhcnJhbnRpZXMgYXJlIGxpbWl0ZWQhIFNlZSBzZWN0aW9uICJMZWdhbCBhbmQg +TGltaXRhdGlvbnMiIG9mIHRoZSBTdGFydENvbSBDQSBwb2xpY3kuMDUGA1UdHwQu +MCwwKqAooCaGJGh0dHA6Ly9jcmwuc3RhcnRzc2wuY29tL2NydDEtY3JsLmNybDCB +jgYIKwYBBQUHAQEEgYEwfzA5BggrBgEFBQcwAYYtaHR0cDovL29jc3Auc3RhcnRz +c2wuY29tL3N1Yi9jbGFzczEvc2VydmVyL2NhMEIGCCsGAQUFBzAChjZodHRwOi8v +YWlhLnN0YXJ0c3NsLmNvbS9jZXJ0cy9zdWIuY2xhc3MxLnNlcnZlci5jYS5jcnQw +IwYDVR0SBBwwGoYYaHR0cDovL3d3dy5zdGFydHNzbC5jb20vMA0GCSqGSIb3DQEB +BQUAA4IBAQClU4BNyXLLlk8PryozRpx0WkkH83e799myaLMAY0IjyroQ3EH1rlXT +vU9QJ8d8br+xm7KnLsrva0BDC/eVeq+7qCyUNoXUAAswBzpXPeL+Rm/4oWmOAcCA +/8MQ6z+YLFPf9MqlWdO24aA5aoN7DwCxIvJ2IfhNbRFLzcztF/kFdqnRtw34S1L4 +GvSx/o0eYZ8sgyj9yET8QqXR/EUs+NvRBmDIjMx8DmJVvLQZHNk1sHnkdx/Z0AWP +WUjaEkhfkvLVCvX+7vGmlMnvGwRZf1U5qaj4sp+O70ANlQVG99R6e8IEcf8JFy3/ +AUumo8jFsCfPIcByKJoctlz1nx9+zEDp +-----END CERTIFICATE-----
new file mode 100644 --- /dev/null +++ b/testing/xpcshell/node-spdy/examples/twitlog/keys/twitlog-csr.pem @@ -0,0 +1,16 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIICijCCAXICAQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUx +ITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDCCASIwDQYJKoZIhvcN +AQEBBQADggEPADCCAQoCggEBANgiMg1hIHoxk1AuMouaERtSv8g/J+JbTgAelD5k +F7hGpQOB+qGLuCU+7QovsjftKee7aXXNduQDkzkCD7wg7C9m3QHJ+YXDk7vEIycg +K6XIK7G1kAfMXfJm6D2gRD3JjM4fWRY/MYTxSvLnqDoAgHIrCBrwauNNuHdl4x5L +clRKAWAl1sixQgdyw/CzLjuFJFgmRn1Oc1T/lwgzwcXcw0tBvTQtTdvaogvJ+cqT ++TrHVm+w+BRfp+CT9slZp+4b9JQTD5u4/0k3RoxbUBHvKRJ9FKOsq/GFNdljKWMI +fQ8ZdHJDKWVX8zlDydhpMTpofaynvdmSPurMcFNGGmYEmkUCAwEAAaAAMA0GCSqG +SIb3DQEBBQUAA4IBAQCQFFq1j4zbi3mqqpRrGjS5NMhCqdS3m8lPgUtPCwDsbtAy +ILtl6clYA5ruwxtLEQYOiowUZ7dgQQ2poqwwelk5hJ92D9JySAyU0bqWUZ3CgccC +7LWhZMlmeyqBnDSE+oaFKsPRs9oTYGxh4zZv9OhPNL5ZFEAwUMccHXYMgsQk5n1E +pbAdxw2KgyrliTz4PO7/hjwAfANszdSweP++qNBceix1HpWIp17hJ9aIH1Q+w5Ym +Uzfykvz7Qc9YoQ/LHKJMxirsmZtdAjC0YApMuAOhjD42OkhL9JBVg5CFMIhXQBOU +1fGAoWdbIKWre4owm9kjsqwe8jg+sWTZHE1h+TJD +-----END CERTIFICATE REQUEST-----
new file mode 100644 --- /dev/null +++ b/testing/xpcshell/node-spdy/examples/twitlog/keys/twitlog-key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDYIjINYSB6MZNQ +LjKLmhEbUr/IPyfiW04AHpQ+ZBe4RqUDgfqhi7glPu0KL7I37Snnu2l1zXbkA5M5 +Ag+8IOwvZt0ByfmFw5O7xCMnICulyCuxtZAHzF3yZug9oEQ9yYzOH1kWPzGE8Ury +56g6AIByKwga8GrjTbh3ZeMeS3JUSgFgJdbIsUIHcsPwsy47hSRYJkZ9TnNU/5cI +M8HF3MNLQb00LU3b2qILyfnKk/k6x1ZvsPgUX6fgk/bJWafuG/SUEw+buP9JN0aM +W1AR7ykSfRSjrKvxhTXZYyljCH0PGXRyQyllV/M5Q8nYaTE6aH2sp73Zkj7qzHBT +RhpmBJpFAgMBAAECggEAdEKos+O8KZ7DRE0laUy9yPnRKfE3Dh7ZLV1Flu4WiEyP +9PwVCpLywi5AKcuQTV8ovHtmdjTIsExwBClkt2jqQ3FMjurLazXSIR2XXzOB5xJu +1o/44wj+vCa45HVyX94r/LCGJl5lz8JP86vDJTgh38ff+0W56X1kLe3Dpwckf8u1 +fMV0apePlyYbPL85EbulnUNToiIgURu/brnsdufB62RZBp1NhUjiaok9jODQq3Ee +8J8fpMZNBhvwZqfv7h1ykMfYUASQ8IDtZKMKk7MCMuPjLmMb9nWKrXmUUIIJDAqL +Z/cgKBE72Swj+l+V54/onr+2g9iTu2hlxsicEYcSIQKBgQD9lRO/BtbB9AXEJ3E5 +I1fwYvV5I1TJiVBNVo7VwC2gtwHN7ftXw06bEc78z2i5Od+/F39XeXes5yBxqCbO +tv0XKF2jOHakMduMch4C7qyMiS4q1JtDkEbJd4jkBGySwePf45gHPmtmso7peQPT +P4OoO/9MLzJjBYDeSCPbqiMvuQKBgQDaMbdvafxpWxg44SNqZtpX54CpLViUMoaY +mrkyDIXrHMi2+yY/PzaNBNUASqlAryy4fxhGRsHFMMssZpjdfCBr2DnXbXSk4Li3 +CK8+m9hk2om9lv2FvDOPCQ+RGigmpYQC/a7UKo8xIjI2wzDRmevXDf8xBFjvLQTc +bpanFbrM7QKBgD1JLUeKwJaJgmdA3RVhHFzFnewUBObcX+MBG24/jwd7k10QuiEg +27uQl0T0X6v8d734UNd0TN8l0OqHKDHnec2B/Pd4qvvN7PDJl8U/p8YjVVwWnBu9 +H86LLDNnelIRuCAhIloF1PEyEGYO0ETa4dfkADSKZ5QU/Ws7ZictvGlJAoGANfA+ +YXt4226agU0enSoJ5dsj0i6UjCYlYco15+pynJmEAL/7R31P9fJw2V6bkpL7YiyB +CrZpJl8WisZeGbqapS5RtjCnui6XWx/5eme6ScxAaq7Nw2av9DcQMxWdQVh/VuHx +ex9+QG4srZ75DYeYZpReNnbVqWKepgNsmKdlg00CgYBsrbksX+66b11z6GwBjEK+ +0rMpzMhnOxhxGaDw6jTuxOIZEC1EyaxI5sxryH6+XdZJZYH/pN6O5skjPD28L8fC +b9900FSsnQlnk/QMb7G5Tqyvf2oAAxSSfvX13zAFGd1Z79TEeiZWeY04hrXACvP+ +cAeTFiSRlCMiOLLgRhFauw== +-----END PRIVATE KEY-----
new file mode 100644 --- /dev/null +++ b/testing/xpcshell/node-spdy/examples/twitlog/keys/twtilog-ca.pem @@ -0,0 +1,36 @@ +-----BEGIN CERTIFICATE----- +MIIGNDCCBBygAwIBAgIBGDANBgkqhkiG9w0BAQUFADB9MQswCQYDVQQGEwJJTDEW +MBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwg +Q2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMgU3RhcnRDb20gQ2VydGlmaWNh +dGlvbiBBdXRob3JpdHkwHhcNMDcxMDI0MjA1NDE3WhcNMTcxMDI0MjA1NDE3WjCB +jDELMAkGA1UEBhMCSUwxFjAUBgNVBAoTDVN0YXJ0Q29tIEx0ZC4xKzApBgNVBAsT +IlNlY3VyZSBEaWdpdGFsIENlcnRpZmljYXRlIFNpZ25pbmcxODA2BgNVBAMTL1N0 +YXJ0Q29tIENsYXNzIDEgUHJpbWFyeSBJbnRlcm1lZGlhdGUgU2VydmVyIENBMIIB +IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtonGrO8JUngHrJJj0PREGBiE +gFYfka7hh/oyULTTRwbw5gdfcA4Q9x3AzhA2NIVaD5Ksg8asWFI/ujjo/OenJOJA +pgh2wJJuniptTT9uYSAK21ne0n1jsz5G/vohURjXzTCm7QduO3CHtPn66+6CPAVv +kvek3AowHpNz/gfK11+AnSJYUq4G2ouHI2mw5CrY6oPSvfNx23BaKA+vWjhwRRI/ +ME3NO68X5Q/LoKldSKqxYVDLNM08XMML6BDAjJvwAwNi/rJsPnIO7hxDKslIDlc5 +xDEhyBDBLIf+VJVSH1I8MRKbf+fAoKVZ1eKPPvDVqOHXcDGpxLPPr21TLwb0pwID +AQABo4IBrTCCAakwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYD +VR0OBBYEFOtCNNCYsKuf9BtrCPfMZC7vDixFMB8GA1UdIwQYMBaAFE4L7xqkQFul +F2mHMMo0aEPQQa7yMGYGCCsGAQUFBwEBBFowWDAnBggrBgEFBQcwAYYbaHR0cDov +L29jc3Auc3RhcnRzc2wuY29tL2NhMC0GCCsGAQUFBzAChiFodHRwOi8vd3d3LnN0 +YXJ0c3NsLmNvbS9zZnNjYS5jcnQwWwYDVR0fBFQwUjAnoCWgI4YhaHR0cDovL3d3 +dy5zdGFydHNzbC5jb20vc2ZzY2EuY3JsMCegJaAjhiFodHRwOi8vY3JsLnN0YXJ0 +c3NsLmNvbS9zZnNjYS5jcmwwgYAGA1UdIAR5MHcwdQYLKwYBBAGBtTcBAgEwZjAu +BggrBgEFBQcCARYiaHR0cDovL3d3dy5zdGFydHNzbC5jb20vcG9saWN5LnBkZjA0 +BggrBgEFBQcCARYoaHR0cDovL3d3dy5zdGFydHNzbC5jb20vaW50ZXJtZWRpYXRl +LnBkZjANBgkqhkiG9w0BAQUFAAOCAgEAIQlJPqWIbuALi0jaMU2P91ZXouHTYlfp +tVbzhUV1O+VQHwSL5qBaPucAroXQ+/8gA2TLrQLhxpFy+KNN1t7ozD+hiqLjfDen +xk+PNdb01m4Ge90h2c9W/8swIkn+iQTzheWq8ecf6HWQTd35RvdCNPdFWAwRDYSw +xtpdPvkBnufh2lWVvnQce/xNFE+sflVHfXv0pQ1JHpXo9xLBzP92piVH0PN1Nb6X +t1gW66pceG/sUzCv6gRNzKkC4/C2BBL2MLERPZBOVmTX3DxDX3M570uvh+v2/miI +RHLq0gfGabDBoYvvF0nXYbFFSF87ICHpW7LM9NfpMfULFWE7epTj69m8f5SuauNi +YpaoZHy4h/OZMn6SolK+u/hlz8nyMPyLwcKmltdfieFcNID1j0cHL7SRv7Gifl9L +WtBbnySGBVFaaQNlQ0lxxeBvlDRr9hvYqbBMflPrj0jfyjO1SPo2ShpTpjMM0InN +SRXNiTE8kMBy12VLUjWKRhFEuT2OKGWmPnmeXAhEKa2wNREuIU640ucQPl2Eg7PD +wuTSxv0JS3QJ3fGz0xk+gA2iCxnwOOfFwq/iI9th4p1cbiCJSS4jarJiwUW0n6+L +p/EiO/h94pDQehn7Skzj0n1fSoMD7SfWI55rjbRZotnvbIIp3XUZPD9MEI3vu3Un +0q6Dp6jOW6c= +-----END CERTIFICATE-----
new file mode 100644 --- /dev/null +++ b/testing/xpcshell/node-spdy/examples/twitlog/package.json @@ -0,0 +1,11 @@ +{ + "name": "application-name", + "version": "0.0.1", + "private": true, + "dependencies": { + "express": "2.5.x", + "jade": ">= 0.0.1", + "socket.io": "0.8.x", + "ntwitter": "0.2.x" + } +}
new file mode 100644 index 0000000000000000000000000000000000000000..d535769aa6af5ff1437e1c1a41db0a02d686e2d8 GIT binary patch literal 2582 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
new file mode 100644 index 0000000000000000000000000000000000000000..c7f41c274463b16ef4b79776b73aebb20cdcd926 GIT binary patch literal 11587 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
new file mode 100644 --- /dev/null +++ b/testing/xpcshell/node-spdy/examples/twitlog/public/javascripts/main.js @@ -0,0 +1,52 @@ +!function() { + var socket = io.connect(), + container = document.getElementById('tweets'), + tweets = [], + first; + + socket.emit('reqTweets'); + socket.on('tweet', function(tweet) { + if (tweets.length > 18) { + // Remove first tweet + container.removeChild(tweets.shift()); + } + tweets.push(createTweet(tweet)); + }); + + function createTweet(tweet) { + var elem = document.createElement('article'), + avatar = document.createElement('img'), + textContainer = document.createElement('div'), + text = document.createElement('div'), + clear = document.createElement('div'); + + avatar.className = 'avatar'; + avatar.src = tweet.user.image.replace( + /^http:\/\/a2\.twimg\.com\//, + '//twimg0-a.akamaihd.net/' + ); + avatar.title = avatar.alt = tweet.user.name; + + text.className = 'text'; + text.innerHTML = tweet.text; + + clear.className = 'clear'; + + textContainer.className = 'text-container'; + textContainer.appendChild(text); + textContainer.appendChild(clear); + + elem.className = 'tweet'; + elem.appendChild(avatar); + elem.appendChild(textContainer); + + if (first) { + container.insertBefore(elem, first); + } else { + container.appendChild(elem); + } + first = elem; + + return elem; + }; +}();
new file mode 100644 --- /dev/null +++ b/testing/xpcshell/node-spdy/examples/twitlog/public/javascripts/socket.io.min.js @@ -0,0 +1,2 @@ +/*! Socket.IO.min.js build:0.8.4, production. Copyright(c) 2011 LearnBoost <dev@learnboost.com> MIT Licensed */ +(function(a){var b=a;b.version="0.8.4",b.protocol=1,b.transports=[],b.j=[],b.sockets={},b.connect=function(a,c){var d=b.util.parseUri(a),e,f;"undefined"!=typeof document&&(d.protocol=d.protocol||document.location.protocol.slice(0,-1),d.host=d.host||document.domain,d.port=d.port||document.location.port),e=b.util.uniqueUri(d);var g={host:d.host,secure:"https"==d.protocol,port:d.port||("https"==d.protocol?443:80),query:d.query||""};b.util.merge(g,c);if(g["force new connection"]||!b.sockets[e])f=new b.Socket(g);!g["force new connection"]&&f&&(b.sockets[e]=f),f=f||b.sockets[e];return f.of(d.path.length>1?d.path:"")}})("object"==typeof module?module.exports:window.io={}),function(a,b){var c=a.util={},d=/^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/,e=["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"];c.parseUri=function(a){var b=d.exec(a||""),c={},f=14;while(f--)c[e[f]]=b[f]||"";return c},c.uniqueUri=function(a){var c=a.protocol,d=a.host,e=a.port;"document"in b?(d=d||document.domain,e=e||(c=="https"&&document.location.protocol!=="https:"?443:document.location.port)):(d=d||"localhost",!e&&c=="https"&&(e=443));return(c||"http")+"://"+d+":"+(e||80)},c.query=function(a,b){var d=c.chunkQuery(a||""),e=[];c.merge(d,c.chunkQuery(b||""));for(var f in d)d.hasOwnProperty(f)&&e.push(f+"="+d[f]);return e.length?"?"+e.join("&"):""},c.chunkQuery=function(a){var b={},c=a.split("&"),d=0,e=c.length,f;for(;d<e;++d)f=c[d].split("="),f[0]&&(b[f[0]]=decodeURIComponent(f[1]));return b};var f=!1;c.load=function(a){if("document"in b&&document.readyState==="complete"||f)return a();c.on(b,"load",a,!1)},c.on=function(a,b,c,d){a.attachEvent?a.attachEvent("on"+b,c):a.addEventListener&&a.addEventListener(b,c,d)},c.request=function(a){if("undefined"!=typeof window){if(a&&window.XDomainRequest)return new XDomainRequest;if(window.XMLHttpRequest&&(!a||c.ua.hasCORS))return new XMLHttpRequest;if(!a)try{return new window.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}return null},"undefined"!=typeof window&&c.load(function(){f=!0}),c.defer=function(a){if(!c.ua.webkit)return a();c.load(function(){setTimeout(a,100)})},c.merge=function g(a,b,d,e){var f=e||[],g=typeof d=="undefined"?2:d,h;for(h in b)b.hasOwnProperty(h)&&c.indexOf(f,h)<0&&(typeof a[h]!="object"||!g?(a[h]=b[h],f.push(b[h])):c.merge(a[h],b[h],g-1,f));return a},c.mixin=function(a,b){c.merge(a.prototype,b.prototype)},c.inherit=function(a,b){function c(){}c.prototype=b.prototype,a.prototype=new c},c.isArray=Array.isArray||function(a){return Object.prototype.toString.call(a)==="[object Array]"},c.intersect=function(a,b){var d=[],e=a.length>b.length?a:b,f=a.length>b.length?b:a;for(var g=0,h=f.length;g<h;g++)~c.indexOf(e,f[g])&&d.push(f[g]);return d},c.indexOf=function(a,b,c){if(Array.prototype.indexOf)return Array.prototype.indexOf.call(a,b,c);for(var d=a.length,c=c<0?c+d<0?0:c+d:c||0;c<d&&a[c]!==b;c++);return d<=c?-1:c},c.toArray=function(a){var b=[];for(var c=0,d=a.length;c<d;c++)b.push(a[c]);return b},c.ua={},c.ua.hasCORS="undefined"!=typeof window&&window.XMLHttpRequest&&function(){try{var a=new XMLHttpRequest}catch(b){return!1}return a.withCredentials!=undefined}(),c.ua.webkit="undefined"!=typeof navigator&&/webkit/i.test(navigator.userAgent)}("undefined"!=typeof window?io:module.exports,this),function(a,b){function c(){}a.EventEmitter=c,c.prototype.on=function(a,c){this.$events||(this.$events={}),this.$events[a]?b.util.isArray(this.$events[a])?this.$events[a].push(c):this.$events[a]=[this.$events[a],c]:this.$events[a]=c;return this},c.prototype.addListener=c.prototype.on,c.prototype.once=function(a,b){function d(){c.removeListener(a,d),b.apply(this,arguments)}var c=this;d.listener=b,this.on(a,d);return this},c.prototype.removeListener=function(a,c){if(this.$events&&this.$events[a]){var d=this.$events[a];if(b.util.isArray(d)){var e=-1;for(var f=0,g=d.length;f<g;f++)if(d[f]===c||d[f].listener&&d[f].listener===c){e=f;break}if(e<0)return this;d.splice(e,1),d.length||delete this.$events[a]}else(d===c||d.listener&&d.listener===c)&&delete this.$events[a]}return this},c.prototype.removeAllListeners=function(a){this.$events&&this.$events[a]&&(this.$events[a]=null);return this},c.prototype.listeners=function(a){this.$events||(this.$events={}),this.$events[a]||(this.$events[a]=[]),b.util.isArray(this.$events[a])||(this.$events[a]=[this.$events[a]]);return this.$events[a]},c.prototype.emit=function(a){if(!this.$events)return!1;var c=this.$events[a];if(!c)return!1;var d=Array.prototype.slice.call(arguments,1);if("function"==typeof c)c.apply(this,d);else{if(!b.util.isArray(c))return!1;var e=c.slice();for(var f=0,g=e.length;f<g;f++)e[f].apply(this,d)}return!0}}("undefined"!=typeof io?io:module.exports,"undefined"!=typeof io?io:module.parent.exports),function(exports,nativeJSON){function str(a,b){var c,d,e,f,g=gap,h,i=b[a];i instanceof Date&&(i=date(a)),typeof rep=="function"&&(i=rep.call(b,a,i));switch(typeof i){case"string":return quote(i);case"number":return isFinite(i)?String(i):"null";case"boolean":case"null":return String(i);case"object":if(!i)return"null";gap+=indent,h=[];if(Object.prototype.toString.apply(i)==="[object Array]"){f=i.length;for(c=0;c<f;c+=1)h[c]=str(c,i)||"null";e=h.length===0?"[]":gap?"[\n"+gap+h.join(",\n"+gap)+"\n"+g+"]":"["+h.join(",")+"]",gap=g;return e}if(rep&&typeof rep=="object"){f=rep.length;for(c=0;c<f;c+=1)typeof rep[c]=="string"&&(d=rep[c],e=str(d,i),e&&h.push(quote(d)+(gap?": ":":")+e))}else for(d in i)Object.prototype.hasOwnProperty.call(i,d)&&(e=str(d,i),e&&h.push(quote(d)+(gap?": ":":")+e));e=h.length===0?"{}":gap?"{\n"+gap+h.join(",\n"+gap)+"\n"+g+"}":"{"+h.join(",")+"}",gap=g;return e}}function quote(a){escapable.lastIndex=0;return escapable.test(a)?'"'+a.replace(escapable,function(a){var b=meta[a];return typeof b=="string"?b:"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)})+'"':'"'+a+'"'}function date(a,b){return isFinite(a.valueOf())?a.getUTCFullYear()+"-"+f(a.getUTCMonth()+1)+"-"+f(a.getUTCDate())+"T"+f(a.getUTCHours())+":"+f(a.getUTCMinutes())+":"+f(a.getUTCSeconds())+"Z":null}function f(a){return a<10?"0"+a:a}"use strict";if(nativeJSON&&nativeJSON.parse)return exports.JSON={parse:nativeJSON.parse,stringify:nativeJSON.stringify};var JSON=exports.JSON={},cx=/[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,escapable=/[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,gap,indent,meta={"\b":"\\b","\t":"\\t","\n":"\\n","\f":"\\f","\r":"\\r",'"':'\\"',"\\":"\\\\"},rep;JSON.stringify=function(a,b,c){var d;gap="",indent="";if(typeof c=="number")for(d=0;d<c;d+=1)indent+=" ";else typeof c=="string"&&(indent=c);rep=b;if(!b||typeof b=="function"||typeof b=="object"&&typeof b.length=="number")return str("",{"":a});throw new Error("JSON.stringify")},JSON.parse=function(text,reviver){function walk(a,b){var c,d,e=a[b];if(e&&typeof e=="object")for(c in e)Object.prototype.hasOwnProperty.call(e,c)&&(d=walk(e,c),d!==undefined?e[c]=d:delete e[c]);return reviver.call(a,b,e)}var j;text=String(text),cx.lastIndex=0,cx.test(text)&&(text=text.replace(cx,function(a){return"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)}));if(/^[\],:{}\s]*$/.test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,"@").replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,"]").replace(/(?:^|:|,)(?:\s*\[)+/g,""))){j=eval("("+text+")");return typeof reviver=="function"?walk({"":j},""):j}throw new SyntaxError("JSON.parse")}}("undefined"!=typeof io?io:module.exports,typeof JSON!="undefined"?JSON:undefined),function(a,b){var c=a.parser={},d=c.packets=["disconnect","connect","heartbeat","message","json","event","ack","error","noop"],e=c.reasons=["transport not supported","client not handshaken","unauthorized"],f=c.advice=["reconnect"],g=b.JSON,h=b.util.indexOf;c.encodePacket=function(a){var b=h(d,a.type),c=a.id||"",i=a.endpoint||"",j=a.ack,k=null;switch(a.type){case"error":var l=a.reason?h(e,a.reason):"",m=a.advice?h(f,a.advice):"";if(l!==""||m!=="")k=l+(m!==""?"+"+m:"");break;case"message":a.data!==""&&(k=a.data);break;case"event":var n={name:a.name};a.args&&a.args.length&&(n.args=a.args),k=g.stringify(n);break;case"json":k=g.stringify(a.data);break;case"connect":a.qs&&(k=a.qs);break;case"ack":k=a.ackId+(a.args&&a.args.length?"+"+g.stringify(a.args):"")}var o=[b,c+(j=="data"?"+":""),i];k!==null&&k!==undefined&&o.push(k);return o.join(":")},c.encodePayload=function(a){var b="";if(a.length==1)return a[0];for(var c=0,d=a.length;c<d;c++){var e=a[c];b+="\ufffd"+e.length+"\ufffd"+a[c]}return b};var i=/([^:]+):([0-9]+)?(\+)?:([^:]+)?:?([\s\S]*)?/;c.decodePacket=function(a){var b=a.match(i);if(!b)return{};var c=b[2]||"",a=b[5]||"",h={type:d[b[1]],endpoint:b[4]||""};c&&(h.id=c,b[3]?h.ack="data":h.ack=!0);switch(h.type){case"error":var b=a.split("+");h.reason=e[b[0]]||"",h.advice=f[b[1]]||"";break;case"message":h.data=a||"";break;case"event":try{var j=g.parse(a);h.name=j.name,h.args=j.args}catch(k){}h.args=h.args||[];break;case"json":try{h.data=g.parse(a)}catch(k){}break;case"connect":h.qs=a||"";break;case"ack":var b=a.match(/^([0-9]+)(\+)?(.*)/);if(b){h.ackId=b[1],h.args=[];if(b[3])try{h.args=b[3]?g.parse(b[3]):[]}catch(k){}}break;case"disconnect":case"heartbeat":}return h},c.decodePayload=function(a){if(a.charAt(0)=="\ufffd"){var b=[];for(var d=1,e="";d<a.length;d++)a.charAt(d)=="\ufffd"?(b.push(c.decodePacket(a.substr(d+1).substr(0,e))),d+=Number(e)+1,e=""):e+=a.charAt(d);return b}return[c.decodePacket(a)]}}("undefined"!=typeof io?io:module.exports,"undefined"!=typeof io?io:module.parent.exports),function(a,b){function c(a,b){this.socket=a,this.sessid=b}a.Transport=c,b.util.mixin(c,b.EventEmitter),c.prototype.onData=function(a){this.clearCloseTimeout(),this.setCloseTimeout();if(a!==""){var c=b.parser.decodePayload(a);if(c&&c.length)for(var d=0,e=c.length;d<e;d++)this.onPacket(c[d])}return this},c.prototype.onPacket=function(a){if(a.type=="heartbeat")return this.onHeartbeat();a.type=="connect"&&a.endpoint==""&&this.onConnect(),this.socket.onPacket(a);return this},c.prototype.setCloseTimeout=function(){if(!this.closeTimeout){var a=this;this.closeTimeout=setTimeout(function(){a.onDisconnect()},this.socket.closeTimeout)}},c.prototype.onDisconnect=function(){this.close&&this.close(),this.clearTimeouts(),this.socket.onDisconnect();return this},c.prototype.onConnect=function(){this.socket.onConnect();return this},c.prototype.clearCloseTimeout=function(){this.closeTimeout&&(clearTimeout(this.closeTimeout),this.closeTimeout=null)},c.prototype.clearTimeouts=function(){this.clearCloseTimeout(),this.reopenTimeout&&clearTimeout(this.reopenTimeout)},c.prototype.packet=function(a){this.send(b.parser.encodePacket(a))},c.prototype.onHeartbeat=function(a){this.packet({type:"heartbeat"})},c.prototype.onOpen=function(){this.open=!0,this.clearCloseTimeout(),this.socket.onOpen()},c.prototype.onClose=function(){var a=this;this.open=!1,this.setCloseTimeout(),this.socket.onClose()},c.prototype.prepareUrl=function(){var a=this.socket.options;return this.scheme()+"://"+a.host+":"+a.port+"/"+a.resource+"/"+b.protocol+"/"+this.name+"/"+this.sessid},c.prototype.ready=function(a,b){b.call(this)}}("undefined"!=typeof io?io:module.exports,"undefined"!=typeof io?io:module.parent.exports),function(a,b,c){function e(){}function d(a){this.options={port:80,secure:!1,document:"document"in c?document:!1,resource:"socket.io",transports:b.transports,"connect timeout":1e4,"try multiple transports":!0,reconnect:!0,"reconnection delay":500,"reconnection limit":Infinity,"reopen delay":3e3,"max reconnection attempts":10,"sync disconnect on unload":!0,"auto connect":!0,"flash policy port":10843},b.util.merge(this.options,a),this.connected=!1,this.open=!1,this.connecting=!1,this.reconnecting=!1,this.namespaces={},this.buffer=[],this.doBuffer=!1;if(this.options["sync disconnect on unload"]&&(!this.isXDomain()||b.util.ua.hasCORS)){var d=this;b.util.on(c,"beforeunload",function(){d.disconnectSync()},!1)}this.options["auto connect"]&&this.connect()}a.Socket=d,b.util.mixin(d,b.EventEmitter),d.prototype.of=function(a){this.namespaces[a]||(this.namespaces[a]=new b.SocketNamespace(this,a),a!==""&&this.namespaces[a].packet({type:"connect"}));return this.namespaces[a]},d.prototype.publish=function(){this.emit.apply(this,arguments);var a;for(var b in this.namespaces)this.namespaces.hasOwnProperty(b)&&(a=this.of(b),a.$emit.apply(a,arguments))},d.prototype.handshake=function(a){function f(b){b instanceof Error?c.onError(b.message):a.apply(null,b.split(":"))}var c=this,d=this.options,g=["http"+(d.secure?"s":"")+":/",d.host+":"+d.port,this.options.resource,b.protocol,b.util.query(this.options.query,"t="+ +(new Date))].join("/");if(this.isXDomain()){var h=document.getElementsByTagName("script")[0],i=document.createElement("script");i.src=g+"&jsonp="+b.j.length,h.parentNode.insertBefore(i,h),b.j.push(function(a){f(a),i.parentNode.removeChild(i)})}else{var j=b.util.request();j.open("GET",g,!0),j.onreadystatechange=function(){j.readyState==4&&(j.onreadystatechange=e,j.status==200?f(j.responseText):!c.reconnecting&&c.onError(j.responseText))},j.send(null)}},d.prototype.getTransport=function(a){var c=a||this.transports,d;for(var e=0,f;f=c[e];e++)if(b.Transport[f]&&b.Transport[f].check(this)&&(!this.isXDomain()||b.Transport[f].xdomainCheck()))return new b.Transport[f](this,this.sessionid);return null},d.prototype.connect=function(a){if(this.connecting)return this;var c=this;this.handshake(function(d,e,f,g){function h(a){c.transport&&c.transport.clearTimeouts(),c.transport=c.getTransport(a);if(!c.transport)return c.publish("connect_failed");c.transport.ready(c,function(){c.connecting=!0,c.publish("connecting",c.transport.name),c.transport.open(),c.options["connect timeout"]&&(c.connectTimeoutTimer=setTimeout(function(){if(!c.connected){c.connecting=!1;if(c.options["try multiple transports"]){c.remainingTransports||(c.remainingTransports=c.transports.slice(0));var a=c.remainingTransports;while(a.length>0&&a.splice(0,1)[0]!=c.transport.name);a.length?h(a):c.publish("connect_failed")}}},c.options["connect timeout"]))})}c.sessionid=d,c.closeTimeout=f*1e3,c.heartbeatTimeout=e*1e3,c.transports=b.util.intersect(g.split(","),c.options.transports),h(),c.once("connect",function(){clearTimeout(c.connectTimeoutTimer),a&&typeof a=="function"&&a()})});return this},d.prototype.packet=function(a){this.connected&&!this.doBuffer?this.transport.packet(a):this.buffer.push(a);return this},d.prototype.setBuffer=function(a){this.doBuffer=a,!a&&this.connected&&this.buffer.length&&(this.transport.payload(this.buffer),this.buffer=[])},d.prototype.disconnect=function(){this.connected&&(this.open&&this.of("").packet({type:"disconnect"}),this.onDisconnect("booted"));return this},d.prototype.disconnectSync=function(){var a=b.util.request(),c=this.resource+"/"+b.protocol+"/"+this.sessionid;a.open("GET",c,!0),this.onDisconnect("booted")},d.prototype.isXDomain=function(){var a=window.location.port||("https:"==window.location.protocol?443:80);return this.options.host!==document.domain||this.options.port!=a},d.prototype.onConnect=function(){this.connected||(this.connected=!0,this.connecting=!1,this.doBuffer||this.setBuffer(!1),this.emit("connect"))},d.prototype.onOpen=function(){this.open=!0},d.prototype.onClose=function(){this.open=!1},d.prototype.onPacket=function(a){this.of(a.endpoint).onPacket(a)},d.prototype.onError=function(a){a&&a.advice&&a.advice==="reconnect"&&this.connected&&(this.disconnect(),this.reconnect()),this.publish("error",a&&a.reason?a.reason:a)},d.prototype.onDisconnect=function(a){var b=this.connected;this.connected=!1,this.connecting=!1,this.open=!1,b&&(this.transport.close(),this.transport.clearTimeouts(),this.publish("disconnect",a),"booted"!=a&&this.options.reconnect&&!this.reconnecting&&this.reconnect())},d.prototype.reconnect=function(){function f(){if(!!a.reconnecting){if(a.connected)return e();if(a.connecting&&a.reconnecting)return a.reconnectionTimer=setTimeout(f,1e3);a.reconnectionAttempts++>=b?a.redoTransports?(a.publish("reconnect_failed"),e()):(a.on("connect_failed",f),a.options["try multiple transports"]=!0,a.transport=a.getTransport(),a.redoTransports=!0,a.connect()):(a.reconnectionDelay<d&&(a.reconnectionDelay*=2),a.connect(),a.publish("reconnecting",a.reconnectionDelay,a.reconnectionAttempts),a.reconnectionTimer=setTimeout(f,a.reconnectionDelay))}}function e(){if(a.connected){for(var b in a.namespaces)a.namespaces.hasOwnProperty(b)&&""!==b&&a.namespaces[b].packet({type:"connect"});a.publish("reconnect",a.transport.name,a.reconnectionAttempts)}a.removeListener("connect_failed",f),a.removeListener("connect",f),a.reconnecting=!1,delete a.reconnectionAttempts,delete a.reconnectionDelay,delete a.reconnectionTimer,delete a.redoTransports,a.options["try multiple transports"]=c}this.reconnecting=!0,this.reconnectionAttempts=0,this.reconnectionDelay=this.options["reconnection delay"];var a=this,b=this.options["max reconnection attempts"],c=this.options["try multiple transports"],d=this.options["reconnection limit"];this.options["try multiple transports"]=!1,this.reconnectionTimer=setTimeout(f,this.reconnectionDelay),this.on("connect",f)}}("undefined"!=typeof io?io:module.exports,"undefined"!=typeof io?io:module.parent.exports,this),function(a,b){function d(a,b){this.namespace=a,this.name=b}function c(a,b){this.socket=a,this.name=b||"",this.flags={},this.json=new d(this,"json"),this.ackPackets=0,this.acks={}}a.SocketNamespace=c,b.util.mixin(c,b.EventEmitter),c.prototype.$emit=b.EventEmitter.prototype.emit,c.prototype.of=function(){return this.socket.of.apply(this.socket,arguments)},c.prototype.packet=function(a){a.endpoint=this.name,this.socket.packet(a),this.flags={};return this},c.prototype.send=function(a,b){var c={type:this.flags.json?"json":"message",data:a};"function"==typeof b&&(c.id=++this.ackPackets,c.ack=!0,this.acks[c.id]=b);return this.packet(c)},c.prototype.emit=function(a){var b=Array.prototype.slice.call(arguments,1),c=b[b.length-1],d={type:"event",name:a};"function"==typeof c&&(d.id=++this.ackPackets,d.ack="data",this.acks[d.id]=c,b=b.slice(0,b.length-1)),d.args=b;return this.packet(d)},c.prototype.disconnect=function(){this.name===""?this.socket.disconnect():(this.packet({type:"disconnect"}),this.$emit("disconnect"));return this},c.prototype.onPacket=function(a){function d(){c.packet({type:"ack",args:b.util.toArray(arguments),ackId:a.id})}var c=this;switch(a.type){case"connect":this.$emit("connect");break;case"disconnect":this.name===""?this.socket.onDisconnect(a.reason||"booted"):this.$emit("disconnect",a.reason);break;case"message":case"json":var e=["message",a.data];a.ack=="data"?e.push(d):a.ack&&this.packet({type:"ack",ackId:a.id}),this.$emit.apply(this,e);break;case"event":var e=[a.name].concat(a.args);a.ack=="data"&&e.push(d),this.$emit.apply(this,e);break;case"ack":this.acks[a.ackId]&&(this.acks[a.ackId].apply(this,a.args),delete this.acks[a.ackId]);break;case"error":a.advice?this.socket.onError(a):a.reason=="unauthorized"?this.$emit("connect_failed",a.reason):this.$emit("error",a.reason)}},d.prototype.send=function(){this.namespace.flags[this.name]=!0,this.namespace.send.apply(this.namespace,arguments)},d.prototype.emit=function(){this.namespace.flags[this.name]=!0,this.namespace.emit.apply(this.namespace,arguments)}}("undefined"!=typeof io?io:module.exports,"undefined"!=typeof io?io:module.parent.exports),function(a,b){function c(a){b.Transport.apply(this,arguments)}a.websocket=c,b.util.inherit(c,b.Transport),c.prototype.name="websocket",c.prototype.open=function(){var a=b.util.query(this.socket.options.query),c=this,d;d||(d=window.MozWebSocket||window.WebSocket),this.websocket=new d(this.prepareUrl()+a),this.websocket.onopen=function(){c.onOpen(),c.socket.setBuffer(!1)},this.websocket.onmessage=function(a){c.onData(a.data)},this.websocket.onclose=function(){c.onClose(),c.socket.setBuffer(!0)},this.websocket.onerror=function(a){c.onError(a)};return this},c.prototype.send=function(a){this.websocket.send(a);return this},c.prototype.payload=function(a){for(var b=0,c=a.length;b<c;b++)this.packet(a[b]);return this},c.prototype.close=function(){this.websocket.close();return this},c.prototype.onError=function(a){this.socket.onError(a)},c.prototype.scheme=function(){return this.socket.options.secure?"wss":"ws"},c.check=function(){return"WebSocket"in window&&!("__addTask"in WebSocket)||"MozWebSocket"in window},c.xdomainCheck=function(){return!0},b.transports.push("websocket")}("undefined"!=typeof io?io.Transport:module.exports,"undefined"!=typeof io?io:module.parent.exports),function(a,b){function c(){b.Transport.websocket.apply(this,arguments)}a.flashsocket=c,b.util.inherit(c,b.Transport.websocket),c.prototype.name="flashsocket",c.prototype.open=function(){var a=this,c=arguments;WebSocket.__addTask(function(){b.Transport.websocket.prototype.open.apply(a,c)});return this},c.prototype.send=function(){var a=this,c=arguments;WebSocket.__addTask(function(){b.Transport.websocket.prototype.send.apply(a,c)});return this},c.prototype.close=function(){WebSocket.__tasks.length=0,b.Transport.websocket.prototype.close.call(this);return this},c.prototype.ready=function(a,d){function e(){var b=a.options,e=b["flash policy port"],g=["http"+(b.secure?"s":"")+":/",b.host+":"+b.port,b.resource,"static/flashsocket","WebSocketMain"+(a.isXDomain()?"Insecure":"")+".swf"];c.loaded||(typeof WEB_SOCKET_SWF_LOCATION=="undefined"&&(WEB_SOCKET_SWF_LOCATION=g.join("/")),e!==843&&WebSocket.loadFlashPolicyFile("xmlsocket://"+b.host+":"+e),WebSocket.__initialize(),c.loaded=!0),d.call(f)}var f=this;if(document.body)return e();b.util.load(e)},c.check=function(){return typeof WebSocket!="undefined"&&"__initialize"in WebSocket&&!!swfobject?swfobject.getFlashPlayerVersion().major>=10:!1},c.xdomainCheck=function(){return!0},typeof window!="undefined"&&(WEB_SOCKET_DISABLE_AUTO_INITIALIZATION=!0),b.transports.push("flashsocket")}("undefined"!=typeof io?io.Transport:module.exports,"undefined"!=typeof io?io:module.parent.exports);var swfobject=function(){function V(b){var c=/[\\\"<>\.;]/,d=c.exec(b)!=null;return d&&typeof encodeURIComponent!=a?encodeURIComponent(b):b}function U(a,b){if(!!x){var c=b?"visible":"hidden";t&&P(a)?P(a).style.visibility=c:T("#"+a,"visibility:"+c)}}function T(c,d,e,f){if(!y.ie||!y.mac){var g=i.getElementsByTagName("head")[0];if(!g)return;var h=e&&typeof e=="string"?e:"screen";f&&(v=null,w=null);if(!v||w!=h){var j=Q("style");j.setAttribute("type","text/css"),j.setAttribute("media",h),v=g.appendChild(j),y.ie&&y.win&&typeof i.styleSheets!=a&&i.styleSheets.length>0&&(v=i.styleSheets[i.styleSheets.length-1]),w=h}y.ie&&y.win?v&&typeof v.addRule==b&&v.addRule(c,d):v&&typeof i.createTextNode!=a&&v.appendChild(i.createTextNode(c+" {"+d+"}"))}}function S(a){var b=y.pv,c=a.split(".");c[0]=parseInt(c[0],10),c[1]=parseInt(c[1],10)||0,c[2]=parseInt(c[2],10)||0;return b[0]>c[0]||b[0]==c[0]&&b[1]>c[1]||b[0]==c[0]&&b[1]==c[1]&&b[2]>=c[2]?!0:!1}function R(a,b,c){a.attachEvent(b,c),o[o.length]=[a,b,c]}function Q(a){return i.createElement(a)}function P(a){var b=null;try{b=i.getElementById(a)}catch(c){}return b}function O(a){var b=P(a);if(b){for(var c in b)typeof b[c]=="function"&&(b[c]=null);b.parentNode.removeChild(b)}}function N(a){var b=P(a);b&&b.nodeName=="OBJECT"&&(y.ie&&y.win?(b.style.display="none",function(){b.readyState==4?O(a):setTimeout(arguments.callee,10)}()):b.parentNode.removeChild(b))}function M(a,b,c){var d=Q("param");d.setAttribute("name",b),d.setAttribute("value",c),a.appendChild(d)}function L(c,d,f){var g,h=P(f);if(y.wk&&y.wk<312)return g;if(h){typeof c.id==a&&(c.id=f);if(y.ie&&y.win){var i="";for(var j in c)c[j]!=Object.prototype[j]&&(j.toLowerCase()=="data"?d.movie=c[j]:j.toLowerCase()=="styleclass"?i+=' class="'+c[j]+'"':j.toLowerCase()!="classid"&&(i+=" "+j+'="'+c[j]+'"'));var k="";for(var l in d)d[l]!=Object.prototype[l]&&(k+='<param name="'+l+'" value="'+d[l]+'" />');h.outerHTML='<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"'+i+">"+k+"</object>",n[n.length]=c.id,g=P(c.id)}else{var m=Q(b);m.setAttribute("type",e);for(var o in c)c[o]!=Object.prototype[o]&&(o.toLowerCase()=="styleclass"?m.setAttribute("class",c[o]):o.toLowerCase()!="classid"&&m.setAttribute(o,c[o]));for(var p in d)d[p]!=Object.prototype[p]&&p.toLowerCase()!="movie"&&M(m,p,d[p]);h.parentNode.replaceChild(m,h),g=m}}return g}function K(a){var c=Q("div");if(y.win&&y.ie)c.innerHTML=a.innerHTML;else{var d=a.getElementsByTagName(b)[0];if(d){var e=d.childNodes;if(e){var f=e.length;for(var g=0;g<f;g++)(e[g].nodeType!=1||e[g].nodeName!="PARAM")&&e[g].nodeType!=8&&c.appendChild(e[g].cloneNode(!0))}}}return c}function J(a){if(y.ie&&y.win&&a.readyState!=4){var b=Q("div");a.parentNode.insertBefore(b,a),b.parentNode.replaceChild(K(a),b),a.style.display="none",function(){a.readyState==4?a.parentNode.removeChild(a):setTimeout(arguments.callee,10)}()}else a.parentNode.replaceChild(K(a),a)}function I(b,c,d,e){u=!0,r=e||null,s={success:!1,id:d};var g=P(d);if(g){g.nodeName=="OBJECT"?(p=K(g),q=null):(p=g,q=d),b.id=f;if(typeof b.width==a||!/%$/.test(b.width)&&parseInt(b.width,10)<310)b.width="310";if(typeof b.height==a||!/%$/.test(b.height)&&parseInt(b.height,10)<137)b.height="137";i.title=i.title.slice(0,47)+" - Flash Player Installation";var j=y.ie&&y.win?"ActiveX":"PlugIn",k="MMredirectURL="+h.location.toString().replace(/&/g,"%26")+"&MMplayerType="+j+"&MMdoctitle="+i.title;typeof c.flashvars!=a?c.flashvars+="&"+k:c.flashvars=k;if(y.ie&&y.win&&g.readyState!=4){var l=Q("div");d+="SWFObjectNew",l.setAttribute("id",d),g.parentNode.insertBefore(l,g),g.style.display="none",function(){g.readyState==4?g.parentNode.removeChild(g):setTimeout(arguments.callee,10)}()}L(b,c,d)}}function H(){return!u&&S("6.0.65")&&(y.win||y.mac)&&!(y.wk&&y.wk<312)}function G(c){var d=null,e=P(c);if(e&&e.nodeName=="OBJECT")if(typeof e.SetVariable!=a)d=e;else{var f=e.getElementsByTagName(b)[0];f&&(d=f)}return d}function F(){var b=m.length;if(b>0)for(var c=0;c<b;c++){var d=m[c].id,e=m[c].callbackFn,f={success:!1,id:d};if(y.pv[0]>0){var g=P(d);if(g)if(S(m[c].swfVersion)&&!(y.wk&&y.wk<312))U(d,!0),e&&(f.success=!0,f.ref=G(d),e(f));else if(m[c].expressInstall&&H()){var h={};h.data=m[c].expressInstall,h.width=g.getAttribute("width")||"0",h.height=g.getAttribute("height")||"0",g.getAttribute("class")&&(h.styleclass=g.getAttribute("class")),g.getAttribute("align")&&(h.align=g.getAttribute("align"));var i={},j=g.getElementsByTagName("param"),k=j.length;for(var l=0;l<k;l++)j[l].getAttribute("name").toLowerCase()!="movie"&&(i[j[l].getAttribute("name")]=j[l].getAttribute("value"));I(h,i,d,e)}else J(g),e&&e(f)}else{U(d,!0);if(e){var n=G(d);n&&typeof n.SetVariable!=a&&(f.success=!0,f.ref=n),e(f)}}}}function E(){var c=i.getElementsByTagName("body")[0],d=Q(b);d.setAttribute("type",e);var f=c.appendChild(d);if(f){var g=0;(function(){if(typeof f.GetVariable!=a){var b=f.GetVariable("$version");b&&(b=b.split(" ")[1].split(","),y.pv=[parseInt(b[0],10),parseInt(b[1],10),parseInt(b[2],10)])}else if(g<10){g++,setTimeout(arguments.callee,10);return}c.removeChild(d),f=null,F()})()}else F()}function D(){k?E():F()}function C(b){if(typeof h.addEventListener!=a)h.addEventListener("load",b,!1);else if(typeof i.addEventListener!=a)i.addEventListener("load",b,!1);else if(typeof h.attachEvent!=a)R(h,"onload",b);else if(typeof h.onload=="function"){var c=h.onload;h.onload=function(){c(),b()}}else h.onload=b}function B(a){t?a():l[l.length]=a}function A(){if(!t){try{var a=i.getElementsByTagName("body")[0].appendChild(Q("span"));a.parentNode.removeChild(a)}catch(b){return}t=!0;var c=l.length;for(var d=0;d<c;d++)l[d]()}}var a="undefined",b="object",c="Shockwave Flash",d="ShockwaveFlash.ShockwaveFlash",e="application/x-shockwave-flash",f="SWFObjectExprInst",g="onreadystatechange",h=window,i=document,j=navigator,k=!1,l=[D],m=[],n=[],o=[],p,q,r,s,t=!1,u=!1,v,w,x=!0,y=function(){var f=typeof i.getElementById!=a&&typeof i.getElementsByTagName!=a&&typeof i.createElement!=a,g=j.userAgent.toLowerCase(),l=j.platform.toLowerCase(),m=l?/win/.test(l):/win/.test(g),n=l?/mac/.test(l):/mac/.test(g),o=/webkit/.test(g)?parseFloat(g.replace(/^.*webkit\/(\d+(\.\d+)?).*$/,"$1")):!1,p=!1,q=[0,0,0],r=null;if(typeof j.plugins!=a&&typeof j.plugins[c]==b)r=j.plugins[c].description,r&&(typeof j.mimeTypes==a||!j.mimeTypes[e]||!!j.mimeTypes[e].enabledPlugin)&&(k=!0,p=!1,r=r.replace(/^.*\s+(\S+\s+\S+$)/,"$1"),q[0]=parseInt(r.replace(/^(.*)\..*$/,"$1"),10),q[1]=parseInt(r.replace(/^.*\.(.*)\s.*$/,"$1"),10),q[2]=/[a-zA-Z]/.test(r)?parseInt(r.replace(/^.*[a-zA-Z]+(.*)$/,"$1"),10):0);else if(typeof h.ActiveXObject!=a)try{var s=new ActiveXObject(d);s&&(r=s.GetVariable("$version"),r&&(p=!0,r=r.split(" ")[1].split(","),q=[parseInt(r[0],10),parseInt(r[1],10),parseInt(r[2],10)]))}catch(t){}return{w3:f,pv:q,wk:o,ie:p,win:m,mac:n}}(),z=function(){!y.w3||((typeof i.readyState!=a&&i.readyState=="complete"||typeof i.readyState==a&&(i.getElementsByTagName("body")[0]||i.body))&&A(),t||(typeof i.addEventListener!=a&&i.addEventListener("DOMContentLoaded",A,!1),y.ie&&y.win&&(i.attachEvent(g,function(){i.readyState=="complete"&&(i.detachEvent(g,arguments.callee),A())}),h==top&&function(){if(!t){try{i.documentElement.doScroll("left")}catch(a){setTimeout(arguments.callee,0);return}A()}}()),y.wk&&function(){if(!t){if(!/loaded|complete/.test(i.readyState)){setTimeout(arguments.callee,0);return}A()}}(),C(A)))}(),W=function(){y.ie&&y.win&&window.attachEvent("onunload",function(){var a=o.length;for(var b=0;b<a;b++)o[b][0].detachEvent(o[b][1],o[b][2]);var c=n.length;for(var d=0;d<c;d++)N(n[d]);for(var e in y)y[e]=null;y=null;for(var f in swfobject)swfobject[f]=null;swfobject=null})}();return{registerObject:function(a,b,c,d){if(y.w3&&a&&b){var e={};e.id=a,e.swfVersion=b,e.expressInstall=c,e.callbackFn=d,m[m.length]=e,U(a,!1)}else d&&d({success:!1,id:a})},getObjectById:function(a){if(y.w3)return G(a)},embedSWF:function(c,d,e,f,g,h,i,j,k,l){var m={success:!1,id:d};y.w3&&!(y.wk&&y.wk<312)&&c&&d&&e&&f&&g?(U(d,!1),B(function(){e+="",f+="";var n={};if(k&&typeof k===b)for(var o in k)n[o]=k[o];n.data=c,n.width=e,n.height=f;var p={};if(j&&typeof j===b)for(var q in j)p[q]=j[q];if(i&&typeof i===b)for(var r in i)typeof p.flashvars!=a?p.flashvars+="&"+r+"="+i[r]:p.flashvars=r+"="+i[r];if(S(g)){var s=L(n,p,d);n.id==d&&U(d,!0),m.success=!0,m.ref=s}else{if(h&&H()){n.data=h,I(n,p,d,l);return}U(d,!0)}l&&l(m)})):l&&l(m)},switchOffAutoHideShow:function(){x=!1},ua:y,getFlashPlayerVersion:function(){return{major:y.pv[0],minor:y.pv[1],release:y.pv[2]}},hasFlashPlayerVersion:S,createSWF:function(a,b,c){return y.w3?L(a,b,c):undefined},showExpressInstall:function(a,b,c,d){y.w3&&H()&&I(a,b,c,d)},removeSWF:function(a){y.w3&&N(a)},createCSS:function(a,b,c,d){y.w3&&T(a,b,c,d)},addDomLoadEvent:B,addLoadEvent:C,getQueryParamValue:function(a){var b=i.location.search||i.location.hash;if(b){/\?/.test(b)&&(b=b.split("?")[1]);if(a==null)return V(b);var c=b.split("&");for(var d=0;d<c.length;d++)if(c[d].substring(0,c[d].indexOf("="))==a)return V(c[d].substring(c[d].indexOf("=")+1))}return""},expressInstallCallback:function(){if(u){var a=P(f);a&&p&&(a.parentNode.replaceChild(p,a),q&&(U(q,!0),y.ie&&y.win&&(p.style.display="block")),r&&r(s)),u=!1}}}}();(function(){if(!window.WebSocket){var a=window.console;if(!a||!a.log||!a.error)a={log:function(){},error:function(){}};if(!swfobject.hasFlashPlayerVersion("10.0.0")){a.error("Flash Player >= 10.0.0 is required.");return}location.protocol=="file:"&&a.error("WARNING: web-socket-js doesn't work in file:///... URL unless you set Flash Security Settings properly. Open the page via Web server i.e. http://..."),WebSocket=function(a,b,c,d,e){var f=this;f.__id=WebSocket.__nextId++,WebSocket.__instances[f.__id]=f,f.readyState=WebSocket.CONNECTING,f.bufferedAmount=0,f.__events={},b?typeof b=="string"&&(b=[b]):b=[],setTimeout(function(){WebSocket.__addTask(function(){WebSocket.__flash.create(f.__id,a,b,c||null,d||0,e||null)})},0)},WebSocket.prototype.send=function(a){if(this.readyState==WebSocket.CONNECTING)throw"INVALID_STATE_ERR: Web Socket connection has not been established";var b=WebSocket.__flash.send(this.__id,encodeURIComponent(a));if(b<0)return!0;this.bufferedAmount+=b;return!1},WebSocket.prototype.close=function(){this.readyState!=WebSocket.CLOSED&&this.readyState!=WebSocket.CLOSING&&(this.readyState=WebSocket.CLOSING,WebSocket.__flash.close(this.__id))},WebSocket.prototype.addEventListener=function(a,b,c){a in this.__events||(this.__events[a]=[]),this.__events[a].push(b)},WebSocket.prototype.removeEventListener=function(a,b,c){if(a in this.__events){var d=this.__events[a];for(var e=d.length-1;e>=0;--e)if(d[e]===b){d.splice(e,1);break}}},WebSocket.prototype.dispatchEvent=function(a){var b=this.__events[a.type]||[];for(var c=0;c<b.length;++c)b[c](a);var d=this["on"+a.type];d&&d(a)},WebSocket.prototype.__handleEvent=function(a){"readyState"in a&&(this.readyState=a.readyState),"protocol"in a&&(this.protocol=a.protocol);var b;if(a.type=="open"||a.type=="error")b=this.__createSimpleEvent(a.type);else if(a.type=="close")b=this.__createSimpleEvent("close");else{if(a.type!="message")throw"unknown event type: "+a.type;var c=decodeURIComponent(a.message);b=this.__createMessageEvent("message",c)}this.dispatchEvent(b)},WebSocket.prototype.__createSimpleEvent=function(a){if(document.createEvent&&window.Event){var b=document.createEvent("Event");b.initEvent(a,!1,!1);return b}return{type:a,bubbles:!1,cancelable:!1}},WebSocket.prototype.__createMessageEvent=function(a,b){if(document.createEvent&&window.MessageEvent&&!window.opera){var c=document.createEvent("MessageEvent");c.initMessageEvent("message",!1,!1,b,null,null,window,null);return c}return{type:a,data:b,bubbles:!1,cancelable:!1}},WebSocket.CONNECTING=0,WebSocket.OPEN=1,WebSocket.CLOSING=2,WebSocket.CLOSED=3,WebSocket.__flash=null,WebSocket.__instances={},WebSocket.__tasks=[],WebSocket.__nextId=0,WebSocket.loadFlashPolicyFile=function(a){WebSocket.__addTask(function(){WebSocket.__flash.loadManualPolicyFile(a)})},WebSocket.__initialize=function(){if(!WebSocket.__flash){WebSocket.__swfLocation&&(window.WEB_SOCKET_SWF_LOCATION=WebSocket.__swfLocation);if(!window.WEB_SOCKET_SWF_LOCATION){a.error("[WebSocket] set WEB_SOCKET_SWF_LOCATION to location of WebSocketMain.swf");return}var b=document.createElement("div");b.id="webSocketContainer",b.style.position="absolute",WebSocket.__isFlashLite()?(b.style.left="0px",b.style.top="0px"):(b.style.left="-100px",b.style.top="-100px");var c=document.createElement("div");c.id="webSocketFlash",b.appendChild(c),document.body.appendChild(b),swfobject.embedSWF(WEB_SOCKET_SWF_LOCATION,"webSocketFlash","1","1","10.0.0",null,null,{hasPriority:!0,swliveconnect:!0,allowScriptAccess:"always"},null,function(b){b.success||a.error("[WebSocket] swfobject.embedSWF failed")})}},WebSocket.__onFlashInitialized=function(){setTimeout(function(){WebSocket.__flash=document.getElementById("webSocketFlash"),WebSocket.__flash.setCallerUrl(location.href),WebSocket.__flash.setDebug(!!window.WEB_SOCKET_DEBUG);for(var a=0;a<WebSocket.__tasks.length;++a)WebSocket.__tasks[a]();WebSocket.__tasks=[]},0)},WebSocket.__onFlashEvent=function(){setTimeout(function(){try{var b=WebSocket.__flash.receiveEvents();for(var c=0;c<b.length;++c)WebSocket.__instances[b[c].webSocketId].__handleEvent(b[c])}catch(d){a.error(d)}},0);return!0},WebSocket.__log=function(b){a.log(decodeURIComponent(b))},WebSocket.__error=function(b){a.error(decodeURIComponent(b))},WebSocket.__addTask=function(a){WebSocket.__flash?a():WebSocket.__tasks.push(a)},WebSocket.__isFlashLite=function(){if(!window.navigator||!window.navigator.mimeTypes)return!1;var a=window.navigator.mimeTypes["application/x-shockwave-flash"];return!a||!a.enabledPlugin||!a.enabledPlugin.filename?!1:a.enabledPlugin.filename.match(/flashlite/i)?!0:!1},window.WEB_SOCKET_DISABLE_AUTO_INITIALIZATION||(window.addEventListener?window.addEventListener("load",function(){WebSocket.__initialize()},!1):window.attachEvent("onload",function(){WebSocket.__initialize()}))}})(),function(a,b,c){function e(){}function d(a){!a||(b.Transport.apply(this,arguments),this.sendBuffer=[])}a.XHR=d,b.util.inherit(d,b.Transport),d.prototype.open=function(){this.socket.setBuffer(!1),this.onOpen(),this.get(),this.setCloseTimeout();return this},d.prototype.payload=function(a){var c=[];for(var d=0,e=a.length;d<e;d++)c.push(b.parser.encodePacket(a[d]));this.send(b.parser.encodePayload(c))},d.prototype.send=function(a){this.post(a);return this},d.prototype.post=function(a){function f(){this.onload=e,b.socket.setBuffer(!1)}function d(){this.readyState==4&&(this.onreadystatechange=e,b.posting=!1,this.status==200?b.socket.setBuffer(!1):b.onClose())}var b=this;this.socket.setBuffer(!0),this.sendXHR=this.request("POST"),c.XDomainRequest&&this.sendXHR instanceof XDomainRequest?this.sendXHR.onload=this.sendXHR.onerror=f:this.sendXHR.onreadystatechange=d,this.sendXHR.send(a)},d.prototype.close=function(){this.onClose();return this},d.prototype.request=function(a){var c=b.util.request(this.socket.isXDomain()),d=b.util.query(this.socket.options.query,"t="+ +(new Date));c.open(a||"GET",this.prepareUrl()+d,!0);if(a=="POST")try{c.setRequestHeader?c.setRequestHeader("Content-type","text/plain;charset=UTF-8"):c.contentType="text/plain"}catch(e){}return c},d.prototype.scheme=function(){return this.socket.options.secure?"https":"http"},d.check=function(a,c){try{if(b.util.request(c))return!0}catch(d){}return!1},d.xdomainCheck=function(){return d.check(null,!0)}}("undefined"!=typeof io?io.Transport:module.exports,"undefined"!=typeof io?io:module.parent.exports,this),function(a,b){function c(a){b.Transport.XHR.apply(this,arguments)}a.htmlfile=c,b.util.inherit(c,b.Transport.XHR),c.prototype.name="htmlfile",c.prototype.get=function(){this.doc=new ActiveXObject("htmlfile"),this.doc.open(),this.doc.write("<html></html>"),this.doc.close(),this.doc.parentWindow.s=this;var a=this.doc.createElement("div");a.className="socketio",this.doc.body.appendChild(a),this.iframe=this.doc.createElement("iframe"),a.appendChild(this.iframe);var c=this,d=b.util.query(this.socket.options.query,"t="+ +(new Date));this.iframe.src=this.prepareUrl()+d,b.util.on(window,"unload",function(){c.destroy()})},c.prototype._=function(a,b){this.onData(a);try{var c=b.getElementsByTagName("script")[0];c.parentNode.removeChild(c)}catch(d){}},c.prototype.destroy=function(){if(this.iframe){try{this.iframe.src="about:blank"}catch(a){}this.doc=null,this.iframe.parentNode.removeChild(this.iframe),this.iframe=null,CollectGarbage()}},c.prototype.close=function(){this.destroy();return b.Transport.XHR.prototype.close.call(this)},c.check=function(){if("ActiveXObject"in window)try{var a=new ActiveXObject("htmlfile");return a&&b.Transport.XHR.check()}catch(c){}return!1},c.xdomainCheck=function(){return!1},b.transports.push("htmlfile")}("undefined"!=typeof io?io.Transport:module.exports,"undefined"!=typeof io?io:module.parent.exports),function(a,b,c){function e(){}function d(){b.Transport.XHR.apply(this,arguments)}a["xhr-polling"]=d,b.util.inherit(d,b.Transport.XHR),b.util.merge(d,b.Transport.XHR),d.prototype.name="xhr-polling",d.prototype.open=function(){var a=this;b.Transport.XHR.prototype.open.call(a);return!1},d.prototype.get=function(){function d(){this.onload=e,a.onData(this.responseText),a.get()}function b(){this.readyState==4&&(this.onreadystatechange=e,this.status==200?(a.onData(this.responseText),a.get()):a.onClose())}if(!!this.open){var a=this;this.xhr=this.request(),c.XDomainRequest&&this.xhr instanceof XDomainRequest?this.xhr.onload=this.xhr.onerror=d:this.xhr.onreadystatechange=b,this.xhr.send(null)}},d.prototype.onClose=function(){b.Transport.XHR.prototype.onClose.call(this);if(this.xhr){this.xhr.onreadystatechange=this.xhr.onload=e;try{this.xhr.abort()}catch(a){}this.xhr=null}},d.prototype.ready=function(a,c){var d=this;b.util.defer(function(){c.call(d)})},b.transports.push("xhr-polling")}("undefined"!=typeof io?io.Transport:module.exports,"undefined"!=typeof io?io:module.parent.exports,this),function(a,b){function c(a){b.Transport["xhr-polling"].apply(this,arguments),this.index=b.j.length;var c=this;b.j.push(function(a){c._(a)})}a["jsonp-polling"]=c,b.util.inherit(c,b.Transport["xhr-polling"]),c.prototype.name="jsonp-polling",c.prototype.post=function(a){function j(){c.iframe&&c.form.removeChild(c.iframe);try{h=document.createElement('<iframe name="'+c.iframeId+'">')}catch(a){h=document.createElement("iframe"),h.name=c.iframeId}h.id=c.iframeId,c.form.appendChild(h),c.iframe=h}function i(){j(),c.socket.setBuffer(!1)}var c=this,d=b.util.query(this.socket.options.query,"t="+ +(new Date)+"&i="+this.index);if(!this.form){var e=document.createElement("form"),f=document.createElement("textarea"),g=this.iframeId="socketio_iframe_"+this.index,h;e.className="socketio",e.style.position="absolute",e.style.top="-1000px",e.style.left="-1000px",e.target=g,e.method="POST",e.setAttribute("accept-charset","utf-8"),f.name="d",e.appendChild(f),document.body.appendChild(e),this.form=e,this.area=f}this.form.action=this.prepareUrl()+d,j(),this.area.value=a;try{this.form.submit()}catch(k){}this.iframe.attachEvent?h.onreadystatechange=function(){c.iframe.readyState=="complete"&&i()}:this.iframe.onload=i,this.socket.setBuffer(!0)},c.prototype.get=function(){var a=this,c=document.createElement("script"),d=b.util.query(this.socket.options.query,"t="+ +(new Date)+"&i="+this.index);this.script&&(this.script.parentNode.removeChild(this.script),this.script=null),c.async=!0,c.src=this.prepareUrl()+d,c.onerror=function(){a.onClose()};var e=document.getElementsByTagName("script")[0];e.parentNode.insertBefore(c,e),this.script=c},c.prototype._=function(a){this.onData(a),this.open&&this.get();return this},c.check=function(){return!0},c.xdomainCheck=function(){return!0},b.transports.push("jsonp-polling")}("undefined"!=typeof io?io.Transport:module.exports,"undefined"!=typeof io?io:module.parent.exports) \ No newline at end of file
new file mode 100644 --- /dev/null +++ b/testing/xpcshell/node-spdy/examples/twitlog/public/stylesheets/bootstrap.min.css @@ -0,0 +1,356 @@ +html,body{margin:0;padding:0;} +h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,cite,code,del,dfn,em,img,q,s,samp,small,strike,strong,sub,sup,tt,var,dd,dl,dt,li,ol,ul,fieldset,form,label,legend,button,table,caption,tbody,tfoot,thead,tr,th,td{margin:0;padding:0;border:0;font-weight:normal;font-style:normal;font-size:100%;line-height:1;font-family:inherit;} +table{border-collapse:collapse;border-spacing:0;} +ol,ul{list-style:none;} +q:before,q:after,blockquote:before,blockquote:after{content:"";} +html{overflow-y:scroll;font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;} +a:focus{outline:thin dotted;} +a:hover,a:active{outline:0;} +article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block;} +audio,canvas,video{display:inline-block;*display:inline;*zoom:1;} +audio:not([controls]){display:none;} +sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline;} +sup{top:-0.5em;} +sub{bottom:-0.25em;} +img{border:0;-ms-interpolation-mode:bicubic;} +button,input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle;} +button,input{line-height:normal;*overflow:visible;} +button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0;} +button,input[type="button"],input[type="reset"],input[type="submit"]{cursor:pointer;-webkit-appearance:button;} +input[type="search"]{-webkit-appearance:textfield;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;} +input[type="search"]::-webkit-search-decoration{-webkit-appearance:none;} +textarea{overflow:auto;vertical-align:top;} +body{background-color:#ffffff;margin:0;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:13px;font-weight:normal;line-height:18px;color:#404040;} +.container{width:940px;margin-left:auto;margin-right:auto;zoom:1;}.container:before,.container:after{display:table;content:"";zoom:1;} +.container:after{clear:both;} +.container-fluid{position:relative;min-width:940px;padding-left:20px;padding-right:20px;zoom:1;}.container-fluid:before,.container-fluid:after{display:table;content:"";zoom:1;} +.container-fluid:after{clear:both;} +.container-fluid>.sidebar{position:absolute;top:0;left:20px;width:220px;} +.container-fluid>.content{margin-left:240px;} +a{color:#0069d6;text-decoration:none;line-height:inherit;font-weight:inherit;}a:hover{color:#00438a;text-decoration:underline;} +.pull-right{float:right;} +.pull-left{float:left;} +.hide{display:none;} +.show{display:block;} +.row{zoom:1;margin-left:-20px;}.row:before,.row:after{display:table;content:"";zoom:1;} +.row:after{clear:both;} +.row>[class*="span"]{display:inline;float:left;margin-left:20px;} +.span1{width:40px;} +.span2{width:100px;} +.span3{width:160px;} +.span4{width:220px;} +.span5{width:280px;} +.span6{width:340px;} +.span7{width:400px;} +.span8{width:460px;} +.span9{width:520px;} +.span10{width:580px;} +.span11{width:640px;} +.span12{width:700px;} +.span13{width:760px;} +.span14{width:820px;} +.span15{width:880px;} +.span16{width:940px;} +.span17{width:1000px;} +.span18{width:1060px;} +.span19{width:1120px;} +.span20{width:1180px;} +.span21{width:1240px;} +.span22{width:1300px;} +.span23{width:1360px;} +.span24{width:1420px;} +.row>.offset1{margin-left:80px;} +.row>.offset2{margin-left:140px;} +.row>.offset3{margin-left:200px;} +.row>.offset4{margin-left:260px;} +.row>.offset5{margin-left:320px;} +.row>.offset6{margin-left:380px;} +.row>.offset7{margin-left:440px;} +.row>.offset8{margin-left:500px;} +.row>.offset9{margin-left:560px;} +.row>.offset10{margin-left:620px;} +.row>.offset11{margin-left:680px;} +.row>.offset12{margin-left:740px;} +.span-one-third{width:300px;} +.span-two-thirds{width:620px;} +.row>.offset-one-third{margin-left:340px;} +.row>.offset-two-thirds{margin-left:660px;} +p{font-size:13px;font-weight:normal;line-height:18px;margin-bottom:9px;}p small{font-size:11px;color:#bfbfbf;} +h1,h2,h3,h4,h5,h6{font-weight:bold;color:#404040;}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small{color:#bfbfbf;} +h1{margin-bottom:18px;font-size:30px;line-height:36px;}h1 small{font-size:18px;} +h2{font-size:24px;line-height:36px;}h2 small{font-size:14px;} +h3,h4,h5,h6{line-height:36px;} +h3{font-size:18px;}h3 small{font-size:14px;} +h4{font-size:16px;}h4 small{font-size:12px;} +h5{font-size:14px;} +h6{font-size:13px;color:#bfbfbf;text-transform:uppercase;} +ul,ol{margin:0 0 18px 25px;} +ul ul,ul ol,ol ol,ol ul{margin-bottom:0;} +ul{list-style:disc;} +ol{list-style:decimal;} +li{line-height:18px;color:#808080;} +ul.unstyled{list-style:none;margin-left:0;} +dl{margin-bottom:18px;}dl dt,dl dd{line-height:18px;} +dl dt{font-weight:bold;} +dl dd{margin-left:9px;} +hr{margin:20px 0 19px;border:0;border-bottom:1px solid #eee;} +strong{font-style:inherit;font-weight:bold;} +em{font-style:italic;font-weight:inherit;line-height:inherit;} +.muted{color:#bfbfbf;} +blockquote{margin-bottom:18px;border-left:5px solid #eee;padding-left:15px;}blockquote p{font-size:14px;font-weight:300;line-height:18px;margin-bottom:0;} +blockquote small{display:block;font-size:12px;font-weight:300;line-height:18px;color:#bfbfbf;}blockquote small:before{content:'\2014 \00A0';} +address{display:block;line-height:18px;margin-bottom:18px;} +code,pre{padding:0 3px 2px;font-family:Monaco, Andale Mono, Courier New, monospace;font-size:12px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;} +code{background-color:#fee9cc;color:rgba(0, 0, 0, 0.75);padding:1px 3px;} +pre{background-color:#f5f5f5;display:block;padding:8.5px;margin:0 0 18px;line-height:18px;font-size:12px;border:1px solid #ccc;border:1px solid rgba(0, 0, 0, 0.15);-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;white-space:pre;white-space:pre-wrap;word-wrap:break-word;} +form{margin-bottom:18px;} +fieldset{margin-bottom:18px;padding-top:18px;}fieldset legend{display:block;padding-left:150px;font-size:19.5px;line-height:1;color:#404040;*padding:0 0 5px 145px;*line-height:1.5;} +form .clearfix{margin-bottom:18px;zoom:1;}form .clearfix:before,form .clearfix:after{display:table;content:"";zoom:1;} +form .clearfix:after{clear:both;} +label,input,select,textarea{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:13px;font-weight:normal;line-height:normal;} +label{padding-top:6px;font-size:13px;line-height:18px;float:left;width:130px;text-align:right;color:#404040;} +form .input{margin-left:150px;} +input[type=checkbox],input[type=radio]{cursor:pointer;} +input,textarea,select,.uneditable-input{display:inline-block;width:210px;height:18px;padding:4px;font-size:13px;line-height:18px;color:#808080;border:1px solid #ccc;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;} +select{padding:initial;} +input[type=checkbox],input[type=radio]{width:auto;height:auto;padding:0;margin:3px 0;*margin-top:0;line-height:normal;border:none;} +input[type=file]{background-color:#ffffff;padding:initial;border:initial;line-height:initial;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;} +input[type=button],input[type=reset],input[type=submit]{width:auto;height:auto;} +select,input[type=file]{height:27px;*height:auto;line-height:27px;*margin-top:4px;} +select[multiple]{height:inherit;background-color:#ffffff;} +textarea{height:auto;} +.uneditable-input{background-color:#ffffff;display:block;border-color:#eee;-webkit-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.025);-moz-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.025);box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.025);cursor:not-allowed;} +:-moz-placeholder{color:#bfbfbf;} +::-webkit-input-placeholder{color:#bfbfbf;} +input,textarea{-webkit-transition:border linear 0.2s,box-shadow linear 0.2s;-moz-transition:border linear 0.2s,box-shadow linear 0.2s;-ms-transition:border linear 0.2s,box-shadow linear 0.2s;-o-transition:border linear 0.2s,box-shadow linear 0.2s;transition:border linear 0.2s,box-shadow linear 0.2s;-webkit-box-shadow:inset 0 1px 3px rgba(0, 0, 0, 0.1);-moz-box-shadow:inset 0 1px 3px rgba(0, 0, 0, 0.1);box-shadow:inset 0 1px 3px rgba(0, 0, 0, 0.1);} +input:focus,textarea:focus{outline:0;border-color:rgba(82, 168, 236, 0.8);-webkit-box-shadow:inset 0 1px 3px rgba(0, 0, 0, 0.1),0 0 8px rgba(82, 168, 236, 0.6);-moz-box-shadow:inset 0 1px 3px rgba(0, 0, 0, 0.1),0 0 8px rgba(82, 168, 236, 0.6);box-shadow:inset 0 1px 3px rgba(0, 0, 0, 0.1),0 0 8px rgba(82, 168, 236, 0.6);} +input[type=file]:focus,input[type=checkbox]:focus,select:focus{-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;outline:1px dotted #666;} +form .clearfix.error>label,form .clearfix.error .help-block,form .clearfix.error .help-inline{color:#b94a48;} +form .clearfix.error input,form .clearfix.error textarea{color:#b94a48;border-color:#ee5f5b;}form .clearfix.error input:focus,form .clearfix.error textarea:focus{border-color:#e9322d;-webkit-box-shadow:0 0 6px #f8b9b7;-moz-box-shadow:0 0 6px #f8b9b7;box-shadow:0 0 6px #f8b9b7;} +form .clearfix.error .input-prepend .add-on,form .clearfix.error .input-append .add-on{color:#b94a48;background-color:#fce6e6;border-color:#b94a48;} +form .clearfix.warning>label,form .clearfix.warning .help-block,form .clearfix.warning .help-inline{color:#c09853;} +form .clearfix.warning input,form .clearfix.warning textarea{color:#c09853;border-color:#ccae64;}form .clearfix.warning input:focus,form .clearfix.warning textarea:focus{border-color:#be9a3f;-webkit-box-shadow:0 0 6px #e5d6b1;-moz-box-shadow:0 0 6px #e5d6b1;box-shadow:0 0 6px #e5d6b1;} +form .clearfix.warning .input-prepend .add-on,form .clearfix.warning .input-append .add-on{color:#c09853;background-color:#d2b877;border-color:#c09853;} +form .clearfix.success>label,form .clearfix.success .help-block,form .clearfix.success .help-inline{color:#468847;} +form .clearfix.success input,form .clearfix.success textarea{color:#468847;border-color:#57a957;}form .clearfix.success input:focus,form .clearfix.success textarea:focus{border-color:#458845;-webkit-box-shadow:0 0 6px #9acc9a;-moz-box-shadow:0 0 6px #9acc9a;box-shadow:0 0 6px #9acc9a;} +form .clearfix.success .input-prepend .add-on,form .clearfix.success .input-append .add-on{color:#468847;background-color:#bcddbc;border-color:#468847;} +.input-mini,input.mini,textarea.mini,select.mini{width:60px;} +.input-small,input.small,textarea.small,select.small{width:90px;} +.input-medium,input.medium,textarea.medium,select.medium{width:150px;} +.input-large,input.large,textarea.large,select.large{width:210px;} +.input-xlarge,input.xlarge,textarea.xlarge,select.xlarge{width:270px;} +.input-xxlarge,input.xxlarge,textarea.xxlarge,select.xxlarge{width:530px;} +textarea.xxlarge{overflow-y:auto;} +input.span1,textarea.span1{display:inline-block;float:none;width:30px;margin-left:0;} +input.span2,textarea.span2{display:inline-block;float:none;width:90px;margin-left:0;} +input.span3,textarea.span3{display:inline-block;float:none;width:150px;margin-left:0;} +input.span4,textarea.span4{display:inline-block;float:none;width:210px;margin-left:0;} +input.span5,textarea.span5{display:inline-block;float:none;width:270px;margin-left:0;} +input.span6,textarea.span6{display:inline-block;float:none;width:330px;margin-left:0;} +input.span7,textarea.span7{display:inline-block;float:none;width:390px;margin-left:0;} +input.span8,textarea.span8{display:inline-block;float:none;width:450px;margin-left:0;} +input.span9,textarea.span9{display:inline-block;float:none;width:510px;margin-left:0;} +input.span10,textarea.span10{display:inline-block;float:none;width:570px;margin-left:0;} +input.span11,textarea.span11{display:inline-block;float:none;width:630px;margin-left:0;} +input.span12,textarea.span12{display:inline-block;float:none;width:690px;margin-left:0;} +input.span13,textarea.span13{display:inline-block;float:none;width:750px;margin-left:0;} +input.span14,textarea.span14{display:inline-block;float:none;width:810px;margin-left:0;} +input.span15,textarea.span15{display:inline-block;float:none;width:870px;margin-left:0;} +input.span16,textarea.span16{display:inline-block;float:none;width:930px;margin-left:0;} +input[disabled],select[disabled],textarea[disabled],input[readonly],select[readonly],textarea[readonly]{background-color:#f5f5f5;border-color:#ddd;cursor:not-allowed;} +.actions{background:#f5f5f5;margin-top:18px;margin-bottom:18px;padding:17px 20px 18px 150px;border-top:1px solid #ddd;-webkit-border-radius:0 0 3px 3px;-moz-border-radius:0 0 3px 3px;border-radius:0 0 3px 3px;}.actions .secondary-action{float:right;}.actions .secondary-action a{line-height:30px;}.actions .secondary-action a:hover{text-decoration:underline;} +.help-inline,.help-block{font-size:13px;line-height:18px;color:#bfbfbf;} +.help-inline{padding-left:5px;*position:relative;*top:-5px;} +.help-block{display:block;max-width:600px;} +.inline-inputs{color:#808080;}.inline-inputs span{padding:0 2px 0 1px;} +.input-prepend input,.input-append input{-webkit-border-radius:0 3px 3px 0;-moz-border-radius:0 3px 3px 0;border-radius:0 3px 3px 0;} +.input-prepend .add-on,.input-append .add-on{position:relative;background:#f5f5f5;border:1px solid #ccc;z-index:2;float:left;display:block;width:auto;min-width:16px;height:18px;padding:4px 4px 4px 5px;margin-right:-1px;font-weight:normal;line-height:18px;color:#bfbfbf;text-align:center;text-shadow:0 1px 0 #ffffff;-webkit-border-radius:3px 0 0 3px;-moz-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px;} +.input-prepend .active,.input-append .active{background:#a9dba9;border-color:#46a546;} +.input-prepend .add-on{*margin-top:1px;} +.input-append input{float:left;-webkit-border-radius:3px 0 0 3px;-moz-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px;} +.input-append .add-on{-webkit-border-radius:0 3px 3px 0;-moz-border-radius:0 3px 3px 0;border-radius:0 3px 3px 0;margin-right:0;margin-left:-1px;} +.inputs-list{margin:0 0 5px;width:100%;}.inputs-list li{display:block;padding:0;width:100%;} +.inputs-list label{display:block;float:none;width:auto;padding:0;margin-left:20px;line-height:18px;text-align:left;white-space:normal;}.inputs-list label strong{color:#808080;} +.inputs-list label small{font-size:11px;font-weight:normal;} +.inputs-list .inputs-list{margin-left:25px;margin-bottom:10px;padding-top:0;} +.inputs-list:first-child{padding-top:6px;} +.inputs-list li+li{padding-top:2px;} +.inputs-list input[type=radio],.inputs-list input[type=checkbox]{margin-bottom:0;margin-left:-20px;float:left;} +.form-stacked{padding-left:20px;}.form-stacked fieldset{padding-top:9px;} +.form-stacked legend{padding-left:0;} +.form-stacked label{display:block;float:none;width:auto;font-weight:bold;text-align:left;line-height:20px;padding-top:0;} +.form-stacked .clearfix{margin-bottom:9px;}.form-stacked .clearfix div.input{margin-left:0;} +.form-stacked .inputs-list{margin-bottom:0;}.form-stacked .inputs-list li{padding-top:0;}.form-stacked .inputs-list li label{font-weight:normal;padding-top:0;} +.form-stacked div.clearfix.error{padding-top:10px;padding-bottom:10px;padding-left:10px;margin-top:0;margin-left:-10px;} +.form-stacked .actions{margin-left:-20px;padding-left:20px;} +table{width:100%;margin-bottom:18px;padding:0;font-size:13px;border-collapse:collapse;}table th,table td{padding:10px 10px 9px;line-height:18px;text-align:left;} +table th{padding-top:9px;font-weight:bold;vertical-align:middle;} +table td{vertical-align:top;border-top:1px solid #ddd;} +table tbody th{border-top:1px solid #ddd;vertical-align:top;} +.condensed-table th,.condensed-table td{padding:5px 5px 4px;} +.bordered-table{border:1px solid #ddd;border-collapse:separate;*border-collapse:collapse;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}.bordered-table th+th,.bordered-table td+td,.bordered-table th+td{border-left:1px solid #ddd;} +.bordered-table thead tr:first-child th:first-child,.bordered-table tbody tr:first-child td:first-child{-webkit-border-radius:4px 0 0 0;-moz-border-radius:4px 0 0 0;border-radius:4px 0 0 0;} +.bordered-table thead tr:first-child th:last-child,.bordered-table tbody tr:first-child td:last-child{-webkit-border-radius:0 4px 0 0;-moz-border-radius:0 4px 0 0;border-radius:0 4px 0 0;} +.bordered-table tbody tr:last-child td:first-child{-webkit-border-radius:0 0 0 4px;-moz-border-radius:0 0 0 4px;border-radius:0 0 0 4px;} +.bordered-table tbody tr:last-child td:last-child{-webkit-border-radius:0 0 4px 0;-moz-border-radius:0 0 4px 0;border-radius:0 0 4px 0;} +table .span1{width:20px;} +table .span2{width:60px;} +table .span3{width:100px;} +table .span4{width:140px;} +table .span5{width:180px;} +table .span6{width:220px;} +table .span7{width:260px;} +table .span8{width:300px;} +table .span9{width:340px;} +table .span10{width:380px;} +table .span11{width:420px;} +table .span12{width:460px;} +table .span13{width:500px;} +table .span14{width:540px;} +table .span15{width:580px;} +table .span16{width:620px;} +.zebra-striped tbody tr:nth-child(odd) td,.zebra-striped tbody tr:nth-child(odd) th{background-color:#f9f9f9;} +.zebra-striped tbody tr:hover td,.zebra-striped tbody tr:hover th{background-color:#f5f5f5;} +table .header{cursor:pointer;}table .header:after{content:"";float:right;margin-top:7px;border-width:0 4px 4px;border-style:solid;border-color:#000 transparent;visibility:hidden;} +table .headerSortUp,table .headerSortDown{background-color:rgba(141, 192, 219, 0.25);text-shadow:0 1px 1px rgba(255, 255, 255, 0.75);} +table .header:hover:after{visibility:visible;} +table .headerSortDown:after,table .headerSortDown:hover:after{visibility:visible;filter:alpha(opacity=60);-khtml-opacity:0.6;-moz-opacity:0.6;opacity:0.6;} +table .headerSortUp:after{border-bottom:none;border-left:4px solid transparent;border-right:4px solid transparent;border-top:4px solid #000;visibility:visible;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;filter:alpha(opacity=60);-khtml-opacity:0.6;-moz-opacity:0.6;opacity:0.6;} +table .blue{color:#049cdb;border-bottom-color:#049cdb;} +table .headerSortUp.blue,table .headerSortDown.blue{background-color:#ade6fe;} +table .green{color:#46a546;border-bottom-color:#46a546;} +table .headerSortUp.green,table .headerSortDown.green{background-color:#cdeacd;} +table .red{color:#9d261d;border-bottom-color:#9d261d;} +table .headerSortUp.red,table .headerSortDown.red{background-color:#f4c8c5;} +table .yellow{color:#ffc40d;border-bottom-color:#ffc40d;} +table .headerSortUp.yellow,table .headerSortDown.yellow{background-color:#fff6d9;} +table .orange{color:#f89406;border-bottom-color:#f89406;} +table .headerSortUp.orange,table .headerSortDown.orange{background-color:#fee9cc;} +table .purple{color:#7a43b6;border-bottom-color:#7a43b6;} +table .headerSortUp.purple,table .headerSortDown.purple{background-color:#e2d5f0;} +.topbar{height:40px;position:fixed;top:0;left:0;right:0;z-index:10000;overflow:visible;}.topbar a{color:#bfbfbf;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);} +.topbar h3 a:hover,.topbar .brand:hover,.topbar ul .active>a{background-color:#333;background-color:rgba(255, 255, 255, 0.05);color:#ffffff;text-decoration:none;} +.topbar h3{position:relative;} +.topbar h3 a,.topbar .brand{float:left;display:block;padding:8px 20px 12px;margin-left:-20px;color:#ffffff;font-size:20px;font-weight:200;line-height:1;} +.topbar p{margin:0;line-height:40px;}.topbar p a:hover{background-color:transparent;color:#ffffff;} +.topbar form{float:left;margin:5px 0 0 0;position:relative;filter:alpha(opacity=100);-khtml-opacity:1;-moz-opacity:1;opacity:1;} +.topbar form.pull-right{float:right;} +.topbar input{background-color:#444;background-color:rgba(255, 255, 255, 0.3);font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:normal;font-weight:13px;line-height:1;padding:4px 9px;color:#ffffff;color:rgba(255, 255, 255, 0.75);border:1px solid #111;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1),0 1px 0px rgba(255, 255, 255, 0.25);-moz-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1),0 1px 0px rgba(255, 255, 255, 0.25);box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1),0 1px 0px rgba(255, 255, 255, 0.25);-webkit-transition:none;-moz-transition:none;-ms-transition:none;-o-transition:none;transition:none;}.topbar input:-moz-placeholder{color:#e6e6e6;} +.topbar input::-webkit-input-placeholder{color:#e6e6e6;} +.topbar input:hover{background-color:#bfbfbf;background-color:rgba(255, 255, 255, 0.5);color:#ffffff;} +.topbar input:focus,.topbar input.focused{outline:0;background-color:#ffffff;color:#404040;text-shadow:0 1px 0 #ffffff;border:0;padding:5px 10px;-webkit-box-shadow:0 0 3px rgba(0, 0, 0, 0.15);-moz-box-shadow:0 0 3px rgba(0, 0, 0, 0.15);box-shadow:0 0 3px rgba(0, 0, 0, 0.15);} +.topbar-inner,.topbar .fill{background-color:#222;background-color:#222222;background-repeat:repeat-x;background-image:-khtml-gradient(linear, left top, left bottom, from(#333333), to(#222222));background-image:-moz-linear-gradient(top, #333333, #222222);background-image:-ms-linear-gradient(top, #333333, #222222);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #333333), color-stop(100%, #222222));background-image:-webkit-linear-gradient(top, #333333, #222222);background-image:-o-linear-gradient(top, #333333, #222222);background-image:linear-gradient(top, #333333, #222222);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#333333', endColorstr='#222222', GradientType=0);-webkit-box-shadow:0 1px 3px rgba(0, 0, 0, 0.25),inset 0 -1px 0 rgba(0, 0, 0, 0.1);-moz-box-shadow:0 1px 3px rgba(0, 0, 0, 0.25),inset 0 -1px 0 rgba(0, 0, 0, 0.1);box-shadow:0 1px 3px rgba(0, 0, 0, 0.25),inset 0 -1px 0 rgba(0, 0, 0, 0.1);} +.topbar div>ul,.nav{display:block;float:left;margin:0 10px 0 0;position:relative;left:0;}.topbar div>ul>li,.nav>li{display:block;float:left;} +.topbar div>ul a,.nav a{display:block;float:none;padding:10px 10px 11px;line-height:19px;text-decoration:none;}.topbar div>ul a:hover,.nav a:hover{color:#ffffff;text-decoration:none;} +.topbar div>ul .active>a,.nav .active>a{background-color:#222;background-color:rgba(0, 0, 0, 0.5);} +.topbar div>ul.secondary-nav,.nav.secondary-nav{float:right;margin-left:10px;margin-right:0;}.topbar div>ul.secondary-nav .menu-dropdown,.nav.secondary-nav .menu-dropdown,.topbar div>ul.secondary-nav .dropdown-menu,.nav.secondary-nav .dropdown-menu{right:0;border:0;} +.topbar div>ul a.menu:hover,.nav a.menu:hover,.topbar div>ul li.open .menu,.nav li.open .menu,.topbar div>ul .dropdown-toggle:hover,.nav .dropdown-toggle:hover,.topbar div>ul .dropdown.open .dropdown-toggle,.nav .dropdown.open .dropdown-toggle{background:#444;background:rgba(255, 255, 255, 0.05);} +.topbar div>ul .menu-dropdown,.nav .menu-dropdown,.topbar div>ul .dropdown-menu,.nav .dropdown-menu{background-color:#333;}.topbar div>ul .menu-dropdown a.menu,.nav .menu-dropdown a.menu,.topbar div>ul .dropdown-menu a.menu,.nav .dropdown-menu a.menu,.topbar div>ul .menu-dropdown .dropdown-toggle,.nav .menu-dropdown .dropdown-toggle,.topbar div>ul .dropdown-menu .dropdown-toggle,.nav .dropdown-menu .dropdown-toggle{color:#ffffff;}.topbar div>ul .menu-dropdown a.menu.open,.nav .menu-dropdown a.menu.open,.topbar div>ul .dropdown-menu a.menu.open,.nav .dropdown-menu a.menu.open,.topbar div>ul .menu-dropdown .dropdown-toggle.open,.nav .menu-dropdown .dropdown-toggle.open,.topbar div>ul .dropdown-menu .dropdown-toggle.open,.nav .dropdown-menu .dropdown-toggle.open{background:#444;background:rgba(255, 255, 255, 0.05);} +.topbar div>ul .menu-dropdown li a,.nav .menu-dropdown li a,.topbar div>ul .dropdown-menu li a,.nav .dropdown-menu li a{color:#999;text-shadow:0 1px 0 rgba(0, 0, 0, 0.5);}.topbar div>ul .menu-dropdown li a:hover,.nav .menu-dropdown li a:hover,.topbar div>ul .dropdown-menu li a:hover,.nav .dropdown-menu li a:hover{background-color:#191919;background-repeat:repeat-x;background-image:-khtml-gradient(linear, left top, left bottom, from(#292929), to(#191919));background-image:-moz-linear-gradient(top, #292929, #191919);background-image:-ms-linear-gradient(top, #292929, #191919);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #292929), color-stop(100%, #191919));background-image:-webkit-linear-gradient(top, #292929, #191919);background-image:-o-linear-gradient(top, #292929, #191919);background-image:linear-gradient(top, #292929, #191919);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#292929', endColorstr='#191919', GradientType=0);color:#ffffff;} +.topbar div>ul .menu-dropdown .active a,.nav .menu-dropdown .active a,.topbar div>ul .dropdown-menu .active a,.nav .dropdown-menu .active a{color:#ffffff;} +.topbar div>ul .menu-dropdown .divider,.nav .menu-dropdown .divider,.topbar div>ul .dropdown-menu .divider,.nav .dropdown-menu .divider{background-color:#222;border-color:#444;} +.topbar ul .menu-dropdown li a,.topbar ul .dropdown-menu li a{padding:4px 15px;} +li.menu,.dropdown{position:relative;} +a.menu:after,.dropdown-toggle:after{width:0;height:0;display:inline-block;content:"↓";text-indent:-99999px;vertical-align:top;margin-top:8px;margin-left:4px;border-left:4px solid transparent;border-right:4px solid transparent;border-top:4px solid #ffffff;filter:alpha(opacity=50);-khtml-opacity:0.5;-moz-opacity:0.5;opacity:0.5;} +.menu-dropdown,.dropdown-menu{background-color:#ffffff;float:left;display:none;position:absolute;top:40px;z-index:900;min-width:160px;max-width:220px;_width:160px;margin-left:0;margin-right:0;padding:6px 0;zoom:1;border-color:#999;border-color:rgba(0, 0, 0, 0.2);border-style:solid;border-width:0 1px 1px;-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px;-webkit-box-shadow:0 2px 4px rgba(0, 0, 0, 0.2);-moz-box-shadow:0 2px 4px rgba(0, 0, 0, 0.2);box-shadow:0 2px 4px rgba(0, 0, 0, 0.2);-webkit-background-clip:padding-box;-moz-background-clip:padding-box;background-clip:padding-box;}.menu-dropdown li,.dropdown-menu li{float:none;display:block;background-color:none;} +.menu-dropdown .divider,.dropdown-menu .divider{height:1px;margin:5px 0;overflow:hidden;background-color:#eee;border-bottom:1px solid #ffffff;} +.topbar .dropdown-menu a,.dropdown-menu a{display:block;padding:4px 15px;clear:both;font-weight:normal;line-height:18px;color:#808080;text-shadow:0 1px 0 #ffffff;}.topbar .dropdown-menu a:hover,.dropdown-menu a:hover,.topbar .dropdown-menu a.hover,.dropdown-menu a.hover{background-color:#dddddd;background-repeat:repeat-x;background-image:-khtml-gradient(linear, left top, left bottom, from(#eeeeee), to(#dddddd));background-image:-moz-linear-gradient(top, #eeeeee, #dddddd);background-image:-ms-linear-gradient(top, #eeeeee, #dddddd);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #eeeeee), color-stop(100%, #dddddd));background-image:-webkit-linear-gradient(top, #eeeeee, #dddddd);background-image:-o-linear-gradient(top, #eeeeee, #dddddd);background-image:linear-gradient(top, #eeeeee, #dddddd);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#eeeeee', endColorstr='#dddddd', GradientType=0);color:#404040;text-decoration:none;-webkit-box-shadow:inset 0 1px 0 rgba(0, 0, 0, 0.025),inset 0 -1px rgba(0, 0, 0, 0.025);-moz-box-shadow:inset 0 1px 0 rgba(0, 0, 0, 0.025),inset 0 -1px rgba(0, 0, 0, 0.025);box-shadow:inset 0 1px 0 rgba(0, 0, 0, 0.025),inset 0 -1px rgba(0, 0, 0, 0.025);} +.open .menu,.dropdown.open .menu,.open .dropdown-toggle,.dropdown.open .dropdown-toggle{color:#ffffff;background:#ccc;background:rgba(0, 0, 0, 0.3);} +.open .menu-dropdown,.dropdown.open .menu-dropdown,.open .dropdown-menu,.dropdown.open .dropdown-menu{display:block;} +.tabs,.pills{margin:0 0 18px;padding:0;list-style:none;zoom:1;}.tabs:before,.pills:before,.tabs:after,.pills:after{display:table;content:"";zoom:1;} +.tabs:after,.pills:after{clear:both;} +.tabs>li,.pills>li{float:left;}.tabs>li>a,.pills>li>a{display:block;} +.tabs{border-color:#ddd;border-style:solid;border-width:0 0 1px;}.tabs>li{position:relative;margin-bottom:-1px;}.tabs>li>a{padding:0 15px;margin-right:2px;line-height:34px;border:1px solid transparent;-webkit-border-radius:4px 4px 0 0;-moz-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0;}.tabs>li>a:hover{text-decoration:none;background-color:#eee;border-color:#eee #eee #ddd;} +.tabs .active>a,.tabs .active>a:hover{color:#808080;background-color:#ffffff;border:1px solid #ddd;border-bottom-color:transparent;cursor:default;} +.tabs .menu-dropdown,.tabs .dropdown-menu{top:35px;border-width:1px;-webkit-border-radius:0 6px 6px 6px;-moz-border-radius:0 6px 6px 6px;border-radius:0 6px 6px 6px;} +.tabs a.menu:after,.tabs .dropdown-toggle:after{border-top-color:#999;margin-top:15px;margin-left:5px;} +.tabs li.open.menu .menu,.tabs .open.dropdown .dropdown-toggle{border-color:#999;} +.tabs li.open a.menu:after,.tabs .dropdown.open .dropdown-toggle:after{border-top-color:#555;} +.pills a{margin:5px 3px 5px 0;padding:0 15px;line-height:30px;text-shadow:0 1px 1px #ffffff;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px;}.pills a:hover{color:#ffffff;text-decoration:none;text-shadow:0 1px 1px rgba(0, 0, 0, 0.25);background-color:#00438a;} +.pills .active a{color:#ffffff;text-shadow:0 1px 1px rgba(0, 0, 0, 0.25);background-color:#0069d6;} +.pills-vertical>li{float:none;} +.tab-content>.tab-pane,.pill-content>.pill-pane,.tab-content>div,.pill-content>div{display:none;} +.tab-content>.active,.pill-content>.active{display:block;} +.breadcrumb{padding:7px 14px;margin:0 0 18px;background-color:#f5f5f5;background-repeat:repeat-x;background-image:-khtml-gradient(linear, left top, left bottom, from(#ffffff), to(#f5f5f5));background-image:-moz-linear-gradient(top, #ffffff, #f5f5f5);background-image:-ms-linear-gradient(top, #ffffff, #f5f5f5);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #ffffff), color-stop(100%, #f5f5f5));background-image:-webkit-linear-gradient(top, #ffffff, #f5f5f5);background-image:-o-linear-gradient(top, #ffffff, #f5f5f5);background-image:linear-gradient(top, #ffffff, #f5f5f5);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#f5f5f5', GradientType=0);border:1px solid #ddd;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;-webkit-box-shadow:inset 0 1px 0 #ffffff;-moz-box-shadow:inset 0 1px 0 #ffffff;box-shadow:inset 0 1px 0 #ffffff;}.breadcrumb li{display:inline;text-shadow:0 1px 0 #ffffff;} +.breadcrumb .divider{padding:0 5px;color:#bfbfbf;} +.breadcrumb .active a{color:#404040;} +.hero-unit{background-color:#f5f5f5;margin-bottom:30px;padding:60px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;}.hero-unit h1{margin-bottom:0;font-size:60px;line-height:1;letter-spacing:-1px;} +.hero-unit p{font-size:18px;font-weight:200;line-height:27px;} +footer{margin-top:17px;padding-top:17px;border-top:1px solid #eee;} +.page-header{margin-bottom:17px;border-bottom:1px solid #ddd;-webkit-box-shadow:0 1px 0 rgba(255, 255, 255, 0.5);-moz-box-shadow:0 1px 0 rgba(255, 255, 255, 0.5);box-shadow:0 1px 0 rgba(255, 255, 255, 0.5);}.page-header h1{margin-bottom:8px;} +.btn.danger,.alert-message.danger,.btn.danger:hover,.alert-message.danger:hover,.btn.error,.alert-message.error,.btn.error:hover,.alert-message.error:hover,.btn.success,.alert-message.success,.btn.success:hover,.alert-message.success:hover,.btn.info,.alert-message.info,.btn.info:hover,.alert-message.info:hover{color:#ffffff;} +.btn .close,.alert-message .close{font-family:Arial,sans-serif;line-height:18px;} +.btn.danger,.alert-message.danger,.btn.error,.alert-message.error{background-color:#c43c35;background-repeat:repeat-x;background-image:-khtml-gradient(linear, left top, left bottom, from(#ee5f5b), to(#c43c35));background-image:-moz-linear-gradient(top, #ee5f5b, #c43c35);background-image:-ms-linear-gradient(top, #ee5f5b, #c43c35);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #ee5f5b), color-stop(100%, #c43c35));background-image:-webkit-linear-gradient(top, #ee5f5b, #c43c35);background-image:-o-linear-gradient(top, #ee5f5b, #c43c35);background-image:linear-gradient(top, #ee5f5b, #c43c35);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ee5f5b', endColorstr='#c43c35', GradientType=0);text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);border-color:#c43c35 #c43c35 #882a25;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);} +.btn.success,.alert-message.success{background-color:#57a957;background-repeat:repeat-x;background-image:-khtml-gradient(linear, left top, left bottom, from(#62c462), to(#57a957));background-image:-moz-linear-gradient(top, #62c462, #57a957);background-image:-ms-linear-gradient(top, #62c462, #57a957);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #62c462), color-stop(100%, #57a957));background-image:-webkit-linear-gradient(top, #62c462, #57a957);background-image:-o-linear-gradient(top, #62c462, #57a957);background-image:linear-gradient(top, #62c462, #57a957);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#62c462', endColorstr='#57a957', GradientType=0);text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);border-color:#57a957 #57a957 #3d773d;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);} +.btn.info,.alert-message.info{background-color:#339bb9;background-repeat:repeat-x;background-image:-khtml-gradient(linear, left top, left bottom, from(#5bc0de), to(#339bb9));background-image:-moz-linear-gradient(top, #5bc0de, #339bb9);background-image:-ms-linear-gradient(top, #5bc0de, #339bb9);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #5bc0de), color-stop(100%, #339bb9));background-image:-webkit-linear-gradient(top, #5bc0de, #339bb9);background-image:-o-linear-gradient(top, #5bc0de, #339bb9);background-image:linear-gradient(top, #5bc0de, #339bb9);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#5bc0de', endColorstr='#339bb9', GradientType=0);text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);border-color:#339bb9 #339bb9 #22697d;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);} +.btn{cursor:pointer;display:inline-block;background-color:#e6e6e6;background-repeat:no-repeat;background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), color-stop(25%, #ffffff), to(#e6e6e6));background-image:-webkit-linear-gradient(#ffffff, #ffffff 25%, #e6e6e6);background-image:-moz-linear-gradient(top, #ffffff, #ffffff 25%, #e6e6e6);background-image:-ms-linear-gradient(#ffffff, #ffffff 25%, #e6e6e6);background-image:-o-linear-gradient(#ffffff, #ffffff 25%, #e6e6e6);background-image:linear-gradient(#ffffff, #ffffff 25%, #e6e6e6);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#e6e6e6', GradientType=0);padding:5px 14px 6px;text-shadow:0 1px 1px rgba(255, 255, 255, 0.75);color:#333;font-size:13px;line-height:normal;border:1px solid #ccc;border-bottom-color:#bbb;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);-webkit-transition:0.1s linear all;-moz-transition:0.1s linear all;-ms-transition:0.1s linear all;-o-transition:0.1s linear all;transition:0.1s linear all;}.btn:hover{background-position:0 -15px;color:#333;text-decoration:none;} +.btn:focus{outline:1px dotted #666;} +.btn.primary{color:#ffffff;background-color:#0064cd;background-repeat:repeat-x;background-image:-khtml-gradient(linear, left top, left bottom, from(#049cdb), to(#0064cd));background-image:-moz-linear-gradient(top, #049cdb, #0064cd);background-image:-ms-linear-gradient(top, #049cdb, #0064cd);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #049cdb), color-stop(100%, #0064cd));background-image:-webkit-linear-gradient(top, #049cdb, #0064cd);background-image:-o-linear-gradient(top, #049cdb, #0064cd);background-image:linear-gradient(top, #049cdb, #0064cd);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#049cdb', endColorstr='#0064cd', GradientType=0);text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);border-color:#0064cd #0064cd #003f81;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);} +.btn.active,.btn:active{-webkit-box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.25),0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.25),0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.25),0 1px 2px rgba(0, 0, 0, 0.05);} +.btn.disabled{cursor:default;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=65);-khtml-opacity:0.65;-moz-opacity:0.65;opacity:0.65;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;} +.btn[disabled]{cursor:default;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=65);-khtml-opacity:0.65;-moz-opacity:0.65;opacity:0.65;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;} +.btn.large{font-size:15px;line-height:normal;padding:9px 14px 9px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;} +.btn.small{padding:7px 9px 7px;font-size:11px;} +:root .alert-message,:root .btn{border-radius:0 \0;} +button.btn::-moz-focus-inner,input[type=submit].btn::-moz-focus-inner{padding:0;border:0;} +.close{float:right;color:#000000;font-size:20px;font-weight:bold;line-height:13.5px;text-shadow:0 1px 0 #ffffff;filter:alpha(opacity=25);-khtml-opacity:0.25;-moz-opacity:0.25;opacity:0.25;}.close:hover{color:#000000;text-decoration:none;filter:alpha(opacity=40);-khtml-opacity:0.4;-moz-opacity:0.4;opacity:0.4;} +.alert-message{position:relative;padding:7px 15px;margin-bottom:18px;color:#404040;background-color:#eedc94;background-repeat:repeat-x;background-image:-khtml-gradient(linear, left top, left bottom, from(#fceec1), to(#eedc94));background-image:-moz-linear-gradient(top, #fceec1, #eedc94);background-image:-ms-linear-gradient(top, #fceec1, #eedc94);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #fceec1), color-stop(100%, #eedc94));background-image:-webkit-linear-gradient(top, #fceec1, #eedc94);background-image:-o-linear-gradient(top, #fceec1, #eedc94);background-image:linear-gradient(top, #fceec1, #eedc94);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fceec1', endColorstr='#eedc94', GradientType=0);text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);border-color:#eedc94 #eedc94 #e4c652;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);text-shadow:0 1px 0 rgba(255, 255, 255, 0.5);border-width:1px;border-style:solid;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.25);-moz-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.25);box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.25);}.alert-message .close{margin-top:1px;*margin-top:0;} +.alert-message a{font-weight:bold;color:#404040;} +.alert-message.danger p a,.alert-message.error p a,.alert-message.success p a,.alert-message.info p a{color:#ffffff;} +.alert-message h5{line-height:18px;} +.alert-message p{margin-bottom:0;} +.alert-message div{margin-top:5px;margin-bottom:2px;line-height:28px;} +.alert-message .btn{-webkit-box-shadow:0 1px 0 rgba(255, 255, 255, 0.25);-moz-box-shadow:0 1px 0 rgba(255, 255, 255, 0.25);box-shadow:0 1px 0 rgba(255, 255, 255, 0.25);} +.alert-message.block-message{background-image:none;background-color:#fdf5d9;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);padding:14px;border-color:#fceec1;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;}.alert-message.block-message ul,.alert-message.block-message p{margin-right:30px;} +.alert-message.block-message ul{margin-bottom:0;} +.alert-message.block-message li{color:#404040;} +.alert-message.block-message .alert-actions{margin-top:5px;} +.alert-message.block-message.error,.alert-message.block-message.success,.alert-message.block-message.info{color:#404040;text-shadow:0 1px 0 rgba(255, 255, 255, 0.5);} +.alert-message.block-message.error{background-color:#fddfde;border-color:#fbc7c6;} +.alert-message.block-message.success{background-color:#d1eed1;border-color:#bfe7bf;} +.alert-message.block-message.info{background-color:#ddf4fb;border-color:#c6edf9;} +.alert-message.block-message.danger p a,.alert-message.block-message.error p a,.alert-message.block-message.success p a,.alert-message.block-message.info p a{color:#404040;} +.pagination{height:36px;margin:18px 0;}.pagination ul{float:left;margin:0;border:1px solid #ddd;border:1px solid rgba(0, 0, 0, 0.15);-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;-webkit-box-shadow:0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:0 1px 2px rgba(0, 0, 0, 0.05);} +.pagination li{display:inline;} +.pagination a{float:left;padding:0 14px;line-height:34px;border-right:1px solid;border-right-color:#ddd;border-right-color:rgba(0, 0, 0, 0.15);*border-right-color:#ddd;text-decoration:none;} +.pagination a:hover,.pagination .active a{background-color:#c7eefe;} +.pagination .disabled a,.pagination .disabled a:hover{background-color:transparent;color:#bfbfbf;} +.pagination .next a{border:0;} +.well{background-color:#f5f5f5;margin-bottom:20px;padding:19px;min-height:20px;border:1px solid #eee;border:1px solid rgba(0, 0, 0, 0.05);-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.05);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.05);}.well blockquote{border-color:#ddd;border-color:rgba(0, 0, 0, 0.15);} +.modal-backdrop{background-color:#000000;position:fixed;top:0;left:0;right:0;bottom:0;z-index:10000;}.modal-backdrop.fade{opacity:0;} +.modal-backdrop,.modal-backdrop.fade.in{filter:alpha(opacity=80);-khtml-opacity:0.8;-moz-opacity:0.8;opacity:0.8;} +.modal{position:fixed;top:50%;left:50%;z-index:11000;width:560px;margin:-250px 0 0 -280px;background-color:#ffffff;border:1px solid #999;border:1px solid rgba(0, 0, 0, 0.3);*border:1px solid #999;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);-moz-box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);-webkit-background-clip:padding-box;-moz-background-clip:padding-box;background-clip:padding-box;}.modal .close{margin-top:7px;} +.modal.fade{-webkit-transition:opacity .3s linear, top .3s ease-out;-moz-transition:opacity .3s linear, top .3s ease-out;-ms-transition:opacity .3s linear, top .3s ease-out;-o-transition:opacity .3s linear, top .3s ease-out;transition:opacity .3s linear, top .3s ease-out;top:-25%;} +.modal.fade.in{top:50%;} +.modal-header{border-bottom:1px solid #eee;padding:5px 15px;} +.modal-body{padding:15px;} +.modal-body form{margin-bottom:0;} +.modal-footer{background-color:#f5f5f5;padding:14px 15px 15px;border-top:1px solid #ddd;-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px;-webkit-box-shadow:inset 0 1px 0 #ffffff;-moz-box-shadow:inset 0 1px 0 #ffffff;box-shadow:inset 0 1px 0 #ffffff;zoom:1;margin-bottom:0;}.modal-footer:before,.modal-footer:after{display:table;content:"";zoom:1;} +.modal-footer:after{clear:both;} +.modal-footer .btn{float:right;margin-left:5px;} +.modal .popover,.modal .twipsy{z-index:12000;} +.twipsy{display:block;position:absolute;visibility:visible;padding:5px;font-size:11px;z-index:1000;filter:alpha(opacity=80);-khtml-opacity:0.8;-moz-opacity:0.8;opacity:0.8;}.twipsy.fade.in{filter:alpha(opacity=80);-khtml-opacity:0.8;-moz-opacity:0.8;opacity:0.8;} +.twipsy.above .twipsy-arrow{bottom:0;left:50%;margin-left:-5px;border-left:5px solid transparent;border-right:5px solid transparent;border-top:5px solid #000000;} +.twipsy.left .twipsy-arrow{top:50%;right:0;margin-top:-5px;border-top:5px solid transparent;border-bottom:5px solid transparent;border-left:5px solid #000000;} +.twipsy.below .twipsy-arrow{top:0;left:50%;margin-left:-5px;border-left:5px solid transparent;border-right:5px solid transparent;border-bottom:5px solid #000000;} +.twipsy.right .twipsy-arrow{top:50%;left:0;margin-top:-5px;border-top:5px solid transparent;border-bottom:5px solid transparent;border-right:5px solid #000000;} +.twipsy-inner{padding:3px 8px;background-color:#000000;color:white;text-align:center;max-width:200px;text-decoration:none;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;} +.twipsy-arrow{position:absolute;width:0;height:0;} +.popover{position:absolute;top:0;left:0;z-index:1000;padding:5px;display:none;}.popover.above .arrow{bottom:0;left:50%;margin-left:-5px;border-left:5px solid transparent;border-right:5px solid transparent;border-top:5px solid #000000;} +.popover.right .arrow{top:50%;left:0;margin-top:-5px;border-top:5px solid transparent;border-bottom:5px solid transparent;border-right:5px solid #000000;} +.popover.below .arrow{top:0;left:50%;margin-left:-5px;border-left:5px solid transparent;border-right:5px solid transparent;border-bottom:5px solid #000000;} +.popover.left .arrow{top:50%;right:0;margin-top:-5px;border-top:5px solid transparent;border-bottom:5px solid transparent;border-left:5px solid #000000;} +.popover .arrow{position:absolute;width:0;height:0;} +.popover .inner{background:#000000;background:rgba(0, 0, 0, 0.8);padding:3px;overflow:hidden;width:280px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);-moz-box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);} +.popover .title{background-color:#f5f5f5;padding:9px 15px;line-height:1;-webkit-border-radius:3px 3px 0 0;-moz-border-radius:3px 3px 0 0;border-radius:3px 3px 0 0;border-bottom:1px solid #eee;} +.popover .content{background-color:#ffffff;padding:14px;-webkit-border-radius:0 0 3px 3px;-moz-border-radius:0 0 3px 3px;border-radius:0 0 3px 3px;-webkit-background-clip:padding-box;-moz-background-clip:padding-box;background-clip:padding-box;}.popover .content p,.popover .content ul,.popover .content ol{margin-bottom:0;} +.fade{-webkit-transition:opacity 0.15s linear;-moz-transition:opacity 0.15s linear;-ms-transition:opacity 0.15s linear;-o-transition:opacity 0.15s linear;transition:opacity 0.15s linear;opacity:0;}.fade.in{opacity:1;} +.label{padding:1px 3px 2px;font-size:9.75px;font-weight:bold;color:#ffffff;text-transform:uppercase;white-space:nowrap;background-color:#bfbfbf;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;}.label.important{background-color:#c43c35;} +.label.warning{background-color:#f89406;} +.label.success{background-color:#46a546;} +.label.notice{background-color:#62cffc;} +.media-grid{margin-left:-20px;margin-bottom:0;zoom:1;}.media-grid:before,.media-grid:after{display:table;content:"";zoom:1;} +.media-grid:after{clear:both;} +.media-grid li{display:inline;} +.media-grid a{float:left;padding:4px;margin:0 0 18px 20px;border:1px solid #ddd;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0, 0, 0, 0.075);-moz-box-shadow:0 1px 1px rgba(0, 0, 0, 0.075);box-shadow:0 1px 1px rgba(0, 0, 0, 0.075);}.media-grid a img{display:block;} +.media-grid a:hover{border-color:#0069d6;-webkit-box-shadow:0 1px 4px rgba(0, 105, 214, 0.25);-moz-box-shadow:0 1px 4px rgba(0, 105, 214, 0.25);box-shadow:0 1px 4px rgba(0, 105, 214, 0.25);}
new file mode 100644 --- /dev/null +++ b/testing/xpcshell/node-spdy/examples/twitlog/public/stylesheets/style.css @@ -0,0 +1,92 @@ +html, body { + padding: 0; + margin: 0; + height: 100%; + + background: white url(/images/nodejs.png) 50% 50% no-repeat; +} + +header { + background: #333; + padding: 0 16px; +} + +header h1 { + color: white; + line-height: 40px; +} + +footer { + width: 100%; + height: 40px; + margin-top: 0; + padding: 16px 16px 0 16px; + line-height: 24px; + position: fixed; + bottom: 0; + background: white; +} + +section#content { + padding: 64px 32px; +} + +section#notice { + font-size: 32px; + line-height: 48px; +} + +section#tweets { +} + +section#tweets article.tweet { + padding: 2px 8px; +} + +section#tweets article.tweet .clear { + clear: both; +} + +section#tweets article.tweet img.avatar { + display: inline; + float: left; + width: 24px; + height: 24px; + padding: 0; + margin: 0 4px 0 0; +} + +section#tweets article.tweet div.text-container { + overflow: hidden; + height: 24px; + position: relative; +} + +section#tweets article.tweet div.text-container div.text { + display: inline-block; + float: left; + line-height: 24px; + width: 10000px; +} + +section#tweets article.tweet div.text:before { + position: absolute; + top: 0; + right: 0px; + + background: -webkit-linear-gradient(right, rgba(255, 255, 255, 1) 0%, rgba(255, 255, 255, 0) 100%); + background: -moz-linear-gradient(right, rgba(255, 255, 255, 1) 0%, rgba(255, 255, 255, 0) 100%); + background: linear-gradient(right, rgba(255, 255, 255, 1) 0%, rgba(255, 255, 255, 0) 100%); + content: ''; + width: 50px; + height: 24px; +} + +div#tweet-button { + line-height: 32px; +} + +div#tweet-button iframe { + position: relative; + top: 6px; +}
new file mode 100644 --- /dev/null +++ b/testing/xpcshell/node-spdy/examples/twitlog/realtime/index.js @@ -0,0 +1,83 @@ +var twitter = new (require('ntwitter'))({ + consumer_key: '', + consumer_secret: '', + access_token_key: '', + access_token_secret: '' +}); + +exports.init = function(io) { + var tweets = []; + twitter.search( + '#spdytwitlog', + { + result_type: 'recent', + include_entities: 1 + }, + function(err, result) { + if (err) return console.error(err); + result.results.sort(function(a, b) { + return (+new Date(a.created_at)) - + (+new Date(b.created_at)); + }).forEach(receive); + } + ); + + function watchStream(method, query) { + twitter.stream(method, query, function(stream) { + stream.on('data', receive); + + stream.on('end', retry); + stream.on('destroy', retry); + + var once = false; + function retry() { + if (once) return; + once = true; + + setTimeout(function() { + watchStream(method, query); + }, 5000); + } + }); + } + + watchStream('statuses/filter', { + track: '#spdytwitlog', + include_entities: 1 + }); + + function receive(tweet) { + if (tweet.entities && tweet.entities.urls) { + tweet.entities.urls.sort(function(a, b) { + return b.indices[0] - a.indices[0]; + }).forEach(function(url) { + tweet.text = tweet.text.slice(0, url.indices[0]) + + url.display_url.link(url.expanded_url || url.url) + + tweet.text.slice(url.indices[1]); + }); + } + tweet = { + text: tweet.text, + user: tweet.user ? { + name: tweet.user.screen_name, + image: tweet.user.profile_image_url + } : { + name: tweet.from_user, + image: tweet.profile_image_url + } + }; + io.sockets.emit('tweet', tweet); + + tweets.push(tweet); + // remember only last 18 tweets + tweets = tweets.slice(tweets.length - 18, tweets.length); + }; + + io.sockets.on('connection', function(socket) { + socket.on('reqTweets', function() { + tweets.forEach(function(tweet) { + socket.emit('tweet', tweet); + }); + }); + }); +};
new file mode 100644 --- /dev/null +++ b/testing/xpcshell/node-spdy/examples/twitlog/routes/index.js @@ -0,0 +1,36 @@ +var fs = require('fs'); + +/* + * GET home page. + */ + +var image = fs.readFileSync(__dirname + '/../public/images/nodejs.png'); + +exports.index = function(req, res){ + if (res.isSpdy) { + var ua = req.headers['user-agent']; + + // Firefox doesn't support push streams now + // should be fixed once: https://bugzilla.mozilla.org/show_bug.cgi?id=718210 + // will reach all it's builds + if (!ua || ua.match(/Firefox/i) === null) { + // Push stream image + res.push( + '/images/nodejs.png', + { 'content-type': 'image/png' }, + function(err, stream) { + if (err) return; + stream.on('error', function() {}); + stream.end(image); + } + ); + } + } + res.render('index', { + title: 'SPDY - Twitlog', + notice: req.isSpdy ? + 'Yay! This page was requested via SPDY protocol' + : + 'Oh, no... your browser requested this page via HTTPS' + }); +};
new file mode 100644 --- /dev/null +++ b/testing/xpcshell/node-spdy/examples/twitlog/views/index.jade @@ -0,0 +1,6 @@ +div(id='tweet-button') + = 'Post a ' + a(href='https://twitter.com/intent/tweet?button_hashtag=spdytwitlog&text=This%20text%20will%20appear%20on',class='twitter-hashtag-button',data-size='small',data-related='indutny',data-url='https://spdy-twitlog.indutny.com/') tweet + = ' and it\'ll be displayed here' + +section(id='tweets')
new file mode 100644 --- /dev/null +++ b/testing/xpcshell/node-spdy/examples/twitlog/views/layout.jade @@ -0,0 +1,29 @@ +!!! 5 +html + head + title= title + link(rel='icon', type='image/png', href='favicon.png') + link(rel='stylesheet', href='/stylesheets/bootstrap.min.css') + link(rel='stylesheet', href='/stylesheets/style.css') + body + header.topbar + h1= title + section#content + - if (notice) + section#notice= notice + != body + footer + = 'Made using ' + a(href='http://nodejs.org/') node.js + = ', ' + a(href='https://github.com/indutny/node-spdy') node-spdy + = ', ' + a(href='http://socket.io/') socket.io + = ' and ' + a(href='http://twitter.github.com/bootstrap/') twitter bootstrap + + script(src='//platform.twitter.com/widgets.js', async=true, defer=true) + script(src='/javascripts/socket.io.min.js') + script(src='/javascripts/main.js') + + script var _gaq =_gaq||[];_gaq.push(['_setAccount', 'UA-28159099-1']);_gaq.push(['_trackPageview']);(function() {var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);})();
new file mode 100644 --- /dev/null +++ b/testing/xpcshell/node-spdy/keys/spdy-cert.pem @@ -0,0 +1,14 @@ +-----BEGIN CERTIFICATE----- +MIICHzCCAYgCCQCPPSUAa8QZojANBgkqhkiG9w0BAQUFADBUMQswCQYDVQQGEwJS +VTETMBEGA1UECBMKU29tZS1TdGF0ZTENMAsGA1UEBxMET21zazEhMB8GA1UEChMY +SW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMB4XDTExMDQwOTEwMDY0NVoXDTExMDUw +OTEwMDY0NVowVDELMAkGA1UEBhMCUlUxEzARBgNVBAgTClNvbWUtU3RhdGUxDTAL +BgNVBAcTBE9tc2sxITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDCB +nzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA1bn25sPkv46wl70BffxradlkRd/x +p5Xf8HDhPSfzNNctERYslXT2fX7Dmfd5w1XTVqqGqJ4izp5VewoVOHA8uavo3ovp +gNWasil5zADWaM1T0nnV0RsFbZWzOTmm1U3D48K8rW3F5kOZ6f4yRq9QT1gF/gN7 +5Pt494YyYyJu/a8CAwEAATANBgkqhkiG9w0BAQUFAAOBgQBuRZisIViI2G/R+w79 +vk21TzC/cJ+O7tKsseDqotXYTH8SuimEH5IWcXNgnWhNzczwN8s2362NixyvCipV +yd4wzMpPbjIhnWGM0hluWZiK2RxfcqimIBjDParTv6CMUIuwGQ257THKY8hXGg7j +Uws6Lif3P9UbsuRiYPxMgg98wg== +-----END CERTIFICATE-----
new file mode 100644 --- /dev/null +++ b/testing/xpcshell/node-spdy/keys/spdy-csr.pem @@ -0,0 +1,11 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIBkzCB/QIBADBUMQswCQYDVQQGEwJSVTETMBEGA1UECBMKU29tZS1TdGF0ZTEN +MAsGA1UEBxMET21zazEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRk +MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDVufbmw+S/jrCXvQF9/Gtp2WRF +3/Gnld/wcOE9J/M01y0RFiyVdPZ9fsOZ93nDVdNWqoaoniLOnlV7ChU4cDy5q+je +i+mA1ZqyKXnMANZozVPSedXRGwVtlbM5OabVTcPjwrytbcXmQ5np/jJGr1BPWAX+ +A3vk+3j3hjJjIm79rwIDAQABoAAwDQYJKoZIhvcNAQEFBQADgYEAiNWhz6EppIVa +FfUaB3sLeqfamb9tg9kBHtvqj/FJni0snqms0kPWaTySEPHZF0irIb7VVdq/sVCb +3gseMVSyoDvPJ4lHC3PXqGQ7kM1mIPhDnR/4HDA3BhlGhTXSDIHgZnvI+HMBdsyC +hC3dz5odyKqe4nmoofomALkBL9t4H8s= +-----END CERTIFICATE REQUEST-----
new file mode 100644 --- /dev/null +++ b/testing/xpcshell/node-spdy/keys/spdy-key.pem @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXAIBAAKBgQDVufbmw+S/jrCXvQF9/Gtp2WRF3/Gnld/wcOE9J/M01y0RFiyV +dPZ9fsOZ93nDVdNWqoaoniLOnlV7ChU4cDy5q+jei+mA1ZqyKXnMANZozVPSedXR +GwVtlbM5OabVTcPjwrytbcXmQ5np/jJGr1BPWAX+A3vk+3j3hjJjIm79rwIDAQAB +AoGAAv2QI9h32epQND9TxwSCKD//dC7W/cZOFNovfKCTeZjNK6EIzKqPTGA6smvR +C1enFl5adf+IcyWqAoe4lkqTvurIj+2EhtXdQ8DBlVuXKr3xvEFdYxXPautdTCF6 +KbXEyS/s1TZCRFjYftvCrXxc3pK45AQX/wg7z1K+YB5pyIECQQD0OJvLoxLYoXAc +FZraIOZiDsEbGuSHqoCReFXH75EC3+XGYkH2bQ/nSIZ0h1buuwQ/ylKXOlTPT3Qt +Xm1OQEBvAkEA4AjWsIO/rRpOm/Q2aCrynWMpoUXTZSbL2yGf8pxp/+8r2br5ier0 +M1LeBb/OPY1+k39NWLXxQoo64xoSFYk2wQJAd2wDCwX4HkR7HNCXw1hZL9QFK6rv +20NN0VSlpboJD/3KT0MW/FiCcVduoCbaJK0Au+zEjDyy4hj5N4I4Mw6KMwJAXVAx +I+psTsxzS4/njXG+BgIEl/C+gRYsuMQDnAi8OebDq/et8l0Tg8ETSu++FnM18neG +ntmBeMacinUUbTXuwQJBAJp/onZdsMzeVulsGrqR1uS+Lpjc5Q1gt5ttt2cxj91D +rio48C/ZvWuKNE8EYj2ALtghcVKRvgaWfOxt2GPguGg= +-----END RSA PRIVATE KEY-----
new file mode 100644 --- /dev/null +++ b/testing/xpcshell/node-spdy/lib/spdy.js @@ -0,0 +1,31 @@ +var spdy = exports; + +// Exports utils +spdy.utils = require('./spdy/utils'); + +// Export parser&framer +spdy.protocol = {}; + +try { + spdy.protocol.generic = require('./spdy/protocol/generic.node'); +} catch (e) { + spdy.protocol.generic = require('./spdy/protocol/generic.js'); +} + +// Only SPDY v2 is supported now +spdy.protocol[2] = require('./spdy/protocol/v2'); + +spdy.parser = require('./spdy/parser'); + +// Export ServerResponse +spdy.response = require('./spdy/response'); + +// Export Scheduler +spdy.scheduler = require('./spdy/scheduler'); + +// Export ZlibPool +spdy.zlibpool = require('./spdy/zlib-pool'); + +// Export server +spdy.server = require('./spdy/server'); +spdy.createServer = spdy.server.create;
new file mode 100644 --- /dev/null +++ b/testing/xpcshell/node-spdy/lib/spdy/parser.js @@ -0,0 +1,185 @@ +var parser = exports; + +var spdy = require('../spdy'), + util = require('util'), + stream = require('stream'), + Buffer = require('buffer').Buffer; + +// +// ### function Parser (connection, deflate, inflate) +// #### @connection {spdy.Connection} connection +// #### @deflate {zlib.Deflate} Deflate stream +// #### @inflate {zlib.Inflate} Inflate stream +// SPDY protocol frames parser's @constructor +// +function Parser(connection, deflate, inflate) { + stream.Stream.call(this); + + this.drained = true; + this.paused = false; + this.buffer = []; + this.buffered = 0; + this.waiting = 8; + + this.state = { type: 'frame-head' }; + this.deflate = deflate; + this.inflate = inflate; + this.framer = null; + + this.connection = connection; + + this.readable = this.writable = true; +} +util.inherits(Parser, stream.Stream); + +// +// ### function create (connection, deflate, inflate) +// #### @connection {spdy.Connection} connection +// #### @deflate {zlib.Deflate} Deflate stream +// #### @inflate {zlib.Inflate} Inflate stream +// @constructor wrapper +// +parser.create = function create(connection, deflate, inflate) { + return new Parser(connection, deflate, inflate); +}; + +// +// ### function write (data) +// #### @data {Buffer} chunk of data +// Writes or buffers data to parser +// +Parser.prototype.write = function write(data) { + if (data !== undefined) { + // Buffer data + this.buffer.push(data); + this.buffered += data.length; + } + + // Notify caller about state (for piping) + if (this.paused) return false; + + // We shall not do anything until we get all expected data + if (this.buffered < this.waiting) return; + + // Mark parser as not drained + if (data !== undefined) this.drained = false; + + var self = this, + buffer = new Buffer(this.waiting), + sliced = 0, + offset = 0; + + while (this.waiting > offset && sliced < this.buffer.length) { + var chunk = this.buffer[sliced++], + overmatched = false; + + // Copy chunk into `buffer` + if (chunk.length > this.waiting - offset) { + chunk.copy(buffer, offset, 0, this.waiting - offset); + + this.buffer[--sliced] = chunk.slice(this.waiting - offset); + this.buffered += this.buffer[sliced].length; + + overmatched = true; + } else { + chunk.copy(buffer, offset); + } + + // Move offset and decrease amount of buffered data + offset += chunk.length; + this.buffered -= chunk.length; + + if (overmatched) break; + } + + // Remove used buffers + this.buffer = this.buffer.slice(sliced); + + // Executed parser for buffered data + this.paused = true; + this.execute(this.state, buffer, function (err, waiting) { + // And unpause once execution finished + self.paused = false; + + // Propagate errors + if (err) return self.emit('error', err); + + // Set new `waiting` + self.waiting = waiting; + + if (self.waiting <= self.buffered) { + self.write(); + } else { + process.nextTick(function() { + if (self.drained) return; + + // Mark parser as drained + self.drained = true; + self.emit('drain'); + }); + } + }); +}; + +// +// ### function end () +// Stream's end() implementation +// +Parser.prototype.end = function end() { + this.emit('end'); +}; + +// +// ### function execute (state, data, callback) +// #### @state {Object} Parser's state +// #### @data {Buffer} Incoming data +// #### @callback {Function} continuation callback +// Parse buffered data +// +Parser.prototype.execute = function execute(state, data, callback) { + if (state.type === 'frame-head') { + var header = state.header = spdy.protocol.generic.parseHeader(data); + + // Lazily create framer + if (!this.framer && header.control) { + if (spdy.protocol[header.version]) { + this.framer = new spdy.protocol[header.version].Framer( + spdy.utils.zwrap(this.deflate), + spdy.utils.zwrap(this.inflate) + ); + + // Propagate framer to connection + this.connection.framer = this.framer; + this.emit('_framer', this.framer); + } + } + + state.type = 'frame-body'; + callback(null, header.length); + } else if (state.type === 'frame-body') { + var self = this; + + // Data frame + if (!state.header.control) { + return onFrame(null, { + type: 'DATA', + id: state.header.id, + fin: (state.header.flags & 0x01) === 0x01, + compressed: (state.header.flags & 0x02) === 0x02, + data: data + }); + } else { + // Control frame + this.framer.execute(state.header, data, onFrame); + } + + function onFrame(err, frame) { + if (err) return callback(err); + + self.emit('frame', frame); + + state.type = 'frame-head'; + callback(null, 8); + }; + } +};
new file mode 100644 --- /dev/null +++ b/testing/xpcshell/node-spdy/lib/spdy/protocol/generic.js @@ -0,0 +1,24 @@ +// +// ### function parseHeader (data) +// ### @data {Buffer} incoming data +// Returns parsed SPDY frame header +// +exports.parseHeader = function parseHeader(data) { + var header = { + control: (data.readUInt8(0) & 0x80) === 0x80 ? true : false, + version: null, + type: null, + id: null, + flags: data.readUInt8(4), + length: data.readUInt32BE(4) & 0x00ffffff + }; + + if (header.control) { + header.version = data.readUInt16BE(0) & 0x7fff; + header.type = data.readUInt16BE(2); + } else { + header.id = data.readUInt32BE(0) & 0x7fffffff; + } + + return header; +};
new file mode 100644 --- /dev/null +++ b/testing/xpcshell/node-spdy/lib/spdy/protocol/v2/framer.js @@ -0,0 +1,280 @@ +var framer = exports; + +var spdy = require('../../../spdy'), + Buffer = require('buffer').Buffer, + protocol = require('./'); + +// +// ### function Framer (deflate, inflate) +// #### @deflate {zlib.Deflate} Deflate stream +// #### @inflate {zlib.Inflate} Inflate stream +// Framer constructor +// +function Framer(deflate, inflate) { + this.deflate = deflate; + this.inflate = inflate; +} +exports.Framer = Framer; + + +// +// ### function execute (header, body, callback) +// #### @header {Object} Frame headers +// #### @body {Buffer} Frame's body +// #### @callback {Function} Continuation callback +// Parse frame (decompress data and create streams) +// +Framer.prototype.execute = function execute(header, body, callback) { + // SYN_STREAM or SYN_REPLY + if (header.type === 0x01 || header.type === 0x02) { + var frame = protocol.parseSynHead(header.type, header.flags, body); + + body = body.slice(frame._offset); + + this.inflate(body, function(err, chunks, length) { + var pairs = new Buffer(length); + for (var i = 0, offset = 0; i < chunks.length; i++) { + chunks[i].copy(pairs, offset); + offset += chunks[i].length; + } + + frame.headers = protocol.parseHeaders(pairs); + + callback(null, frame); + }); + // RST_STREAM + } else if (header.type === 0x03) { + callback(null, protocol.parseRst(body)); + // SETTINGS + } else if (header.type === 0x04) { + callback(null, { type: 'SETTINGS' }); + } else if (header.type === 0x05) { + callback(null, { type: 'NOOP' }); + // PING + } else if (header.type === 0x06) { + callback(null, { type: 'PING', pingId: body }); + // GOAWAY + } else if (header.type === 0x07) { + callback(null, protocol.parseGoaway(body)); + } else { + callback(null, { type: 'unknown: ' + header.type, body: body }); + } +}; + +// +// internal, converts object into spdy dictionary +// +function headersToDict(headers, preprocess) { + function stringify(value) { + if (value !== undefined) { + if (Array.isArray(value)) { + return value.join('\x00'); + } else if (typeof value === 'string') { + return value; + } else { + return value.toString(); + } + } else { + return ''; + } + } + + // Lower case of all headers keys + var loweredHeaders = {}; + Object.keys(headers || {}).map(function(key) { + loweredHeaders[key.toLowerCase()] = headers[key]; + }); + + // Allow outer code to add custom headers or remove something + if (preprocess) preprocess(loweredHeaders); + + // Transform object into kv pairs + var len = 2, + pairs = Object.keys(loweredHeaders).filter(function(key) { + var lkey = key.toLowerCase(); + return lkey !== 'connection' && lkey !== 'keep-alive' && + lkey !== 'proxy-connection' && lkey !== 'transfer-encoding'; + }).map(function(key) { + var klen = Buffer.byteLength(key), + value = stringify(loweredHeaders[key]), + vlen = Buffer.byteLength(value); + + len += 4 + klen + vlen; + return [klen, key, vlen, value]; + }), + result = new Buffer(len); + + result.writeUInt16BE(pairs.length, 0, true); + + var offset = 2; + pairs.forEach(function(pair) { + // Write key length + result.writeUInt16BE(pair[0], offset, true); + // Write key + result.write(pair[1], offset + 2); + + offset += pair[0] + 2; + + // Write value length + result.writeUInt16BE(pair[2], offset, true); + // Write value + result.write(pair[3], offset + 2); + + offset += pair[2] + 2; + }); + + return result; +}; + +Framer.prototype._synFrame = function _synFrame(type, id, assoc, dict, + callback) { + // Compress headers + this.deflate(dict, function (err, chunks, size) { + if (err) return callback(err); + + var offset = type === 'SYN_STREAM' ? 18 : 14, + total = (type === 'SYN_STREAM' ? 10 : 6) + size, + frame = new Buffer(offset + size);; + + frame.writeUInt16BE(0x8002, 0, true); // Control + Version + frame.writeUInt16BE(type === 'SYN_STREAM' ? 1 : 2, 2, true); // type + frame.writeUInt32BE(total & 0x00ffffff, 4, true); // No flag support + frame.writeUInt32BE(id & 0x7fffffff, 8, true); // Stream-ID + + if (type === 'SYN_STREAM') { + frame[4] = 2; + frame.writeUInt32BE(assoc & 0x7fffffff, 12, true); // Stream-ID + } + + for (var i = 0; i < chunks.length; i++) { + chunks[i].copy(frame, offset); + offset += chunks[i].length; + } + + callback(null, frame); + }); +}; + +// +// ### function replyFrame (id, code, reason, headers, callback) +// #### @id {Number} Stream ID +// #### @code {Number} HTTP Status Code +// #### @reason {String} (optional) +// #### @headers {Object|Array} (optional) HTTP headers +// #### @callback {Function} Continuation function +// Sends SYN_REPLY frame +// +Framer.prototype.replyFrame = function replyFrame(id, code, reason, headers, + callback) { + var dict = headersToDict(headers, function(headers) { + headers.status = code + ' ' + reason; + headers.version = 'HTTP/1.1'; + }); + + this._synFrame('SYN_REPLY', id, null, dict, callback); +}; + +// +// ### function streamFrame (id, assoc, headers, callback) +// #### @id {Number} stream id +// #### @assoc {Number} associated stream id +// #### @meta {Object} meta headers ( method, scheme, url, version ) +// #### @headers {Object} stream headers +// #### @callback {Function} continuation callback +// Create SYN_STREAM frame +// (needed for server push and testing) +// +Framer.prototype.streamFrame = function streamFrame(id, assoc, meta, headers, + callback) { + var dict = headersToDict(headers, function(headers) { + headers.status = 200; + headers.version = 'HTTP/1.1'; + headers.url = meta.url; + }); + + this._synFrame('SYN_STREAM', id, assoc, dict, callback); +}; + +// +// ### function dataFrame (id, fin, data) +// #### @id {Number} Stream id +// #### @fin {Bool} Is this data frame last frame +// #### @data {Buffer} Response data +// Sends DATA frame +// +Framer.prototype.dataFrame = function dataFrame(id, fin, data) { + if (!fin && !data.length) return []; + + var frame = new Buffer(8 + data.length); + + frame.writeUInt32BE(id & 0x7fffffff, 0, true); + frame.writeUInt32BE(data.length & 0x00ffffff, 4, true); + frame.writeUInt8(fin ? 0x01 : 0x0, 4, true); + + if (data.length) data.copy(frame, 8); + + return frame; +}; + +// +// ### function pingFrame (id) +// #### @id {Buffer} Ping ID +// Sends PING frame +// +Framer.prototype.pingFrame = function pingFrame(id) { + var header = new Buffer(12); + + header.writeUInt32BE(0x80020006, 0, true); // Version and type + header.writeUInt32BE(0x00000004, 4, true); // Length + id.copy(header, 8, 0, 4); // ID + + return header; +}; + +// +// ### function rstFrame (id, code) +// #### @id {Number} Stream ID +// #### @code {NUmber} RST Code +// Sends PING frame +// +Framer.prototype.rstFrame = function rstFrame(id, code) { + var header; + + if (!(header = Framer.rstCache[code])) { + header = new Buffer(16); + + header.writeUInt32BE(0x80020003, 0, true); // Version and type + header.writeUInt32BE(0x00000008, 4, true); // Length + header.writeUInt32BE(id & 0x7fffffff, 8, true); // Stream ID + header.writeUInt32BE(code, 12, true); // Status Code + + Framer.rstCache[code] = header; + } + + return header; +}; +Framer.rstCache = {}; + +// +// ### function maxStreamsFrame (count) +// #### @count {Number} Max Concurrent Streams count +// Sends SETTINGS frame with MAX_CONCURRENT_STREAMS +// +Framer.prototype.maxStreamsFrame = function maxStreamsFrame(count) { + var settings; + + if (!(settings = Framer.settingsCache[count])) { + settings = new Buffer(20); + + settings.writeUInt32BE(0x80020004, 0, true); // Version and type + settings.writeUInt32BE(0x0000000C, 4, true); // length + settings.writeUInt32BE(0x00000001, 8, true); // Count of entries + settings.writeUInt32LE(0x01000004, 12, true); // Entry ID and Persist flag + settings.writeUInt32BE(count, 16, true); // 100 Streams + + Framer.settingsCache[count] = settings; + } + + return settings; +}; +Framer.settingsCache = {};
new file mode 100644 --- /dev/null +++ b/testing/xpcshell/node-spdy/lib/spdy/protocol/v2/index.js @@ -0,0 +1,10 @@ +var v2; + +try { + v2 = require('./protocol.node'); +} catch (e) { + v2 = require('./protocol.js'); +} +module.exports = v2; + +v2.Framer = require('./framer').Framer;
new file mode 100644 --- /dev/null +++ b/testing/xpcshell/node-spdy/lib/spdy/protocol/v2/protocol.js @@ -0,0 +1,67 @@ +var protocol = exports; + +// +// ### function parseSynHead (type, flags, data) +// #### @type {Number} Frame type +// #### @flags {Number} Frame flags +// #### @data {Buffer} input data +// Returns parsed syn_* frame's head +// +protocol.parseSynHead = function parseSynHead(type, flags, data) { + var stream = type === 0x01; + + return { + type: stream ? 'SYN_STREAM' : 'SYN_REPLY', + id: data.readUInt32BE(0, true) & 0x7fffffff, + associated: stream ? data.readUInt32BE(4, true) & 0x7fffffff : 0, + priority: stream ? data[8] >> 6 : 0, + fin: (flags & 0x01) === 0x01, + unidir: (flags & 0x02) === 0x02, + _offset: stream ? 10 : 6 + }; +}; + +// +// ### function parseHeaders (pairs) +// #### @pairs {Buffer} header pairs +// Returns hashmap of parsed headers +// +protocol.parseHeaders = function parseHeaders(pairs) { + var count = pairs.readUInt16BE(0, true), + headers = {}; + + pairs = pairs.slice(2); + + function readString() { + var len = pairs.readUInt16BE(0, true), + value = pairs.slice(2, 2 + len); + + pairs = pairs.slice(2 + len); + + return value.toString(); + } + + while(count > 0) { + headers[readString()] = readString(); + count--; + } + + return headers; +}; + +// +// ### function parsesRst frame +protocol.parseRst = function parseRst(data) { + return { + type: 'RST_STREAM', + id: data.readUInt32BE(0, true) & 0x7fffffff, + status: data.readUInt32BE(4, true) + }; +}; + +protocol.parseGoaway = function parseGoaway(data) { + return { + type: 'GOAWAY', + lastId: data.readUInt32BE(0, true) & 0x7fffffff + }; +};
new file mode 100644 --- /dev/null +++ b/testing/xpcshell/node-spdy/lib/spdy/response.js @@ -0,0 +1,146 @@ +var spdy = require('../spdy'), + http = require('http'); + +// +// ### function _renderHeaders () +// Copy pasted from lib/http.js +// (added lowercase) +// +exports._renderHeaders = function() { + if (this._header) { + throw new Error("Can't render headers after they are sent to the client."); + } + + if (!this._headers) return {}; + + var headers = {}; + var keys = Object.keys(this._headers); + for (var i = 0, l = keys.length; i < l; i++) { + var key = keys[i]; + headers[(this._headerNames[key] || '').toLowerCase()] = this._headers[key]; + } + return headers; +}; + +// +// ### function writeHead (statusCode) +// #### @statusCode {Number} HTTP Status code +// .writeHead() wrapper +// (Sorry, copy pasted from lib/http.js) +// +exports.writeHead = function(statusCode) { + if (this._headerSent) return; + this._headerSent = true; + + var reasonPhrase, headers, headerIndex; + + if (typeof arguments[1] == 'string') { + reasonPhrase = arguments[1]; + headerIndex = 2; + } else { + reasonPhrase = http.STATUS_CODES[statusCode] || 'unknown'; + headerIndex = 1; + } + this.statusCode = statusCode; + + var obj = arguments[headerIndex]; + + if (obj && this._headers) { + // Slow-case: when progressive API and header fields are passed. + headers = this._renderHeaders(); + + // handle object case + var keys = Object.keys(obj); + for (var i = 0; i < keys.length; i++) { + var k = keys[i]; + if (k) headers[k] = obj[k]; + } + } else if (this._headers) { + // only progressive api is used + headers = this._renderHeaders(); + } else { + // only writeHead() called + headers = obj; + } + + // cleanup + this._header = ''; + + // Do not send data to new connections after GOAWAY + if (this.socket.isGoaway()) return; + + this.socket.lock(function() { + var socket = this; + + this.framer.replyFrame( + this.id, + statusCode, + reasonPhrase, + headers, + function (err, frame) { + // TODO: Handle err + socket.connection.write(frame); + socket.unlock(); + } + ); + }); +}; + +// +// ### function push (url, headers, callback) +// #### @url {String} absolute or relative url (from root anyway) +// #### @headers {Object} response headers +// #### @callbacks {Function} continuation that will receive stream object +// Initiates push stream +// +exports.push = function push(url, headers, callback) { + if (this.socket._destroyed) { + return callback(Error('Can\'t open push stream, parent socket destroyed')); + } + + this.socket.lock(function() { + var socket = this, + id = socket.connection.pushId += 2, + fullUrl = /^\//.test(url) ? + this.frame.headers.scheme + '://' + + (this.frame.headers.host || 'localhost') + + url + : + url; + + this.framer.streamFrame( + id, + this.id, + { + method: 'GET', + url: fullUrl, + schema: 'https', + version: 'HTTP/1.1' + }, + headers, + function(err, frame) { + if (err) { + socket.unlock(); + callback(err); + } else { + socket.connection.write(frame); + socket.unlock(); + + var stream = new spdy.server.Stream(socket.connection, { + type: 'SYN_STREAM', + push: true, + id: id, + assoc: socket.id, + priority: 0, + headers: {} + }); + + socket.connection.streams[id] = stream; + socket.pushes.push(stream); + + callback(null, stream); + } + } + ); + }); +};
new file mode 100644 --- /dev/null +++ b/testing/xpcshell/node-spdy/lib/spdy/scheduler.js @@ -0,0 +1,60 @@ +var scheduler = exports; + +// +// ### function Scheduler (connection) +// #### @connection {spdy.Connection} active connection +// Connection's streams scheduler +// +function Scheduler(connection) { + this.connection = connection; + this.priorities = [[], [], [], []]; + this._tickListener = null; +} + +// +// ### function create (connection) +// #### @connection {spdy.Connection} active connection +// +exports.create = function create(connection) { + return new Scheduler(connection); +}; + +// +// ### function schedule (stream, data) +// #### @stream {spdy.Stream} Source stream +// #### @data {Buffer} data to write on tick +// Use stream priority to invoke callbacks in right order +// +Scheduler.prototype.schedule = function schedule(stream, data) { + this.priorities[stream.priority].push(data); +}; + +// +// ### function tick () +// Add .nextTick callback if not already present +// +Scheduler.prototype.tick = function tick() { + if (this._tickListener !== null) return; + var self = this; + this._tickListener = function() { + var priorities = self.priorities; + + self._tickListener = null; + self.priorities = [[], [], [], []]; + + // Run all priorities + for (var i = 0; i < 4; i++) { + for (var j = 0; j < priorities[i].length; j++) { + self.connection.write( + priorities[i][j] + ); + } + } + }; + + if (this.connection.parser.drained) { + process.nextTick(this._tickListener); + } else { + this.connection.parser.once('drain', this._tickListener); + } +};
new file mode 100644 --- /dev/null +++ b/testing/xpcshell/node-spdy/lib/spdy/server.js @@ -0,0 +1,544 @@ +var spdy = require('../spdy'), + util = require('util'), + https = require('https'), + stream = require('stream'), + Buffer = require('buffer').Buffer; + +// +// ### function instantiate (HTTPSServer) +// #### @HTTPSServer {https.Server|Function} Base server class +// Will return constructor for SPDY Server, based on the HTTPSServer class +// +function instantiate(HTTPSServer) { + // + // ### function Server (options, requestListener) + // #### @options {Object} tls server options + // #### @requestListener {Function} (optional) request callback + // SPDY Server @constructor + // + function Server(options, requestListener) { + if (!options) options = {}; + if (!options.maxStreams) options.maxStreams = 100; + options.NPNProtocols = ['spdy/2', 'http/1.1', 'http/1.0']; + + HTTPSServer.call(this, options, requestListener); + + // Use https if NPN is not supported + if (!process.features.tls_npn && !options.debug) return; + + // Wrap connection handler + var self = this, + connectionHandler = this.listeners('secureConnection')[0]; + + var pool = spdy.zlibpool.create(); + + this.removeAllListeners('secureConnection'); + this.on('secureConnection', function secureConnection(socket) { + // Fallback to HTTPS if needed + if (socket.npnProtocol !== 'spdy/2' && !options.debug) { + return connectionHandler.call(this, socket); + } + + // Wrap incoming socket into abstract class + var connection = new Connection(socket, pool, options); + + // Emulate each stream like connection + connection.on('stream', connectionHandler); + + connection.on('request', function(req, res) { + res._renderHeaders = spdy.response._renderHeaders; + res.writeHead = spdy.response.writeHead; + res.push = spdy.response.push; + res.streamID = req.streamID = req.socket.id; + res.isSpdy = req.isSpdy = true; + + res.on('finish', function() { + req.connection.end(); + }); + self.emit('request', req, res); + }); + + connection.on('error', function(e) { + socket.destroy(e.errno === 'EPIPE' ? undefined : e); + }); + }); + } + util.inherits(Server, HTTPSServer); + + return Server; +} +exports.instantiate = instantiate; + +// Export Server instantiated from https.Server +var Server = instantiate(https.Server); +exports.Server = Server; + +// +// ### function create (base, options, requestListener) +// #### @base {Function} (optional) base server class (https.Server) +// #### @options {Object} tls server options +// #### @requestListener {Function} (optional) request callback +// @constructor wrapper +// +exports.create = function create(base, options, requestListener) { + var server; + if (typeof base === 'function') { + server = instantiate(base); + } else { + server = Server; + + requestListener = options; + options = base; + base = null; + } + + return new server(options, requestListener); +}; + +// +// ### function Connection (socket, pool, options) +// #### @socket {net.Socket} server's connection +// #### @pool {spdy.ZlibPool} zlib pool +// #### @options {Object} server's options +// Abstract connection @constructor +// +function Connection(socket, pool, options) { + process.EventEmitter.call(this); + + var self = this; + + this._closed = false; + + this.pool = pool; + var pair = pool.get(); + + this.deflate = pair.deflate; + this.inflate = pair.inflate; + + // Init streams list + this.streams = {}; + this.streamsCount = 0; + this.pushId = 0; + this.goaway = false; + + this.framer = null; + + // Initialize parser + this.parser = spdy.parser.create(this, this.deflate, this.inflate); + this.parser.on('frame', function (frame) { + if (this._closed) return; + + var stream; + + // Create new stream + if (frame.type === 'SYN_STREAM') { + self.streamsCount++; + + stream = self.streams[frame.id] = new Stream(self, frame); + + // If we reached stream limit + if (self.streamsCount > options.maxStreams) { + stream.once('error', function() {}); + // REFUSED_STREAM + stream.rstCode = 3; + stream.destroy(true); + } else { + self.emit('stream', stream); + + stream.init(); + } + } else { + if (frame.id) { + // Load created one + stream = self.streams[frame.id]; + + // Fail if not found + if (stream === undefined) { + if (frame.type === 'RST_STREAM') return; + return self.emit('error', 'Stream ' + frame.id + ' not found'); + } + } + + // Emit 'data' event + if (frame.type === 'DATA') { + if (frame.data.length > 0){ + if (stream.closedBy.client) { + stream.rstCode = 2; + stream.emit('error', 'Writing to half-closed stream'); + } else { + stream._read(frame.data); + } + } + // Destroy stream if we was asked to do this + } else if (frame.type === 'RST_STREAM') { + stream.rstCode = 0; + if (frame.status === 5) { + // If client "cancels" connection - close stream and + // all associated push streams without error + stream.pushes.forEach(function(stream) { + stream.close(); + }); + stream.close(); + } else { + // Emit error on destroy + stream.destroy(new Error('Received rst: ' + frame.status)); + } + // Respond with same PING + } else if (frame.type === 'PING') { + self.write(self.framer.pingFrame(frame.pingId)); + // Ignore SETTINGS for now + } else if (frame.type === 'SETTINGS' || frame.type === 'NOOP') { + // TODO: Handle something? + } else if (frame.type === 'GOAWAY') { + self.goaway = frame.lastId; + } else { + console.error('Unknown type: ', frame.type); + } + } + + // Handle half-closed + if (frame.fin) { + // Don't allow to close stream twice + if (stream.closedBy.client) { + stream.rstCode = 2; + stream.emit('error', 'Already half-closed'); + } else { + stream.closedBy.client = true; + stream.handleClose(); + } + } + }); + + this.parser.on('_framer', function(framer) { + // Generate custom settings frame and send + self.write( + framer.maxStreamsFrame(options.maxStreams) + ); + }); + + // Initialize scheduler + this.scheduler = spdy.scheduler.create(this); + + // Store socket and pipe it to parser + this.socket = socket; + + socket.pipe(this.parser); + + // 2 minutes socket timeout + socket.setTimeout(2 * 60 * 1000); + socket.once('timeout', function() { + socket.destroy(); + }); + + // Allow high-level api to catch socket errors + socket.on('error', function(e) { + self.emit('error', e); + }); + + socket.on('close', function() { + self._closed = true; + pool.put(pair); + }); + + socket.on('drain', function () { + self.emit('drain'); + }); +} +util.inherits(Connection, process.EventEmitter); +exports.Connection = Connection; + +// +// ### function write (data, encoding) +// #### @data {String|Buffer} data +// #### @encoding {String} (optional) encoding +// Writes data to socket +// +Connection.prototype.write = function write(data, encoding) { + if (this.socket.writable) { + return this.socket.write(data, encoding); + } +}; + +// +// ### function Stream (connection, frame) +// #### @connection {Connection} SPDY Connection +// #### @frame {Object} SYN_STREAM data +// Abstract stream @constructor +// +function Stream(connection, frame) { + var self = this; + stream.Stream.call(this); + + this.connection = connection; + this.framer = connection.framer; + + this.ondata = this.onend = null; + + // RST_STREAM code if any + this.rstCode = 1; + this._destroyed = false; + + this.closedBy = { + client: false, + server: false + }; + + // Lock data + this.locked = false; + this.lockBuffer = []; + + // Store id + this.id = frame.id; + + // Store priority + this.priority = frame.priority; + + // Array of push streams associated to that one + this.pushes = []; + + this._paused = false; + this._buffer = []; + + // Create compression streams + this.deflate = connection.deflate; + this.inflate = connection.inflate; + + // Store headers + this.headers = frame.headers; + + this.frame = frame; + + this.readable = this.writable = true; +} +util.inherits(Stream, stream.Stream); +exports.Stream = Stream; + +// +// ### function isGoaway () +// Returns true if any writes to that stream should be ignored +// +Stream.prototype.isGoaway = function isGoaway() { + return this.connection.goaway && this.id > this.connection.goaway; +}; + +// +// ### function init () +// Initialize stream, internal +// +Stream.prototype.init = function init() { + var headers = this.headers, + req = [headers.method + ' ' + headers.url + ' ' + headers.version]; + + Object.keys(headers).forEach(function (key) { + if (key !== 'method' && key !== 'url' && key !== 'version' && + key !== 'scheme') { + req.push(key + ': ' + headers[key]); + } + }); + + // Add '\r\n\r\n' + req.push('', ''); + + req = new Buffer(req.join('\r\n')); + + this._read(req); +}; + +// +// ### function lock (callback) +// #### @callback {Function} continuation callback +// Acquire lock +// +Stream.prototype.lock = function lock(callback) { + if (!callback) return; + + if (this.locked) { + this.lockBuffer.push(callback); + } else { + this.locked = true; + callback.call(this, null); + } +}; + +// +// ### function unlock () +// Release lock and call all buffered callbacks +// +Stream.prototype.unlock = function unlock() { + if (this.locked) { + this.locked = false; + this.lock(this.lockBuffer.shift()); + } +}; + +// +// ### function setTimeout () +// TODO: use timers.enroll, timers.active, timers.unenroll +// +Stream.prototype.setTimeout = function setTimeout(time) {}; + +// +// ### function handleClose () +// Close stream if it was closed by both server and client +// +Stream.prototype.handleClose = function handleClose() { + if (this.closedBy.client && this.closedBy.server) { + this.close(); + } +}; + +// +// ### function close () +// Destroys stream +// +Stream.prototype.close = function close() { + this.destroy(); +}; + +// +// ### function destroy (error) +// #### @error {Error} (optional) error +// Destroys stream +// +Stream.prototype.destroy = function destroy(error) { + if (this._destroyed) return; + this._destroyed = true; + + delete this.connection.streams[this.id]; + this.connection.streamsCount--; + + if (error) { + if (this.rstCode) { + this.connection.write(this.framer.rstFrame(this.id, this.rstCode)); + } + } + + var self = this; + process.nextTick(function() { + if (error) self.emit('error', error); + self.emit('close'); + }); +}; + +// +// ### function _writeData (fin, buffer) +// #### @fin {Boolean} +// #### @buffer {Buffer} +// Internal function +// +Stream.prototype._writeData = function _writeData(fin, buffer) { + this.lock(function() { + var stream = this, + frame = this.framer.dataFrame(this.id, fin, buffer); + + stream.connection.scheduler.schedule(stream, frame); + stream.connection.scheduler.tick(); + + if (fin) this.close(); + + this.unlock(); + }); +}; + +// +// ### function write (data, encoding) +// #### @data {Buffer|String} data +// #### @encoding {String} data encoding +// Writes data to connection +// +Stream.prototype.write = function write(data, encoding) { + // Do not send data to new connections after GOAWAY + if (this.isGoaway()) return; + + var buffer; + + // Send DATA + if (typeof data === 'string') { + buffer = new Buffer(data, encoding); + } else { + buffer = data; + } + + // TCP Frame size is equal to: + // data frame size + data frame headers (8 bytes) + + // tls frame headers and data (encrypted) ~ 140 bytes + // So setting MTU equal to 1300 is most optimal + // as end packet will have size ~ 1442 bytes + // (My MTU is 1492 bytes) + var MTU = 1300; + + // Try to fit MTU + if (buffer.length < MTU) { + this._writeData(false, buffer); + } else { + var len = buffer.length - MTU; + for (var offset = 0; offset < len; offset += MTU) { + this._writeData(false, buffer.slice(offset, offset + MTU)); + } + this._writeData(false, buffer.slice(offset)); + } +}; + +// +// ### function end (data) +// #### @data {Buffer|String} (optional) data to write before ending stream +// #### @encoding {String} (optional) string encoding +// Send FIN data frame +// +Stream.prototype.end = function end(data, encoding) { + // Do not send data to new connections after GOAWAY + if (this.isGoaway()) return; + + if (data) this.write(data, encoding); + + this._writeData(true, []); + this.closedBy.server = true; + this.handleClose(); +}; + +// +// ### function pause () +// Start buffering all data +// +Stream.prototype.pause = function pause() { + if (this._paused) return; + this._paused = true; +}; + +// +// ### function resume () +// Start buffering all data +// +Stream.prototype.resume = function resume() { + if (!this._paused) return; + this._paused = false; + + var self = this, + buffer = this._buffer; + + this._buffer = []; + + process.nextTick(function() { + buffer.forEach(function(chunk) { + self._read(chunk); + }); + }); +}; + +// +// ### function _read (data) +// #### @data {Buffer} buffer to read +// (internal) +// +Stream.prototype._read = function _read(data) { + if (this._paused) { + this._buffer.push(data); + return; + } + + var self = this; + process.nextTick(function() { + if (self.ondata) self.ondata(data, 0, data.length); + self.emit('data', data); + }); +};
new file mode 100644 --- /dev/null +++ b/testing/xpcshell/node-spdy/lib/spdy/utils.js @@ -0,0 +1,120 @@ +var utils = exports; + +var zlib = require('zlib'), + Buffer = require('buffer').Buffer; + +// SPDY deflate/inflate dictionary +var dictionary = new Buffer([ + 'optionsgetheadpostputdeletetraceacceptaccept-charsetaccept-encodingaccept-', + 'languageauthorizationexpectfromhostif-modified-sinceif-matchif-none-matchi', + 'f-rangeif-unmodifiedsincemax-forwardsproxy-authorizationrangerefererteuser', + '-agent10010120020120220320420520630030130230330430530630740040140240340440', + '5406407408409410411412413414415416417500501502503504505accept-rangesageeta', + 'glocationproxy-authenticatepublicretry-afterservervarywarningwww-authentic', + 'ateallowcontent-basecontent-encodingcache-controlconnectiondatetrailertran', + 'sfer-encodingupgradeviawarningcontent-languagecontent-lengthcontent-locati', + 'oncontent-md5content-rangecontent-typeetagexpireslast-modifiedset-cookieMo', + 'ndayTuesdayWednesdayThursdayFridaySaturdaySundayJanFebMarAprMayJunJulAugSe', + 'pOctNovDecchunkedtext/htmlimage/pngimage/jpgimage/gifapplication/xmlapplic', + 'ation/xhtmltext/plainpublicmax-agecharset=iso-8859-1utf-8gzipdeflateHTTP/1', + '.1statusversionurl\x00' +].join('')); + +// +// ### function createDeflate () +// Creates deflate stream with SPDY dictionary +// +utils.createDeflate = function createDeflate() { + var deflate = zlib.createDeflate({ dictionary: dictionary, windowBits: 11 }); + + // Define lock information early + deflate.locked = false; + deflate.lockBuffer = []; + + return deflate; +}; + +// +// ### function createInflate () +// Creates inflate stream with SPDY dictionary +// +utils.createInflate = function createInflate() { + var inflate = zlib.createInflate({ dictionary: dictionary, windowBits: 15 }); + + // Define lock information early + inflate.locked = false; + inflate.lockBuffer = []; + + return inflate; +}; + +// +// ### function resetZlibStream (stream) +// #### @stream {zlib.Stream} stream +// Resets zlib stream and associated locks +// +utils.resetZlibStream = function resetZlibStream(stream, callback) { + if (stream.locked) { + stream.lockBuffer.push(function() { + resetZlibStream(stream, callback); + }); + return; + } + + stream.reset(); + stream.lockBuffer = []; + + callback(null); +}; + +var delta = 0; +// +// ### function zstream (stream, buffer, callback) +// #### @stream {Deflate|Inflate} One of streams above +// #### @buffer {Buffer} Input data (to compress or to decompress) +// #### @callback {Function} Continuation to callback +// Compress/decompress data and pass it to callback +// +utils.zstream = function zstream(stream, buffer, callback) { + var flush = stream._flush, + chunks = [], + total = 0; + + if (stream.locked) { + stream.lockBuffer.push(function() { + zstream(stream, buffer, callback); + }); + return; + } + stream.locked = true; + + function collect(chunk) { + chunks.push(chunk); + total += chunk.length; + } + stream.on('data', collect); + stream.write(buffer); + + stream.flush(function() { + stream.removeAllListeners('data'); + stream._flush = flush; + + callback(null, chunks, total); + + stream.locked = false; + var deferred = stream.lockBuffer.shift(); + if (deferred) deferred(); + }); +}; + +// +// ### function zwrap (stream) +// #### @stream {zlib.Stream} stream to wrap +// Wraps stream within function to allow simple deflate/inflate +// +utils.zwrap = function zwrap(stream) { + return function(data, callback) { + utils.zstream(stream, data, callback); + }; +}; +
new file mode 100644 --- /dev/null +++ b/testing/xpcshell/node-spdy/lib/spdy/zlib-pool.js @@ -0,0 +1,52 @@ +var zlibpool = exports, + spdy = require('../spdy'); + +// +// ### function Pool () +// Zlib streams pool +// +function Pool() { + this.pool = []; +} + +// +// ### function create () +// Returns instance of Pool +// +zlibpool.create = function create() { + return new Pool(); +}; + +var x = 0; +// +// ### function get () +// Returns pair from pool or a new one +// +Pool.prototype.get = function get(callback) { + if (this.pool.length > 0) { + return this.pool.pop(); + } else { + return { + deflate: spdy.utils.createDeflate(), + inflate: spdy.utils.createInflate() + }; + } +}; + +// +// ### function put (pair) +// Puts pair into pool +// +Pool.prototype.put = function put(pair) { + var self = this, + waiting = 2; + + spdy.utils.resetZlibStream(pair.inflate, done); + spdy.utils.resetZlibStream(pair.deflate, done); + + function done() { + if (--waiting === 0) { + self.pool.push(pair); + } + } +};
new file mode 100644 --- /dev/null +++ b/testing/xpcshell/node-spdy/package.json @@ -0,0 +1,33 @@ +{ + "name": "spdy", + "version": "1.2.0", + "description": "Implementation of the SPDY protocol on node.js.", + "keywords": [ + "spdy" + ], + "repository": { + "type": "git", + "url": "git://github.com/indutny/node-spdy.git" + }, + "homepage": "https://github.com/indutny/node-spdy", + "bugs": { + "email": "node-spdy+bugs@indutny.com", + "url": "https://github.com/indunty/node-spdy/issues" + }, + "author": "Fedor Indutny <fedor.indutny@gmail.com>", + "contributors": [ + "Chris Storm <github@eeecooks.com>", + "François de Metz <francois@2metz.fr>" + ], + "dependencies": {}, + "devDependencies": { + "mocha": "~ 0.10.1" + }, + "scripts": { + "test": "mocha --ui tdd --growl --reporter spec test/unit/*-test.js" + }, + "engines": [ + "node ~ 0.7.0" + ], + "main": "./lib/spdy" +}
new file mode 100644 --- /dev/null +++ b/testing/xpcshell/node-spdy/test/benchmarks/syn.js @@ -0,0 +1,48 @@ +var tls = require('tls'), + frames = require('../fixtures/frames'); + +var uri = require('url').parse(process.argv[2]), + host = uri.hostname, + port = +uri.port, + url = uri.path; + +frames.createSynStream(host, url, function(syn_stream) { + var start = +new Date, + num = 3000; + + batch(port, host, syn_stream, 100, num, function() { + var end = +new Date; + console.log('requests/sec : %d', 1000 * num / (end - start)); + }); +}); + +function request(port, host, data, callback) { + var socket = tls.connect(port, host, {NPNProtocols: ['spdy/2']}, function() { + socket.write(data); + socket.once('data', function() { + socket.destroy(); + callback(); + }); + }); + + return socket; +}; + +function batch(port, host, data, parallel, num, callback) { + var left = num, + done = 0; + + for (var i = 0; i < parallel; i++) { + run(i); + } + + function run(i) { + left--; + if (left < 0) return; + + request(port, host, data, function() { + if (++done === num) return callback(); + run(i); + }); + } +};
new file mode 100644 --- /dev/null +++ b/testing/xpcshell/node-spdy/test/fixtures/frames.js @@ -0,0 +1,38 @@ +var spdy = require('../../lib/spdy'), + Buffer = require('buffer').Buffer; + +exports.createSynStream = function(host, url, callback) { + var deflate = spdy.utils.createDeflate(), + chunks = [], + chunksTotal = 0, + syn_stream; + + deflate.on('data', function(chunk) { + chunks.push(chunk); + chunksTotal += chunk.length; + }); + deflate.write(new Buffer([ 0x00, 0x02, 0x00, 0x04 ])); + deflate.write('host'); + deflate.write(new Buffer([ 0x00, host.length ])); + deflate.write(host); + deflate.write(new Buffer([ 0x00, 0x03 ])); + deflate.write('url'); + deflate.write(new Buffer([ 0x00, url.length ])); + deflate.write(url); + + deflate.flush(function() { + syn_stream = new Buffer(18 + chunksTotal); + syn_stream.writeUInt32BE(0x80020001, 0); + syn_stream.writeUInt32BE(chunksTotal + 8, 4); + syn_stream.writeUInt32BE(0x00000001, 8); + syn_stream.writeUInt32BE(0x00000000, 12); + + var offset = 18; + chunks.forEach(function(chunk) { + chunk.copy(syn_stream, offset); + offset += chunk.length; + }); + + callback(syn_stream); + }); +};
new file mode 100644 --- /dev/null +++ b/testing/xpcshell/node-spdy/test/fixtures/keys.js @@ -0,0 +1,11 @@ +var fs = require('fs'), + path = require('path'); + +var keysDir = require('path').resolve(__dirname, '../../keys/'), + options = { + key: fs.readFileSync(keysDir + '/spdy-key.pem'), + cert: fs.readFileSync(keysDir + '/spdy-cert.pem'), + ca: fs.readFileSync(keysDir + '/spdy-csr.pem') + }; + +module.exports = options;
new file mode 100644 --- /dev/null +++ b/testing/xpcshell/node-spdy/test/unit/framer-test.js @@ -0,0 +1,190 @@ +var assert = require('assert'), + spdy = require('../../'), + Buffer = require('buffer').Buffer, + Stream = require('stream').Stream; + +suite('A Framer of SPDY module', function() { + var inflate, + deflate, + framer; + + setup(function() { + inflate = spdy.utils.zwrap(spdy.utils.createInflate()); + deflate = spdy.utils.zwrap(spdy.utils.createDeflate()); + framer = new spdy.protocol[2].Framer(deflate, inflate); + }); + + /* + deflate.on('data', function(b) {console.log(b)}); + deflate.write(new Buffer([ + 0x00, 0x02, // Number of name+value + 0, 0x04, // Name length + 0x68, 0x6f, 0x73, 0x74, // 'host' + 0, 0x09, // Value length + 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x68, 0x6f, 0x73, 0x74, // 'localhost' + 0, 0x06, + 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, // 'custom', + 0, 0x1, + 0x31 // '1' + ])); + deflate.flush(); + */ + + suite('frame parsing', function() { + test('given a SYN_STREAM should return correct frame', function(done) { + var body = new Buffer([ + 0x00, 0x00, 0x00, 0x01, // Stream ID + 0x00, 0x00, 0x00, 0x00, // Associated Stream ID + 0x00, 0x00, // Priority + Unused + 0x78, 0xbb, 0xdf, 0xa2, 0x51, 0xb2, // Deflated Name/Value pairs + 0x62, 0x60, 0x62, 0x60, 0x01, 0xe5, 0x12, + 0x06, 0x4e, 0x50, 0x50, 0xe6, 0x80, 0x99, + 0x6c, 0xc9, 0xa5, 0xc5, 0x25, 0xf9, 0xb9, + 0x0c, 0x8c, 0x86, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff + ]); + framer.execute({ + control: true, + type: 1, + length: body.length + }, body, function(err, frame) { + assert.ok(!err); + assert.equal(frame.type, 'SYN_STREAM'); + assert.equal(frame.id, 1); + assert.equal(frame.associated, 0); + assert.equal(frame.headers.host, 'localhost'); + assert.equal(frame.headers.custom, '1'); + done(); + }); + }); + + test('given a SYN_REPLY should return correct frame', function(done) { + var body = new Buffer([ + 0x00, 0x00, 0x00, 0x01, // Stream ID + 0x00, 0x00, // Unused + 0x78, 0xbb, 0xdf, 0xa2, 0x51, 0xb2, // Deflated Name/Value pairs + 0x62, 0x60, 0x62, 0x60, 0x01, 0xe5, 0x12, + 0x06, 0x4e, 0x50, 0x50, 0xe6, 0x80, 0x99, + 0x6c, 0xc9, 0xa5, 0xc5, 0x25, 0xf9, 0xb9, + 0x0c, 0x8c, 0x86, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff + ]); + framer.execute({ + control: true, + type: 2, + length: body.length + }, body, function(err, frame) { + assert.ok(!err); + assert.equal(frame.type, 'SYN_REPLY'); + assert.equal(frame.id, 1); + assert.equal(frame.headers.host, 'localhost'); + assert.equal(frame.headers.custom, '1'); + done(); + }); + }); + + test('given a RST_STREAM should return correct frame', function(done) { + var body = new Buffer([0, 0, 0, 1, 0, 0, 0, 2]); + framer.execute({ + control: true, + type: 3, + length: body.length + }, body, function(err, frame) { + assert.ok(!err); + assert.equal(frame.type, 'RST_STREAM'); + assert.equal(frame.id, 1); + assert.equal(frame.status, 2); + done(); + }); + }); + + test('given a NOOP frame should return correct frame', function(done) { + framer.execute({ + control: true, + type: 5, + length: 0 + }, new Buffer(0), function(err, frame) { + assert.ok(!err); + assert.equal(frame.type, 'NOOP'); + done(); + }); + }); + + test('given a PING frame should return correct frame', function(done) { + framer.execute({ + control: true, + type: 6, + length: 0 + }, new Buffer(0), function(err, frame) { + assert.ok(!err); + assert.equal(frame.type, 'PING'); + done(); + }); + }); + + test('given a GOAWAY frame should return correct frame', function(done) { + var body = new Buffer([0, 0, 0, 1]); + framer.execute({ + control: true, + type: 7, + length: body.length + }, body, function(err, frame) { + assert.ok(!err); + assert.equal(frame.type, 'GOAWAY'); + assert.equal(frame.lastId, 1); + done(); + }); + }); + }); + + suite('frame generation', function() { + test('.replyFrame() should generate correct frame', function(done) { + framer.replyFrame(1, 200, 'ok', {}, function(err, chunks) { + assert.equal(err, null); + assert.ok(chunks.length > 1); + done(); + }); + }); + + test('.streamFrame() should generate correct frame', function(done) { + framer.streamFrame(2, 1, { url : '/' }, {}, function(err, chunks) { + assert.equal(err, null); + assert.ok(chunks.length > 1); + done(); + }); + }); + + test('.dataFrame() w/o fin should generate correct frame', function() { + var frame = framer.dataFrame(1, false, new Buffer(123)); + assert.equal(frame[4], 0); + assert.ok(frame.length > 8); + }); + + test('.dataFrame() with fin should generate correct frame', function() { + var frame = framer.dataFrame(1, true, new Buffer(123)); + assert.equal(frame[4], 1); + assert.ok(frame.length > 8); + }); + + test('.pingFrame() should generate correct frame', function() { + var frame = framer.pingFrame(new Buffer([0, 1, 2, 3])); + assert.ok(frame.length > 0); + }); + + test('.rstFrame() should generate correct frame', function() { + var frame = framer.rstFrame(1, 2); + assert.ok(frame.length > 0); + + // Verify that cache works + var frame = framer.rstFrame(1, 2); + assert.ok(frame.length > 0); + }); + + test('.maxStreamsFrame() should generate correct frame', function() { + var frame = framer.maxStreamsFrame(13); + assert.ok(frame.length > 0); + + // Verify that cache works + var frame = framer.maxStreamsFrame(13); + assert.ok(frame.length > 0); + }); + }); +});
new file mode 100644 --- /dev/null +++ b/testing/xpcshell/node-spdy/test/unit/parser-test.js @@ -0,0 +1,91 @@ +var assert = require('assert'), + spdy = require('../../'), + Buffer = require('buffer').Buffer; + +suite('A Parser of SPDY module', function() { + var parser; + + setup(function() { + var deflate = spdy.utils.createDeflate(), + inflate = spdy.utils.createInflate(); + + parser = new spdy.parser.create(deflate, inflate); + }); + + test('should wait for headers initially', function() { + assert.equal(parser.waiting, 8); + }); + + test('should update buffered property once given < 8 bytes', function() { + parser.write(new Buffer(5)); + assert.equal(parser.buffered, 5); + }); + + test('given SYN_STREAM header should start waiting for body', function() { + parser.write(new Buffer([ + 0x80, 0x02, 0x00, 0x01, // Control frame, version, type (SYN_STREAM) + 0x00, 0x00, 0x12, 0x34 + ])); + + assert.equal(parser.waiting, 0x1234); + assert.equal(parser.state.type, 'frame-body'); + assert.ok(parser.state.header.control); + assert.equal(parser.state.header.flags, 0); + assert.equal(parser.state.header.length, 0x1234); + }); + + test('given DATA header should start waiting for body', function() { + parser.write(new Buffer([ + 0x00, 0x00, 0x00, 0x01, // Data frame, stream ID + 0x00, 0x00, 0x12, 0x34 + ])); + + assert.equal(parser.waiting, 0x1234); + assert.equal(parser.state.type, 'frame-body'); + assert.ok(!parser.state.header.control); + assert.equal(parser.state.header.id, 1); + assert.equal(parser.state.header.flags, 0); + assert.equal(parser.state.header.length, 0x1234); + }); + + test('given chunked header should not fail', function() { + parser.write(new Buffer([ + 0x80, 0x02, 0x00, 0x01 // Control frame, version, type (SYN_STREAM) + ])); + assert.equal(parser.buffered, 4); + + parser.write(new Buffer([ + 0x00, 0x00, 0x12, 0x34 + ])); + assert.equal(parser.buffered, 0); + + assert.equal(parser.waiting, 0x1234); + assert.equal(parser.state.type, 'frame-body'); + assert.ok(parser.state.header.control); + assert.equal(parser.state.header.flags, 0); + assert.equal(parser.state.header.length, 0x1234); + }); + + test('given header and body should emit `frame`', function(done) { + parser.on('frame', function(frame) { + assert.ok(frame.type === 'DATA'); + assert.equal(frame.id, 1); + assert.equal(frame.data.length, 4); + assert.equal(frame.data[0], 0x01); + assert.equal(frame.data[1], 0x02); + assert.equal(frame.data[2], 0x03); + assert.equal(frame.data[3], 0x04); + done(); + }); + + parser.write(new Buffer([ + 0x00, 0x00, 0x00, 0x01, // Data frame, stream ID + 0x00, 0x00, 0x00, 0x04, + 0x01, 0x02, 0x03, 0x04 // Body + ])); + + // Waits for next frame + assert.equal(parser.waiting, 8); + assert.equal(parser.state.type, 'frame-head'); + }); +});
new file mode 100644 --- /dev/null +++ b/testing/xpcshell/node-spdy/test/unit/server-test.js @@ -0,0 +1,142 @@ +var assert = require('assert'), + spdy = require('../../'), + keys = require('../fixtures/keys'), + https = require('https'), + tls = require('tls'),request + Buffer = require('buffer').Buffer; + + +suite('A SPDY Server', function() { + var server; + setup(function(done) { + server = spdy.createServer(keys, function(req, res) { + res.end('ok'); + }); + + server.listen(8081, done); + }); + + teardown(function(done) { + server.once('close', done); + server.close(); + }); + + test('should respond on regular https requests', function(done) { + https.request({ + host: 'localhost', + port: 8081, + path: '/', + method: 'GET' + }, function(res) { + assert.equal(res.statusCode, 200); + done(); + }).end(); + }); + + test('should respond on spdy requests', function(done) { + var socket = tls.connect( + 8081, + 'localhost', + { NPNProtocols: ['spdy/2'] }, + function() { + var deflate = spdy.utils.createDeflate(), + chunks = [], + length = 0; + + deflate.on('data', function(chunk) { + chunks.push(chunk); + length += chunk.length; + }); + + // Deflate headers + deflate.write(new Buffer([ + 0x00, 0x04, // method, url, version = 3 fields + 0x00, 0x06, + 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, // method + 0x00, 0x03, + 0x47, 0x45, 0x54, // get + 0x00, 0x03, + 0x75, 0x72, 0x6c, // url + 0x00, 0x01, + 0x2f, // '/' + 0x00, 0x07, + 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, // version + 0x00, 0x08, + 0x48, 0x54, 0x54, 0x50, 0x2f, 0x31, 0x2e, 0x31, // HTTP/1.1 + 0x00, 0x04, + 0x68, 0x6f, 0x73, 0x74, // host + 0x00, 0x09, + 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x68, 0x6f, 0x73, 0x74 //localhost + ])); + + deflate.flush(function() { + // StreamID + Associated StreamID + length += 10; + + // Headers + socket.write(new Buffer([ + 0x80, 0x02, 0x00, 0x01, // Control, Version, SYN_STREAM + 0x00, 0x00, 0x00, length, // Flags, length (1 byte in this case) + 0x00, 0x00, 0x00, 0x01, // StreamID + 0x00, 0x00, 0x00, 0x00, // Associated StreamID + 0x00, 0x00 // Priority + Unused + ])); + + // Write compressed headers + chunks.forEach(function(chunk) { + socket.write(chunk); + }); + }); + + var response = new Buffer(85), + offset = 0; + + socket.on('data', function(chunk) { + assert.ok(offset + chunk.length <= 85); + + chunk.copy(response, offset); + offset += chunk.length; + + if (offset === 85) { + var frames = []; + + offset = 0; + while (offset < response.length) { + var len = (response.readUInt32BE(offset + 4) & 0x00ffffff) + 8; + frames.push(response.slice(offset, offset + len)); + + offset += len; + } + + // SYN_STREAM frame + assert.ok(frames.some(function(frame) { + return frame[0] === 0x80 && // Control frame + frame[1] === 0x02 && // Version + frame.readUInt16BE(2) === 0x0002 && // SYN_STREAM + frame.readUInt32BE(8) === 0x0001; // StreamID + })); + + // Data frames + assert.ok(frames.some(function(frame) { + return frame[0] === 0x00 && // Data frame + frame.readUInt32BE(0) === 0x0001 && // StreamID + frame.slice(8).toString() === 'ok'; + })); + + socket.destroy(); + } + }); + + socket.on('close', function() { + done(); + }); + } + ); + + server.on('request', function(req, res) { + assert.equal(req.url, '/'); + assert.equal(req.method, 'GET'); + res.end('ok'); + }); + }); +});