aboutsummaryrefslogtreecommitdiff
path: root/src/com/juick/jabber/ws/WSData.java
diff options
context:
space:
mode:
authorGravatar Ugnich Anton2013-08-14 09:21:47 +0700
committerGravatar Ugnich Anton2013-08-14 09:21:47 +0700
commit2a8c4f613f397619c563fbb74c04e39074d6e98e (patch)
tree233015f82f5766515fe9aeeeb07605bcf9ef217a /src/com/juick/jabber/ws/WSData.java
parentdb2d48c068715c2841fd60b955c98580236c400d (diff)
RFC 6455
Diffstat (limited to 'src/com/juick/jabber/ws/WSData.java')
-rw-r--r--src/com/juick/jabber/ws/WSData.java337
1 files changed, 190 insertions, 147 deletions
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<SelectionKey> 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<SocketSubscribed> 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 + "'");
}
}
}