aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/com/juick/server/xmpp/router
diff options
context:
space:
mode:
authorGravatar Vitaly Takmazov2018-11-08 21:38:27 +0300
committerGravatar Vitaly Takmazov2018-11-08 21:38:27 +0300
commit7aaa3f9a29c280f01c677c918932620be45cdbd7 (patch)
tree39947b2c889afd08f9c73ba54fab91159d2af258 /src/main/java/com/juick/server/xmpp/router
parent3ea9770d0d43fbe45449ac4531ec4b0a374d98ea (diff)
Merge everything into single Spring Boot application
Diffstat (limited to 'src/main/java/com/juick/server/xmpp/router')
-rw-r--r--src/main/java/com/juick/server/xmpp/router/Handshake.java39
-rw-r--r--src/main/java/com/juick/server/xmpp/router/Stream.java202
-rw-r--r--src/main/java/com/juick/server/xmpp/router/StreamComponentServer.java57
-rw-r--r--src/main/java/com/juick/server/xmpp/router/StreamError.java57
-rw-r--r--src/main/java/com/juick/server/xmpp/router/StreamFeatures.java95
-rw-r--r--src/main/java/com/juick/server/xmpp/router/StreamHandler.java13
-rw-r--r--src/main/java/com/juick/server/xmpp/router/StreamNamespaces.java10
-rw-r--r--src/main/java/com/juick/server/xmpp/router/XMPPError.java73
-rw-r--r--src/main/java/com/juick/server/xmpp/router/XMPPRouter.java220
-rw-r--r--src/main/java/com/juick/server/xmpp/router/XmlUtils.java88
10 files changed, 854 insertions, 0 deletions
diff --git a/src/main/java/com/juick/server/xmpp/router/Handshake.java b/src/main/java/com/juick/server/xmpp/router/Handshake.java
new file mode 100644
index 00000000..0bc501dd
--- /dev/null
+++ b/src/main/java/com/juick/server/xmpp/router/Handshake.java
@@ -0,0 +1,39 @@
+package com.juick.server.xmpp.router;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+
+/**
+ * Created by vitalyster on 30.01.2017.
+ */
+public class Handshake {
+ private String value;
+
+ public static Handshake parse(XmlPullParser parser) throws IOException, XmlPullParserException {
+ parser.next();
+ Handshake handshake = new Handshake();
+ handshake.setValue(XmlUtils.getTagText(parser));
+ return handshake;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ public void setValue(String value) {
+ this.value = value;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder str = new StringBuilder("<handshake");
+ if (getValue() != null) {
+ str.append(">").append(getValue()).append("</handshake>");
+ } else {
+ str.append("/>");
+ }
+ return str.toString();
+ }
+}
diff --git a/src/main/java/com/juick/server/xmpp/router/Stream.java b/src/main/java/com/juick/server/xmpp/router/Stream.java
new file mode 100644
index 00000000..2154edf6
--- /dev/null
+++ b/src/main/java/com/juick/server/xmpp/router/Stream.java
@@ -0,0 +1,202 @@
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+package com.juick.server.xmpp.router;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlPullParserFactory;
+import rocks.xmpp.addr.Jid;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.nio.charset.StandardCharsets;
+import java.time.Instant;
+import java.util.UUID;
+
+/**
+ *
+ * @author Ugnich Anton
+ */
+public abstract class Stream {
+
+ public boolean isLoggedIn() {
+ return loggedIn;
+ }
+
+ public void setLoggedIn(boolean loggedIn) {
+ this.loggedIn = loggedIn;
+ }
+
+ public Jid from;
+ public Jid to;
+ private InputStream is;
+ private OutputStream os;
+ private XmlPullParserFactory factory;
+ protected XmlPullParser parser;
+ private OutputStreamWriter writer;
+ StreamHandler streamHandler;
+ private boolean loggedIn;
+ private Instant created;
+ private Instant updated;
+ String streamId;
+ private boolean secured;
+
+ public Stream(final Jid from, final Jid to, final InputStream is, final OutputStream os) throws XmlPullParserException {
+ this.from = from;
+ this.to = to;
+ this.is = is;
+ this.os = os;
+ factory = XmlPullParserFactory.newInstance();
+ created = updated = Instant.now();
+ streamId = UUID.randomUUID().toString();
+ }
+
+ public void restartStream() throws XmlPullParserException {
+ parser = factory.newPullParser();
+ parser.setInput(new InputStreamReader(is, StandardCharsets.UTF_8));
+ parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
+ writer = new OutputStreamWriter(os, StandardCharsets.UTF_8);
+ }
+
+ public void connect() {
+ try {
+ restartStream();
+ handshake();
+ parse();
+ } catch (XmlPullParserException e) {
+ StreamError invalidXmlError = new StreamError("invalid-xml");
+ send(invalidXmlError.toString());
+ connectionFailed(new Exception(invalidXmlError.getCondition()));
+ } catch (IOException e) {
+ connectionFailed(e);
+ }
+ }
+
+ public void setHandler(final StreamHandler streamHandler) {
+ this.streamHandler = streamHandler;
+ }
+
+ public abstract void handshake() throws XmlPullParserException, IOException;
+
+ public void logoff() {
+ setLoggedIn(false);
+ try {
+ writer.flush();
+ writer.close();
+ //TODO close parser
+ } catch (final Exception e) {
+ connectionFailed(e);
+ }
+ }
+
+ public void send(final String str) {
+ try {
+ updated = Instant.now();
+ writer.write(str);
+ writer.flush();
+ } catch (final Exception e) {
+ connectionFailed(e);
+ }
+ }
+
+ private void parse() throws IOException, XmlPullParserException {
+ while (parser.next() != XmlPullParser.END_DOCUMENT) {
+ if (parser.getEventType() == XmlPullParser.IGNORABLE_WHITESPACE) {
+ setUpdated();
+ }
+ if (parser.getEventType() != XmlPullParser.START_TAG) {
+ continue;
+ }
+ setUpdated();
+ final String tag = parser.getName();
+ switch (tag) {
+ case "message":
+ case "presence":
+ case "iq":
+ streamHandler.stanzaReceived(XmlUtils.parseToString(parser, false));
+ break;
+ case "error":
+ StreamError error = StreamError.parse(parser);
+ connectionFailed(new Exception(error.getCondition()));
+ return;
+ default:
+ XmlUtils.skip(parser);
+ break;
+ }
+ }
+ }
+
+ /**
+ * This method is used to be called on a parser or a connection error.
+ * It tries to close the XML-Reader and XML-Writer one last time.
+ */
+ private void connectionFailed(final Exception ex) {
+ if (isLoggedIn()) {
+ try {
+ writer.close();
+ //TODO close parser
+ } catch (Exception e) {
+ }
+ }
+ if (streamHandler != null) {
+ streamHandler.fail(ex);
+ }
+ }
+
+ public Instant getCreated() {
+ return created;
+ }
+
+ public Instant getUpdated() {
+ return updated;
+ }
+ public String getStreamId() {
+ return streamId;
+ }
+
+ public boolean isSecured() {
+ return secured;
+ }
+
+ public void setSecured(boolean secured) {
+ this.secured = secured;
+ }
+
+ public void setUpdated() {
+ this.updated = Instant.now();
+ }
+
+ public InputStream getInputStream() {
+ return is;
+ }
+
+ public void setInputStream(InputStream is) {
+ this.is = is;
+ }
+
+ public OutputStream getOutputStream() {
+ return os;
+ }
+
+ public void setOutputStream(OutputStream os) {
+ this.os = os;
+ }
+}
diff --git a/src/main/java/com/juick/server/xmpp/router/StreamComponentServer.java b/src/main/java/com/juick/server/xmpp/router/StreamComponentServer.java
new file mode 100644
index 00000000..a58adfc5
--- /dev/null
+++ b/src/main/java/com/juick/server/xmpp/router/StreamComponentServer.java
@@ -0,0 +1,57 @@
+package com.juick.server.xmpp.router;
+
+import org.apache.commons.codec.digest.DigestUtils;
+import org.xmlpull.v1.XmlPullParserException;
+import rocks.xmpp.addr.Jid;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.UUID;
+
+/**
+ * Created by vitalyster on 30.01.2017.
+ */
+public class StreamComponentServer extends Stream {
+
+ private String streamId, secret;
+
+ public String getStreamId() {
+ return streamId;
+ }
+
+
+ public StreamComponentServer(InputStream is, OutputStream os, String password) throws XmlPullParserException {
+ super(null, null, is, os);
+ secret = password;
+ streamId = UUID.randomUUID().toString();
+ }
+ @Override
+ public void handshake() throws XmlPullParserException, IOException {
+ parser.next();
+ if (!parser.getName().equals("stream")
+ || !parser.getNamespace(null).equals(StreamNamespaces.NS_COMPONENT_ACCEPT)
+ || !parser.getNamespace("stream").equals(StreamNamespaces.NS_STREAM)) {
+ throw new IOException("invalid stream");
+ }
+ Jid domain = Jid.of(parser.getAttributeValue(null, "to"));
+ if (streamHandler.filter(null, domain)) {
+ send(new XMPPError(XMPPError.Type.cancel, "forbidden").toString());
+ throw new IOException("invalid domain");
+ }
+ from = domain;
+ to = domain;
+ send(String.format("<stream:stream xmlns:stream='%s' " +
+ "xmlns='%s' from='%s' id='%s'>", StreamNamespaces.NS_STREAM, StreamNamespaces.NS_COMPONENT_ACCEPT, from.asBareJid().toEscapedString(), streamId));
+ Handshake handshake = Handshake.parse(parser);
+ boolean authenticated = handshake.getValue().equals(DigestUtils.sha1Hex(streamId + secret));
+ setLoggedIn(authenticated);
+ if (!authenticated) {
+ send(new XMPPError(XMPPError.Type.cancel, "not-authorized").toString());
+ streamHandler.fail(new IOException("stream:stream, failed authentication"));
+ return;
+ }
+ send(new Handshake().toString());
+ streamHandler.ready(this);
+ }
+}
diff --git a/src/main/java/com/juick/server/xmpp/router/StreamError.java b/src/main/java/com/juick/server/xmpp/router/StreamError.java
new file mode 100644
index 00000000..f731f039
--- /dev/null
+++ b/src/main/java/com/juick/server/xmpp/router/StreamError.java
@@ -0,0 +1,57 @@
+package com.juick.server.xmpp.router;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+
+
+/**
+ * Created by vitalyster on 03.02.2017.
+ */
+public class StreamError {
+
+ private String condition;
+ private String text;
+
+ public StreamError() {}
+
+ public StreamError(String condition) {
+ this.condition = condition;
+ }
+
+ public static StreamError parse(XmlPullParser parser) throws IOException, XmlPullParserException {
+ StreamError streamError = new StreamError();
+ final int initial = parser.getDepth();
+ while (true) {
+ int eventType = parser.next();
+ if (eventType == XmlPullParser.START_TAG && parser.getDepth() == initial + 1) {
+ final String tag = parser.getName();
+ final String xmlns = parser.getNamespace();
+ if (tag.equals("text") && xmlns.equals(StreamNamespaces.NS_XMPP_STREAMS)) {
+ streamError.text = XmlUtils.getTagText(parser);
+ } else if (xmlns.equals(StreamNamespaces.NS_XMPP_STREAMS)) {
+ streamError.condition = tag;
+ } else {
+ XmlUtils.skip(parser);
+ }
+ } else if (eventType == XmlPullParser.END_TAG && parser.getDepth() == initial) {
+ break;
+ }
+ }
+ return streamError;
+ }
+
+ public String getCondition() {
+ return condition;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("<stream:error><%s xmlns='%s'/></stream:error>", condition, StreamNamespaces.NS_XMPP_STREAMS);
+ }
+
+ public String getText() {
+ return text;
+ }
+}
diff --git a/src/main/java/com/juick/server/xmpp/router/StreamFeatures.java b/src/main/java/com/juick/server/xmpp/router/StreamFeatures.java
new file mode 100644
index 00000000..e8fc324f
--- /dev/null
+++ b/src/main/java/com/juick/server/xmpp/router/StreamFeatures.java
@@ -0,0 +1,95 @@
+/*
+ * Juick
+ * Copyright (C) 2008-2013, 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 <http://www.gnu.org/licenses/>.
+ */
+package com.juick.server.xmpp.router;
+
+import java.io.IOException;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+/**
+ *
+ * @author Ugnich Anton
+ */
+public class StreamFeatures {
+
+ public static final int NOTAVAILABLE = -1;
+ public static final int AVAILABLE = 0;
+ public static final int REQUIRED = 1;
+ public int STARTTLS = NOTAVAILABLE;
+ public int ZLIB = NOTAVAILABLE;
+ public int PLAIN = NOTAVAILABLE;
+ public int DIGEST_MD5 = NOTAVAILABLE;
+ public int REGISTER = NOTAVAILABLE;
+ public int EXTERNAL = NOTAVAILABLE;
+
+ public static StreamFeatures parse(final XmlPullParser parser) throws XmlPullParserException, IOException {
+ StreamFeatures features = new StreamFeatures();
+ final int initial = parser.getDepth();
+ while (true) {
+ int eventType = parser.next();
+ if (eventType == XmlPullParser.START_TAG && parser.getDepth() == initial + 1) {
+ final String tag = parser.getName();
+ final String xmlns = parser.getNamespace();
+ if (tag.equals("starttls") && xmlns != null && xmlns.equals("urn:ietf:params:xml:ns:xmpp-tls")) {
+ features.STARTTLS = AVAILABLE;
+ while (parser.next() == XmlPullParser.START_TAG) {
+ if (parser.getName().equals("required")) {
+ features.STARTTLS = REQUIRED;
+ } else {
+ XmlUtils.skip(parser);
+ }
+ }
+ } else if (tag.equals("compression") && xmlns != null && xmlns.equals("http://jabber.org/features/compress")) {
+ while (parser.next() == XmlPullParser.START_TAG) {
+ if (parser.getName().equals("method")) {
+ final String method = XmlUtils.getTagText(parser).toUpperCase();
+ if (method.equals("ZLIB")) {
+ features.ZLIB = AVAILABLE;
+ }
+ } else {
+ XmlUtils.skip(parser);
+ }
+ }
+ } else if (tag.equals("mechanisms") && xmlns != null && xmlns.equals("urn:ietf:params:xml:ns:xmpp-sasl")) {
+ while (parser.next() == XmlPullParser.START_TAG) {
+ if (parser.getName().equals("mechanism")) {
+ final String mechanism = XmlUtils.getTagText(parser).toUpperCase();
+ if (mechanism.equals("PLAIN")) {
+ features.PLAIN = AVAILABLE;
+ } else if (mechanism.equals("DIGEST-MD5")) {
+ features.DIGEST_MD5 = AVAILABLE;
+ } else if (mechanism.equals("EXTERNAL")) {
+ features.EXTERNAL = AVAILABLE;
+ }
+ } else {
+ XmlUtils.skip(parser);
+ }
+ }
+ } else if (tag.equals("register") && xmlns != null && xmlns.equals("http://jabber.org/features/iq-register")) {
+ features.REGISTER = AVAILABLE;
+ XmlUtils.skip(parser);
+ } else {
+ XmlUtils.skip(parser);
+ }
+ } else if (eventType == XmlPullParser.END_TAG && parser.getDepth() == initial) {
+ break;
+ }
+ }
+ return features;
+ }
+}
diff --git a/src/main/java/com/juick/server/xmpp/router/StreamHandler.java b/src/main/java/com/juick/server/xmpp/router/StreamHandler.java
new file mode 100644
index 00000000..048c61ec
--- /dev/null
+++ b/src/main/java/com/juick/server/xmpp/router/StreamHandler.java
@@ -0,0 +1,13 @@
+package com.juick.server.xmpp.router;
+
+import rocks.xmpp.addr.Jid;
+
+/**
+ * Created by vitalyster on 01.02.2017.
+ */
+public interface StreamHandler {
+ void ready(StreamComponentServer componentServer);
+ void fail(final Exception ex);
+ boolean filter(Jid from, Jid to);
+ void stanzaReceived(String stanza);
+}
diff --git a/src/main/java/com/juick/server/xmpp/router/StreamNamespaces.java b/src/main/java/com/juick/server/xmpp/router/StreamNamespaces.java
new file mode 100644
index 00000000..1b9b1965
--- /dev/null
+++ b/src/main/java/com/juick/server/xmpp/router/StreamNamespaces.java
@@ -0,0 +1,10 @@
+package com.juick.server.xmpp.router;
+
+public class StreamNamespaces {
+ public static final String NS_STREAM = "http://etherx.jabber.org/streams";
+ public static final String NS_TLS = "urn:ietf:params:xml:ns:xmpp-tls";
+ public static final String NS_DB = "jabber:server:dialback";
+ public static final String NS_SERVER = "jabber:server";
+ public static final String NS_COMPONENT_ACCEPT = "jabber:component:accept";
+ public static final String NS_XMPP_STREAMS = "urn:ietf:params:xml:ns:xmpp-streams";
+}
diff --git a/src/main/java/com/juick/server/xmpp/router/XMPPError.java b/src/main/java/com/juick/server/xmpp/router/XMPPError.java
new file mode 100644
index 00000000..0cf9a3bc
--- /dev/null
+++ b/src/main/java/com/juick/server/xmpp/router/XMPPError.java
@@ -0,0 +1,73 @@
+/*
+ * Juick
+ * Copyright (C) 2008-2013, ugnich
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+package com.juick.server.xmpp.router;
+
+import org.apache.commons.text.StringEscapeUtils;
+
+/**
+ *
+ * @author ugnich
+ */
+public class XMPPError {
+
+ public static final class Type {
+
+ public static final String auth = "auth";
+ public static final String cancel = "cancel";
+ public static final String continue_ = "continue";
+ public static final String modify = "modify";
+ public static final String wait = "wait";
+ }
+ private final static String TagName = "error";
+ public String by = null;
+ private String type;
+ private String condition;
+ private String text = null;
+
+ public XMPPError(String type, String condition) {
+ this.type = type;
+ this.condition = condition;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder str = new StringBuilder("<").append(TagName).append("");
+ if (by != null) {
+ str.append(" by=\"").append(StringEscapeUtils.escapeXml10(by)).append("\"");
+ }
+ if (type != null) {
+ str.append(" type=\"").append(StringEscapeUtils.escapeXml10(type)).append("\"");
+ }
+
+ if (condition != null) {
+ str.append(">");
+ str.append("<").append(StringEscapeUtils.escapeXml10(condition)).append(" xmlns=\"urn:ietf:params:xml:ns:xmpp-stanzas\"");
+ if (text != null) {
+ str.append(">").append(StringEscapeUtils.escapeXml10(text)).append("</").append(StringEscapeUtils.escapeXml10(condition))
+ .append(">");
+ } else {
+ str.append("/>");
+ }
+ str.append("</").append(TagName).append(">");
+ } else {
+ str.append("/>");
+ }
+
+ return str.toString();
+ }
+}
diff --git a/src/main/java/com/juick/server/xmpp/router/XMPPRouter.java b/src/main/java/com/juick/server/xmpp/router/XMPPRouter.java
new file mode 100644
index 00000000..6d67fa9c
--- /dev/null
+++ b/src/main/java/com/juick/server/xmpp/router/XMPPRouter.java
@@ -0,0 +1,220 @@
+package com.juick.server.xmpp.router;
+
+import com.juick.server.XMPPServer;
+import com.juick.server.xmpp.s2s.BasicXmppSession;
+import com.juick.server.xmpp.s2s.CacheEntry;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+import org.xmlpull.v1.XmlPullParserException;
+import rocks.xmpp.addr.Jid;
+import rocks.xmpp.core.stanza.model.IQ;
+import rocks.xmpp.core.stanza.model.Message;
+import rocks.xmpp.core.stanza.model.Presence;
+import rocks.xmpp.core.stanza.model.Stanza;
+import rocks.xmpp.core.stanza.model.server.ServerIQ;
+import rocks.xmpp.core.stanza.model.server.ServerMessage;
+import rocks.xmpp.core.stanza.model.server.ServerPresence;
+import rocks.xmpp.util.XmppUtils;
+
+import javax.annotation.PostConstruct;
+import javax.annotation.PreDestroy;
+import javax.inject.Inject;
+import javax.xml.bind.JAXBException;
+import javax.xml.bind.Unmarshaller;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamWriter;
+import java.io.IOException;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.SocketException;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.ExecutorService;
+
+public class XMPPRouter implements StreamHandler {
+ private static final Logger logger = LoggerFactory.getLogger("com.juick.server.xmpp");
+
+ @Inject
+ private ExecutorService service;
+
+ private final List<StreamComponentServer> connections = Collections.synchronizedList(new ArrayList<>());
+ private final List<CacheEntry> outCache = new CopyOnWriteArrayList<>();
+
+ private ServerSocket listener;
+
+ @Inject
+ private BasicXmppSession session;
+
+ @Value("${router_port:5347}")
+ private int routerPort;
+
+ @Inject
+ private XMPPServer xmppServer;
+
+ @PostConstruct
+ public void init() {
+ logger.info("component router initialized");
+ service.submit(() -> {
+ try {
+ listener = new ServerSocket(routerPort);
+ logger.info("component router listening on {}", routerPort);
+ while (!listener.isClosed()) {
+ if (Thread.currentThread().isInterrupted()) break;
+ Socket socket = listener.accept();
+ service.submit(() -> {
+ try {
+ StreamComponentServer client = new StreamComponentServer(socket.getInputStream(), socket.getOutputStream(), "secret");
+ addConnectionIn(client);
+ client.setHandler(this);
+ client.connect();
+ } catch (IOException e) {
+ logger.error("component error", e);
+ } catch (XmlPullParserException e) {
+ e.printStackTrace();
+ }
+ });
+ }
+ } catch (SocketException e) {
+ // shutdown
+ } catch (IOException e) {
+ logger.warn("io exception", e);
+ }
+ });
+ }
+
+ @PreDestroy
+ public void close() throws Exception {
+ if (!listener.isClosed()) {
+ listener.close();
+ }
+ synchronized (getConnections()) {
+ for (Iterator<StreamComponentServer> i = getConnections().iterator(); i.hasNext(); ) {
+ StreamComponentServer c = i.next();
+ c.logoff();
+ i.remove();
+ }
+ }
+ service.shutdown();
+ logger.info("XMPP router destroyed");
+ }
+
+ private void addConnectionIn(StreamComponentServer c) {
+ synchronized (getConnections()) {
+ getConnections().add(c);
+ }
+ }
+
+ private void sendOut(Stanza s) {
+ try {
+ StringWriter stanzaWriter = new StringWriter();
+ XMLStreamWriter xmppStreamWriter = XmppUtils.createXmppStreamWriter(
+ session.getConfiguration().getXmlOutputFactory().createXMLStreamWriter(stanzaWriter));
+ session.createMarshaller().marshal(s, xmppStreamWriter);
+ xmppStreamWriter.flush();
+ xmppStreamWriter.close();
+ String xml = stanzaWriter.toString();
+ logger.info("XMPPRouter (out): {}", xml);
+ sendOut(s.getTo().getDomain(), xml);
+ } catch (XMLStreamException | JAXBException e1) {
+ logger.info("jaxb exception", e1);
+ }
+ }
+
+ private void sendOut(String hostname, String xml) {
+ boolean haveAnyConn = false;
+
+ StreamComponentServer connOut = null;
+ synchronized (getConnections()) {
+ for (StreamComponentServer c : getConnections()) {
+ if (c.to != null && c.to.getDomain().equals(hostname)) {
+ if (c.isLoggedIn()) {
+ connOut = c;
+ break;
+ } else {
+ logger.info("bouncing stanza to {} component until it will be ready", hostname);
+ boolean haveCache = false;
+ for (CacheEntry entry : outCache) {
+ if (entry.hostname != null && entry.hostname.equals(hostname)) {
+ entry.xml += xml;
+ entry.updated = Instant.now();
+ haveCache = true;
+ break;
+ }
+ }
+ if (!haveCache) {
+ outCache.add(new CacheEntry(Jid.of(hostname), xml));
+ }
+ }
+ }
+ }
+ }
+ if (connOut != null) {
+ connOut.send(xml);
+ return;
+ }
+ xmppServer.sendOut(Jid.of(hostname), xml);
+
+ }
+
+ public List<StreamComponentServer> getConnections() {
+ return connections;
+ }
+
+ private Stanza parse(String xml) {
+ try {
+ Unmarshaller unmarshaller = session.createUnmarshaller();
+ return (Stanza)unmarshaller.unmarshal(new StringReader(xml));
+ } catch (JAXBException e) {
+ logger.error("JAXB exception", e);
+ }
+ return null;
+ }
+ @Override
+ public void stanzaReceived(String stanza) {
+ Stanza input = parse(stanza);
+ if (input instanceof Message) {
+ sendOut(ServerMessage.from((Message)input));
+ } else if (input instanceof IQ) {
+ sendOut(ServerIQ.from((IQ)input));
+ } else {
+ sendOut(ServerPresence.from((Presence) input));
+ }
+ }
+
+ public String getFromCache(Jid to) {
+ final String[] cache = new String[1];
+ outCache.stream().filter(c -> c.hostname != null && c.hostname.equals(to)).findFirst().ifPresent(c -> {
+ cache[0] = c.xml;
+ outCache.remove(c);
+ });
+ return cache[0];
+ }
+
+ @Override
+ public void ready(StreamComponentServer componentServer) {
+ logger.info("component {} ready", componentServer.to);
+ String cache = getFromCache(componentServer.to);
+ if (cache != null) {
+ logger.debug("sending cache to {}", componentServer.to);
+ componentServer.send(cache);
+ }
+ }
+
+ @Override
+ public void fail(Exception e) {
+
+ }
+
+ @Override
+ public boolean filter(Jid jid, Jid jid1) {
+ return false;
+ }
+} \ No newline at end of file
diff --git a/src/main/java/com/juick/server/xmpp/router/XmlUtils.java b/src/main/java/com/juick/server/xmpp/router/XmlUtils.java
new file mode 100644
index 00000000..7579489f
--- /dev/null
+++ b/src/main/java/com/juick/server/xmpp/router/XmlUtils.java
@@ -0,0 +1,88 @@
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+package com.juick.server.xmpp.router;
+
+import java.io.IOException;
+
+import org.apache.commons.text.StringEscapeUtils;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+/**
+ *
+ * @author Ugnich Anton
+ */
+public class XmlUtils {
+
+ public static void skip(XmlPullParser parser) throws XmlPullParserException, IOException {
+ String tag = parser.getName();
+ while (parser.getName() != null && !(parser.next() == XmlPullParser.END_TAG && parser.getName().equals(tag))) {
+ }
+ }
+
+ public static String getTagText(XmlPullParser parser) throws XmlPullParserException, IOException {
+ String ret = "";
+ String tag = parser.getName();
+
+ if (parser.next() == XmlPullParser.TEXT) {
+ ret = parser.getText();
+ }
+
+ while (!(parser.getEventType() == XmlPullParser.END_TAG && parser.getName().equals(tag))) {
+ parser.next();
+ }
+
+ return ret;
+ }
+
+ public static String parseToString(XmlPullParser parser, boolean skipXMLNS) throws XmlPullParserException, IOException {
+ String tag = parser.getName();
+ StringBuilder ret = new StringBuilder("<").append(tag);
+
+ // skipXMLNS for xmlns="jabber:client"
+
+ String ns = parser.getNamespace();
+ if (!skipXMLNS && ns != null && !ns.isEmpty()) {
+ ret.append(" xmlns=\"").append(ns).append("\"");
+ }
+
+ for (int i = 0; i < parser.getAttributeCount(); i++) {
+ String attr = parser.getAttributeName(i);
+ if ((!skipXMLNS || !attr.equals("xmlns")) && !attr.contains(":")) {
+ ret.append(" ").append(attr).append("=\"").append(StringEscapeUtils.escapeXml10(parser.getAttributeValue(i))).append("\"");
+ }
+ }
+ ret.append(">");
+
+ while (!(parser.next() == XmlPullParser.END_TAG && parser.getName().equals(tag))) {
+ int event = parser.getEventType();
+ if (event == XmlPullParser.START_TAG) {
+ if (!parser.getName().contains(":")) {
+ ret.append(parseToString(parser, false));
+ } else {
+ skip(parser);
+ }
+ } else if (event == XmlPullParser.TEXT) {
+ ret.append(StringEscapeUtils.escapeXml10(parser.getText()));
+ }
+ }
+
+ ret.append("</").append(tag).append(">");
+ return ret.toString();
+ }
+}