diff --git a/VERSION.txt b/VERSION.txt index cd06f959ac4..4640b1e35da 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -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 diff --git a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketConnection.java b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketConnection.java index 0935b740e65..6f37933cdfa 100644 --- a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketConnection.java +++ b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketConnection.java @@ -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(); diff --git a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketFactory.java b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketFactory.java index 1b3fc5b3c1c..37f5de71d09 100644 --- a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketFactory.java +++ b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketFactory.java @@ -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(); } } diff --git a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketGenerator.java b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketGenerator.java index 75b607d7570..7c4b4ef0422 100644 --- a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketGenerator.java +++ b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketGenerator.java @@ -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); + } + } + } diff --git a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketParser.java b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketParser.java index e0a0160837c..49449fbe521 100644 --- a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketParser.java +++ b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketParser.java @@ -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; + } + /* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */ diff --git a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketServlet.java b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketServlet.java index e89d5f31d09..43abe6e7be1 100644 --- a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketServlet.java +++ b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketServlet.java @@ -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); diff --git a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketGeneratorTest.java b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketGeneratorTest.java index 5fbe1512dad..755d905af18 100644 --- a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketGeneratorTest.java +++ b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketGeneratorTest.java @@ -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