From 2a8c4f613f397619c563fbb74c04e39074d6e98e Mon Sep 17 00:00:00 2001 From: Ugnich Anton Date: Wed, 14 Aug 2013 09:21:47 +0700 Subject: RFC 6455 --- src/com/juick/jabber/ws/WSData.java | 337 ++++++++++++++++++++---------------- 1 file changed, 190 insertions(+), 147 deletions(-) (limited to 'src/com/juick/jabber/ws/WSData.java') diff --git a/src/com/juick/jabber/ws/WSData.java b/src/com/juick/jabber/ws/WSData.java index 97193510..d77257e6 100644 --- a/src/com/juick/jabber/ws/WSData.java +++ b/src/com/juick/jabber/ws/WSData.java @@ -1,14 +1,19 @@ package com.juick.jabber.ws; +import com.juick.server.MessagesQueries; +import com.juick.server.UserQueries; +import com.juick.xmpp.utils.Base64; import java.io.IOException; -import java.math.BigInteger; +import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; +import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.nio.charset.Charset; import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import java.sql.Connection; import java.util.Iterator; @@ -17,46 +22,77 @@ import java.util.Iterator; * @author ugnich */ public class WSData implements Runnable { - + + static final String WEBSOCKET_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; Connection sql; - public static Selector sel; - + public Selector sel; + public WSData(Connection sql) { this.sql = sql; } - + @Override public void run() { try { sel = Selector.open(); + ServerSocketChannel listensock = ServerSocketChannel.open(); + listensock.configureBlocking(false); + listensock.socket().bind(new InetSocketAddress(8081)); + listensock.register(sel, SelectionKey.OP_ACCEPT); + while (true) { sel.select(); Iterator it = sel.selectedKeys().iterator(); while (it.hasNext()) { SelectionKey selKey = it.next(); it.remove(); - - SocketChannel sChannel = (SocketChannel) selKey.channel(); - ByteBuffer buf = ByteBuffer.allocate(10240); - try { - if (sChannel.read(buf) > 0) { - buf.flip(); - CharBuffer charbuf = Charset.forName("ISO-8859-1").decode(buf); - if (charbuf.charAt(0) == 0 && charbuf.charAt(charbuf.length() - 1) == 0xFF) { - wsTextFrame(sChannel, charbuf.subSequence(1, charbuf.length() - 2)); - } else if (charbuf.charAt(0) == 'G' && charbuf.charAt(1) == 'E' && charbuf.charAt(2) == 'T' && charbuf.charAt(3) == ' ') { - wsHandshake(sChannel, buf); - } else { - throw new IOException(sChannel.socket().getRemoteSocketAddress().toString() + " INVALID FRAME"); + + if (selKey.isAcceptable()) { + ServerSocketChannel ssChannel = (ServerSocketChannel) selKey.channel(); + SocketChannel sChannel = ssChannel.accept(); + sChannel.configureBlocking(false); + sChannel.register(sel, SelectionKey.OP_READ); + System.out.println(sChannel.socket().getRemoteSocketAddress().toString() + " ACCEPTED"); + } else if (selKey.isReadable()) { + SocketChannel sChannel = (SocketChannel) selKey.channel(); + ByteBuffer buf = ByteBuffer.allocate(10240); + try { + int readbytes = sChannel.read(buf); + if (readbytes > 0) { + buf.flip(); + + CharBuffer charbuf = Charset.forName("ISO-8859-1").decode(buf); + System.out.println("DATA: " + charbuf.toString()); + buf.rewind(); + + switch (buf.get(0)) { + case (byte) 0x89: // PING + updateSocketTS(sChannel); + wsPing(sChannel); + break; + case (byte) 0x8A: // PONG + updateSocketTS(sChannel); + break; + case (byte) 0x81: // TEXT FRAME + updateSocketTS(sChannel); + wsTextFrame(sChannel, buf); + break; + case (byte) 'G': // HTTP + updateSocketTS(sChannel); + wsHandshake(sChannel, buf); + break; + case (byte) 0x88: // CONNECTION CLOSE + throw new IOException(sChannel.socket().getRemoteSocketAddress().toString() + " CONNECTION CLOSE"); + } + } else if (readbytes < 0) { + throw new IOException(sChannel.socket().getRemoteSocketAddress().toString() + " END OF STREAM"); } - } else { - throw new IOException(sChannel.socket().getRemoteSocketAddress().toString()+ " NO DATA"); + } catch (IOException e) { + System.err.println("WSData: " + e); + sChannel.socket().close(); + sChannel.close(); + selKey.cancel(); } - } catch (IOException e) { - System.err.println("WSData: " + e); - sChannel.socket().close(); - sChannel.close(); - selKey.cancel(); } } } @@ -64,15 +100,15 @@ public class WSData implements Runnable { System.err.println("WSData: " + e); } } - + public void wsHandshake(SocketChannel sock, ByteBuffer buf) throws Exception { String hOrigin = null; String hHost = null; String hLocation = null; - String hSecWebSocketKey1 = null; - String hSecWebSocketKey2 = null; - String hCookie = null; - + String hSecWebSocketKey = null; + String hSecWebSocketVersion = null; + String hXRealIP = null; + buf.rewind(); CharBuffer charbuf = Charset.forName("ISO-8859-1").decode(buf); String headers[] = charbuf.toString().split("\r\n"); @@ -85,149 +121,156 @@ public class WSData implements Runnable { hOrigin = h[1]; } else if (h[0].equals("Host:")) { hHost = h[1]; - } else if (h[0].equals("Sec-WebSocket-Key1:")) { - hSecWebSocketKey1 = h[1]; - } else if (h[0].equals("Sec-WebSocket-Key2:")) { - hSecWebSocketKey2 = h[1]; - } else if (h[0].equals("Cookie:")) { - hCookie = h[1]; + } else if (h[0].equals("Sec-WebSocket-Key:")) { + hSecWebSocketKey = h[1]; + } else if (h[0].equals("Sec-WebSocket-Version:")) { + hSecWebSocketVersion = h[1]; + } else if (h[0].equals("X-Real-IP:")) { + hXRealIP = h[1]; } } } - - if (hOrigin == null || hHost == null || hLocation == null || hSecWebSocketKey1 == null || hSecWebSocketKey2 == null) { + + if (hOrigin == null || hHost == null || hLocation == null || hSecWebSocketKey == null || hSecWebSocketVersion == null || !hSecWebSocketVersion.equals("13")) { throw new IOException(sock.socket().getRemoteSocketAddress().toString() + " Invalid headers"); } - // Cookies - int UID = 0; - String hash = null; - if (hCookie != null) { - String cookies[] = hCookie.split("; "); - for (int i = 0; i < cookies.length; i++) { - String cookie[] = cookies[i].split("=", 2); - if (cookie[0].equals("hash")) { - hash = cookie[1]; - break; - } + // Auth + int VUID = 0; + int hashloc = hLocation.indexOf("hash="); + if (hashloc > 0) { + String hash = hLocation.substring(hashloc + 5); + if (hash.indexOf('&') > 0) { + hash = hash.substring(0, hash.indexOf('&')); } - if (hash != null) { - UID = com.juick.server.UserQueries.getUIDbyHash(sql, hash); + if (hash.length() == 16) { + VUID = com.juick.server.UserQueries.getUIDbyHash(sql, hash); } } // URL - String loc[] = hLocation.split("/"); + int hLocationQM = hLocation.indexOf('?'); + if (hLocationQM > 0) { + hLocation = hLocation.substring(0, hLocationQM); + } + int MID = 0; - if (hLocation.equals("/my") && UID > 0) { - Main.sockMessages.add(new SocketSubscribed(sock, UID, 0)); - } else if (hLocation.equals("/all")) { - Main.sockAll.add(new SocketSubscribed(sock, UID, 0)); - } else if ((loc.length == 2 || loc.length == 3) && loc[1].equals("replies")) { - if (loc.length == 2) { - Main.sockReplies.add(new SocketSubscribed(sock, UID, 0)); - } else { - try { - MID = Integer.parseInt(loc[2]); - } catch (Exception e) { - } - if (MID > 0) { - Main.sockReplies.add(new SocketSubscribed(sock, UID, MID)); + int responseCode = 404; + SocketSubscribed sockSubscr = null; + if (hLocation.equals("/") && VUID > 0) { + sockSubscr = new SocketSubscribed(sock, hXRealIP, VUID); + responseCode = 101; + } else if (hLocation.equals("/_all")) { + sockSubscr = new SocketSubscribed(sock, hXRealIP, VUID); + sockSubscr.allMessages = true; + responseCode = 101; + } else if (hLocation.equals("/_replies")) { + sockSubscr = new SocketSubscribed(sock, hXRealIP, VUID); + sockSubscr.allReplies = true; + responseCode = 101; + } else if (hLocation.matches("^/\\d+$")) { + try { + MID = Integer.parseInt(hLocation.substring(1)); + } catch (Exception e) { + } + if (MID > 0) { + if (MessagesQueries.canViewThread(sql, MID, VUID)) { + sockSubscr = new SocketSubscribed(sock, hXRealIP, VUID); + sockSubscr.MID = MID; + responseCode = 101; } else { - throw new IOException(sock.socket().getRemoteSocketAddress().toString() + " Invalid MID"); + responseCode = 403; } } - } else { - throw new IOException(sock.socket().getRemoteSocketAddress().toString() + " Invalid location"); - } - - System.out.println(sock.socket().getRemoteSocketAddress().toString() + " HANDSHAKE (Hash=" + hash + "; UID = " + UID + "; MID = " + MID + ")"); - - Long lSecNum1 = calcSecKeyNum(hSecWebSocketKey1); - Long lSecNum2 = calcSecKeyNum(hSecWebSocketKey2); - - BigInteger sec1 = new BigInteger(lSecNum1.toString()); - BigInteger sec2 = new BigInteger(lSecNum2.toString()); - - // concatenate 3 parts secNum1 + secNum2 + secKey (16 Bytes) - byte[] l128Bit = new byte[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; - byte[] lTmp; - - lTmp = sec1.toByteArray(); - int lIdx = lTmp.length; - int lCnt = 0; - while (lIdx > 0 && lCnt < 4) { - lIdx--; - lCnt++; - l128Bit[4 - lCnt] = lTmp[lIdx]; + } else if (hLocation.matches("^/[a-zA-Z0-9\\-]{2,16}/?$")) { + String uname; + if (hLocation.endsWith("/")) { + uname = hLocation.substring(1, hLocation.length() - 2); + } else { + uname = hLocation.substring(1); + } + + int UID = UserQueries.getUIDbyName(sql, uname); + if (UID > 0) { + // check access + sockSubscr = new SocketSubscribed(sock, hXRealIP, VUID); + sockSubscr.UID = UID; + responseCode = 101; + } } - - lTmp = sec2.toByteArray(); - lIdx = lTmp.length; - lCnt = 0; - while (lIdx > 0 && lCnt < 4) { - lIdx--; - lCnt++; - l128Bit[8 - lCnt] = lTmp[lIdx]; + if (sockSubscr != null) { + synchronized (Main.clients) { + Main.clients.add(sockSubscr); + } } - buf.rewind(); - for (int i = 0; i < 8; i++) { - l128Bit[8 + i] = buf.get(buf.limit() - 8 + i); + // Response + String outstr; + if (responseCode == 101) { + outstr = "HTTP/1.1 101 Switching Protocols\r\n" + + "Upgrade: websocket\r\n" + + "Connection: Upgrade\r\n" + + "Sec-WebSocket-Accept: " + calcHeaderAccept(hSecWebSocketKey) + "\r\n" + + "\r\n"; + } else if (responseCode == 403) { + outstr = "HTTP/1.1 403 Forbidden\r\n\r\n"; + } else { + outstr = "HTTP/1.1 404 Not Found\r\n\r\n"; } - - String outstr = "HTTP/1.1 101 Web Socket Protocol Handshake\r\n" - + "Upgrade: WebSocket\r\n" - + "Connection: Upgrade\r\n" - + "Sec-WebSocket-Origin: " + hOrigin + "\r\n" - + "Sec-WebSocket-Location: ws://" + hHost + hLocation + "\r\n" - + "Sec-WebSocket-Protocol: sample\r\n" - + "\r\n"; - ByteBuffer out = ByteBuffer.allocate(4096); + ByteBuffer out = ByteBuffer.allocate(1024); out.put(Charset.forName("ISO-8859-1").encode(outstr)); - out.put(MessageDigest.getInstance("MD5").digest(l128Bit)); out.flip(); - sock.write(out); - } - - private static long calcSecKeyNum(String aKey) { - StringBuilder lSB = new StringBuilder(); - int lSpaces = 0; - for (int i = 0; i < aKey.length(); i++) { - char lC = aKey.charAt(i); - if (lC == ' ') { - lSpaces++; - } else if (lC >= '0' && lC <= '9') { - lSB.append(lC); - } + + if (responseCode == 101) { + System.out.println(sock.socket().getRemoteSocketAddress().toString() + " HANDSHAKE (VUID = " + VUID + "; MID = " + MID + ")"); + } else { + throw new IOException(sock.socket().getRemoteSocketAddress().toString() + " " + responseCode); } - long lRes = -1; - if (lSpaces > 0) { - try { - lRes = Long.parseLong(lSB.toString()) / lSpaces; - } catch (NumberFormatException ex) { - // use default result - } + } + + private String calcHeaderAccept(String key) { + String base = key + WEBSOCKET_GUID; + try { + MessageDigest md = MessageDigest.getInstance("SHA-1"); + return Base64.encode(md.digest(base.getBytes())); + } catch (NoSuchAlgorithmException e) { + System.err.println("calcHeaderAccept: " + e); } - return lRes; + return ""; } - - public void wsTextFrame(SocketChannel sock, CharSequence csbuf) { - String buf = csbuf.toString(); - if (buf.equals(" ")) { - ByteBuffer out = ByteBuffer.allocate(4); - out.put((byte) 0x00); - out.put((byte) 0x20); - out.put((byte) 0xFF); - out.flip(); - out.rewind(); - try { - sock.write(out); - } catch (IOException e) { + + public void wsPing(SocketChannel sock) throws Exception { + ByteBuffer out = ByteBuffer.allocate(2); + out.put((byte) 0x8A); // PONG FRAME + out.put((byte) 0x00); // 1 byte long + out.flip(); + out.rewind(); + sock.write(out); + } + + public void wsTextFrame(SocketChannel sock, ByteBuffer buf) throws Exception { + /* + ByteBuffer out = ByteBuffer.allocate(3); + out.put((byte) 0x81); // TEXT FRAME + out.put((byte) 0x01); // 1 byte long + out.put((byte) 0x20); // ' ' + out.flip(); + out.rewind(); + sock.write(out); + */ + } + + public void updateSocketTS(SocketChannel sock) { + synchronized (Main.clients) { + Iterator i = Main.clients.iterator(); + while (i.hasNext()) { + SocketSubscribed s = i.next(); + if (s.sock == sock) { + s.tsLastData = System.currentTimeMillis(); + break; + } } - } else { - System.out.println(sock.socket().getRemoteSocketAddress().toString() + " DATA '" + buf + "'"); } } } -- cgit v1.2.3