package com.juick.jabber.ws; import java.io.IOException; import java.math.BigInteger; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.SocketChannel; import java.nio.charset.Charset; import java.security.MessageDigest; import java.sql.Connection; import java.util.Iterator; /** * * @author ugnich */ public class WSData implements Runnable { Connection sql; public static Selector sel; public WSData(Connection sql) { this.sql = sql; } @Override public void run() { try { sel = Selector.open(); 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"); } } 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 (Exception e) { 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; buf.rewind(); CharBuffer charbuf = Charset.forName("ISO-8859-1").decode(buf); String headers[] = charbuf.toString().split("\r\n"); for (int i = 0; i < headers.length; i++) { String h[] = headers[i].split(" ", 2); if (h.length == 2) { if (h[0].equals("GET")) { hLocation = headers[i].split(" ", 3)[1]; } else if (h[0].equals("Origin:")) { 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]; } } } if (hOrigin == null || hHost == null || hLocation == null || hSecWebSocketKey1 == null || hSecWebSocketKey2 == null) { 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; } } if (hash != null) { UID = com.juick.server.UserQueries.getUIDbyHash(sql, hash); } } // URL String loc[] = hLocation.split("/"); 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)); } else { throw new IOException(sock.socket().getRemoteSocketAddress().toString() + " Invalid MID"); } } } 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]; } lTmp = sec2.toByteArray(); lIdx = lTmp.length; lCnt = 0; while (lIdx > 0 && lCnt < 4) { lIdx--; lCnt++; l128Bit[8 - lCnt] = lTmp[lIdx]; } buf.rewind(); for (int i = 0; i < 8; i++) { l128Bit[8 + i] = buf.get(buf.limit() - 8 + i); } 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); 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); } } long lRes = -1; if (lSpaces > 0) { try { lRes = Long.parseLong(lSB.toString()) / lSpaces; } catch (NumberFormatException ex) { // use default result } } return lRes; } 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) { } } else { System.out.println(sock.socket().getRemoteSocketAddress().toString() + " DATA '" + buf + "'"); } } }