Created
July 24, 2012 17:09
-
-
Save Jxck/3171239 to your computer and use it in GitHub Desktop.
Revisions
-
Jxck revised this gist
Jul 24, 2012 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -147,7 +147,7 @@ server.on('upgrade', function(req, socket, head) { , 'Sec-WebSocket-Accept: ' + key // option fields can be included // main is subprotocol that indicates server has selected , 'Sec-WebSocket-Protocol: ' + protocol ].concat('', '').join('\r\n'); socket.write(headers); -
Jxck revised this gist
Jul 24, 2012 . 1 changed file with 50 additions and 43 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -16,19 +16,23 @@ var host = 'localhost' , http_port = process.argv[2] || 3000 ; var clientScript = function () { var ws = new WebSocket("ws://localhost:3000/", ["test", "chat"]); // var ws = new WebSocket("ws://localhost:3000/", "test"); ws.onopen = function() { console.log(ws); ws.send("test"); ws.onmessage = function(message) { console.log(message.data); }; } } var server = http.createServer(function(req, res) { res.writeHead(200, {'Content-Type': 'text/html'}); var html = '<html><head><title>wsserver</title>' + '<script type="text/javascript">' + '(' + clientScript + ')();' + '</script>' + '</head>' + '<body>hello world</body>' + @@ -37,25 +41,29 @@ var server = http.createServer(function(req, res) { }); server.on('upgrade', function(req, socket, head) { /** * 1.2. Protocol Overview * http://tools.ietf.org/html/rfc6455#section-1.2 * * GET /chat HTTP/1.1 * Host: server.example.com * Upgrade: websocket * Connection: Upgrade * Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== * Origin: http://example.com * Sec-WebSocket-Protocol: chat, superchat * Sec-WebSocket-Version: 13 * * actual [new WebSocket('ws://localhost:3000/', 'test');] * { host: 'localhost:3000', * upgrade: 'websocket', * connection: 'Upgrade', * origin: 'http://localhost:3000', * 'sec-websocket-protocol': 'test, chat', * 'sec-websocket-extensions': 'x-webkit-deflate-frame', * 'sec-websocket-key': 'NblXHeIwGDpoQ2GFAGzwzw==', * 'sec-websocket-version': '13' } */ if (req.headers['sec-websocket-key1'] && req.headers['sec-websocket-key2']) { @@ -150,13 +158,16 @@ server.on('upgrade', function(req, socket, head) { log('\n======== Response Header ========='); log(headers); socket.on('data', function(receivedData) { /** * 5, Data Framing * http://tools.ietf.org/html/rfc6455#section-5.2 * Client must masks all frames because of * intermediaries(e.g proxy) and security reason. * Server must not mask all frames */ var firstByte = receivedData[0]; /** * fin * axxx xxxx first byte @@ -202,7 +213,7 @@ server.on('upgrade', function(req, socket, head) { assert.fail('this script dosen\'t supports without text'); } var secondByte = receivedData[1]; /** * mask @@ -233,17 +244,13 @@ server.on('upgrade', function(req, socket, head) { if (payloadLength === 0x7f) { assert.fail('next 64bit is length but not supported'); } /** * masking key * 3rd to 6th byte * (total 32bit) */ var maskingKey = receivedData.readUInt32BE(2); /** * Payload Data = Extention Data + Application Data @@ -261,7 +268,7 @@ server.on('upgrade', function(req, socket, head) { * length of this is payload length minus * extention data. */ var applicationData = receivedData.readUInt32BE(6); /** * unmask the data @@ -288,7 +295,7 @@ server.on('upgrade', function(req, socket, head) { log('unmasked', unmasked); log('encoded data:', encoded); console.log('\n======== Recieved Frame ==============='); display(receivedData); /** -
Jxck revised this gist
Jul 24, 2012 . 1 changed file with 30 additions and 27 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,19 +1,22 @@ // Reference // http://tools.ietf.org/html/rfc6455 // http://www.w3.org/TR/2011/WD-websockets-20110929/ // https://github.com/einaros/ws // https://github.com/Worlize/WebSocket-Node // http://ja.wikipedia.org/wiki/WebSocket // http://www.slideshare.net/You_Kinjoh/javascript-websocket P.68 // http://d.hatena.ne.jp/gtk2k/20120203/1328274962 var log = console.log.bind(console); var http = require('http'), assert = require('assert'); var host = 'localhost' , http_port = process.argv[2] || 3000 ; var server = http.createServer(function(req, res) { res.writeHead(200, {'Content-Type': 'text/html'}); var html = '<html><head><title>wsserver</title>' + '<script type="text/javascript">' + @@ -55,15 +58,15 @@ server.on('upgrade', function(req, socket, head) { // 'sec-websocket-version': '13' } if (req.headers['sec-websocket-key1'] && req.headers['sec-websocket-key2']) { assert.fail('Old Header hybi-00(hixi-76) to hybi-03'); } var version = parseInt(req.headers['sec-websocket-version']); // 13 is hibi-13 to 17 & RFC if (version !== 13) { var v; switch (version) { case 4: v = '04'; break; @@ -86,7 +89,7 @@ server.on('upgrade', function(req, socket, head) { } if (req.headers['sec-websocket-origin']) { assert.fail('Old Header'); } @@ -122,7 +125,7 @@ server.on('upgrade', function(req, socket, head) { // base64="s3pPLMBiTxaQ9kYGzzhZRbK+xOo=" key = require('crypto') .createHash('sha1') .update(key + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11') .digest('base64'); var headers = [ @@ -133,18 +136,18 @@ server.on('upgrade', function(req, socket, head) { , 'Upgrade: websocket' , 'Connection: Upgrade' // Accept will checked by client which is expected , 'Sec-WebSocket-Accept: ' + key // option fields can be included // main is subprotocol that indicates server has selected , 'Sec-WebSocket-Protocol: ' + protocol ].concat('', '').join('\r\n'); socket.write(headers); log('\n======== Request Header ========='); log(req.headers); log('\n======== Response Header ========='); log(headers); socket.on('data', function(data) { @@ -173,7 +176,7 @@ server.on('upgrade', function(req, socket, head) { */ var opcode = firstByte & 0x0f; var payloadType; switch (opcode) { case 0x0: payloadType = 'continuation'; break; @@ -195,7 +198,7 @@ server.on('upgrade', function(req, socket, head) { default: payloadType = 'reserved for non-control'; } if (payloadType !== 'text') { assert.fail('this script dosen\'t supports without text'); } @@ -210,7 +213,7 @@ server.on('upgrade', function(req, socket, head) { * 0000 0000 is not masked */ var mask = (secondByte & 0x80) >>> 7; if (mask === 0) { assert.fail('browse should always mask the payload data'); } @@ -224,10 +227,10 @@ server.on('upgrade', function(req, socket, head) { * 0111 1111 127(next UInt64) */ var payloadLength = (secondByte & 0x7f); if (payloadLength === 0x7e) { assert.fail('next 16bit is length but not supported'); } if (payloadLength === 0x7f) { assert.fail('next 64bit is length but not supported'); } // log('fin:', fin); @@ -250,7 +253,7 @@ server.on('upgrade', function(req, socket, head) { * extention data * 0 byte unless negotiated during handshake */ var extentionData = null; /** * application data @@ -325,20 +328,20 @@ console.log('Server running at', host, http_port); function display(buffer) { // display data frame function zeropadding(str, len) { while (str.length < len) { str = '0' + str; } return str; } var temp = []; for (var i = 0; i < buffer.length; i++) { var d = buffer[i]; var hex = d.toString(2); hex = zeropadding(hex, 8); temp.push(hex); } for (var j = 0; j < temp.length; j += 4) { log(temp[j], temp[j + 1], temp[j + 2], temp[j + 3]); } } -
Jxck revised this gist
Jul 24, 2012 . 1 changed file with 29 additions and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -284,6 +284,35 @@ server.on('upgrade', function(req, socket, head) { log('applicationData:', applicationData); log('unmasked', unmasked); log('encoded data:', encoded); console.log('\n======== Recieved Frame ==============='); display(data); /** * Sending data to client * data must not mask */ var sendData = new Buffer(6); // FIN:1, opcode:1 // 0x81 = 10000001 sendData[0] = 0x81; // MASK:0, len:4 // 0x4 = 100 sendData[1] = 0x4; // payload data // send data "test" sendData[2] = 'test'.charCodeAt(0); sendData[3] = 'test'.charCodeAt(1); sendData[4] = 'test'.charCodeAt(2); sendData[5] = 'test'.charCodeAt(3); console.log('\n======== Sending Frame ==============='); display(sendData); // send to client socket.end(sendData); }); }); -
Jxck revised this gist
Jul 24, 2012 . 1 changed file with 31 additions and 25 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -141,8 +141,11 @@ server.on('upgrade', function(req, socket, head) { socket.write(headers); log('\n======== Request Header =========') log(req.headers); log('\n======== Response Header =========') log(headers); socket.on('data', function(data) { // 5, Data Framing @@ -272,38 +275,41 @@ server.on('upgrade', function(req, socket, head) { var encoded = unmaskedBuf.toString(); log('======== Parsed Data ==============='); log('fin:', fin); log('opcode:', payloadType); log('mask:', mask); log('payloadLength:', payloadLength); log('maskingkey:', maskingKey); log('applicationData:', applicationData); log('unmasked', unmasked); log('encoded data:', encoded); }); }); server.listen(http_port); console.log('Server running at', host, http_port); function display(buffer) { // display data frame function zeropadding(str, len) { while(str.length < len) { str = '0' + str; } return str; } var temp = []; for(var i=0; i<buffer.length; i++) { var d=buffer[i]; var hex=d.toString(2); hex = zeropadding(hex, 8); temp.push(hex); }; for(var j=0; j<temp.length; j+=4) { log(temp[j], temp[j+1], temp[j+2], temp[j+3]); }; } -
Jxck revised this gist
Jul 24, 2012 . 1 changed file with 3 additions and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -22,6 +22,9 @@ var server = http.createServer(function (req, res) { 'ws.onopen = function() {' + ' console.log(ws);' + ' ws.send("test");' + ' ws.onmessage = function(message) {' + ' console.log(message.data);' + ' };' + '}' + '</script>' + '</head>' + -
Jxck revised this gist
Jul 24, 2012 . 1 changed file with 87 additions and 15 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -142,25 +142,25 @@ server.on('upgrade', function(req, socket, head) { log('response========\n',headers); socket.on('data', function(data) { // 5, Data Framing // Client must masks all frames because of // intermediaries(e.g proxy) and security reason. // Server must not mask all frames var firstByte = data[0]; /** * fin * axxx xxxx first byte * 1000 0000 mask with 0x80 >>> 7 * --------- * 1 is final frame * 0 is continue after this frame */ var fin = (firstByte & 0x80) >>> 7; /** * opcode * xxxx aaaa first byte * 0000 1111 mask with 0x0f * --------- * 0000 0001 is text frame @@ -197,33 +197,105 @@ log(data); /** * mask * axxx xxxx second byte * 1000 0000 mask with 0x80 * --------- * 1000 0000 is masked * 0000 0000 is not masked */ var mask = (secondByte & 0x80) >>> 7; if(mask === 0) { assert.fail('browse should always mask the payload data'); } /** * Payload Length * xaaa aaaa second byte * 0111 1111 mask with 0x7f * --------- * 0000 0100 4(4) * 0111 1110 126(next UInt16) * 0111 1111 127(next UInt64) */ var payloadLength = (secondByte & 0x7f); if(payloadLength === 0x7e) { assert.fail('next 16bit is length but not supported'); } if(payloadLength === 0x7f) { assert.fail('next 64bit is length but not supported'); } // log('fin:', fin); // log('opcode:', payloadType); // log('mask:', mask); // log('payloadLength', payloadLength); // log('maskingkey', maskingKey); /** * masking key * 3rd to 6th byte * (total 32bit) */ var maskingKey = data.readUInt32BE(2); /** * Payload Data = Extention Data + Application Data */ /** * extention data * 0 byte unless negotiated during handshake */ var extentionData=null; /** * application data * remainder of frame after extention data. * length of this is payload length minus * extention data. */ var applicationData = data.readUInt32BE(6); /** * unmask the data * application data XOR mask */ var unmasked = applicationData ^ maskingKey; /** * write to temp buffer and * encoding to utf8 */ var unmaskedBuf = new Buffer(4); unmaskedBuf.writeInt32BE(unmasked, 0); var encoded = unmaskedBuf.toString(); log('fin:', fin); log('opcode:', payloadType); log('mask:', mask); log('payloadLength', payloadLength); log('maskingkey', maskingKey); log('applicationData', applicationData); log('encoded data:', encoded); // display data frame function zeropadding(str, len) { while(str.length < len) { str = '0' + str; } return str; } var temp = []; for(var i=0; i<data.length; i++) { var d=data[i]; var hex=d.toString(2); hex = zeropadding(hex, 8); temp.push(hex); }; log('\n'); log('======== Data Frame ==============='); for(var j=0; j<temp.length; j+=4) { log(temp[j], temp[j+1], temp[j+2], temp[j+3]); }; }); }); -
Jxck revised this gist
Jul 24, 2012 . 1 changed file with 41 additions and 8 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -31,7 +31,7 @@ var server = http.createServer(function (req, res) { }); server.on('upgrade', function(req, socket, head) { // 1.2. Protocol Overview // GET /chat HTTP/1.1 // Host: server.example.com // Upgrade: websocket @@ -45,14 +45,15 @@ server.on('upgrade', function(req, socket, head) { // { host: 'localhost:3000', // upgrade: 'websocket', // connection: 'Upgrade', // origin: 'http://localhost:3000', // 'sec-websocket-protocol': 'test, chat', // 'sec-websocket-extensions': 'x-webkit-deflate-frame', // 'sec-websocket-key': 'NblXHeIwGDpoQ2GFAGzwzw==', // 'sec-websocket-version': '13' } if(req.headers['sec-websocket-key1'] && req.headers['sec-websocket-key2']) { assert.fail('Old Header hybi-00(hixi-76) to hybi-03'); } var version = parseInt(req.headers['sec-websocket-version']); @@ -86,30 +87,62 @@ server.on('upgrade', function(req, socket, head) { assert.fail('Old Header'); } // Subprotocol selector // this case, use first one var protocol = req.headers['sec-websocket-protocol'].split(',')[0]; // list of extensions support by the client // this used to indecate application-level protocol // server selects one or none of acceptable protocol // echoes that value in its handshake. var extensions = req.headers['sec-websocket-extensions']; // use for reject browser if not acceptable // you can reject by sending appropriate HTTP error code var origin = req.headers['origin']; // to prove the client that handshake was recieved, // use the sec-websocket-key to prevent an attacker // from sending crafted XHR or Form packet. var key = req.headers['sec-websocket-key']; // take sec-websocket-key without any trailing whitespace // concatnate this with Globally Unique Identifier // "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"(GUID, [RFC4122]) // A Sha1 hash(160 bits), base64 encoded of this is for returning. // this would be echoed in the |Sec-WebSocket-Accept| header field. // // test case // sec-websocket-key="dGhlIHNhbXBsZSBub25jZQ==" // cat="dGhlIHNhbXBsZSBub25jZQ==258EAFA5-E914-47DA-95CA-C5AB0DC85B11" // sha1="0xb3 0x7a 0x4f 0x2c 0xc0 0x62 0x4f 0x16 0x90 0xf6 0x46 0x06 0xcf 0x38 0x59 0x45 0xb2 0xbe 0xc4 0xea" // base64="s3pPLMBiTxaQ9kYGzzhZRbK+xOo=" key = require('crypto') .createHash('sha1') .update(key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11") .digest('base64'); var headers = [ // The first line is an HTTP Status-Line // code 101, Switching Protocols 'HTTP/1.1 101 Switching Protocols' // Upgrade, Connection fields complete the Upgrade , 'Upgrade: websocket' , 'Connection: Upgrade' // Accept will checked by client which is expected , 'Sec-WebSocket-Accept: '+key // option fields can be included // main is subprotocol that indicates server has selected , 'Sec-WebSocket-Protocol: '+protocol ].concat('','').join('\r\n'); socket.write(headers); log('request=========\n',req.headers); log('response========\n',headers); socket.on('data', function(data) { log(data); var firstByte = data[0]; /** -
Jxck revised this gist
Jul 24, 2012 . 1 changed file with 15 additions and 3 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -10,12 +10,24 @@ var http = require('http'), var host = 'localhost' , http_port = process.argv[2] || 3000 ; var server = http.createServer(function (req, res) { res.writeHead(200, {'Content-Type': 'text/html'}); var html = '<html><head><title>wsserver</title>' + '<script type="text/javascript">' + 'var ws = new WebSocket("ws://localhost:3000/", ["test", "chat"]);' + // 'var ws = new WebSocket("ws://localhost:3000/", "test");' + 'ws.onopen = function() {' + ' console.log(ws);' + ' ws.send("test");' + '}' + '</script>' + '</head>' + '<body>hello world</body>' + '<html>'; res.end(html); }); server.on('upgrade', function(req, socket, head) { -
Jxck renamed this gist
Jul 19, 2012 . 1 changed file with 0 additions and 0 deletions.There are no files selected for viewing
File renamed without changes. -
Jxck revised this gist
Jul 20, 2012 . 2 changed files with 189 additions and 9 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,9 +0,0 @@ This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,189 @@ // Reference // http://ja.wikipedia.org/wiki/WebSocket // http://www.slideshare.net/You_Kinjoh/javascript-websocket P.68 // http://d.hatena.ne.jp/gtk2k/20120203/1328274962 log = console.log.bind(console); var http = require('http'), assert = require('assert'); var host = 'localhost' , http_port = process.argv[2] ; var server = http.createServer(function (req, res) { res.writeHead(200, {'Content-Type': 'text/plain'}); res.end('Hello World\n'); }); server.on('upgrade', function(req, socket, head) { // draft // GET /chat HTTP/1.1 // Host: server.example.com // Upgrade: websocket // Connection: Upgrade // Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== // Origin: http://example.com // Sec-WebSocket-Protocol: chat, superchat // Sec-WebSocket-Version: 13 // actual [new WebSocket('ws://localhost:3000/', 'test');] // { host: 'localhost:3000', // upgrade: 'websocket', // connection: 'Upgrade', // 'sec-websocket-protocol': 'test', // 'sec-websocket-key': 'NblXHeIwGDpoQ2GFAGzwzw==', // origin: 'http://127.0.0.1:3000', // 'sec-websocket-version': '13' } if(req.headers['sec-websocket-key1'] && req.headers['sec-websocket-key2']) { assert.fail('Old Header' + 'hybi-00(hixi-76) to hybi-03'); } var version = parseInt(req.headers['sec-websocket-version']); // 13 is hibi-13 to 17 & RFC if(version !== 13) { var v; switch(version) { case 4: v = '04'; break; case 5: v = '05'; break; case 6: v = '06'; break; case 7: v = '07'; break; case 8: v = '08-12'; break; default: v = 'unknown'; } assert.fail('Old version hibi-' + v); } if(req.headers['sec-websocket-origin']) { assert.fail('Old Header'); } var origin = req.headers['origin']; var protocol = req.headers['sec-websocket-protocol']; var key = req.headers['sec-websocket-key']; log('protocol', protocol); key = require('crypto') .createHash('sha1') .update(key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11") .digest('base64'); var headers = [ 'HTTP/1.1 101 Switching Protocols' , 'Upgrade: websocket' , 'Connection: Upgrade' , 'Sec-WebSocket-Accept: '+key , 'Sec-WebSocket-Protocol: '+protocol ].concat('','').join('\r\n'); socket.write(headers); log('req',req.headers); log('res',headers); socket.on('data', function(data) { var firstByte = data[0]; /** * fin * axxx xxxx row * 1000 0000 mask with 0x80 * --------- * 1000 0000 is final frame * 0000 0000 is continue after this frame */ var fin = (firstByte & 0x80) !== 0; if(!fin) { assert.fail('this script dosen\'t supports multi frame'); } /** * opcode * xxxx aaaa row * 0000 1111 mask with 0x0f * --------- * 0000 0001 is text frame */ var opcode = firstByte & 0x0f; var payloadType; switch(opcode) { case 0x0: payloadType = 'continuation'; break; case 0x1: payloadType = 'text'; break; case 0x2: payloadType = 'binary'; break; case 0x8: payloadType = 'connection close'; break; case 0x9: payloadType = 'ping'; break; case 0xA: payloadType = 'pong'; break; default: payloadType = 'reserved for non-control'; } if(payloadType !== 'text') { assert.fail('this script dosen\'t supports without text'); } var secondByte = data[1]; /** * mask * axxx xxxx row * 1000 0000 mask with 0x80 * --------- * 1000 0000 is masked * 0000 0000 is not masked */ var mask = (secondByte & 0x80) !== 0; if(!mask) { assert.fail('browse should always mask the payload data'); } // log('fin:', fin); // log('opcode:', payloadType); // log('mask:', mask); function zeropadding(str, len) { while(str.length < len) { str = '0' + str; } return str; } for(var i=0; i<data.length; i++) { var d=data[i]; var hex=d.toString(2); d = zeropadding(d.toString(), 3); hex = zeropadding(hex, 8); log(d, hex); }; }); }); server.listen(http_port); console.log('Server running at', host, http_port); -
Jxck revised this gist
Jul 19, 2012 . 1 changed file with 9 additions and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1 +1,9 @@ # hoge ## gufa ### test ```javascript console.log('hello'); ``` -
Jxck created this gist
Jul 19, 2012 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1 @@ #hoge