314581 Implement the Sec-Websocket handshake

git-svn-id: svn+ssh://dev.eclipse.org/svnroot/rt/org.eclipse.jetty/jetty/trunk@1899 7e9141cc-0065-0410-87d8-b60c137991c4
This commit is contained in:
Greg Wilkins 2010-05-26 22:56:02 +00:00
parent 02be70f5e1
commit d29c110a31
8 changed files with 207 additions and 14 deletions

View File

@ -1,4 +1,6 @@
jetty-7.1.3
+ 314581 Implement the Sec-Websocket handshake
jetty-7.1.3.v20100526
+ 296567 HttpClient RedirectListener handles new HttpDestination
+ 297598 JDBCLoginService uses hardcoded credential class
+ 305898 Websocket handles query string in URI

View File

@ -1,12 +1,20 @@
package org.eclipse.jetty.websocket;
import java.io.IOException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.zip.Checksum;
import org.eclipse.jetty.http.HttpParser;
import org.eclipse.jetty.http.security.Credential.MD5;
import org.eclipse.jetty.io.AsyncEndPoint;
import org.eclipse.jetty.io.Buffer;
import org.eclipse.jetty.io.ByteArrayBuffer;
import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.nio.IndirectNIOBuffer;
import org.eclipse.jetty.io.nio.SelectChannelEndPoint;
import org.eclipse.jetty.util.TypeUtil;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.thread.Timeout;
@ -18,6 +26,9 @@ public class WebSocketConnection implements Connection, WebSocket.Outbound
final WebSocketGenerator _generator;
final long _timestamp;
final WebSocket _websocket;
String _key1;
String _key2;
ByteArrayBuffer _hixie;
public WebSocketConnection(WebSocket websocket, EndPoint endpoint)
throws IOException
@ -98,6 +109,13 @@ public class WebSocketConnection implements Connection, WebSocket.Outbound
};
}
}
public void setHixieKeys(String key1,String key2)
{
_key1=key1;
_key2=key2;
_hixie=new IndirectNIOBuffer(16);
}
public Connection handle() throws IOException
{
@ -105,6 +123,53 @@ public class WebSocketConnection implements Connection, WebSocket.Outbound
try
{
// handle stupid hixie random bytes
if (_hixie!=null)
{
while(progress)
{
// take bytes from the parser buffer.
if (_parser.getBuffer().length()>0)
{
int l=_parser.getBuffer().length();
if (l>8)
l=8;
_hixie.put(_parser.getBuffer().peek(_parser.getBuffer().getIndex(),l));
_parser.getBuffer().skip(l);
progress=true;
}
// do we have enough?
if (_hixie.length()<8)
{
// no, then let's fill
int filled=_endp.fill(_hixie);
progress |= filled>0;
if (filled<0)
{
_endp.close();
break;
}
}
// do we now have enough
if (_hixie.length()==8)
{
// we have the silly random bytes
// so let's work out the stupid 16 byte reply.
doTheHixieHixieShake();
_endp.flush(_hixie);
_hixie=null;
_endp.flush();
break;
}
}
return this;
}
// handle the framing protocol
while (progress)
{
int flushed=_generator.flush();
@ -138,6 +203,16 @@ public class WebSocketConnection implements Connection, WebSocket.Outbound
return this;
}
private void doTheHixieHixieShake()
{
byte[] result=WebSocketGenerator.doTheHixieHixieShake(
WebSocketParser.hixieCrypt(_key1),
WebSocketParser.hixieCrypt(_key2),
_hixie.asArray());
_hixie.clear();
_hixie.put(result);
}
public boolean isOpen()
{
return _endp!=null&&_endp.isOpen();

View File

@ -91,19 +91,38 @@ public class WebSocketFactory
HttpConnection http = HttpConnection.getCurrentConnection();
ConnectedEndPoint endp = (ConnectedEndPoint)http.getEndPoint();
WebSocketConnection connection = new WebSocketConnection(websocket,endp,_buffers,http.getTimeStamp(), _maxIdleTime);
String uri=request.getRequestURI();
String query=request.getQueryString();
if (query!=null && query.length()>0)
uri+="?"+query;
String host=request.getHeader("Host");
response.setHeader("Upgrade","WebSocket");
response.addHeader("Connection","Upgrade");
response.addHeader("WebSocket-Origin",origin);
response.addHeader("WebSocket-Location",(request.isSecure()?"wss://":"ws://")+host+uri);
if (protocol!=null)
response.addHeader("WebSocket-Protocol",protocol);
response.sendError(101,"Web Socket Protocol Handshake");
String key1 = request.getHeader("Sec-WebSocket-Key1");
if (key1!=null)
{
String key2 = request.getHeader("Sec-WebSocket-Key2");
connection.setHixieKeys(key1,key2);
response.setHeader("Upgrade","WebSocket");
response.addHeader("Connection","Upgrade");
response.addHeader("Sec-WebSocket-Origin",origin);
response.addHeader("Sec-WebSocket-Location",(request.isSecure()?"wss://":"ws://")+host+uri);
if (protocol!=null)
response.addHeader("Sec-WebSocket-Protocol",protocol);
response.sendError(101,"WebSocket Protocol Handshake");
}
else
{
response.setHeader("Upgrade","WebSocket");
response.addHeader("Connection","Upgrade");
response.addHeader("WebSocket-Origin",origin);
response.addHeader("WebSocket-Location",(request.isSecure()?"wss://":"ws://")+host+uri);
if (protocol!=null)
response.addHeader("WebSocket-Protocol",protocol);
response.sendError(101,"Web Socket Protocol Handshake");
}
response.flushBuffer();
connection.fill(((HttpParser)http.getParser()).getHeaderBuffer());
@ -111,6 +130,5 @@ public class WebSocketFactory
websocket.onConnect(connection);
request.setAttribute("org.eclipse.jetty.io.Connection",connection);
response.flushBuffer();
}
}

View File

@ -2,9 +2,13 @@ package org.eclipse.jetty.websocket;
import java.io.IOException;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import org.eclipse.jetty.io.Buffer;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.util.TypeUtil;
import org.eclipse.jetty.util.log.Log;
/* ------------------------------------------------------------ */
@ -157,4 +161,32 @@ public class WebSocketGenerator
{
return _buffer==null || _buffer.length()==0;
}
public static byte[] doTheHixieHixieShake(long key1,long key2,byte[] key3)
{
try
{
MessageDigest md = MessageDigest.getInstance("MD5");
byte [] fodder = new byte[16];
fodder[0]=(byte)(0xff&(key1>>24));
fodder[1]=(byte)(0xff&(key1>>16));
fodder[2]=(byte)(0xff&(key1>>8));
fodder[3]=(byte)(0xff&key1);
fodder[4]=(byte)(0xff&(key2>>24));
fodder[5]=(byte)(0xff&(key2>>16));
fodder[6]=(byte)(0xff&(key2>>8));
fodder[7]=(byte)(0xff&key2);
for (int i=0;i<8;i++)
fodder[8+i]=key3[i];
md.update(fodder);
byte[] result=md.digest();
return result;
}
catch (NoSuchAlgorithmException e)
{
throw new IllegalStateException(e);
}
}
}

View File

@ -190,10 +190,26 @@ public class WebSocketParser
_buffer.put(buffer);
buffer.clear();
}
}
/* ------------------------------------------------------------ */
static long hixieCrypt(String key)
{
// Don't ask me what all this is about.
// I think it's pretend secret stuff, kind of
// like talking in pig latin!
long number=0;
int spaces=0;
for (char c : key.toCharArray())
{
if (Character.isDigit(c))
number=number*10+(c-'0');
else if (c==' ')
spaces++;
}
return number/spaces;
}
/* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */

View File

@ -49,7 +49,11 @@ public abstract class WebSocketServlet extends HttpServlet
{
if ("WebSocket".equals(request.getHeader("Upgrade")))
{
String protocol=request.getHeader("WebSocket-Protocol");
boolean hixie = request.getHeader("Sec-WebSocket-Key1")!=null;
String protocol=request.getHeader(hixie?"Sec-WebSocket-Protocol":"WebSocket-Protocol");
if (protocol==null)
protocol=request.getHeader("Sec-WebSocket-Protocol");
WebSocket websocket=doWebSocketConnect(request,protocol);
String host=request.getHeader("Host");
@ -59,7 +63,11 @@ public abstract class WebSocketServlet extends HttpServlet
if (websocket!=null)
_websocket.upgrade(request,response,websocket,origin,protocol);
else
{
if (hixie)
response.setHeader("Connection","close");
response.sendError(503);
}
}
else
super.service(request,response);

View File

@ -1,8 +1,11 @@
package org.eclipse.jetty.websocket;
import java.security.MessageDigest;
import org.eclipse.jetty.io.ByteArrayBuffer;
import org.eclipse.jetty.io.ByteArrayEndPoint;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.TypeUtil;
import org.junit.Before;
import org.junit.Test;
@ -90,4 +93,35 @@ public class WebSocketGeneratorTest
for (int i=0;i<b.length;i++)
assertEquals('0'+(i%10),0xff&_out.get());
}
@Test
public void testHixie() throws Exception
{
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] result;
byte[] expected;
expected=md.digest(TypeUtil.fromHexString("00000000000000000000000000000000"));
result=WebSocketGenerator.doTheHixieHixieShake(
0 ,0, new byte[8]);
assertEquals(TypeUtil.toHexString(expected),TypeUtil.toHexString(result));
expected=md.digest(TypeUtil.fromHexString("01020304050607080000000000000000"));
result=WebSocketGenerator.doTheHixieHixieShake(
0x01020304,
0x05060708,
new byte[8]);
assertEquals(TypeUtil.toHexString(expected),TypeUtil.toHexString(result));
byte[] random = new byte[8];
for (int i=0;i<8;i++)
random[i]=(byte)(0xff&"Tm[K T2u".charAt(i));
result=WebSocketGenerator.doTheHixieHixieShake(
155712099,173347027,random);
StringBuilder b = new StringBuilder();
for (int i=0;i<16;i++)
b.append((char)result[i]);
assertEquals("fQJ,fN/4F4!~K~MH",b.toString());
}
}

View File

@ -129,6 +129,14 @@ public class WebSocketParserTest
assertTrue(_parser.getBuffer()==null);
}
@Test
public void testHixieCrypt() throws Exception
{
assertEquals(155712099,WebSocketParser.hixieCrypt("18x 6]8vM;54 *(5: { U1]8 z [ 8"));
assertEquals(173347027,WebSocketParser.hixieCrypt("1_ tx7X d < nw 334J702) 7]o}` 0"));
}
// TODO test:
// blocking,
// async