From d416f39516085329263dccfe2521f5fe64ed6cf5 Mon Sep 17 00:00:00 2001 From: Ugnich Anton Date: Sun, 25 Dec 2011 15:11:43 +0700 Subject: Initial commit --- .gitignore | 3 + build.xml | 74 +++ manifest.mf | 3 + nbproject/build-impl.xml | 1075 +++++++++++++++++++++++++++++++++++++ nbproject/genfiles.properties | 8 + nbproject/project.properties | 80 +++ nbproject/project.xml | 42 ++ src/com/juick/jabber/ws/Main.java | 518 ++++++++++++++++++ 8 files changed, 1803 insertions(+) create mode 100644 .gitignore create mode 100644 build.xml create mode 100644 manifest.mf create mode 100644 nbproject/build-impl.xml create mode 100644 nbproject/genfiles.properties create mode 100644 nbproject/project.properties create mode 100644 nbproject/project.xml create mode 100644 src/com/juick/jabber/ws/Main.java diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..315ea15c --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/dist/ +/build/ +/nbproject/private/ \ No newline at end of file diff --git a/build.xml b/build.xml new file mode 100644 index 00000000..c7a42b44 --- /dev/null +++ b/build.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + Builds, tests, and runs the project com.juick.jabber.ws. + + + diff --git a/manifest.mf b/manifest.mf new file mode 100644 index 00000000..328e8e5b --- /dev/null +++ b/manifest.mf @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +X-COMMENT: Main-Class will be added automatically by build + diff --git a/nbproject/build-impl.xml b/nbproject/build-impl.xml new file mode 100644 index 00000000..d38c01df --- /dev/null +++ b/nbproject/build-impl.xml @@ -0,0 +1,1075 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must set src.dir + Must set build.dir + Must set dist.dir + Must set build.classes.dir + Must set dist.javadoc.dir + Must set build.test.classes.dir + Must set build.test.results.dir + Must set build.classes.excludes + Must set dist.jar + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must set javac.includes + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must set JVM to use for profiling in profiler.info.jvm + Must set profiler agent JVM arguments in profiler.info.jvmargs.agent + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must select some files in the IDE or set javac.includes + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + To run this application from the command line without Ant, try: + + + + + + + java -cp "${run.classpath.with.dist.jar}" ${main.class} + + + + + + + + + + + + + + + + + + + + + + + + + To run this application from the command line without Ant, try: + + java -jar "${dist.jar.resolved}" + + + + + + + + + + + + + + + + + + + + + + + + + Must select one file in the IDE or set run.class + + + + Must select one file in the IDE or set run.class + + + + + + + + + + + + + + + + + + + + + + + Must select one file in the IDE or set debug.class + + + + + Must select one file in the IDE or set debug.class + + + + + Must set fix.includes + + + + + + + + + + + + + + + + + Must select one file in the IDE or set profile.class + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must select some files in the IDE or set javac.includes + + + + + + + + + + + + + + + + + + Some tests failed; see details above. + + + + + + + + + Must select some files in the IDE or set test.includes + + + + Some tests failed; see details above. + + + + + Must select one file in the IDE or set test.class + + + + + + + + + + + + + + + + + + + + + + + + + + + Must select one file in the IDE or set applet.url + + + + + + + + + Must select one file in the IDE or set applet.url + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/nbproject/genfiles.properties b/nbproject/genfiles.properties new file mode 100644 index 00000000..d01461cb --- /dev/null +++ b/nbproject/genfiles.properties @@ -0,0 +1,8 @@ +build.xml.data.CRC32=935965cc +build.xml.script.CRC32=2ec0c6a9 +build.xml.stylesheet.CRC32=28e38971@1.44.1.45 +# This file is used by a NetBeans-based IDE to track changes in generated files such as build-impl.xml. +# Do not edit this file. You may delete it but then the IDE will never regenerate such files for you. +nbproject/build-impl.xml.data.CRC32=935965cc +nbproject/build-impl.xml.script.CRC32=3d733aec +nbproject/build-impl.xml.stylesheet.CRC32=0ae3a408@1.44.1.45 diff --git a/nbproject/project.properties b/nbproject/project.properties new file mode 100644 index 00000000..75d09036 --- /dev/null +++ b/nbproject/project.properties @@ -0,0 +1,80 @@ +annotation.processing.enabled=true +annotation.processing.enabled.in.editor=false +annotation.processing.run.all.processors=true +annotation.processing.source.output=${build.generated.sources.dir}/ap-source-output +application.title=com.juick.jabber.ws +application.vendor=ugnich +build.classes.dir=${build.dir}/classes +build.classes.excludes=**/*.java,**/*.form +# This directory is removed when the project is cleaned: +build.dir=build +build.generated.dir=${build.dir}/generated +build.generated.sources.dir=${build.dir}/generated-sources +# Only compile against the classpath explicitly listed here: +build.sysclasspath=ignore +build.test.classes.dir=${build.dir}/test/classes +build.test.results.dir=${build.dir}/test/results +# Uncomment to specify the preferred debugger connection transport: +#debug.transport=dt_socket +debug.classpath=\ + ${run.classpath} +debug.test.classpath=\ + ${run.test.classpath} +# This directory is removed when the project is cleaned: +dist.dir=dist +dist.jar=${dist.dir}/com.juick.jabber.ws.jar +dist.javadoc.dir=${dist.dir}/javadoc +endorsed.classpath= +excludes= +includes=** +jar.compress=false +javac.classpath=\ + ${reference.com_juick_xmpp.jar}:\ + ${reference.com_juick_server.jar}:\ + ${reference.com_juick.jar} +# Space-separated list of extra javac options +javac.compilerargs= +javac.deprecation=false +javac.processorpath=\ + ${javac.classpath} +javac.source=1.6 +javac.target=1.6 +javac.test.classpath=\ + ${javac.classpath}:\ + ${build.classes.dir} +javac.test.processorpath=\ + ${javac.test.classpath} +javadoc.additionalparam= +javadoc.author=false +javadoc.encoding=${source.encoding} +javadoc.noindex=false +javadoc.nonavbar=false +javadoc.notree=false +javadoc.private=false +javadoc.splitindex=true +javadoc.use=true +javadoc.version=false +javadoc.windowtitle= +main.class=com.juick.jabber.ws.Main +manifest.file=manifest.mf +meta.inf.dir=${src.dir}/META-INF +mkdist.disabled=true +platform.active=default_platform +project.com_juick=../com.juick +project.com_juick_server=../com.juick.server +project.com_juick_xmpp=../com.juick.xmpp +reference.com_juick.jar=${project.com_juick}/dist/com.juick.jar +reference.com_juick_server.jar=${project.com_juick_server}/dist/com.juick.server.jar +reference.com_juick_xmpp.jar=${project.com_juick_xmpp}/dist/com.juick.xmpp.jar +run.classpath=\ + ${javac.classpath}:\ + ${build.classes.dir} +# Space-separated list of JVM arguments used when running the project +# (you may also define separate properties like run-sys-prop.name=value instead of -Dname=value +# or test-sys-prop.name=value to set system properties for unit tests): +run.jvmargs= +run.test.classpath=\ + ${javac.test.classpath}:\ + ${build.test.classes.dir} +source.encoding=UTF-8 +src.dir=src diff --git a/nbproject/project.xml b/nbproject/project.xml new file mode 100644 index 00000000..0445b566 --- /dev/null +++ b/nbproject/project.xml @@ -0,0 +1,42 @@ + + + org.netbeans.modules.java.j2seproject + + + com.juick.jabber.ws + + + + + + + + com_juick + jar + + jar + clean + jar + + + com_juick_server + jar + + jar + clean + jar + + + com_juick_xmpp + jar + + jar + clean + jar + + + + + + + diff --git a/src/com/juick/jabber/ws/Main.java b/src/com/juick/jabber/ws/Main.java new file mode 100644 index 00000000..f8cad85b --- /dev/null +++ b/src/com/juick/jabber/ws/Main.java @@ -0,0 +1,518 @@ +/* + * Juick + * Copyright (C) 2008-2011, Ugnich Anton + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package com.juick.jabber.ws; + +import com.juick.xmpp.JID; +import com.juick.xmpp.MessageListener; +import com.juick.xmpp.XmppConnection; +import com.juick.xmpp.XmppConnectionComponent; +import com.juick.xmpp.XmppListener; +import com.juick.xmpp.extensions.JuickMessage; +import java.io.FileInputStream; +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.sql.Connection; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.Iterator; +import java.util.Properties; +import java.util.Vector; + +/** + * + * @author Ugnich Anton + */ +public class Main implements XmppListener, MessageListener { + + Connection sql; + XmppConnection xmpp; + Vector sockReplies = new Vector(); + Vector sockMessages = new Vector(); + Vector sockAll = new Vector(); + Selector sel; + + public static void main(String[] args) { + new Main().start(); + } + + public void start() { + try { + Properties conf = new Properties(); + conf.load(new FileInputStream("/etc/juick/ws.conf")); + + setupSql(conf.getProperty("mysql_username", ""), conf.getProperty("mysql_password", "")); + setupXmppComponent(conf.getProperty("xmpp_password", "")); + setupWsServer(); + } catch (Exception e) { + System.err.println(e); + } + } + + public void setupSql(String username, String password) { + try { + sql = DriverManager.getConnection("jdbc:mysql://localhost/juick?autoReconnect=true&user=" + username + "&password=" + password); + } catch (SQLException e) { + System.err.println(e); + } + } + + public void setupXmppComponent(String password) { + xmpp = new XmppConnectionComponent(new JID("ws.juick.com"), password, "127.0.0.1", 5347, false); + xmpp.addListener((XmppListener) this); + xmpp.addListener((MessageListener) this); + xmpp.start(); + } + + @Override + public void onConnectionFailed(String msg) { + System.err.println("XMPP onConnFailed " + msg); + } + + @Override + public void onAuth(String resource) { + System.err.println("XMPP onAuth " + resource); + } + + @Override + public void onAuthFailed(String message) { + System.err.println("XMPP onAuthFailed " + message); + } + + public void setupWsServer() { + try { + sel = Selector.open(); + ServerSocketChannel listensock = ServerSocketChannel.open(); + listensock.configureBlocking(false); + listensock.socket().bind(new InetSocketAddress(8080)); + listensock.register(sel, SelectionKey.OP_ACCEPT); + + while (true) { + sel.select(); + System.out.println("ONE"); + Iterator it = sel.selectedKeys().iterator(); + while (it.hasNext()) { + System.out.println("TWO"); + SelectionKey selKey = (SelectionKey) it.next(); + it.remove(); + if (selKey.isAcceptable()) { + ServerSocketChannel ssChannel = (ServerSocketChannel) selKey.channel(); + SocketChannel sChannel = ssChannel.accept(); + System.out.println(sChannel.socket().getRemoteSocketAddress().toString() + " ACCEPTED"); + sChannel.configureBlocking(false); + sChannel.register(sel, SelectionKey.OP_READ); + } else if (selKey.isReadable()) { + System.out.println("THREE"); + 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 { + System.out.println(sChannel.socket().getRemoteSocketAddress().toString() + " INVALID FRAME"); + System.out.println("FOUR"); + sChannel.close(); + selKey.cancel(); + } + } else { + sChannel.close(); + selKey.cancel(); + System.out.println("SIX"); + } + } catch (IOException e) { + System.err.println(e); + sChannel.close(); + System.out.println("FIVE"); + selKey.cancel(); + } + } + } + } + } catch (Exception e) { + System.err.println(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) { + System.err.println(sock.socket().getRemoteSocketAddress().toString() + " Invalid headers"); + sock.close(); + return; + } + + // Cookies + int UID = 0; + + if (hCookie != null) { + String hash = 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) { + sockMessages.add(new SocketSubscribed(sock, UID, 0)); + } else if (hLocation.equals("/all")) { + sockAll.add(new SocketSubscribed(sock, UID, 0)); + } else if ((loc.length == 2 || loc.length == 3) && loc[1].equals("replies")) { + if (loc.length == 2) { + sockReplies.add(new SocketSubscribed(sock, UID, 0)); + } else { + try { + MID = Integer.parseInt(loc[2]); + } catch (Exception e) { + } + if (MID > 0) { + sockReplies.add(new SocketSubscribed(sock, UID, MID)); + } else { + System.err.println(sock.socket().getRemoteSocketAddress().toString() + " Invalid MID"); + sock.close(); + return; + } + } + } else { + System.err.println(sock.socket().getRemoteSocketAddress().toString() + " Invalid location"); + sock.close(); + return; + } + + System.out.println(sock.socket().getRemoteSocketAddress().toString() + " HANDSHAKE (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(); + // StringBuuffer lSB = new StringBuuffer(); + 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; + // log.debug("Key: " + aKey + ", Numbers: " + lSB.toString() + + // ", Spaces: " + lSpaces + ", Result: " + lRes); + } 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 + "'"); + } + } + + @Override + public void onMessage(com.juick.xmpp.Message msg) { + JuickMessage jmsg = (JuickMessage) msg.getChild(JuickMessage.XMLNS); + if (jmsg != null) { + if (jmsg.RID == 0) { + onJuickMessagePost(jmsg); + } else { + onJuickMessageReply(jmsg); + } + } + } + + private void onJuickMessagePost(com.juick.Message jmsg) { + String json = "{" + + "\"mid\":" + jmsg.MID + "," + + "\"user\":{" + "\"uid\":" + jmsg.User.UID + "," + "\"uname\":\"" + encloseJSON(jmsg.User.UName) + "\"}," + + "\"timestamp\":\"" + jmsg.Timestamp + "\"," + + "\"body\":\"" + encloseJSON(jmsg.Text) + "\""; + if (jmsg.tags.size() > 0) { + json += ",\"tags\":["; + for (int i = 0; i < jmsg.tags.size(); i++) { + if (i > 0) { + json += ","; + } + json += "\"" + encloseJSON((String) jmsg.tags.get(i)) + "\""; + } + json += "]"; + } + json += "}"; + + ByteBuffer out = ByteBuffer.allocate(10240); + out.put((byte) 0x00); + out.put(Charset.forName("UTF-8").encode(json)); + out.put((byte) 0xFF); + out.flip(); + + + String query = "SELECT suser_id FROM subscr_users WHERE user_id=" + jmsg.User.UID + " AND suser_id NOT IN (SELECT user_id FROM bl_tags INNER JOIN messages_tags USING(tag_id) WHERE message_id=" + jmsg.MID + ")"; + if (jmsg.Privacy < 0) { + query += " AND suser_id IN (SELECT wl_user_id FROM wl_users WHERE user_id=" + jmsg.User.UID + ")"; + } + + Statement stmt = null; + ResultSet rs = null; + try { + stmt = sql.createStatement(); + rs = stmt.executeQuery(query); + rs.beforeFirst(); + while (rs.next()) { + int UID = rs.getInt(1); + + for (int i = sockMessages.size() - 1; i >= 0; i--) { + SocketSubscribed ss = sockMessages.get(i); + if (ss.UID == UID) { + try { + out.rewind(); + ss.sock.write(out); + } catch (IOException e) { + sockMessages.remove(i); + try { + ss.sock.close(); + } catch (IOException ex) { + } + } + } + } + + if (jmsg.Privacy <= 0) { + for (int i = sockAll.size() - 1; i >= 0; i--) { + SocketSubscribed ss = sockAll.get(i); + if (ss.UID == UID) { + try { + out.rewind(); + ss.sock.write(out); + } catch (IOException e) { + sockAll.remove(i); + try { + ss.sock.close(); + } catch (IOException ex) { + } + } + } + } + } + + } + } catch (SQLException e) { + System.err.println(e); + } finally { + if (rs != null) { + try { + rs.close(); + } catch (SQLException e) { + } + } + if (stmt != null) { + try { + stmt.close(); + } catch (SQLException e) { + } + } + } + + // Send to all + if (jmsg.Privacy > 0) { + for (int i = sockAll.size() - 1; i >= 0; i--) { + SocketSubscribed ss = sockAll.get(i); + try { + out.rewind(); + ss.sock.write(out); + } catch (IOException e) { + sockAll.remove(i); + try { + ss.sock.close(); + } catch (IOException ex) { + } + } + } + } + + + + } + + private void onJuickMessageReply(com.juick.Message jmsg) { + String json = "{" + + "\"mid\":" + jmsg.MID + "," + + "\"rid\":" + jmsg.RID + "," + + "\"user\":{" + "\"uid\":" + jmsg.User.UID + "," + "\"uname\":\"" + encloseJSON(jmsg.User.UName) + "\"}," + + "\"timestamp\":\"" + jmsg.Timestamp + "\"," + + "\"body\":\"" + encloseJSON(jmsg.Text) + "\"" + + "}"; + + ByteBuffer out = ByteBuffer.allocate(10240); + out.put((byte) 0x00); + out.put(Charset.forName("UTF-8").encode(json)); + out.put((byte) 0xFF); + out.flip(); + + for (int i = sockReplies.size() - 1; i >= 0; i--) { + SocketSubscribed ss = sockReplies.get(i); + if (ss.MID == 0 || ss.MID == jmsg.MID) { + try { + out.rewind(); + ss.sock.write(out); + } catch (IOException e) { + sockReplies.remove(i); + try { + ss.sock.close(); + } catch (IOException ex) { + } + } + } + } + } + + public static String encloseJSON(String str) { + return str.replace("\"", """).replace("\\", "\\\\").replace("\n", "\\n"); + } +} + +class SocketSubscribed { + + public SocketChannel sock; + public int UID; + public int MID; + + public SocketSubscribed(SocketChannel sock, int UID, int MID) { + this.sock = sock; + this.UID = UID; + this.MID = MID; + } +} -- cgit v1.2.3