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:
parent
02be70f5e1
commit
d29c110a31
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/* ------------------------------------------------------------ */
|
||||
/* ------------------------------------------------------------ */
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue