aboutsummaryrefslogtreecommitdiff
path: root/juick-www/src
diff options
context:
space:
mode:
Diffstat (limited to 'juick-www/src')
-rw-r--r--juick-www/src/main/java/com/juick/Application.java3
-rw-r--r--juick-www/src/main/java/com/juick/www/configuration/SapeConfiguration.java (renamed from juick-www/src/main/java/com/juick/configuration/SapeConfiguration.java)2
-rw-r--r--juick-www/src/main/java/com/juick/www/configuration/WebSecurityConfig.java (renamed from juick-www/src/main/java/com/juick/configuration/WebSecurityConfig.java)4
-rw-r--r--juick-www/src/main/java/com/juick/www/configuration/WwwAppConfiguration.java (renamed from juick-www/src/main/java/com/juick/configuration/WwwAppConfiguration.java)2
-rw-r--r--juick-www/src/main/java/com/juick/www/configuration/XMPPConfiguration.java (renamed from juick-www/src/main/java/com/juick/configuration/XMPPConfiguration.java)2
-rw-r--r--juick-www/src/main/java/com/juick/www/controllers/MessagesWWW.java (renamed from juick-www/src/main/java/com/juick/www/controllers/Messages.java)2
-rw-r--r--juick-www/src/main/java/com/juick/www/controllers/NewMessage.java13
-rw-r--r--juick-www/src/test/java/com/juick/WebAppTests.java50
-rw-r--r--juick-www/src/test/java/com/juick/server/Stream.java184
-rw-r--r--juick-www/src/test/java/com/juick/server/StreamComponentServer.java61
-rw-r--r--juick-www/src/test/java/com/juick/server/StreamError.java46
-rw-r--r--juick-www/src/test/java/com/juick/server/StreamHandler.java13
-rw-r--r--juick-www/src/test/java/com/juick/server/StreamNamespaces.java10
-rw-r--r--juick-www/src/test/java/com/juick/server/XMPPError.java73
-rw-r--r--juick-www/src/test/java/com/juick/server/XMPPRouter.java173
-rw-r--r--juick-www/src/test/java/com/juick/server/XmlUtils.java88
16 files changed, 698 insertions, 28 deletions
diff --git a/juick-www/src/main/java/com/juick/Application.java b/juick-www/src/main/java/com/juick/Application.java
index f8e5d333..a7a7a654 100644
--- a/juick-www/src/main/java/com/juick/Application.java
+++ b/juick-www/src/main/java/com/juick/Application.java
@@ -4,10 +4,13 @@ import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Primary;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@SpringBootApplication
@EnableTransactionManagement
+@ComponentScan(basePackages = {"com.juick.www", "com.juick.service"})
public class Application extends SpringBootServletInitializer {
@Override
diff --git a/juick-www/src/main/java/com/juick/configuration/SapeConfiguration.java b/juick-www/src/main/java/com/juick/www/configuration/SapeConfiguration.java
index 2630c05b..68ff28d2 100644
--- a/juick-www/src/main/java/com/juick/configuration/SapeConfiguration.java
+++ b/juick-www/src/main/java/com/juick/www/configuration/SapeConfiguration.java
@@ -15,7 +15,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-package com.juick.configuration;
+package com.juick.www.configuration;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
diff --git a/juick-www/src/main/java/com/juick/configuration/WebSecurityConfig.java b/juick-www/src/main/java/com/juick/www/configuration/WebSecurityConfig.java
index 92f43e64..909b6cd3 100644
--- a/juick-www/src/main/java/com/juick/configuration/WebSecurityConfig.java
+++ b/juick-www/src/main/java/com/juick/www/configuration/WebSecurityConfig.java
@@ -15,7 +15,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-package com.juick.configuration;
+package com.juick.www.configuration;
import com.juick.service.UserService;
import com.juick.service.security.HashParamAuthenticationFilter;
@@ -72,7 +72,7 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
http.addFilterAfter(hashParamAuthenticationFilter(), BasicAuthenticationFilter.class);
http
.authorizeRequests()
- .antMatchers("/settings", "/pm/**", "/**/bl", "/_twitter", "/post", "/comment")
+ .antMatchers("/settings", "/pm/**", "/**/bl", "/_twitter", "/post", "/post2", "/comment")
.authenticated()
.anyRequest().permitAll()
.and()
diff --git a/juick-www/src/main/java/com/juick/configuration/WwwAppConfiguration.java b/juick-www/src/main/java/com/juick/www/configuration/WwwAppConfiguration.java
index ea585a6f..13a394cb 100644
--- a/juick-www/src/main/java/com/juick/configuration/WwwAppConfiguration.java
+++ b/juick-www/src/main/java/com/juick/www/configuration/WwwAppConfiguration.java
@@ -15,7 +15,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-package com.juick.configuration;
+package com.juick.www.configuration;
import com.juick.server.configuration.BaseWebConfiguration;
import com.juick.server.configuration.StorageConfiguration;
diff --git a/juick-www/src/main/java/com/juick/configuration/XMPPConfiguration.java b/juick-www/src/main/java/com/juick/www/configuration/XMPPConfiguration.java
index 91f55759..1396f9f9 100644
--- a/juick-www/src/main/java/com/juick/configuration/XMPPConfiguration.java
+++ b/juick-www/src/main/java/com/juick/www/configuration/XMPPConfiguration.java
@@ -1,4 +1,4 @@
-package com.juick.configuration;
+package com.juick.www.configuration;
import com.juick.Message;
import org.slf4j.Logger;
diff --git a/juick-www/src/main/java/com/juick/www/controllers/Messages.java b/juick-www/src/main/java/com/juick/www/controllers/MessagesWWW.java
index 65e122a6..e6662c4e 100644
--- a/juick-www/src/main/java/com/juick/www/controllers/Messages.java
+++ b/juick-www/src/main/java/com/juick/www/controllers/MessagesWWW.java
@@ -52,7 +52,7 @@ import java.util.stream.Collectors;
* @author Ugnich Anton
*/
@Controller
-public class Messages {
+public class MessagesWWW {
@Inject
private UserService userService;
@Inject
diff --git a/juick-www/src/main/java/com/juick/www/controllers/NewMessage.java b/juick-www/src/main/java/com/juick/www/controllers/NewMessage.java
index f8a11d82..6e16ae50 100644
--- a/juick-www/src/main/java/com/juick/www/controllers/NewMessage.java
+++ b/juick-www/src/main/java/com/juick/www/controllers/NewMessage.java
@@ -16,13 +16,10 @@
*/
package com.juick.www.controllers;
-import com.juick.Status;
-import com.juick.Tag;
import com.juick.User;
import com.juick.server.helpers.AnonymousUser;
import com.juick.server.util.*;
import com.juick.service.*;
-import com.juick.util.MessageUtils;
import com.juick.www.WebApp;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.text.StringEscapeUtils;
@@ -34,7 +31,6 @@ import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
-import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;
import rocks.xmpp.addr.Jid;
import rocks.xmpp.core.stanza.model.Message;
@@ -47,7 +43,6 @@ import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
-import java.util.List;
import java.util.stream.Collectors;
/**
@@ -76,6 +71,8 @@ public class NewMessage {
private String imgDir;
@Value("${upload_tmp_dir:#{systemEnvironment['TEMP'] ?: '/tmp'}}")
private String tmpDir;
+ @Value("${xmppbot_jid:juick@localhost}")
+ private Jid botJid;
private static final Logger logger = LoggerFactory.getLogger(NewMessage.class);
@@ -247,11 +244,11 @@ public class NewMessage {
if (visitor.getUid() == 0 || visitor.isBanned()) {
throw new HttpForbiddenException();
}
- String body = bodyParam.replace("\r", StringUtils.EMPTY);
+ String body = StringUtils.isNotEmpty(bodyParam) ? bodyParam.replace("\r", StringUtils.EMPTY) : StringUtils.EMPTY;
String attachmentFName = HttpUtils.receiveMultiPartFile(attach, tmpDir);
- if (StringUtils.isBlank(attachmentFName) && img != null && img.length() > 10) {
+ if (StringUtils.isBlank(attachmentFName) && StringUtils.isNotBlank(img)) {
try {
URL imgUrl = new URL(img);
attachmentFName = HttpUtils.downloadImage(imgUrl, tmpDir);
@@ -263,7 +260,7 @@ public class NewMessage {
Message msg = new Message();
msg.setType(Message.Type.CHAT);
msg.setFrom(Jid.of(String.valueOf(visitor.getUid()), "uid.juick.com", "perl"));
- msg.setTo(Jid.of("juick@juick.com/Juick"));
+ msg.setTo(botJid);
msg.setBody(body);
try {
if (StringUtils.isNotEmpty(attachmentFName)) {
diff --git a/juick-www/src/test/java/com/juick/WebAppTests.java b/juick-www/src/test/java/com/juick/WebAppTests.java
index e9908acd..43198859 100644
--- a/juick-www/src/test/java/com/juick/WebAppTests.java
+++ b/juick-www/src/test/java/com/juick/WebAppTests.java
@@ -26,6 +26,11 @@ import com.gargoylesoftware.htmlunit.html.HtmlPage;
import com.juick.Message;
import com.juick.Tag;
import com.juick.User;
+import com.juick.server.XMPPConnection;
+import com.juick.server.XMPPRouter;
+import com.juick.server.XMPPServer;
+import com.juick.server.configuration.ApiAppConfiguration;
+import com.juick.server.configuration.BaseWebConfiguration;
import com.juick.service.*;
import com.juick.util.MessageUtils;
import com.juick.www.WebApp;
@@ -38,9 +43,14 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
+import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
+import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.context.annotation.ComponentScan;
import org.springframework.core.io.ClassPathResource;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.mock.web.MockMultipartFile;
@@ -49,6 +59,7 @@ import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.util.FileSystemUtils;
+import org.springframework.web.servlet.resource.ResourceUrlProvider;
import javax.inject.Inject;
import javax.servlet.http.Cookie;
@@ -74,9 +85,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
*/
@RunWith(SpringRunner.class)
@AutoConfigureMockMvc
-@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
-@TestPropertySource(properties = {"xmpp_disabled=true"})
-
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT, classes = {Application.class, XMPPRouter.class})
public class WebAppTests {
@MockBean
private ImagesService imagesService;
@@ -239,30 +248,37 @@ public class WebAppTests {
}
@Test
public void postMessageTests() throws Exception {
- mockMvc.perform(post("/post").param("body", "yo")).andExpect(redirectedUrl("http://localhost/login"));
+ ConfigurableApplicationContext context = new SpringApplicationBuilder(
+ ApiServer.class)
+ .properties("server.port=8081")
+ .run();
+ XMPPServer xmpp = context.getBean(XMPPServer.class);
+ assertThat(xmpp.getInConnections().size(), is(0));
+ mockMvc.perform(post("/post2").param("body", "yo")).andExpect(redirectedUrl("http://localhost/login"));
MvcResult loginResult = mockMvc.perform(post("/login")
.param("username", ugnichName)
.param("password", ugnichPassword)).andReturn();
- mockMvc.perform(post("/post")
- .cookie(loginResult.getResponse().getCookies())
- .param("wrong_param", "yo")).andExpect(status().isBadRequest());
String msgText = "yoppppl";
- mockMvc.perform(post("/post")
+ mockMvc.perform(post("/post2")
.cookie(loginResult.getResponse().getCookies())
- .param("body", msgText)).andExpect(status().isOk());
+ .param("body", msgText)).andExpect(status().isFound());
+ Thread.sleep(5000);
Message lastMessage = messagesService.getMessage(messagesService.getMyFeed(ugnich.getUid(), 0, false).get(0));
assertThat(lastMessage.getText(), equalTo(msgText));
- mockMvc.perform(post("/post")
+ mockMvc.perform(post("/post2")
.cookie(loginResult.getResponse().getCookies())
- .param("img", "http://static.juick.com/settings/facebook.png")).andExpect(status().isOk());
- mockMvc.perform(post("/post")
+ .param("img", "http://static.juick.com/settings/facebook.png")).andExpect(status().isFound());
+ Thread.sleep(5000);
+ lastMessage = messagesService.getMessage(messagesService.getMyFeed(ugnich.getUid(), 0, false).get(0));
+ assertThat(lastMessage.getAttachmentType(), equalTo("png"));
+ mockMvc.perform(post("/post2")
.cookie(loginResult.getResponse().getCookies())
.param("img", "bad_url")).andExpect(status().isBadRequest());
FileInputStream fi = new FileInputStream(new ClassPathResource("static/tagscloud.png").getFile());
MockMultipartFile file = new MockMultipartFile("attach", fi);
- mockMvc.perform(multipart("/post")
+ mockMvc.perform(multipart("/post2")
.file(file)
- .cookie(loginResult.getResponse().getCookies())).andExpect(status().isOk());
+ .cookie(loginResult.getResponse().getCookies())).andExpect(status().isFound());
int mid = messagesService.createMessage(ugnich.getUid(), "dummy message", null, null);
mockMvc.perform(post("/comment")
.param("mid", String.valueOf(mid))
@@ -286,6 +302,12 @@ public class WebAppTests {
.cookie(loginResult.getResponse().getCookies())
.param("mid", String.valueOf(mid))
.param("body", "yo")).andExpect(redirectedUrl(String.format("/%s/%d#%d", ugnichName, mid, 3)));
+ mockMvc.perform(post("/post2")
+ .cookie(loginResult.getResponse().getCookies())
+ .param("body", String.format("D #%d/%d", mid, 3)))
+ .andExpect(status().isFound());
+ Thread.sleep(5000);
+ assertThat(messagesService.getReplies(mid).size(), equalTo(2));
}
@Test
public void hashLoginShouldNotUseSession() throws Exception {
diff --git a/juick-www/src/test/java/com/juick/server/Stream.java b/juick-www/src/test/java/com/juick/server/Stream.java
new file mode 100644
index 00000000..9dbea3b2
--- /dev/null
+++ b/juick-www/src/test/java/com/juick/server/Stream.java
@@ -0,0 +1,184 @@
+/*
+ * 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;
+
+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;
+ }
+
+ 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();
+ }
+
+ 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) {
+ }
+ }
+ 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();
+ }
+}
diff --git a/juick-www/src/test/java/com/juick/server/StreamComponentServer.java b/juick-www/src/test/java/com/juick/server/StreamComponentServer.java
new file mode 100644
index 00000000..8c66c2e8
--- /dev/null
+++ b/juick-www/src/test/java/com/juick/server/StreamComponentServer.java
@@ -0,0 +1,61 @@
+package com.juick.server;
+
+import com.juick.xmpp.extensions.Handshake;
+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;
+
+import static com.juick.server.StreamNamespaces.NS_COMPONENT_ACCEPT;
+import static com.juick.server.StreamNamespaces.NS_STREAM;
+
+/**
+ * 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(NS_COMPONENT_ACCEPT)
+ || !parser.getNamespace("stream").equals(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'>", NS_STREAM, 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();
+ }
+}
diff --git a/juick-www/src/test/java/com/juick/server/StreamError.java b/juick-www/src/test/java/com/juick/server/StreamError.java
new file mode 100644
index 00000000..d552b590
--- /dev/null
+++ b/juick-www/src/test/java/com/juick/server/StreamError.java
@@ -0,0 +1,46 @@
+package com.juick.server;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+
+import static com.juick.server.StreamNamespaces.NS_XMPP_STREAMS;
+
+
+/**
+ * Created by vitalyster on 03.02.2017.
+ */
+public class StreamError {
+
+ private String condition;
+
+ public StreamError() {}
+
+ public StreamError(String condition) {
+ this.condition = condition;
+ }
+
+ public static StreamError parse(XmlPullParser parser) throws IOException, XmlPullParserException {
+ StreamError streamError = new StreamError();
+ while (parser.next() == XmlPullParser.START_TAG) {
+ final String tag = parser.getName();
+ final String xmlns = parser.getNamespace();
+ if (xmlns.equals(NS_XMPP_STREAMS)) {
+ streamError.condition = tag;
+ } else {
+ XmlUtils.skip(parser);
+ }
+ }
+ return streamError;
+ }
+
+ public String getCondition() {
+ return condition;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("<stream:error><%s xmlns='%s'/></stream:error>", condition, NS_XMPP_STREAMS);
+ }
+}
diff --git a/juick-www/src/test/java/com/juick/server/StreamHandler.java b/juick-www/src/test/java/com/juick/server/StreamHandler.java
new file mode 100644
index 00000000..d11fba1f
--- /dev/null
+++ b/juick-www/src/test/java/com/juick/server/StreamHandler.java
@@ -0,0 +1,13 @@
+package com.juick.server;
+
+import rocks.xmpp.addr.Jid;
+
+/**
+ * Created by vitalyster on 01.02.2017.
+ */
+public interface StreamHandler {
+ void ready();
+ void fail(final Exception ex);
+ boolean filter(Jid from, Jid to);
+ void stanzaReceived(String stanza);
+}
diff --git a/juick-www/src/test/java/com/juick/server/StreamNamespaces.java b/juick-www/src/test/java/com/juick/server/StreamNamespaces.java
new file mode 100644
index 00000000..fbedcae6
--- /dev/null
+++ b/juick-www/src/test/java/com/juick/server/StreamNamespaces.java
@@ -0,0 +1,10 @@
+package com.juick.server;
+
+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/juick-www/src/test/java/com/juick/server/XMPPError.java b/juick-www/src/test/java/com/juick/server/XMPPError.java
new file mode 100644
index 00000000..66e4ec44
--- /dev/null
+++ b/juick-www/src/test/java/com/juick/server/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;
+
+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/juick-www/src/test/java/com/juick/server/XMPPRouter.java b/juick-www/src/test/java/com/juick/server/XMPPRouter.java
new file mode 100644
index 00000000..d03a0880
--- /dev/null
+++ b/juick-www/src/test/java/com/juick/server/XMPPRouter.java
@@ -0,0 +1,173 @@
+package com.juick.server;
+
+import com.juick.server.xmpp.s2s.BasicXmppSession;
+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.Stanza;
+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.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.concurrent.ExecutorService;
+
+@Component
+public class XMPPRouter implements StreamHandler {
+ private static final Logger logger = LoggerFactory.getLogger(XMPPRouter.class);
+
+ @Inject
+ private ExecutorService service;
+
+ private final List<StreamComponentServer> connections = Collections.synchronizedList(new ArrayList<>());
+
+ private ServerSocket listener;
+
+ @Inject
+ private BasicXmppSession session;
+
+ @Value("${router_port:5347}")
+ private int routerPort;
+
+ @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;
+ }
+ }
+ }
+ }
+ if (connOut != null) {
+ connOut.send(xml);
+ return;
+ }
+ logger.error("component unavailable: {}", hostname);
+
+ }
+
+ 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) {
+ sendOut(parse(stanza));
+ }
+
+ @Override
+ public void ready() {
+
+ }
+
+ @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/juick-www/src/test/java/com/juick/server/XmlUtils.java b/juick-www/src/test/java/com/juick/server/XmlUtils.java
new file mode 100644
index 00000000..85fd352c
--- /dev/null
+++ b/juick-www/src/test/java/com/juick/server/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;
+
+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();
+ }
+}