aboutsummaryrefslogtreecommitdiff
path: root/src/main
diff options
context:
space:
mode:
authorGravatar alx2019-03-16 23:56:27 +0300
committerGravatar alx2019-03-16 23:56:27 +0300
commit06105f76dbfa3b65e63ed06f9c4d5107bd49ed88 (patch)
tree5702c01cec9688039d891f4a711878706101c1c5 /src/main
parent3ea4cd1942fa4e763034da11c5fa429407b67829 (diff)
parenta49105285d0d7719d7f222a507af2d5ac5b4bdb1 (diff)
Merge remote-tracking branch 'origin/master'
Diffstat (limited to 'src/main')
-rw-r--r--src/main/assets/embed.js4
-rw-r--r--src/main/assets/scripts.js2
-rw-r--r--src/main/java/com/juick/Message.java40
-rw-r--r--src/main/java/com/juick/User.java36
-rw-r--r--src/main/java/com/juick/formatters/PlainTextFormatter.java10
-rw-r--r--src/main/java/com/juick/model/UserInfo.java60
-rw-r--r--src/main/java/com/juick/model/facebook/User.java70
-rw-r--r--src/main/java/com/juick/model/twitter/User.java4
-rw-r--r--src/main/java/com/juick/model/vk/Token.java14
-rw-r--r--src/main/java/com/juick/model/vk/User.java18
-rw-r--r--src/main/java/com/juick/model/vk/UsersResponse.java4
-rw-r--r--src/main/java/com/juick/server/ActivityPubManager.java7
-rw-r--r--src/main/java/com/juick/server/CommandsManager.java34
-rw-r--r--src/main/java/com/juick/server/KeystoreManager.java9
-rw-r--r--src/main/java/com/juick/server/ServerManager.java120
-rw-r--r--src/main/java/com/juick/server/SignatureManager.java92
-rw-r--r--src/main/java/com/juick/server/WebsocketManager.java174
-rw-r--r--src/main/java/com/juick/server/XMPPManager.java (renamed from src/main/java/com/juick/server/XMPPConnection.java)92
-rw-r--r--src/main/java/com/juick/server/XMPPServer.java429
-rw-r--r--src/main/java/com/juick/server/api/ApiSocialLogin.java70
-rw-r--r--src/main/java/com/juick/server/api/Index.java14
-rw-r--r--src/main/java/com/juick/server/api/Messages.java3
-rw-r--r--src/main/java/com/juick/server/api/Notifications.java21
-rw-r--r--src/main/java/com/juick/server/api/PM.java1
-rw-r--r--src/main/java/com/juick/server/api/Users.java59
-rw-r--r--src/main/java/com/juick/server/api/activity/Profile.java117
-rw-r--r--src/main/java/com/juick/server/api/activity/helpers/ActivityIdDeserializer.java22
-rw-r--r--src/main/java/com/juick/server/api/activity/helpers/LinkValueDeserializer.java21
-rw-r--r--src/main/java/com/juick/server/api/activity/model/Activity.java4
-rw-r--r--src/main/java/com/juick/server/api/activity/model/Context.java3
-rw-r--r--src/main/java/com/juick/server/api/rss/MessagesView.java2
-rw-r--r--src/main/java/com/juick/server/api/webfinger/Resource.java2
-rw-r--r--src/main/java/com/juick/server/api/xnodeinfo2/Info.java40
-rw-r--r--src/main/java/com/juick/server/api/xnodeinfo2/model/NodeInfo.java35
-rw-r--r--src/main/java/com/juick/server/api/xnodeinfo2/model/Server.java6
-rw-r--r--src/main/java/com/juick/server/api/xnodeinfo2/model/ServiceInfo.java3
-rw-r--r--src/main/java/com/juick/server/api/xnodeinfo2/model/Usage.java3
-rw-r--r--src/main/java/com/juick/server/api/xnodeinfo2/model/UserStats.java3
-rw-r--r--src/main/java/com/juick/server/configuration/ActivityPubClientConfig.java8
-rw-r--r--src/main/java/com/juick/server/configuration/ApiAppConfiguration.java29
-rw-r--r--src/main/java/com/juick/server/configuration/BaseWebConfiguration.java18
-rw-r--r--src/main/java/com/juick/server/configuration/SecurityConfig.java40
-rw-r--r--src/main/java/com/juick/server/configuration/XMPPConfig.java34
-rw-r--r--src/main/java/com/juick/server/helpers/HeaderRequestInterceptor.java26
-rw-r--r--src/main/java/com/juick/server/util/ImageUtils.java2
-rw-r--r--src/main/java/com/juick/server/www/VaryHandler.java14
-rw-r--r--src/main/java/com/juick/server/www/controllers/AnythingFilter.java2
-rw-r--r--src/main/java/com/juick/server/www/controllers/MessagesWWW.java42
-rw-r--r--src/main/java/com/juick/server/www/controllers/Settings.java14
-rw-r--r--src/main/java/com/juick/server/www/controllers/SocialLogin.java70
-rw-r--r--src/main/java/com/juick/server/xmpp/XMPPStatusPage.java32
-rw-r--r--src/main/java/com/juick/server/xmpp/helpers/XMPPStatus.java48
-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
-rw-r--r--src/main/java/com/juick/server/xmpp/s2s/BasicXmppSession.java68
-rw-r--r--src/main/java/com/juick/server/xmpp/s2s/CacheEntry.java40
-rw-r--r--src/main/java/com/juick/server/xmpp/s2s/Connection.java158
-rw-r--r--src/main/java/com/juick/server/xmpp/s2s/ConnectionIn.java231
-rw-r--r--src/main/java/com/juick/server/xmpp/s2s/ConnectionListener.java16
-rw-r--r--src/main/java/com/juick/server/xmpp/s2s/ConnectionOut.java189
-rw-r--r--src/main/java/com/juick/server/xmpp/s2s/DNSQueries.java65
-rw-r--r--src/main/java/com/juick/server/xmpp/s2s/StanzaListener.java28
-rw-r--r--src/main/java/com/juick/server/xmpp/s2s/util/DialbackUtils.java37
-rw-r--r--src/main/java/com/juick/service/CrosspostService.java4
-rw-r--r--src/main/java/com/juick/service/CrosspostServiceImpl.java12
-rw-r--r--src/main/java/com/juick/service/MessagesService.java6
-rw-r--r--src/main/java/com/juick/service/MessagesServiceImpl.java143
-rw-r--r--src/main/java/com/juick/service/PMQueriesServiceImpl.java6
-rw-r--r--src/main/java/com/juick/service/TagServiceImpl.java30
-rw-r--r--src/main/java/com/juick/service/UserService.java5
-rw-r--r--src/main/java/com/juick/service/UserServiceImpl.java55
-rw-r--r--src/main/java/com/juick/service/security/HTTPSignatureAuthenticationFilter.java71
-rw-r--r--src/main/java/com/juick/service/security/HashParamAuthenticationFilter.java7
-rw-r--r--src/main/java/com/juick/util/MessageUtils.java78
-rw-r--r--src/main/java/com/juick/util/StreamUtils.java38
-rw-r--r--src/main/java/com/mitchellbosecke/pebble/extension/filters/TagsListFilter.java5
-rw-r--r--src/main/java/ru/sape/Sape.java4
-rw-r--r--src/main/java/ru/sape/SapePageLinks.java15
-rw-r--r--src/main/resources/banner.txt13
-rw-r--r--src/main/resources/db/migration/V1.17__drop tags column.sql1
-rw-r--r--src/main/resources/db/migration/V1.18__increase messages and replies timestamp precision.sql5
-rw-r--r--src/main/resources/schema.sql2
-rw-r--r--src/main/resources/templates/layouts/note.html2
-rw-r--r--src/main/resources/templates/views/login.html2
-rw-r--r--src/main/resources/templates/views/partial/footer.html2
-rw-r--r--src/main/resources/templates/views/partial/message.html6
-rw-r--r--src/main/resources/templates/views/pm_inbox.html2
-rw-r--r--src/main/resources/templates/views/pm_sent.html2
-rw-r--r--src/main/resources/templates/views/thread.html18
97 files changed, 965 insertions, 3282 deletions
diff --git a/src/main/assets/embed.js b/src/main/assets/embed.js
index d4cbab8e..3b8946bf 100644
--- a/src/main/assets/embed.js
+++ b/src/main/assets/embed.js
@@ -72,8 +72,8 @@ function makeIframe(src, w, h, scrolling='no') {
}
function makeResizableToRatio(element, ratio) {
- element.dataset['ratio'] = ratio;
- makeResizable(element, w => w * element.dataset['ratio']);
+ element.setAttribute('data-ratio', ratio);
+ makeResizable(element, w => w * element.getAttribute('data-ratio'));
}
// calcHeight :: Number -> Number -- calculate element height for a given width
diff --git a/src/main/assets/scripts.js b/src/main/assets/scripts.js
index f90fe2c5..e7396c31 100644
--- a/src/main/assets/scripts.js
+++ b/src/main/assets/scripts.js
@@ -1,4 +1,4 @@
-require('element-closest');
+require('element-closest/browser');
require('classlist.js');
require('url-polyfill');
require('formdata-polyfill');
diff --git a/src/main/java/com/juick/Message.java b/src/main/java/com/juick/Message.java
index 10380826..2035d4c6 100644
--- a/src/main/java/com/juick/Message.java
+++ b/src/main/java/com/juick/Message.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2008-2017, Juick
+ * Copyright (C) 2008-2019, Juick
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
@@ -21,15 +21,15 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.juick.adapters.SimpleDateAdapter;
import com.juick.model.Entity;
-import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.builder.ToStringBuilder;
+import javax.annotation.Nonnull;
import javax.xml.bind.annotation.*;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import java.net.URI;
import java.time.Instant;
-import java.util.ArrayList;
import java.util.HashSet;
+import java.util.LinkedHashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
@@ -45,8 +45,8 @@ public class Message implements Comparable {
private int replyto = 0;
private String text = null;
private User user = null;
- private final List<Tag> tags;
- private Instant ts;
+ private Set<Tag> tags;
+ private Instant created;
private Instant updated;
private Instant updatedAt;
private boolean unread;
@@ -81,12 +81,12 @@ public class Message implements Comparable {
private URI replyToUri;
private boolean html;
- private Set<String> recommendations;
+ private Set<User> recommendations;
private List<Entity> entities;
public Message() {
- tags = new ArrayList<>();
+ tags = new LinkedHashSet<>();
reactions = new HashSet<>();
}
@@ -120,7 +120,7 @@ public class Message implements Comparable {
}
@Override
- public int compareTo(Object obj) throws ClassCastException {
+ public int compareTo(@Nonnull Object obj) throws ClassCastException {
if (obj == this)
return 0;
@@ -180,15 +180,15 @@ public class Message implements Comparable {
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "UTC")
@XmlAttribute(name = "ts")
@XmlJavaTypeAdapter(SimpleDateAdapter.class)
- public Instant getTimestamp() {
- return ts;
+ public Instant getCreated() {
+ return created;
}
- public void setTimestamp(Instant timestamp) {
- this.ts = timestamp;
+ public void setCreated(Instant timestamp) {
+ this.created = timestamp;
}
- @XmlElement(name = "to", namespace = "http://juick.com/user")
+ @XmlTransient
public User getTo() {
return to;
}
@@ -218,14 +218,12 @@ public class Message implements Comparable {
@JsonProperty("tags")
@XmlElement(name = "tag")
- public List<Tag> getTags() {
+ public Set<Tag> getTags() {
return tags;
}
- public void setTags(List<Tag> tags) {
- this.tags.clear();
- if (CollectionUtils.isNotEmpty(tags))
- this.tags.addAll(tags);
+ public void setTags(Set<Tag> tags) {
+ this.tags = tags;
}
@XmlAttribute
@@ -333,11 +331,11 @@ public class Message implements Comparable {
this.service = service;
}
- public Set<String> getRecommendations() {
+ public Set<User> getRecommendations() {
return recommendations;
}
- public void setRecommendations(Set<String> recommendations) {
+ public void setRecommendations(Set<User> recommendations) {
this.recommendations = recommendations;
}
@@ -363,6 +361,7 @@ public class Message implements Comparable {
this.replyUri = replyUri;
}
+ @XmlAttribute(name = "html")
public boolean isHtml() {
return html;
}
@@ -371,6 +370,7 @@ public class Message implements Comparable {
this.html = html;
}
+ @XmlTransient
public URI getReplyToUri() {
return replyToUri;
}
diff --git a/src/main/java/com/juick/User.java b/src/main/java/com/juick/User.java
index 7221e416..f34f07a8 100644
--- a/src/main/java/com/juick/User.java
+++ b/src/main/java/com/juick/User.java
@@ -53,6 +53,9 @@ public class User {
private URI uri;
private Instant seen;
private boolean verified;
+ private String country;
+ private String url;
+ private String description;
public User() {
tokens = new ArrayList<>();
@@ -62,12 +65,13 @@ public class User {
@Override
public boolean equals(Object obj) {
return obj == this ||
- (obj instanceof User && ((User) obj).getUid() == this.getUid());
+ (obj instanceof User && ((User) obj).getUid() == this.getUid()
+ && ((User) obj).getUri().toString().equals(this.getUri().toString()));
}
@Override
public int hashCode() {
- return Objects.hash(uid);
+ return Objects.hash(uid, uri);
}
@Override
@@ -170,7 +174,7 @@ public class User {
@XmlTransient
@JsonIgnore
public boolean isAnonymous() {
- return false;
+ return uid == 0;
}
@Nonnull
@@ -206,6 +210,7 @@ public class User {
}
@Nonnull
+ @XmlTransient
public URI getUri() {
if (uri == null) {
uri = URI.create(StringUtils.EMPTY);
@@ -225,6 +230,7 @@ public class User {
this.seen = seen;
}
+ @XmlTransient
public boolean isVerified() {
return verified;
}
@@ -232,4 +238,28 @@ public class User {
public void setVerified(boolean verified) {
this.verified = verified;
}
+
+ public String getCountry() {
+ return country;
+ }
+
+ public void setCountry(String country) {
+ this.country = country;
+ }
+
+ public String getUrl() {
+ return url;
+ }
+
+ public void setUrl(String url) {
+ this.url = url;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
}
diff --git a/src/main/java/com/juick/formatters/PlainTextFormatter.java b/src/main/java/com/juick/formatters/PlainTextFormatter.java
index 378a523f..41d24e93 100644
--- a/src/main/java/com/juick/formatters/PlainTextFormatter.java
+++ b/src/main/java/com/juick/formatters/PlainTextFormatter.java
@@ -57,7 +57,7 @@ public class PlainTextFormatter {
public static String formatPostSummary(Message m) {
int cropLength = 384;
- String timeAgo = pt.format(Date.from(m.getTimestamp()));
+ String timeAgo = pt.format(Date.from(m.getCreated()));
String repliesCount = m.getReplies() == 1 ? "; 1 reply" : m.getReplies() == 0 ? ""
: String.format("; %d replies", m.getReplies());
StringBuilder sb = new StringBuilder();
@@ -84,6 +84,14 @@ public class PlainTextFormatter {
return "https://juick.com/m/" + jmsg.getMid();
}
+ public static String markdownUrl(String url, String description) {
+ if (StringUtils.isNotBlank(description)) {
+ return String.format("[%s](%s)", description, url);
+ } else {
+ return url;
+ }
+ }
+
public static String formatPostNumber(com.juick.Message jmsg) {
if (jmsg.getRid() > 0) {
return String.format("%d/%d", jmsg.getMid(), jmsg.getRid());
diff --git a/src/main/java/com/juick/model/UserInfo.java b/src/main/java/com/juick/model/UserInfo.java
deleted file mode 100644
index ca5d75e0..00000000
--- a/src/main/java/com/juick/model/UserInfo.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright (C) 2008-2017, Juick
- *
- * 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.model;
-
-/**
- * Created by vt on 03/09/16.
- */
-public class UserInfo {
- private String fullName;
- private String country;
- private String url;
- private String description;
-
- public String getFullName() {
- return fullName;
- }
-
- public void setFullName(String fullName) {
- this.fullName = fullName;
- }
-
- public String getCountry() {
- return country;
- }
-
- public void setCountry(String country) {
- this.country = country;
- }
-
- public String getUrl() {
- return url;
- }
-
- public void setUrl(String url) {
- this.url = url;
- }
-
- public String getDescription() {
- return description;
- }
-
- public void setDescription(String description) {
- this.description = description;
- }
-}
diff --git a/src/main/java/com/juick/model/facebook/User.java b/src/main/java/com/juick/model/facebook/User.java
index 80838de6..a9288fe4 100644
--- a/src/main/java/com/juick/model/facebook/User.java
+++ b/src/main/java/com/juick/model/facebook/User.java
@@ -27,98 +27,28 @@ import com.fasterxml.jackson.annotation.JsonProperty;
public class User {
private String id;
private String name;
- private String link;
- private boolean verified;
private String firstName;
private String lastName;
- private String gender;
- private String locale;
- private String timezone;
- private String updatedTime;
private String email;
public String getId() {
return id;
}
- public void setId(String id) {
- this.id = id;
- }
-
public String getName() {
return name;
}
- public void setName(String name) {
- this.name = name;
- }
-
- public String getLink() {
- return link;
- }
-
- public void setLink(String link) {
- this.link = link;
- }
-
- public boolean getVerified() {
- return verified;
- }
-
- public void setVerified(boolean verified) {
- this.verified = verified;
- }
-
@JsonProperty("first_name")
public String getFirstName() {
return firstName;
}
- public void setFirstName(String firstName) {
- this.firstName = firstName;
- }
-
- public String getGender() {
- return gender;
- }
-
- public void setGender(String gender) {
- this.gender = gender;
- }
@JsonProperty("last_name")
public String getLastName() {
return lastName;
}
- public void setLastName(String lastName) {
- this.lastName = lastName;
- }
-
- public String getLocale() {
- return locale;
- }
-
- public void setLocale(String locale) {
- this.locale = locale;
- }
-
- public String getTimezone() {
- return timezone;
- }
-
- public void setTimezone(String timezone) {
- this.timezone = timezone;
- }
-
- @JsonProperty("updated_time")
- public String getUpdatedTime() {
- return updatedTime;
- }
-
- public void setUpdatedTime(String updatedTime) {
- this.updatedTime = updatedTime;
- }
-
public String getEmail() {
return email;
}
diff --git a/src/main/java/com/juick/model/twitter/User.java b/src/main/java/com/juick/model/twitter/User.java
index 3c80eff4..22f44cf6 100644
--- a/src/main/java/com/juick/model/twitter/User.java
+++ b/src/main/java/com/juick/model/twitter/User.java
@@ -31,8 +31,4 @@ public class User {
public String getScreenName() {
return screenName;
}
-
- public void setScreenName(String screenName) {
- this.screenName = screenName;
- }
}
diff --git a/src/main/java/com/juick/model/vk/Token.java b/src/main/java/com/juick/model/vk/Token.java
index ed93a3ab..15645f99 100644
--- a/src/main/java/com/juick/model/vk/Token.java
+++ b/src/main/java/com/juick/model/vk/Token.java
@@ -17,11 +17,13 @@
package com.juick.model.vk;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
/**
* Created by vitalyster on 28.11.2016.
*/
+@JsonIgnoreProperties(ignoreUnknown = true)
public class Token {
private Long userId;
private String accessToken;
@@ -32,25 +34,13 @@ public class Token {
return userId;
}
- public void setUserId(Long userId) {
- this.userId = userId;
- }
-
@JsonProperty("access_token")
public String getAccessToken() {
return accessToken;
}
- public void setAccessToken(String accessToken) {
- this.accessToken = accessToken;
- }
-
@JsonProperty("expires_in")
public String getExpiresIn() {
return expiresIn;
}
-
- public void setExpiresIn(String expiresIn) {
- this.expiresIn = expiresIn;
- }
}
diff --git a/src/main/java/com/juick/model/vk/User.java b/src/main/java/com/juick/model/vk/User.java
index aeb18285..7eb03182 100644
--- a/src/main/java/com/juick/model/vk/User.java
+++ b/src/main/java/com/juick/model/vk/User.java
@@ -17,11 +17,13 @@
package com.juick.model.vk;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
/**
* Created by vitalyster on 28.11.2016.
*/
+@JsonIgnoreProperties(ignoreUnknown = true)
public class User {
private String id;
private String firstName;
@@ -33,33 +35,17 @@ public class User {
return firstName;
}
- public void setFirstName(String firstName) {
- this.firstName = firstName;
- }
-
@JsonProperty("last_name")
public String getLastName() {
return lastName;
}
- public void setLastName(String lastName) {
- this.lastName = lastName;
- }
-
@JsonProperty("screen_name")
public String getScreenName() {
return screenName;
}
- public void setScreenName(String screenName) {
- this.screenName = screenName;
- }
-
public String getId() {
return id;
}
-
- public void setId(String id) {
- this.id = id;
- }
}
diff --git a/src/main/java/com/juick/model/vk/UsersResponse.java b/src/main/java/com/juick/model/vk/UsersResponse.java
index 67505703..d82b0c4b 100644
--- a/src/main/java/com/juick/model/vk/UsersResponse.java
+++ b/src/main/java/com/juick/model/vk/UsersResponse.java
@@ -31,8 +31,4 @@ public class UsersResponse {
public List<User> getUsers() {
return users;
}
-
- public void setUsers(List<User> users) {
- this.users = users;
- }
}
diff --git a/src/main/java/com/juick/server/ActivityPubManager.java b/src/main/java/com/juick/server/ActivityPubManager.java
index 5249f3c9..a5398325 100644
--- a/src/main/java/com/juick/server/ActivityPubManager.java
+++ b/src/main/java/com/juick/server/ActivityPubManager.java
@@ -182,9 +182,10 @@ public class ActivityPubManager implements ActivityListener, NotificationListene
cc.add(replier);
note.setCc(cc);
}
+ subscribers.addAll(note.getCc());
subscribers.forEach(acct -> {
Optional<Context> context = signatureManager.getContext(URI.create(acct));
- if (context.isPresent()) {
+ if (context.isPresent() && context.get() instanceof Person) {
Person follower = (Person)context.get();
Create create = new Create();
create.setId(note.getId());
@@ -268,7 +269,7 @@ public class ActivityPubManager implements ActivityListener, NotificationListene
note.setTo(Collections.singletonList("https://www.w3.org/ns/activitystreams#Public"));
note.setCc(Collections.singletonList(followersUri(msg.getUser())));
}
- note.setPublished(msg.getTimestamp());
+ note.setPublished(msg.getCreated());
if (StringUtils.isNotBlank(msg.getAttachmentType())) {
Image attachment = new Image();
attachment.setId(msg.getAttachment().getMedium().getUrl());
@@ -304,7 +305,7 @@ public class ActivityPubManager implements ActivityListener, NotificationListene
Person person = (Person) personContext.get();
note.getTags().add(new Mention(person.getUrl(), person.getPreferredUsername()));
List<String> cc = new ArrayList<>(note.getCc());
- cc.add(person.getUrl());
+ cc.add(person.getId());
note.setCc(cc);
}
});
diff --git a/src/main/java/com/juick/server/CommandsManager.java b/src/main/java/com/juick/server/CommandsManager.java
index fa3c5537..f6f29941 100644
--- a/src/main/java/com/juick/server/CommandsManager.java
+++ b/src/main/java/com/juick/server/CommandsManager.java
@@ -25,6 +25,7 @@ import com.juick.model.CommandResult;
import com.juick.model.TagStats;
import com.juick.server.helpers.annotation.UserCommand;
import com.juick.server.util.HttpUtils;
+import com.juick.server.www.WebApp;
import com.juick.service.*;
import com.juick.service.activities.DeleteMessageEvent;
import com.juick.service.component.*;
@@ -34,12 +35,15 @@ import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.apache.commons.lang3.reflect.MethodUtils;
import org.apache.commons.lang3.tuple.Pair;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Component;
import javax.annotation.Nonnull;
import javax.inject.Inject;
+import java.io.IOException;
import java.lang.reflect.Method;
import java.net.URI;
import java.util.*;
@@ -53,6 +57,7 @@ import java.util.stream.Collectors;
*/
@Component
public class CommandsManager {
+ private static final Logger logger = LoggerFactory.getLogger(CommandsManager.class);
@Inject
private MessagesService messagesService;
@Inject
@@ -75,6 +80,8 @@ public class CommandsManager {
private ApplicationEventPublisher applicationEventPublisher;
@Inject
private ImagesService imagesService;
+ @Inject
+ private WebApp webApp;
public CommandResult processCommand(@Nonnull User user, String data, @Nonnull URI attachment) throws Exception {
if (!user.isAnonymous()) {
@@ -121,12 +128,16 @@ public class CommandsManager {
: HttpUtils.downloadImage(attachment.toURL(), tmpDir).getHost();
attachmentType = attachmentFName.substring(attachmentFName.length() - 3);
}
+ if (StringUtils.isEmpty(body) && !haveAttachment) {
+ return CommandResult.fromString("Empty message");
+ }
int mid = messagesService.createMessage(user.getUid(), body, attachmentType, tags.getRight());
if (haveAttachment) {
String fname = String.format("%d.%s", mid, attachmentType);
imagesService.saveImageWithPreviews(attachmentFName, fname);
}
Message msg = messagesService.getMessage(mid).orElseThrow(IllegalStateException::new);
+ msg.getUser().setAvatar(webApp.getAvatarUrl(msg.getUser()));
subscriptionService.subscribeMessage(msg, user);
applicationEventPublisher.publishEvent(new MessageReadEvent(this, user, msg));
@@ -160,7 +171,7 @@ public class CommandsManager {
String body = arguments[1];
User user_to = userService.getUserByName(arguments[0]);
-
+ user_to.setAvatar(webApp.getAvatarUrl(user_to));
if (!user_to.isAnonymous()) {
if (!userService.isInBLAny(user_to.getUid(), user_from.getUid())) {
if (pmQueriesService.createPM(user_from.getUid(), user_to.getUid(), body)) {
@@ -523,12 +534,22 @@ public class CommandsManager {
String attachmentFName = null;
String attachmentType = null;
if (haveAttachment) {
- attachmentFName = attachment.getScheme().equals("juick") ? attachment.getHost()
- : HttpUtils.downloadImage(attachment.toURL(), tmpDir).getHost();
- attachmentType = attachmentFName.substring(attachmentFName.length() - 3);
+ if (attachment.getScheme().equals("juick")) {
+ attachmentFName = attachment.getHost();
+ attachmentType = attachmentFName.substring(attachmentFName.length() - 3);
+ } else {
+ try {
+ attachmentFName = HttpUtils.downloadImage(attachment.toURL(), tmpDir).getHost();
+ attachmentType = attachmentFName.substring(attachmentFName.length() - 3);
+ } catch (IOException e) {
+ logger.warn("Can not download {}", attachment.toURL());
+ }
+ }
}
- int newrid = messagesService.createReply(mid, rid, user, txt, attachmentType);
- if (haveAttachment) {
+ boolean attachmentProcessed = !haveAttachment || StringUtils.isNotEmpty(attachmentType);
+ String messageText = attachmentProcessed ? txt : String.format("%s %s", txt, attachment.toASCIIString());
+ int newrid = messagesService.createReply(mid, rid, user, messageText, attachmentType);
+ if (haveAttachment && attachmentProcessed) {
String fname = String.format("%d-%d.%s", mid, newrid, attachmentType);
imagesService.saveImageWithPreviews(attachmentFName, fname);
}
@@ -537,6 +558,7 @@ public class CommandsManager {
Message original = messagesService.getMessage(mid).orElseThrow(IllegalStateException::new);
subscriptionService.subscribeMessage(original, user);
Message reply = messagesService.getReply(mid, newrid);
+ reply.getUser().setAvatar(webApp.getAvatarUrl(reply.getUser()));
applicationEventPublisher.publishEvent(new MessageEvent(this, reply, subscriptionService.getUsersSubscribedToComments(original, reply)));
return CommandResult.build(reply,"Reply posted.\n#" + mid + "/" + newrid + " "
+ "https://juick.com/m/" + mid + "#" + newrid,
diff --git a/src/main/java/com/juick/server/KeystoreManager.java b/src/main/java/com/juick/server/KeystoreManager.java
index 67a24f11..3ae7b866 100644
--- a/src/main/java/com/juick/server/KeystoreManager.java
+++ b/src/main/java/com/juick/server/KeystoreManager.java
@@ -19,20 +19,17 @@ import java.security.spec.X509EncodedKeySpec;
import java.util.Arrays;
import java.util.stream.Collectors;
-@Component
public class KeystoreManager {
private static final Logger logger = LoggerFactory.getLogger("com.juick.server");
- @Value("${keystore:juick.p12}")
- private String keystore;
- @Value("${keystore_password:secret}")
+
private String keystorePassword;
private KeyStore ks;
private KeyManagerFactory kmf;
- @PostConstruct
- public void init() {
+ public KeystoreManager(String keystore, String keystorePassword) {
+ this.keystorePassword = keystorePassword;
try (InputStream ksIs = new FileInputStream(keystore)) {
ks = KeyStore.getInstance("PKCS12");
ks.load(ksIs, keystorePassword.toCharArray());
diff --git a/src/main/java/com/juick/server/ServerManager.java b/src/main/java/com/juick/server/ServerManager.java
index 7a95ec43..46faf9eb 100644
--- a/src/main/java/com/juick/server/ServerManager.java
+++ b/src/main/java/com/juick/server/ServerManager.java
@@ -16,7 +16,6 @@
*/
package com.juick.server;
-import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.juick.Message;
import com.juick.User;
@@ -24,25 +23,18 @@ import com.juick.model.AnonymousUser;
import com.juick.service.MessagesService;
import com.juick.service.SubscriptionService;
import com.juick.service.UserService;
-import com.juick.service.component.LikeEvent;
-import com.juick.service.component.MessageEvent;
-import com.juick.service.component.MessageReadEvent;
-import com.juick.service.component.NotificationListener;
-import com.juick.service.component.PingEvent;
-import com.juick.service.component.SubscribeEvent;
-import com.juick.service.component.TopEvent;
+import com.juick.service.component.*;
import com.juick.util.MessageUtils;
+import org.apache.commons.collections4.ListUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
-import org.springframework.web.socket.TextMessage;
import javax.annotation.PostConstruct;
import javax.inject.Inject;
-import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -62,8 +54,6 @@ public class ServerManager implements NotificationListener {
@Inject
private MessagesService messagesService;
@Inject
- private WebsocketManager wsHandler;
- @Inject
private SubscriptionService subscriptionService;
@Inject
private UserService userService;
@@ -81,96 +71,15 @@ public class ServerManager implements NotificationListener {
private void onJuickPM(final User to, final com.juick.Message jmsg) {
- try {
- String json = jsonMapper.writeValueAsString(jmsg);
- synchronized (wsHandler.getClients()) {
- wsHandler.getClients().stream().filter(c ->
- (!c.legacy && c.visitor.getUid() == to.getUid()) || c.visitor.equals(serviceUser))
- .forEach(c -> {
- try {
- logger.debug("sending pm to {}", c.visitor.getUid());
- c.sendMessage(new TextMessage(json));
- } catch (IOException e) {
- logger.warn("ws error", e);
- }
- });
- }
- } catch (JsonProcessingException e) {
- logger.warn("Invalid JSON", e);
- }
- messageEvent(jmsg, Collections.singletonList(to));
+ messageEvent(jmsg, Arrays.asList(to, jmsg.getUser()));
}
private void onJuickMessagePost(final com.juick.Message jmsg, List<User> subscribedUsers) {
- try {
- String json = jsonMapper.writeValueAsString(jmsg);
- List<Integer> uids = subscribedUsers
- .stream().map(User::getUid).collect(Collectors.toList());
- synchronized (wsHandler.getClients()) {
- wsHandler.getClients().stream().filter(c ->
- (!c.legacy && c.visitor.isAnonymous()) // anonymous users
- || c.visitor.equals(serviceUser) // services
- || (!c.legacy && uids.contains(c.visitor.getUid()))) // subscriptions
- .forEach(c -> {
- try {
- logger.debug("sending message to {}", c.visitor.getUid());
- c.sendMessage(new TextMessage(json));
- } catch (IOException e) {
- logger.warn("ws error", e);
- }
- });
- wsHandler.getClients().stream().filter(c ->
- c.legacy && c.allMessages) // legacy all posts
- .forEach(c -> {
- try {
- logger.debug("sending message to legacy client {}", c.visitor.getUid());
- c.sendMessage(new TextMessage(json));
- } catch (IOException e) {
- logger.warn("ws error", e);
- }
- });
- }
- } catch (JsonProcessingException e) {
- logger.warn("Invalid JSON", e);
- }
messageEvent(jmsg, subscribedUsers);
messageEvent(jmsg, Collections.singletonList(AnonymousUser.INSTANCE));
}
private void onJuickMessageReply(final com.juick.Message jmsg, final List<User> subscribedUsers) {
- try {
-
- String json = jsonMapper.writeValueAsString(jmsg);
- List<Integer> threadUsers =
- subscribedUsers
- .stream().map(User::getUid).collect(Collectors.toList());
- synchronized (wsHandler.getClients()) {
- wsHandler.getClients().stream().filter(c ->
- (!c.legacy && c.visitor.isAnonymous()) // anonymous users
- || c.visitor.equals(serviceUser) // services
- || (!c.legacy && threadUsers.contains(c.visitor.getUid()))) // subscriptions
- .forEach(c -> {
- try {
- logger.debug("sending reply to {}", c.visitor.getUid());
- c.sendMessage(new TextMessage(json));
- } catch (IOException e) {
- logger.warn("ws error", e);
- }
- });
- wsHandler.getClients().stream().filter(c ->
- (c.legacy && c.allReplies) || (c.legacy && c.MID == jmsg.getMid())) // legacy replies
- .forEach(c -> {
- try {
- logger.debug("sending reply to legacy client {}", c.visitor.getUid());
- c.sendMessage(new TextMessage(json));
- } catch (IOException e) {
- logger.warn("ws error", e);
- }
- });
- }
- } catch (JsonProcessingException e) {
- logger.warn("Invalid JSON", e);
- }
messageEvent(jmsg, subscribedUsers);
messageEvent(jmsg, Collections.singletonList(AnonymousUser.INSTANCE));
}
@@ -178,7 +87,7 @@ public class ServerManager implements NotificationListener {
@Override
public void processMessageEvent(MessageEvent event) {
com.juick.Message jmsg = event.getMessage();
- List<User> subscribedUsers = event.getUsers();
+ List<User> subscribedUsers = ListUtils.union(event.getUsers(), Collections.singletonList(jmsg.getUser()));
if (jmsg.isService()) {
readEvent(jmsg, Collections.singletonList(serviceUser));
return;
@@ -186,16 +95,14 @@ public class ServerManager implements NotificationListener {
if (MessageUtils.isPM(jmsg)) {
onJuickPM(jmsg.getTo(), jmsg);
} else if (!MessageUtils.isReply(jmsg)) {
- // to get full message with attachment, etc.
- onJuickMessagePost(messagesService.getMessage(jmsg.getMid()).orElseThrow(IllegalStateException::new), subscribedUsers);
+ onJuickMessagePost(jmsg, subscribedUsers);
} else {
// to get quote and attachment
Message op = messagesService.getMessage(jmsg.getMid()).orElseThrow(IllegalStateException::new);
- com.juick.Message reply = messagesService.getReply(jmsg.getMid(), jmsg.getRid());
- subscriptionService.getUsersSubscribedToComments(op, reply, true).stream()
- .filter(u -> userService.isReplyToBL(u, reply))
- .forEach(b -> messagesService.setLastReadComment(b, reply.getMid(), reply.getRid()));
- onJuickMessageReply(reply, subscribedUsers);
+ subscriptionService.getUsersSubscribedToComments(op, jmsg, true).stream()
+ .filter(u -> userService.isReplyToBL(u, jmsg))
+ .forEach(b -> messagesService.setLastReadComment(b, jmsg.getMid(), jmsg.getRid()));
+ onJuickMessageReply(jmsg, subscribedUsers);
}
messageEvent(jmsg, Collections.singletonList(serviceUser));
}
@@ -226,15 +133,6 @@ public class ServerManager implements NotificationListener {
serviceMessage.setUser(user);
serviceMessage.setMid(source.getMid());
serviceMessage.setUnread(false);
- wsHandler.getClients().stream().filter(c ->
- (!c.legacy && c.visitor == user) || c.visitor.equals(serviceUser)
- ).forEach(u -> {
- try {
- u.sendMessage(new TextMessage(jsonMapper.writeValueAsString(serviceMessage)));
- } catch (IOException e) {
- logger.error("JSON error", e);
- }
- });
readEvent(serviceMessage, Collections.singletonList(serviceUser));
}
diff --git a/src/main/java/com/juick/server/SignatureManager.java b/src/main/java/com/juick/server/SignatureManager.java
index b3b7a301..755575ce 100644
--- a/src/main/java/com/juick/server/SignatureManager.java
+++ b/src/main/java/com/juick/server/SignatureManager.java
@@ -1,14 +1,17 @@
package com.juick.server;
import com.fasterxml.jackson.databind.ObjectMapper;
+import com.juick.User;
+import com.juick.model.AnonymousUser;
import com.juick.server.api.activity.model.Context;
import com.juick.server.api.activity.model.objects.Person;
import com.juick.server.api.webfinger.model.Account;
import com.juick.server.api.webfinger.model.Link;
+import com.juick.service.UserService;
import com.juick.util.DateFormattersHolder;
+import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import org.springframework.context.ApplicationEventPublisher;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
@@ -41,7 +44,7 @@ public class SignatureManager {
@Inject
private ObjectMapper jsonMapper;
@Inject
- private ApplicationEventPublisher applicationEventPublisher;
+ private UserService userService;
@Inject
private RestTemplate apClient;
@@ -50,56 +53,89 @@ public class SignatureManager {
URI inbox = uriComponentsBuilder.build().toUri();
Instant now = Instant.now();
String requestDate = DateFormattersHolder.getHttpDateFormatter().format(now);
- Signature templateSignature = new Signature(from.getPublicKey().getId(), "rsa-sha256", null,
- "(request-target)", "host", "date");
- Signer signer = new Signer(keystoreManager.getPrivateKey(), templateSignature);
- Map<String, String> headers = new HashMap<>();
- headers.put("host", inbox.getHost());
- headers.put("date", requestDate);
- Signature signature = signer.sign("POST", inbox.getPath(), headers);
+ String host = inbox.getPort() > 0 ? String.format("%s:%d", inbox.getHost(), inbox.getPort()) : inbox.getHost();
+ String signatureString = addSignature(from, host, "POST", inbox.getPath(), requestDate);
+
HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.add("Content-Type", Context.ACTIVITYSTREAMS_PROFILE_MEDIA_TYPE);
requestHeaders.add("Date", requestDate);
- requestHeaders.add("Signature", signature.toString().substring(10));
+ requestHeaders.add("Host", host);
+ requestHeaders.add("Signature", signatureString);
HttpEntity<Context> request = new HttpEntity<>(Context.build(data), requestHeaders);
- //boolean valid = verifySignature(Signature.fromString(requestHeaders.getFirst("Signature")),
- // keystoreManager.getPublicKey(), "POST", inbox.getPath(), headers);
logger.info("Sending context: {}", jsonMapper.writeValueAsString(data));
logger.info("Request date: {}", requestDate);
ResponseEntity<Void> response = apClient.postForEntity(inbox, request, Void.class);
logger.info("accepted follower: {}", response.getStatusCodeValue());
+ }
+ public String addSignature(Person from, String host, String method, String path, String dateString) throws IOException {
+ return addSignature(from, host, method, path, dateString, keystoreManager);
}
- public boolean verifySignature(String signatureString, URI actor, String method, String path, Map<String, String> headers) {
- Optional<Context> context = getContext(actor);
+
+ public String addSignature(Person from, String host, String method, String path, String dateString, KeystoreManager keystoreManager) throws IOException {
+ Signature templateSignature = new Signature(from.getPublicKey().getId(), "rsa-sha256", null,
+ "(request-target)", "host", "date");
+ Map<String, String> headers = new HashMap<>();
+ headers.put("host", host);
+ headers.put("date", dateString);
+ Signer signer = new Signer(keystoreManager.getPrivateKey(), templateSignature);
+ Signature signature = signer.sign(method, path, headers);
+ // remove "Signature: " from result
+ return signature.toString().substring(10);
+ }
+
+ public User verifySignature(String method, String path, Map<String, String> headers) {
+ String signatureString = headers.get("signature");
+ logger.info("Signature: {}", signatureString);
+ Signature signature = Signature.fromString(signatureString);
+ Optional<Context> context = getContext(UriComponentsBuilder.fromUriString(signature.getKeyId())
+ .fragment(null).build().toUri());
if (context.isPresent() && context.get() instanceof Person) {
Person person = (Person) context.get();
Key key = KeystoreManager.publicKeyOf(person);
- Verifier verifier = new Verifier(key, Signature.fromString(signatureString));
+
+ Verifier verifier = new Verifier(key, signature);
try {
boolean result = verifier.verify(method, path, headers);
- logger.info("signature is valid: {}", result);
- return result;
+ logger.info("signature of {} is valid: {}", signature.getKeyId(), result);
+ if (result) {
+ User user = new User();
+ user.setUri(URI.create(person.getId()));
+ if (key.equals(keystoreManager.getPublicKey())) {
+ return userService.getUserByName(person.getName());
+ }
+ return user;
+ } else {
+ return AnonymousUser.INSTANCE;
+ }
} catch (NoSuchAlgorithmException | SignatureException | IOException e) {
- logger.info("signature exception", e);
- return false;
+ logger.warn("Invalid signature {}", signatureString);
}
+ } else {
+ logger.warn("Unknown keyId");
}
- logger.info("person not found");
- return false;
+ return AnonymousUser.INSTANCE;
}
public Optional<Context> getContext(URI contextUri) {
- Context context = apClient.getForEntity(contextUri, Context.class).getBody();
- if (context == null) {
- logger.warn("Cannot identify {}", contextUri);
- return Optional.empty();
+ try {
+ Context context = apClient.getForEntity(contextUri, Context.class).getBody();
+ if (context == null) {
+ logger.warn("Cannot identify {}", contextUri);
+ return Optional.empty();
+ }
+ return Optional.of(context);
+ } catch (Exception e) {
+ logger.warn("REST Exception on {}: {}", contextUri, e.getMessage());
}
- return Optional.of(context);
+ return Optional.empty();
}
public Optional<Context> discoverPerson(String acct) {
- Jid acctId = Jid.of(acct);
+ String[] accountParts = acct.split(":", 2);
+ String account = accountParts[0];
+ int port = accountParts.length > 1 ? Integer.valueOf(accountParts[1]) : 80;
+ Jid acctId = Jid.of(account);
URI resourceUri = UriComponentsBuilder.fromUriString(
- String.format("https://%s/.well-known/webfinger?resource=acct:%s", acctId.getDomain(), acct)).build().toUri();
+ String.format("http://%s:%d/.well-known/webfinger?resource=acct:%s", acctId.getDomain(), port, account)).build().toUri();
Account acctData = apClient.getForEntity(resourceUri, Account.class).getBody();
if (acctData != null) {
for (Link l : acctData.getLinks()) {
diff --git a/src/main/java/com/juick/server/WebsocketManager.java b/src/main/java/com/juick/server/WebsocketManager.java
deleted file mode 100644
index 1b62b984..00000000
--- a/src/main/java/com/juick/server/WebsocketManager.java
+++ /dev/null
@@ -1,174 +0,0 @@
-/*
- * Copyright (C) 2008-2017, Juick
- *
- * 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 com.juick.User;
-import com.juick.model.AnonymousUser;
-import com.juick.model.CommandResult;
-import com.juick.server.util.HttpForbiddenException;
-import com.juick.server.util.HttpNotFoundException;
-import com.juick.service.MessagesService;
-import com.juick.service.UserService;
-import org.apache.commons.lang3.StringUtils;
-import org.apache.commons.lang3.math.NumberUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.http.HttpHeaders;
-import org.springframework.scheduling.annotation.Scheduled;
-import org.springframework.stereotype.Component;
-import org.springframework.web.socket.CloseStatus;
-import org.springframework.web.socket.PingMessage;
-import org.springframework.web.socket.TextMessage;
-import org.springframework.web.socket.WebSocketSession;
-import org.springframework.web.socket.handler.ConcurrentWebSocketSessionDecorator;
-import org.springframework.web.socket.handler.TextWebSocketHandler;
-import org.springframework.web.util.UriComponents;
-import org.springframework.web.util.UriComponentsBuilder;
-
-import javax.annotation.Nonnull;
-import javax.inject.Inject;
-import java.io.IOException;
-import java.net.URI;
-import java.time.Instant;
-import java.util.Collections;
-import java.util.List;
-import java.util.concurrent.CopyOnWriteArrayList;
-
-/**
- * Created by vitalyster on 28.06.2016.
- */
-@Component
-public class WebsocketManager extends TextWebSocketHandler {
- private static final Logger logger = LoggerFactory.getLogger(WebsocketManager.class);
-
- private final List<UserSession> clients = new CopyOnWriteArrayList<>();
-
- @Inject
- private UserService userService;
- @Inject
- private MessagesService messagesService;
- @Inject
- private CommandsManager commandsManager;
-
-
- @Override
- public void afterConnectionEstablished(WebSocketSession session) {
-
- UserSession userSession = new UserSession(session);
- URI hLocation = session.getUri();
-
- // Auth
- UriComponents uriComponents = UriComponentsBuilder.fromUri(hLocation).build();
- List<String> hash = uriComponents.getQueryParams().get("hash");
- if (hash != null && hash.get(0).length() == 16) {
- userSession.visitor = userService.getUserByHash(hash.get(0));
- } else {
- logger.debug("wrong hash for {} from {}", userSession.visitor.getUid(), userSession);
- }
-
- if (hLocation.getPath().equals("/ws/")) {
- logger.debug("user {} connected", userSession.visitor.getUid());
- } else if (hLocation.getPath().equals("/ws/_all")) {
- logger.debug("user {} connected to legacy _all ({})", userSession.visitor.getUid(), hLocation.getPath());
- userSession.legacy = true;
- userSession.allMessages = true;
- } else if (hLocation.getPath().equals("/ws/_replies")) {
- logger.debug("user {} connected to legacy _replies ({})", userSession.visitor.getUid(), hLocation.getPath());
- userSession.legacy = true;
- userSession.allReplies = true;
- } else if (hLocation.getPath().matches("^/ws/(\\d)+$")) {
- int MID = NumberUtils.toInt(hLocation.getPath().substring(4), 0);
- if (MID > 0) {
- if (messagesService.canViewThread(MID, userSession.visitor.getUid())) {
- logger.debug("user {} connected to legacy thread ({}) from {}", userSession.visitor.getUid(), MID, userSession);
- userSession.legacy = true;
- userSession.MID = MID;
- } else {
- throw new HttpForbiddenException();
- }
- }
- } else {
- throw new HttpNotFoundException();
- }
- clients.add(userSession);
- logger.debug("{} clients connected", clients.size());
- }
-
- @Override
- public void afterConnectionClosed(WebSocketSession session, CloseStatus status) {
- logger.debug("session closed with status {}: {}", status.getCode(), status.getReason());
- clients.removeIf(c -> c.getDelegate().getId().equals(session.getId()));
- logger.debug("{} clients connected", clients.size());
- }
-
- @Scheduled(fixedRate = 30000)
- public void ping() {
- clients.forEach(c -> {
- try {
- if (c.isOpen()) {
- c.sendMessage(new PingMessage());
- }
- } catch (IOException e) {
- logger.error("WebSocket PING exception", e);
- }
- });
- }
-
- @Override
- protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
- UserSession ws = clients.stream().filter(c -> c.getDelegate().equals(session))
- .findFirst().orElseThrow(IllegalStateException::new);
- if (!ws.visitor.isAnonymous()) {
- String command = message.getPayload().trim();
- if (StringUtils.isNotEmpty(command)) {
- CommandResult result = commandsManager.processCommand(ws.visitor, command, URI.create(""));
- ws.sendMessage(new TextMessage(result.getText()));
- }
- } else {
- ws.sendMessage(new TextMessage("Authorization required"));
- }
- }
-
- public List<UserSession> getClients() {
- return clients;
- }
-
- class UserSession extends ConcurrentWebSocketSessionDecorator {
- User visitor;
- int MID;
- boolean allMessages;
- boolean allReplies;
- Instant tsConnected;
- Instant tsLastData;
- boolean legacy;
-
- UserSession(WebSocketSession session) {
- super(session, 60000, 65536);
- this.visitor = AnonymousUser.INSTANCE;
- tsConnected = tsLastData = Instant.now();
- }
-
- @Nonnull
- @Override
- public String toString() {
- HttpHeaders headers = getHandshakeHeaders();
- return headers.getOrDefault("X-Real-IP",
- Collections.singletonList(getRemoteAddress().toString())).get(0);
- }
- }
-}
diff --git a/src/main/java/com/juick/server/XMPPConnection.java b/src/main/java/com/juick/server/XMPPManager.java
index f77b2354..51b3b04e 100644
--- a/src/main/java/com/juick/server/XMPPConnection.java
+++ b/src/main/java/com/juick/server/XMPPManager.java
@@ -19,14 +19,13 @@ package com.juick.server;
import com.juick.User;
import com.juick.formatters.PlainTextFormatter;
-import com.juick.server.www.WebApp;
-import com.juick.service.component.*;
import com.juick.model.CommandResult;
-import com.juick.model.UserInfo;
+import com.juick.server.www.WebApp;
import com.juick.server.xmpp.iq.MessageQuery;
-import com.juick.server.xmpp.s2s.BasicXmppSession;
-import com.juick.server.xmpp.s2s.StanzaListener;
-import com.juick.service.*;
+import com.juick.service.MessagesService;
+import com.juick.service.PMQueriesService;
+import com.juick.service.UserService;
+import com.juick.service.component.*;
import com.juick.util.MessageUtils;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.io.FilenameUtils;
@@ -35,12 +34,17 @@ import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
-import org.springframework.context.ApplicationEventPublisher;
import rocks.xmpp.addr.Jid;
import rocks.xmpp.core.XmppException;
+import rocks.xmpp.core.session.Extension;
import rocks.xmpp.core.session.XmppSession;
+import rocks.xmpp.core.session.XmppSessionConfiguration;
+import rocks.xmpp.core.session.debug.LogbackDebugger;
import rocks.xmpp.core.stanza.AbstractIQHandler;
-import rocks.xmpp.core.stanza.model.*;
+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.StanzaError;
import rocks.xmpp.core.stanza.model.client.ClientMessage;
import rocks.xmpp.core.stanza.model.client.ClientPresence;
import rocks.xmpp.core.stanza.model.errors.Condition;
@@ -57,23 +61,19 @@ import rocks.xmpp.extensions.receipts.MessageDeliveryReceiptsManager;
import rocks.xmpp.extensions.vcard.temp.model.VCard;
import rocks.xmpp.extensions.version.SoftwareVersionManager;
import rocks.xmpp.extensions.version.model.SoftwareVersion;
-import rocks.xmpp.util.XmppUtils;
import javax.annotation.Nonnull;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.inject.Inject;
-import javax.xml.bind.JAXBException;
-import javax.xml.stream.XMLStreamException;
-import javax.xml.stream.XMLStreamWriter;
import java.io.IOException;
-import java.io.StringWriter;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Path;
import java.nio.file.Paths;
+import java.time.Duration;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
@@ -83,14 +83,12 @@ import java.util.concurrent.ExecutorService;
/**
* @author ugnich
*/
-public class XMPPConnection implements StanzaListener, NotificationListener {
+public class XMPPManager implements NotificationListener {
private static final Logger logger = LoggerFactory.getLogger("com.juick.server.xmpp");
private ExternalComponent router;
@Inject
- private XMPPServer xmpp;
- @Inject
private CommandsManager commandsManager;
@Value("${xmppbot_jid:juick@localhost}")
private Jid jid;
@@ -98,6 +96,8 @@ public class XMPPConnection implements StanzaListener, NotificationListener {
private String componentName;
@Value("${component_port:5347}")
private int componentPort;
+ @Value("${component_host:localhost}")
+ private String componentHost;
@Value("${xmpp_password:secret}")
private String password;
@Value("${upload_tmp_dir:#{systemEnvironment['TEMP'] ?: '/tmp'}}")
@@ -110,9 +110,7 @@ public class XMPPConnection implements StanzaListener, NotificationListener {
@Inject
private PMQueriesService pmQueriesService;
@Inject
- private BasicXmppSession session;
- @Inject
- private ExecutorService service;
+ private ExecutorService executorService;
@Value("${service_user:juick}")
private String serviceUsername;
@Inject
@@ -123,8 +121,12 @@ public class XMPPConnection implements StanzaListener, NotificationListener {
@PostConstruct
public void init() {
logger.info("stream router start connecting to {}", componentPort);
- xmpp.addStanzaListener(this);
- router = ExternalComponent.create(componentName, password, session.getConfiguration(), "localhost", componentPort);
+ XmppSessionConfiguration configuration = XmppSessionConfiguration.builder()
+ .extensions(Extension.of(com.juick.Message.class), Extension.of(MessageQuery.class))
+ .debugger(LogbackDebugger.class)
+ .defaultResponseTimeout(Duration.ofMillis(120000))
+ .build();
+ router = ExternalComponent.create(componentName, password, configuration, componentHost, componentPort);
ServiceDiscoveryManager serviceDiscoveryManager = router.getManager(ServiceDiscoveryManager.class);
serviceDiscoveryManager.addIdentity(Identity.clientBot().withName("Juick"));
EntityCapabilitiesManager entityCapabilitiesManager = router.getManager(EntityCapabilitiesManager.class);
@@ -164,7 +166,7 @@ public class XMPPConnection implements StanzaListener, NotificationListener {
}
User user = userService.getUserByName(iq.getTo().getLocal());
if (!user.isAnonymous()) {
- UserInfo info = userService.getUserInfo(user);
+ User info = userService.getUserInfo(user);
VCard userVCard = new VCard();
userVCard.setFormattedName(info.getFullName());
userVCard.setNickname(user.getName());
@@ -187,13 +189,6 @@ public class XMPPConnection implements StanzaListener, NotificationListener {
router.send(result);
}
});
- router.addInboundIQListener(e -> {
- IQ iq = e.getIQ();
- Jid jid = iq.getTo();
- if (!jid.getDomain().equals(this.jid.getDomain())) {
- router.send(iq);
- }
- });
FileTransferManager fileTransferManager = router.getManager(FileTransferManager.class);
fileTransferManager.addFileTransferOfferListener(e -> {
try {
@@ -261,7 +256,7 @@ public class XMPPConnection implements StanzaListener, NotificationListener {
router.addInboundPresenceListener(event -> {
incomingPresence(event.getPresence());
});
- service.submit(() -> {
+ executorService.submit(() -> {
try {
router.connect();
} catch (XmppException e) {
@@ -271,16 +266,6 @@ public class XMPPConnection implements StanzaListener, NotificationListener {
serviceUser = userService.getUserByName(serviceUsername);
}
- private String stanzaToString(Stanza stanza) throws XMLStreamException, JAXBException {
- StringWriter stanzaWriter = new StringWriter();
- XMLStreamWriter xmppStreamWriter = XmppUtils.createXmppStreamWriter(
- router.getConfiguration().getXmlOutputFactory().createXMLStreamWriter(stanzaWriter));
- router.createMarshaller().marshal(stanza, xmppStreamWriter);
- xmppStreamWriter.flush();
- xmppStreamWriter.close();
- return stanzaWriter.toString();
- }
-
private void sendJuickMessage(com.juick.Message jmsg, List<User> users) {
List<String> jids = new ArrayList<>();
@@ -319,7 +304,7 @@ public class XMPPConnection implements StanzaListener, NotificationListener {
}
}
- public void sendJuickComment(com.juick.Message jmsg, List<User> users) {
+ private void sendJuickComment(com.juick.Message jmsg, List<User> users) {
String replyQuote;
String replyTo;
@@ -569,7 +554,6 @@ public class XMPPConnection implements StanzaListener, NotificationListener {
}
public ClientMessage incomingMessage(Message msg) {
- ClientMessage result = null;
if (msg.getType() != null && msg.getType().equals(Message.Type.ERROR)) {
StanzaError error = msg.getError();
if (error != null && error.getCondition().equals(Condition.RESOURCE_CONSTRAINT)) {
@@ -598,19 +582,10 @@ public class XMPPConnection implements StanzaListener, NotificationListener {
} catch (Exception e1) {
logger.warn("message exception", e1);
}
- } else if (to.getDomain().endsWith(jid.getDomain()) && (to.getDomain().equals(jid.getDomain())
- || to.getDomain().endsWith("." + jid.getDomain()))) {
- if (logger.isInfoEnabled()) {
- try {
- logger.info("unhandled message: {}", stanzaToString(msg));
- } catch (JAXBException | XMLStreamException ex) {
- logger.error("JAXB exception", ex);
- }
- }
- } else {
- return ClientMessage.from(msg);
}
- return result;
+ ClientMessage errorMessage = ClientMessage.from(msg);
+ errorMessage.setError(new StanzaError(StanzaError.Type.CANCEL, Condition.ITEM_NOT_FOUND));
+ return errorMessage;
}
private ClientMessage incomingMessageJuick(User user_from, Jid from, String to, String command, @Nonnull URI attachment) {
if (StringUtils.isBlank(command) && attachment.toString().isEmpty()) {
@@ -646,11 +621,6 @@ public class XMPPConnection implements StanzaListener, NotificationListener {
return null;
}
- @Override
- public void stanzaReceived(Stanza xmlValue) {
- router.send(xmlValue);
- }
-
private void broadcastPresence(Presence.Type type) {
Presence presence = new Presence();
presence.setFrom(jid);
@@ -674,8 +644,4 @@ public class XMPPConnection implements StanzaListener, NotificationListener {
router.close();
}
}
-
- public ExternalComponent getRouter() {
- return router;
- }
}
diff --git a/src/main/java/com/juick/server/XMPPServer.java b/src/main/java/com/juick/server/XMPPServer.java
deleted file mode 100644
index 86ab6a78..00000000
--- a/src/main/java/com/juick/server/XMPPServer.java
+++ /dev/null
@@ -1,429 +0,0 @@
-/*
- * Copyright (C) 2008-2017, Juick
- *
- * 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 com.juick.server.xmpp.router.StreamError;
-import com.juick.server.xmpp.s2s.*;
-import com.juick.service.UserService;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.scheduling.annotation.Scheduled;
-import org.xmlpull.v1.XmlPullParserException;
-import rocks.xmpp.addr.Jid;
-import rocks.xmpp.core.stanza.model.Stanza;
-
-import javax.annotation.PostConstruct;
-import javax.annotation.PreDestroy;
-import javax.inject.Inject;
-import javax.net.ssl.*;
-import javax.xml.bind.JAXBException;
-import javax.xml.bind.Unmarshaller;
-import java.io.IOException;
-import java.io.StringReader;
-import java.net.ServerSocket;
-import java.net.Socket;
-import java.net.SocketException;
-import java.security.InvalidAlgorithmParameterException;
-import java.security.KeyStore;
-import java.security.KeyStoreException;
-import java.security.SecureRandom;
-import java.security.cert.*;
-import java.time.Duration;
-import java.time.Instant;
-import java.util.*;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-/**
- * @author ugnich
- */
-public class XMPPServer implements ConnectionListener {
- private static final Logger logger = LoggerFactory.getLogger("com.juick.server.xmpp");
-
- private static final int TIMEOUT_MINUTES = 15;
-
- @Inject
- public ExecutorService service;
- @Value("${hostname:localhost}")
- private Jid jid;
- @Value("${s2s_port:5269}")
- private int s2sPort;
- @Value("${broken_ssl_hosts:}")
- public String[] brokenSSLhosts;
- @Value("${banned_hosts:}")
- public String[] bannedHosts;
-
- private final List<ConnectionIn> inConnections = new CopyOnWriteArrayList<>();
- private final Map<ConnectionOut, Optional<Socket>> outConnections = new ConcurrentHashMap<>();
- private final List<CacheEntry> outCache = new CopyOnWriteArrayList<>();
- private final List<StanzaListener> stanzaListeners = new CopyOnWriteArrayList<>();
- private final AtomicBoolean closeFlag = new AtomicBoolean(false);
-
- SSLContext sc;
- CertificateFactory cf;
- CertPathValidator cpv;
- PKIXParameters params;
- private TrustManager[] trustAllCerts = new TrustManager[]{
- new X509TrustManager() {
- public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType) {
- }
-
- public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType) {
- }
-
- public java.security.cert.X509Certificate[] getAcceptedIssuers() {
- return new X509Certificate[0];
- }
- }
- };
- private boolean tlsConfigured = false;
-
-
- private ServerSocket listener;
-
- @Inject
- private BasicXmppSession session;
- @Inject
- private UserService userService;
- @Inject
- private KeystoreManager keystoreManager;
-
- @PostConstruct
- public void init() throws KeyStoreException {
- closeFlag.set(false);
- try {
- sc = SSLContext.getInstance("TLSv1.2");
- sc.init(keystoreManager.getKeymanagerFactory().getKeyManagers(), trustAllCerts, new SecureRandom());
- TrustManagerFactory trustManagerFactory =
- TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
- Set<TrustAnchor> ca = new HashSet<>();
- trustManagerFactory.init((KeyStore)null);
- Arrays.stream(trustManagerFactory.getTrustManagers()).forEach(t -> Arrays.stream(((X509TrustManager)t).getAcceptedIssuers()).forEach(cert -> ca.add(new TrustAnchor(cert, null))));
- params = new PKIXParameters(ca);
- params.setRevocationEnabled(false);
- cpv = CertPathValidator.getInstance("PKIX");
- cf = CertificateFactory.getInstance( "X.509" );
- tlsConfigured = true;
- } catch (Exception e) {
- logger.warn("tls unavailable");
- }
- service.submit(() -> {
- try {
- listener = new ServerSocket(s2sPort);
- logger.info("s2s listener ready");
- while (!listener.isClosed()) {
- if (Thread.currentThread().isInterrupted()) break;
- Socket socket = listener.accept();
- ConnectionIn client = new ConnectionIn(this, socket);
- addConnectionIn(client);
- service.submit(client);
- }
- } catch (SocketException e) {
- // shutdown
- } catch (IOException | XmlPullParserException e) {
- logger.warn("xmpp exception", e);
- }
- });
- }
-
- public void addConnectionIn(ConnectionIn c) {
- c.setListener(this);
- inConnections.add(c);
- }
-
- public void addConnectionOut(ConnectionOut c, Optional<Socket> socket) {
- c.setListener(this);
- outConnections.put(c, socket);
- }
-
- public void removeConnectionIn(ConnectionIn c) {
- inConnections.remove(c);
- }
-
- public void removeConnectionOut(ConnectionOut c) {
- outConnections.remove(c);
- }
-
- 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];
- }
-
- public Optional<ConnectionOut> getConnectionOut(Jid hostname, boolean needReady) {
- return outConnections.keySet().stream().filter(c -> c.to != null &&
- c.to.equals(hostname) && (!needReady || c.streamReady)).findFirst();
- }
-
- public Optional<ConnectionIn> getConnectionIn(String streamID) {
- return inConnections.stream().filter(c -> c.streamID != null && c.streamID.equals(streamID)).findFirst();
- }
-
- public void sendOut(Jid hostname, String xml) {
- boolean haveAnyConn = false;
-
- ConnectionOut connOut = null;
- for (ConnectionOut c : outConnections.keySet()) {
- if (c.to != null && c.to.equals(hostname)) {
- if (c.streamReady) {
- connOut = c;
- break;
- } else {
- haveAnyConn = true;
- break;
- }
- }
- }
- if (connOut != null) {
- connOut.send(xml);
- return;
- }
-
- boolean haveCache = false;
- for (CacheEntry c : outCache) {
- if (c.hostname != null && c.hostname.equals(hostname)) {
- c.xml += xml;
- c.updated = Instant.now();
- haveCache = true;
- break;
- }
- }
- if (!haveCache) {
- outCache.add(new CacheEntry(hostname, xml));
- }
-
- if (!haveAnyConn && !closeFlag.get()) {
- try {
- createDialbackConnection(hostname.toEscapedString(), null, null);
- } catch (Exception e) {
- logger.warn("dialback error", e);
- }
- }
- }
-
- void createDialbackConnection(String to, String checkSID, String dbKey) throws Exception {
- ConnectionOut connectionOut = new ConnectionOut(getJid(), Jid.of(to), null, null, checkSID, dbKey);
- addConnectionOut(connectionOut, Optional.empty());
- service.submit(() -> {
- try {
- Socket socket = new Socket();
- socket.connect(DNSQueries.getServerAddress(to));
- connectionOut.setInputStream(socket.getInputStream());
- connectionOut.setOutputStream(socket.getOutputStream());
- addConnectionOut(connectionOut, Optional.of(socket));
- connectionOut.connect();
- } catch (IOException e) {
- logger.info("dialback to " + to + " exception", e);
- }
- });
- }
-
- public void startDialback(Jid from, String streamId, String dbKey) throws Exception {
- Optional<ConnectionOut> c = getConnectionOut(from, false);
- if (c.isPresent()) {
- c.get().sendDialbackVerify(streamId, dbKey);
- } else {
- createDialbackConnection(from.toEscapedString(), streamId, dbKey);
- }
- }
-
- public void addStanzaListener(StanzaListener listener) {
- stanzaListeners.add(listener);
- }
-
- public void onStanzaReceived(String xmlValue) {
- logger.info("S2S: {}", xmlValue);
- Stanza stanza = parse(xmlValue);
- stanzaListeners.forEach(l -> l.stanzaReceived(stanza));
- }
-
- public BasicXmppSession getSession() {
- return session;
- }
-
- public List<ConnectionIn> getInConnections() {
- return inConnections;
- }
-
- public Map<ConnectionOut, Optional<Socket>> getOutConnections() {
- return outConnections;
- }
-
- @Override
- public boolean isTlsAvailable() {
- return tlsConfigured;
- }
-
- @Override
- public void starttls(ConnectionIn connection) {
- logger.debug("stream {} securing", connection.streamID);
- connection.sendStanza("<proceed xmlns=\"" + Connection.NS_TLS + "\" />");
- try {
- connection.setSocket(sc.getSocketFactory().createSocket(connection.getSocket(), connection.getSocket().getInetAddress().getHostAddress(),
- connection.getSocket().getPort(), false));
- SSLSocket sslSocket = (SSLSocket) connection.getSocket();
- sslSocket.addHandshakeCompletedListener(handshakeCompletedEvent -> {
- try {
- CertPath certPath = cf.generateCertPath(Arrays.asList(handshakeCompletedEvent.getPeerCertificates()));
- cpv.validate(certPath, params);
- connection.setTrusted(true);
- logger.info("connection from {} is trusted", connection.from);
- } catch (SSLPeerUnverifiedException | CertificateException | CertPathValidatorException | InvalidAlgorithmParameterException e) {
- logger.info("connection from {} is NOT trusted, falling back to dialback", connection.from);
- }
- });
- sslSocket.setUseClientMode(false);
- sslSocket.setNeedClientAuth(true);
- sslSocket.startHandshake();
- connection.setSecured(true);
- logger.debug("stream from {} secured", connection.streamID);
- connection.restartParser();
- } catch (XmlPullParserException | IOException sex) {
- logger.warn("stream {} ssl error {}", connection.streamID, sex);
- connection.sendStanza("<failure xmlns=\"" + Connection.NS_TLS + "\" />");
- removeConnectionIn(connection);
- connection.closeConnection();
- }
- }
-
- @Override
- public void proceed(ConnectionOut connection) {
- try {
- Socket socket = outConnections.get(connection).get();
- socket = sc.getSocketFactory().createSocket(socket, socket.getInetAddress().getHostAddress(),
- socket.getPort(), false);
- SSLSocket sslSocket = (SSLSocket) socket;
- sslSocket.addHandshakeCompletedListener(handshakeCompletedEvent -> {
- try {
- CertPath certPath = cf.generateCertPath(Arrays.asList(handshakeCompletedEvent.getPeerCertificates()));
- cpv.validate(certPath, params);
- connection.setTrusted(true);
- logger.info("connection to {} is trusted", connection.to);
- } catch (SSLPeerUnverifiedException | CertificateException | CertPathValidatorException | InvalidAlgorithmParameterException e) {
- logger.info("connection to {} is NOT trusted, falling back to dialback", connection.to);
- }
- });
- sslSocket.setNeedClientAuth(true);
- sslSocket.startHandshake();
- connection.setSecured(true);
- logger.debug("stream to {} secured", connection.getStreamID());
- connection.setInputStream(socket.getInputStream());
- connection.setOutputStream(socket.getOutputStream());
- connection.restartStream();
- connection.sendOpenStream();
- } catch (NoSuchElementException | XmlPullParserException | IOException sex) {
- logger.error("s2s ssl error: {} {}, error {}", connection.to, connection.getStreamID(), sex);
- connection.send("<failure xmlns=\"" + Connection.NS_TLS + "\" />");
- removeConnectionOut(connection);
- connection.logoff();
- }
- }
-
- @Override
- public void verify(ConnectionOut connection, String from, String type, String sid) {
- if (from != null && from.equals(connection.to.toEscapedString()) && sid != null && !sid.isEmpty() && type != null) {
- getConnectionIn(sid).ifPresent(c -> c.sendDialbackResult(Jid.of(from), type));
- }
- }
-
- @Override
- public void dialbackError(ConnectionOut connection, StreamError error) {
- logger.warn("Stream error from {}: {}", connection.getStreamID(), error.getCondition());
- removeConnectionOut(connection);
- connection.logoff();
- }
-
- @Override
- public void finished(ConnectionOut connection, boolean dirty) {
- logger.warn("stream to {} {} finished, dirty={}", connection.to, connection.getStreamID(), dirty);
- removeConnectionOut(connection);
- connection.logoff();
- }
-
- @Override
- public void exception(ConnectionOut connection, Exception ex) {
- logger.error("s2s out exception: {} {}, exception {}", connection.to, connection.getStreamID(), ex);
- removeConnectionOut(connection);
- connection.logoff();
- }
-
- @Override
- public void ready(ConnectionOut connection) {
- logger.debug("stream to {} {} ready", connection.to, connection.getStreamID());
- String cache = getFromCache(connection.to);
- if (cache != null) {
- logger.debug("stream to {} {} sending cache", connection.to, connection.getStreamID());
- connection.send(cache);
- }
- }
-
- @Override
- public boolean securing(ConnectionOut connection) {
- return tlsConfigured && !Arrays.asList(brokenSSLhosts).contains(connection.to.toEscapedString());
- }
-
- public 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;
- }
-
- public Jid getJid() {
- return jid;
- }
- @Scheduled(fixedDelay = 10000)
- public void cleanUp() {
- Instant now = Instant.now();
- outConnections.keySet().stream().filter(c -> Duration.between(c.getUpdated(), now).toMinutes() > TIMEOUT_MINUTES)
- .forEach(c -> {
- logger.info("closing idle outgoing connection to {}", c.to);
- c.logoff();
- outConnections.remove(c);
- });
-
- inConnections.stream().filter(c -> Duration.between(c.updated, now).toMinutes() > TIMEOUT_MINUTES)
- .forEach(c -> {
- logger.info("closing idle incoming connection from {}", c.from);
- c.closeConnection();
- inConnections.remove(c);
- });
- }
- @PreDestroy
- public void preDestroy() throws IOException {
- closeFlag.set(true);
- if (listener != null && !listener.isClosed()) {
- listener.close();
- }
- service.shutdown();
- logger.info("XMPP server destroyed");
- }
-
- public int getServerPort() {
- return s2sPort;
- }
-}
diff --git a/src/main/java/com/juick/server/api/ApiSocialLogin.java b/src/main/java/com/juick/server/api/ApiSocialLogin.java
index 72cda0af..be306fe9 100644
--- a/src/main/java/com/juick/server/api/ApiSocialLogin.java
+++ b/src/main/java/com/juick/server/api/ApiSocialLogin.java
@@ -82,6 +82,7 @@ public class ApiSocialLogin {
@Inject
private ObjectMapper jsonMapper;
private ServiceBuilder facebookBuilder, twitterBuilder, vkBuilder;
+ private OAuth20Service facebookAuthService, vkAuthService;
@Value("${twitter_consumer_key:appid}")
private String twitterConsumerKey;
@@ -117,6 +118,16 @@ public class ApiSocialLogin {
verifier = new GoogleIdTokenVerifier.Builder(transport, jsonFactory)
.setAudience(Collections.singletonList(googleClientId))
.build();
+ facebookAuthService = facebookBuilder
+ .apiSecret(FACEBOOK_SECRET)
+ .callback(FACEBOOK_REDIRECT)
+ .scope("email")
+ .build(FacebookApi.instance());
+ vkAuthService = vkBuilder
+ .apiSecret(VK_SECRET)
+ .scope("friends,wall,offline")
+ .callback(VK_REDIRECT)
+ .build(VkontakteApi.instance());
}
@GetMapping("/api/_fblogin")
@@ -125,13 +136,7 @@ public class ApiSocialLogin {
if (StringUtils.isBlank(code)) {
String fbstate = UUID.randomUUID().toString();
crosspostService.addFacebookState(fbstate, state);
- OAuth20Service facebookAuthService = facebookBuilder
- .apiSecret(FACEBOOK_SECRET)
- .callback(FACEBOOK_REDIRECT)
- .scope("email")
- .state(fbstate)
- .build(FacebookApi.instance());
- return "redirect:" + facebookAuthService.getAuthorizationUrl();
+ return "redirect:" + facebookAuthService.getAuthorizationUrl(fbstate);
}
String redirectUrl = crosspostService.verifyFacebookState(state);
@@ -140,53 +145,43 @@ public class ApiSocialLogin {
logger.error("state is missing");
throw new HttpBadRequestException();
}
- OAuth20Service facebookService = facebookBuilder
- .apiKey(FACEBOOK_APPID)
- .apiSecret(FACEBOOK_SECRET)
- .callback(FACEBOOK_REDIRECT)
- .scope("email")
- .state(state)
- .build(FacebookApi.instance());
- OAuth2AccessToken token = facebookService.getAccessToken(code);
- final OAuthRequest meRequest = new OAuthRequest(Verb.GET, "https://graph.facebook.com/v2.10/me?fields=id,name,link,verified,email");
- facebookService.signRequest(token, meRequest);
- String graph = facebookService.execute(meRequest).getBody();
+ OAuth2AccessToken token = facebookAuthService.getAccessToken(code);
+ final OAuthRequest meRequest = new OAuthRequest(Verb.GET, "https://graph.facebook.com/v3.2/me?fields=id,name,email");
+ facebookAuthService.signRequest(token, meRequest);
+ String graph = facebookAuthService.execute(meRequest).getBody();
if (StringUtils.isBlank(graph)) {
logger.error("FACEBOOK GRAPH ERROR");
throw new HttpBadRequestException();
}
User fb = jsonMapper.readValue(graph, User.class);
long fbID = NumberUtils.toLong(fb.getId(), 0);
- if (fbID == 0 || StringUtils.isBlank(fb.getName()) || StringUtils.isBlank(fb.getLink())) {
- logger.error("Missing required fields, id: {}, name: {}, link: {}", fbID, fb.getName(), fb.getLink());
+ if (fbID == 0 || StringUtils.isBlank(fb.getName())) {
+ logger.error("Missing required fields, id: {}, name: {}", fbID, fb.getName());
throw new HttpBadRequestException();
}
int uid = crosspostService.getUIDbyFBID(fbID);
if (uid > 0) {
- if (!crosspostService.updateFacebookUser(fbID, token.getAccessToken(), fb.getName(), fb.getLink())) {
+ if (!crosspostService.updateFacebookUser(fbID, token.getAccessToken(), fb.getName())) {
logger.error("error updating facebook user, id: {}, token: {}", fbID, token.getAccessToken());
throw new HttpBadRequestException();
}
UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder.fromUriString(redirectUrl);
uriComponentsBuilder.queryParam("hash", userService.getHashByUID(uid));
return "redirect:" + uriComponentsBuilder.build().toUriString();
- } else if (fb.getVerified()) {
- if (!crosspostService.createFacebookUser(fbID, state, token.getAccessToken(), fb.getName(), fb.getLink())) {
+ } else {
+ if (!crosspostService.createFacebookUser(fbID, state, token.getAccessToken(), fb.getName())) {
if (StringUtils.isNotEmpty(fb.getEmail())) {
- logger.info("found {} for facebook user {}", fb.getEmail(), fb.getLink());
+ logger.info("found {} for facebook user {}", fb.getEmail());
Integer userId = crosspostService.getUIDbyFBID(fbID);
if (!emailService.getEmails(userId, false).contains(fb.getEmail())) {
emailService.addEmail(userId, fb.getEmail());
}
}
- logger.info("email not found for facebook user {}", fb.getLink());
+ logger.info("email not found for facebook user {}", fb.getName());
throw new HttpBadRequestException();
}
return "redirect:/signup?type=fb&hash=" + state;
- } else {
- logger.error("Facebook account is not verified, id: {}", fbID);
- throw new HttpBadRequestException();
}
}/*
@GetMapping("/_twitter")
@@ -244,13 +239,7 @@ public class ApiSocialLogin {
if (StringUtils.isBlank(code)) {
String vkstate = UUID.randomUUID().toString();
crosspostService.addVKState(vkstate, state);
- OAuth20Service vkAuthService = vkBuilder
- .apiSecret(VK_SECRET)
- .scope("friends,wall,offline")
- .state(vkstate)
- .callback(VK_REDIRECT)
- .build(VkontakteApi.instance());
- return "redirect:" + vkAuthService.getAuthorizationUrl();
+ return "redirect:" + vkAuthService.getAuthorizationUrl(vkstate);
}
String redirectUrl = crosspostService.verifyVKState(state);
@@ -258,16 +247,11 @@ public class ApiSocialLogin {
logger.error("state is missing");
throw new HttpBadRequestException();
}
-
- OAuth20Service vkService = vkBuilder
- .apiKey(VK_APPID)
- .apiSecret(VK_SECRET)
- .build(VkontakteApi.instance());
- OAuth2AccessToken token = vkService.getAccessToken(code);
+ OAuth2AccessToken token = vkAuthService.getAccessToken(code);
OAuthRequest meRequest = new OAuthRequest(Verb.GET, "https://api.vk.com/method/users.get?fields=screen_name&v=5.73");
- vkService.signRequest(token, meRequest);
- String graph = vkService.execute(meRequest).getBody();
+ vkAuthService.signRequest(token, meRequest);
+ String graph = vkAuthService.execute(meRequest).getBody();
com.juick.model.vk.User jsonUser = jsonMapper.readValue(graph, UsersResponse.class).getUsers().get(0);
String vkName = jsonUser.getFirstName() + " " + jsonUser.getLastName();
diff --git a/src/main/java/com/juick/server/api/Index.java b/src/main/java/com/juick/server/api/Index.java
index 56f01370..4573641b 100644
--- a/src/main/java/com/juick/server/api/Index.java
+++ b/src/main/java/com/juick/server/api/Index.java
@@ -17,10 +17,7 @@
package com.juick.server.api;
-import com.juick.Status;
-import com.juick.server.WebsocketManager;
import org.springframework.http.HttpStatus;
-import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@@ -28,7 +25,6 @@ import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
import springfox.documentation.annotations.ApiIgnore;
-import javax.inject.Inject;
import java.net.URI;
/**
@@ -36,19 +32,11 @@ import java.net.URI;
*/
@RestController
public class Index {
- @Inject
- private WebsocketManager wsHandler;
@ApiIgnore
- @RequestMapping(value = { "/api/", "/ws/" }, method = RequestMethod.GET, headers = "Connection!=Upgrade")
+ @RequestMapping(value = { "/api/", "/ws/" }, method = RequestMethod.GET)
public ResponseEntity<Void> description() {
URI redirectUri = ServletUriComponentsBuilder.fromCurrentRequestUri().path("/swagger-ui.html").build().toUri();
return ResponseEntity.status(HttpStatus.MOVED_PERMANENTLY).location(redirectUri).build();
}
- @ApiIgnore
- @RequestMapping(value = "/api/status", method = RequestMethod.GET,
- produces = MediaType.APPLICATION_JSON_UTF8_VALUE, headers = "Connection!=Upgrade")
- public Status status() {
- return Status.getStatus(String.valueOf(wsHandler.getClients().size()));
- }
}
diff --git a/src/main/java/com/juick/server/api/Messages.java b/src/main/java/com/juick/server/api/Messages.java
index e105890f..5c791df4 100644
--- a/src/main/java/com/juick/server/api/Messages.java
+++ b/src/main/java/com/juick/server/api/Messages.java
@@ -179,6 +179,9 @@ public class Messages {
}
msg.getUser().setAvatar(webApp.getAvatarUrl(msg.getUser()));
msg.setRecommendations(new HashSet<>(messagesService.getMessageRecommendations(msg.getMid())));
+ msg.getRecommendations().forEach(r -> {
+ r.setAvatar(webApp.getAvatarUrl(r));
+ });
List<com.juick.Message> replies = messagesService.getReplies(visitor, mid);
replies.forEach(m -> m.getUser().setAvatar(webApp.getAvatarUrl(m.getUser())));
if (!visitor.isAnonymous()) {
diff --git a/src/main/java/com/juick/server/api/Notifications.java b/src/main/java/com/juick/server/api/Notifications.java
index 000a9f3b..ea1d5c54 100644
--- a/src/main/java/com/juick/server/api/Notifications.java
+++ b/src/main/java/com/juick/server/api/Notifications.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2008-2017, Juick
+ * Copyright (C) 2008-2019, Juick
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
@@ -24,14 +24,19 @@ import com.juick.User;
import com.juick.model.AnonymousUser;
import com.juick.server.util.HttpBadRequestException;
import com.juick.server.util.HttpForbiddenException;
+import com.juick.server.util.UserUtils;
import com.juick.service.MessagesService;
import com.juick.service.PushQueriesService;
import com.juick.service.SubscriptionService;
-import com.juick.server.util.UserUtils;
+import com.juick.service.TelegramService;
import com.juick.service.UserService;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
-import org.springframework.web.bind.annotation.*;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
import springfox.documentation.annotations.ApiIgnore;
import javax.inject.Inject;
@@ -55,6 +60,8 @@ public class Notifications {
private SubscriptionService subscriptionService;
@Inject
private UserService userService;
+ @Inject
+ private TelegramService telegramService;
private User collectTokens(Integer uid) {
@@ -63,6 +70,14 @@ public class Notifications {
pushQueriesService.getGCMRegID(uid).forEach(t -> user.getTokens().add(new ExternalToken(null, "gcm", t, null)));
pushQueriesService.getAPNSToken(uid).forEach(t -> user.getTokens().add(new ExternalToken(null, "apns", t, null)));
pushQueriesService.getMPNSURL(uid).forEach(t -> user.getTokens().add(new ExternalToken(null, "mpns", t, null)));
+ List<ExternalToken> xmppJids = userService.getJIDsbyUID(uid).stream()
+ .map(jid -> new ExternalToken(null, "xmpp", jid, null))
+ .collect(Collectors.toList());
+ user.getTokens().addAll(xmppJids);
+ List<ExternalToken> tgIds = telegramService.getTelegramIdentifiers(Collections.singletonList(user)).stream()
+ .map(tgId -> new ExternalToken(null, "durov", String.valueOf(tgId), null))
+ .collect(Collectors.toList());
+ user.getTokens().addAll(tgIds);
return user;
}
diff --git a/src/main/java/com/juick/server/api/PM.java b/src/main/java/com/juick/server/api/PM.java
index a80ad0dc..e61fef6e 100644
--- a/src/main/java/com/juick/server/api/PM.java
+++ b/src/main/java/com/juick/server/api/PM.java
@@ -99,6 +99,7 @@ public class PM {
jmsg.setUser(visitor);
jmsg.setText(body);
jmsg.setTo(userTo);
+ jmsg.getUser().setAvatar(webApp.getAvatarUrl(jmsg.getUser()));
applicationEventPublisher.publishEvent(new MessageEvent(this, jmsg, Collections.singletonList(jmsg.getTo())));
return jmsg;
diff --git a/src/main/java/com/juick/server/api/Users.java b/src/main/java/com/juick/server/api/Users.java
index 6f9ab290..33b3704b 100644
--- a/src/main/java/com/juick/server/api/Users.java
+++ b/src/main/java/com/juick/server/api/Users.java
@@ -18,21 +18,24 @@
package com.juick.server.api;
import com.juick.User;
+import com.juick.model.AnonymousUser;
import com.juick.model.ApplicationStatus;
-import com.juick.model.UserInfo;
-import com.juick.server.util.HttpForbiddenException;
import com.juick.server.util.HttpNotFoundException;
-import com.juick.server.www.WebApp;
-import com.juick.service.CrosspostService;
-import com.juick.service.EmailService;
-import com.juick.service.MessagesService;
-import com.juick.service.UserService;
+import com.juick.server.util.HttpUtils;
import com.juick.server.util.UserUtils;
import com.juick.server.util.WebUtils;
+import com.juick.server.www.WebApp;
+import com.juick.service.*;
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
import javax.inject.Inject;
+import java.io.IOException;
+import java.net.URI;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -52,6 +55,10 @@ public class Users {
private EmailService emailService;
@Inject
private WebApp webApp;
+ @Inject
+ private ImagesService imagesService;
+ @Value("${upload_tmp_dir:#{systemEnvironment['TEMP'] ?: '/tmp'}}")
+ private String tmpDir;
@RequestMapping(value = "/api/auth", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public String getAuthToken() {
@@ -94,16 +101,21 @@ public class Users {
me.setRead(userService.getUserFriends(visitor.getUid()));
me.setReaders(userService.getUserReaders(visitor.getUid()));
me.setAvatar(webApp.getAvatarUrl(visitor));
- return me;
+ return (SecureUser)userService.getUserInfo(me);
+ }
+ @PostMapping("/api/me/upload")
+ public void updateInfo(@RequestParam MultipartFile avatar) throws IOException {
+ User visitor = UserUtils.getCurrentUser();
+ String avatarTmpPath = HttpUtils.receiveMultiPartFile(avatar, tmpDir).getHost();
+ if (StringUtils.isNotEmpty(avatarTmpPath)) {
+ imagesService.saveAvatar(avatarTmpPath, visitor.getUid());
+ }
}
@RequestMapping(value = "/api/users/read", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public List<User> doGetUserRead(
@RequestParam String uname) {
User visitor = UserUtils.getCurrentUser();
- if (visitor.isAnonymous()) {
- throw new HttpForbiddenException();
- }
int uid = 0;
if (uname == null) {
uid = visitor.getUid();
@@ -128,9 +140,6 @@ public class Users {
public List<User> doGetUserReaders(
@RequestParam String uname) {
User visitor = UserUtils.getCurrentUser();
- if (visitor.isAnonymous()) {
- throw new HttpForbiddenException();
- }
int uid = 0;
if (uname == null) {
uid = visitor.getUid();
@@ -152,22 +161,36 @@ public class Users {
}
@GetMapping("/api/info/{uname}")
- public UserInfo getUserInfo(@PathVariable String uname) {
+ public User getUserInfo(@PathVariable String uname) {
User user = userService.getUserByName(uname);
if (!user.isBanned()) {
+ user.setRead(doGetUserRead(uname));
+ user.setReaders(doGetUserReaders(uname));
user.setAvatar(webApp.getAvatarUrl(user));
return userService.getUserInfo(user);
}
throw new HttpNotFoundException();
}
+ @Deprecated
+ @GetMapping(value = "/api/avatar", produces = MediaType.IMAGE_PNG_VALUE)
+ public byte[] getAvatarUrl(
+ @RequestParam(required = false) String uname,
+ @RequestParam(required = false) String jid)
+ throws IOException {
+ User user = AnonymousUser.INSTANCE;
+ if (StringUtils.isNotEmpty(uname)) {
+ user = userService.getUserByName(uname);
+ }
+ if (user.isAnonymous() && StringUtils.isNotEmpty(jid)) {
+ user = userService.getUserByJID(jid);
+ }
+ return IOUtils.toByteArray(URI.create(webApp.getAvatarUrl(user)));
+ }
class SecureUser extends User {
public String getHash() {
return getAuthHash();
}
- public UserInfo getUserInfo() {
- return userService.getUserInfo(this);
- }
public List<String> getJIDs() {
return userService.getAllJIDs(this);
}
diff --git a/src/main/java/com/juick/server/api/activity/Profile.java b/src/main/java/com/juick/server/api/activity/Profile.java
index 305b7c4a..4e375a54 100644
--- a/src/main/java/com/juick/server/api/activity/Profile.java
+++ b/src/main/java/com/juick/server/api/activity/Profile.java
@@ -3,6 +3,7 @@ package com.juick.server.api.activity;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.juick.Message;
import com.juick.User;
+import com.juick.formatters.PlainTextFormatter;
import com.juick.model.CommandResult;
import com.juick.server.ActivityPubManager;
import com.juick.server.CommandsManager;
@@ -29,6 +30,8 @@ import com.juick.server.www.WebApp;
import com.juick.service.MessagesService;
import com.juick.service.UserService;
import com.juick.service.activities.*;
+import com.overzealous.remark.Remark;
+import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -44,12 +47,15 @@ import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.client.RestTemplate;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;
import javax.inject.Inject;
+import java.io.InputStream;
import java.net.URI;
+import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -81,6 +87,8 @@ public class Profile {
private ObjectMapper jsonMapper;
@Inject
private WebApp webApp;
+ @Inject
+ private Remark remarkConverter;
@GetMapping(value = "/u/{userName}", produces = {Context.LD_JSON_MEDIA_TYPE, Context.ACTIVITYSTREAMS_PROFILE_MEDIA_TYPE})
public Person getUser(@PathVariable String userName) {
@@ -252,32 +260,17 @@ public class Profile {
}
@PostMapping(value = "/api/inbox", consumes = {Context.LD_JSON_MEDIA_TYPE, Context.ACTIVITYSTREAMS_PROFILE_MEDIA_TYPE})
- public ResponseEntity<Void> processInbox(@RequestBody Activity activity,
- @RequestHeader(name = "Host") String host,
- @RequestHeader(name = "Date") String date,
- @RequestHeader(name = "Digest", required = false) String digest,
- @RequestHeader(name = "Content-Type") String contentType,
- @RequestHeader(name = "User-Agent", required = false) String userAgent,
- @RequestHeader(name = "Accept-Encoding", required = false) String acceptEncoding,
- @RequestHeader(name = "Signature", required = false) String signature) throws Exception {
- UriComponents componentsBuilder = ServletUriComponentsBuilder.fromCurrentRequestUri().build();
- Map<String, String> headers = new HashMap<>();
- headers.put("host", host.split(":", 2)[0]);
- headers.put("date", date);
- headers.put("digest", digest);
- headers.put("content-type", contentType);
- headers.put("user-agent", userAgent);
- headers.put("accept-encoding", acceptEncoding);
- boolean valid = signatureManager.verifySignature(signature, URI.create(activity.getActor()), "POST",
- componentsBuilder.getPath(), headers);
- if (valid) {
+ public ResponseEntity<CommandResult> processInbox(InputStream inboxData) throws Exception {
+ String inbox = IOUtils.toString(inboxData, StandardCharsets.UTF_8);
+ logger.info("Inbox: {}", inbox);
+ Activity activity = jsonMapper.readValue(inbox, Activity.class);
+ User visitor = UserUtils.getCurrentUser();
+ if ((StringUtils.isNotEmpty(visitor.getUri().toString()) && visitor.getUri().equals(URI.create(activity.getActor()))) || !visitor.isAnonymous()) {
if (activity instanceof Follow) {
Follow followRequest = (Follow) activity;
- String actor = followRequest.getActor();
- Person follower = (Person) signatureManager.getContext(URI.create(actor)).orElseThrow(HttpBadRequestException::new);
applicationEventPublisher.publishEvent(
new FollowEvent(this, followRequest));
- return new ResponseEntity<>(HttpStatus.ACCEPTED);
+ return new ResponseEntity<>(CommandResult.fromString("Follow request accepted"), HttpStatus.ACCEPTED);
}
if (activity instanceof Undo) {
@@ -286,17 +279,10 @@ public class Profile {
String objectObject = (String) object.get("object");
if (objectType.equals("Follow")) {
applicationEventPublisher.publishEvent(new UndoFollowEvent(this, activity.getActor(), objectObject));
- return new ResponseEntity<>(HttpStatus.OK);
+ return new ResponseEntity<>(CommandResult.fromString("Undo follow request accepted"), HttpStatus.OK);
} else if (objectType.equals("Like") || objectType.equals("Announce")) {
applicationEventPublisher.publishEvent(new UndoAnnounceEvent(this, activity.getActor(), objectObject));
- return new ResponseEntity<>(HttpStatus.OK);
- }
- }
- if (activity instanceof Delete) {
- if (activity.getObject() instanceof String) {
- // Delete user
- applicationEventPublisher.publishEvent(new DeleteUserEvent(this, (String)activity.getObject()));
- return new ResponseEntity<>(HttpStatus.OK);
+ return new ResponseEntity<>(CommandResult.fromString("Undo like/announce request accepted"), HttpStatus.OK);
}
}
if (activity instanceof Create) {
@@ -305,7 +291,7 @@ public class Profile {
if (note.get("type").equals("Note")) {
URI noteId = URI.create((String) note.get("id"));
if (messagesService.replyExists(noteId)) {
- return new ResponseEntity<>(HttpStatus.OK);
+ return new ResponseEntity<>(CommandResult.fromString("Reply already exists"), HttpStatus.OK);
} else {
String inReplyTo = (String) note.get("inReplyTo");
if (StringUtils.isNotBlank(inReplyTo)) {
@@ -313,36 +299,50 @@ public class Profile {
String postId = activityPubManager.postId(inReplyTo);
User user = new User();
user.setUri(URI.create(activity.getActor()));
- String attachment = StringUtils.EMPTY;
- if (note.get("attachment") != null && ((List) note.get("attachment")).size() > 0) {
- Map<String, Object> attachmentObj = (Map<String, Object>) ((List<Object>) note.get("attachment")).get(0);
- attachment = (String) attachmentObj.get("url");
- }
- CommandResult result = commandsManager.processCommand(user, String.format("#%s %s", postId, note.get("content")), URI.create(attachment));
+ String markdown = remarkConverter.convertFragment((String) note.get("content"));
+ String commandBody = note.get("attachment") == null ? markdown :
+ ((List<Object>) note.get("attachment")).stream().map(attachmentObj -> {
+ Map<String, String> attachment = (Map<String, String>) attachmentObj;
+ String attachmentUrl = attachment.get("url");
+ String attachmentName = attachment.get("name");
+ return PlainTextFormatter.markdownUrl(attachmentUrl, attachmentName);
+ }).reduce((source, url) -> String.format("%s\n%s", source, url))
+ .orElse(markdown);
+
+ CommandResult result = commandsManager.processCommand(
+ user, String.format("#%s %s", postId, commandBody),
+ URI.create(StringUtils.EMPTY));
logger.info(jsonMapper.writeValueAsString(result));
if (result.getNewMessage().isPresent()) {
messagesService.updateReplyUri(result.getNewMessage().get(), noteId);
- return new ResponseEntity<>(HttpStatus.OK);
+ return new ResponseEntity<>(result, HttpStatus.OK);
} else {
- return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
+ return new ResponseEntity<>(result, HttpStatus.BAD_REQUEST);
}
} else {
Message reply = messagesService.getReplyByUri(inReplyTo);
if (reply != null) {
User user = new User();
user.setUri(URI.create(activity.getActor()));
- String attachment = StringUtils.EMPTY;
- if (note.get("attachment") != null && ((List) note.get("attachment")).size() > 0) {
- Map<String, Object> attachmentObj = (Map<String, Object>) ((List<Object>) note.get("attachment")).get(0);
- attachment = (String) attachmentObj.get("url");
- }
- CommandResult result = commandsManager.processCommand(user, String.format("#%d/%d %s", reply.getMid(), reply.getRid(), note.get("content")), URI.create(attachment));
+ String markdown = remarkConverter.convertFragment((String)note.get("content"));
+ String commandBody = note.get("attachment") == null ? markdown :
+ ((List<Object>) note.get("attachment")).stream().map(attachmentObj -> {
+ Map<String, String> attachment = (Map<String, String>) attachmentObj;
+ String attachmentUrl = attachment.get("url");
+ String attachmentName = attachment.get("name");
+ return PlainTextFormatter.markdownUrl(attachmentUrl, attachmentName);
+ }).reduce((source, url) -> String.format("%s\n%s", source, url))
+ .orElse(markdown);
+ CommandResult result = commandsManager.processCommand(
+ user,
+ String.format("#%d/%d %s", reply.getMid(), reply.getRid(), commandBody),
+ URI.create(StringUtils.EMPTY));
logger.info(jsonMapper.writeValueAsString(result));
if (result.getNewMessage().isPresent()) {
messagesService.updateReplyUri(result.getNewMessage().get(), noteId);
- return new ResponseEntity<>(HttpStatus.OK);
+ return new ResponseEntity<>(result, HttpStatus.OK);
} else {
- return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
+ return new ResponseEntity<>(result, HttpStatus.BAD_REQUEST);
}
}
}
@@ -357,17 +357,28 @@ public class Profile {
URI actor = URI.create(activity.getActor());
URI reply = URI.create((String)tombstone.get("id"));
messagesService.deleteReply(actor, reply);
- return new ResponseEntity<>(HttpStatus.OK);
+ return new ResponseEntity<>(CommandResult.fromString("Delete request accepted"), HttpStatus.OK);
}
}
if (activity instanceof Like || activity instanceof Announce) {
- applicationEventPublisher.publishEvent(new AnnounceEvent(this, activity.getActor(), (String)activity.getObject()));
- return new ResponseEntity<>(HttpStatus.OK);
+ String messageUri = activity.getObject() instanceof String ? (String) activity.getObject()
+ : activity.getObject() instanceof Context ? ((Context) activity.getObject()).getId()
+ : (String) ((Map)activity.getObject()).get("id");
+ applicationEventPublisher.publishEvent(new AnnounceEvent(this, activity.getActor(), messageUri));
+ return new ResponseEntity<>(CommandResult.fromString("Like/announce request accepted"), HttpStatus.OK);
}
logger.warn("Unknown activity: {}", jsonMapper.writeValueAsString(activity));
- return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
+ return new ResponseEntity<>(CommandResult.fromString("Unknown activity"), HttpStatus.NOT_IMPLEMENTED);
+ }
+ if (activity instanceof Delete) {
+ if (activity.getObject() instanceof String) {
+ // Delete gone user
+ if (activity.getActor().equals(activity.getObject())) {
+ return new ResponseEntity<>(CommandResult.fromString("Delete request accepted"), HttpStatus.ACCEPTED);
+ }
+ }
}
- return new ResponseEntity<>(HttpStatus.UNAUTHORIZED);
+ return new ResponseEntity<>(CommandResult.fromString("Can not authenticate"), HttpStatus.UNAUTHORIZED);
}
@PostMapping(value = "/u/", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public User fetchUser(@RequestParam URI uri) {
diff --git a/src/main/java/com/juick/server/api/activity/helpers/ActivityIdDeserializer.java b/src/main/java/com/juick/server/api/activity/helpers/ActivityIdDeserializer.java
new file mode 100644
index 00000000..ba8cfb87
--- /dev/null
+++ b/src/main/java/com/juick/server/api/activity/helpers/ActivityIdDeserializer.java
@@ -0,0 +1,22 @@
+package com.juick.server.api.activity.helpers;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.JsonToken;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JsonDeserializer;
+import com.fasterxml.jackson.databind.JsonNode;
+
+import java.io.IOException;
+
+public class ActivityIdDeserializer extends JsonDeserializer<String> {
+ @Override
+ public String deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
+ JsonToken jsonToken = p.getCurrentToken();
+ if (jsonToken == JsonToken.START_OBJECT) {
+ JsonNode node = p.getCodec().readTree(p);
+ return node.get("id").textValue();
+ }
+ return p.getValueAsString();
+ }
+}
diff --git a/src/main/java/com/juick/server/api/activity/helpers/LinkValueDeserializer.java b/src/main/java/com/juick/server/api/activity/helpers/LinkValueDeserializer.java
new file mode 100644
index 00000000..a635ea95
--- /dev/null
+++ b/src/main/java/com/juick/server/api/activity/helpers/LinkValueDeserializer.java
@@ -0,0 +1,21 @@
+package com.juick.server.api.activity.helpers;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonToken;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JsonDeserializer;
+import com.fasterxml.jackson.databind.JsonNode;
+
+import java.io.IOException;
+
+public class LinkValueDeserializer extends JsonDeserializer<String> {
+ @Override
+ public String deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
+ JsonToken jsonToken = p.getCurrentToken();
+ if (jsonToken == JsonToken.VALUE_STRING) {
+ return p.getValueAsString();
+ }
+ JsonNode node = p.getCodec().readTree(p);
+ return node.get("href").textValue();
+ }
+}
diff --git a/src/main/java/com/juick/server/api/activity/model/Activity.java b/src/main/java/com/juick/server/api/activity/model/Activity.java
index ec126b88..2af14479 100644
--- a/src/main/java/com/juick/server/api/activity/model/Activity.java
+++ b/src/main/java/com/juick/server/api/activity/model/Activity.java
@@ -1,7 +1,11 @@
package com.juick.server.api.activity.model;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.juick.server.api.activity.helpers.ActivityIdDeserializer;
+
public abstract class Activity extends Context {
+ @JsonDeserialize(using = ActivityIdDeserializer.class)
private String actor;
private Object object;
diff --git a/src/main/java/com/juick/server/api/activity/model/Context.java b/src/main/java/com/juick/server/api/activity/model/Context.java
index 515ee3da..2ba4606e 100644
--- a/src/main/java/com/juick/server/api/activity/model/Context.java
+++ b/src/main/java/com/juick/server/api/activity/model/Context.java
@@ -4,6 +4,8 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.juick.server.api.activity.helpers.LinkValueDeserializer;
import com.juick.server.api.activity.model.activities.*;
import com.juick.server.api.activity.model.objects.*;
@@ -46,6 +48,7 @@ public abstract class Context {
private Instant published;
+ @JsonDeserialize(using = LinkValueDeserializer.class)
private String url;
private List<String> to;
diff --git a/src/main/java/com/juick/server/api/rss/MessagesView.java b/src/main/java/com/juick/server/api/rss/MessagesView.java
index 05bc0bd6..2c05c8cb 100644
--- a/src/main/java/com/juick/server/api/rss/MessagesView.java
+++ b/src/main/java/com/juick/server/api/rss/MessagesView.java
@@ -126,7 +126,7 @@ public class MessagesView extends AbstractRssFeedView {
description.setType("text/html");
description.setValue(messageDescription);
item.setDescription(description);
- item.setPubDate(Date.from(msg.getTimestamp()));
+ item.setPubDate(Date.from(msg.getCreated()));
item.setComments(messageUrl);
msg.getTags().stream().map(t -> {
Category category = new Category();
diff --git a/src/main/java/com/juick/server/api/webfinger/Resource.java b/src/main/java/com/juick/server/api/webfinger/Resource.java
index 71a0ca31..4e0447f7 100644
--- a/src/main/java/com/juick/server/api/webfinger/Resource.java
+++ b/src/main/java/com/juick/server/api/webfinger/Resource.java
@@ -26,7 +26,7 @@ public class Resource {
@Value("${ap_base_uri:http://localhost:8080/}")
private String baseUri;
- @GetMapping("/.well-known/webfinger")
+ @GetMapping(value = "/.well-known/webfinger", produces = "application/jrd+json;charset=utf-8")
public Account getWebResource(@RequestParam String resource) {
if (resource.startsWith("acct:")) {
Jid account = Jid.of(resource.substring(5));
diff --git a/src/main/java/com/juick/server/api/xnodeinfo2/Info.java b/src/main/java/com/juick/server/api/xnodeinfo2/Info.java
index 36e36bdd..493bff6a 100644
--- a/src/main/java/com/juick/server/api/xnodeinfo2/Info.java
+++ b/src/main/java/com/juick/server/api/xnodeinfo2/Info.java
@@ -1,15 +1,24 @@
package com.juick.server.api.xnodeinfo2;
+import com.cliqset.xrd.Link;
+import com.cliqset.xrd.XRD;
+import com.fasterxml.jackson.annotation.JsonView;
import com.juick.server.api.xnodeinfo2.model.*;
import org.springframework.beans.factory.annotation.Value;
+import org.springframework.http.MediaType;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.util.UriComponentsBuilder;
import javax.inject.Inject;
+import java.net.URI;
import java.time.ZonedDateTime;
import java.time.temporal.ChronoUnit;
import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
@RestController
public class Info {
@@ -18,9 +27,9 @@ public class Info {
@Inject
private JdbcTemplate jdbcTemplate;
- @GetMapping("/.well-known/x-nodeinfo2")
- public NodeInfo showNodeInfo() {
+ private NodeInfo getCurrentNodeInfo(String version) {
NodeInfo nodeInfo = new NodeInfo();
+ nodeInfo.setVersion(version);
Server server = new Server();
server.setBaseUrl(baseUri);
server.setName("Juick");
@@ -28,6 +37,9 @@ public class Info {
server.setVersion("2.x");
nodeInfo.setServer(server);
nodeInfo.setProtocols(Arrays.asList("xmpp", "activitypub", "smtp"));
+ Map<String, String> metadata = new HashMap<>();
+ metadata.put("email", "support@juick.com");
+ nodeInfo.setMetadata(metadata);
ServiceInfo serviceInfo = new ServiceInfo();
serviceInfo.setInbound(Arrays.asList("jabber", "mastodon", "email", "telegram"));
serviceInfo.setOutbound(Arrays.asList("jabber", "mastodon", "telegram", "twitter", "email", "rss"));
@@ -47,4 +59,28 @@ public class Info {
nodeInfo.setUsage(usage);
return nodeInfo;
}
+
+ @GetMapping(value = "/.well-known/x-nodeinfo2", produces = MediaType.APPLICATION_JSON_VALUE)
+ @JsonView(NodeInfo.XNodeInfoView.class)
+ public NodeInfo showXNodeInfo() {
+ return getCurrentNodeInfo("1.0");
+ }
+
+ @GetMapping(value = "/.well-known/nodeinfo", produces = MediaType.APPLICATION_JSON_VALUE)
+ public XRD getNodeInfoLinks() {
+ Link nodeinfo = new Link();
+ nodeinfo.setRel(URI.create("http://nodeinfo.diaspora.software/ns/schema/2.0"));
+ UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder.fromUriString(baseUri);
+ uriComponentsBuilder.replacePath("/api/nodeinfo/2.0");
+ nodeinfo.setHref(uriComponentsBuilder.build().toUri());
+ XRD xrd = new XRD();
+ xrd.setLinks(Collections.singletonList(nodeinfo));
+ return xrd;
+ }
+
+ @GetMapping(value = "/api/nodeinfo/2.0", produces = MediaType.APPLICATION_JSON_VALUE)
+ @JsonView(NodeInfo.NodeInfoView.class)
+ public NodeInfo showNodeInfo() {
+ return getCurrentNodeInfo("2.0");
+ }
}
diff --git a/src/main/java/com/juick/server/api/xnodeinfo2/model/NodeInfo.java b/src/main/java/com/juick/server/api/xnodeinfo2/model/NodeInfo.java
index 06fe354f..1ebe39a8 100644
--- a/src/main/java/com/juick/server/api/xnodeinfo2/model/NodeInfo.java
+++ b/src/main/java/com/juick/server/api/xnodeinfo2/model/NodeInfo.java
@@ -1,27 +1,45 @@
package com.juick.server.api.xnodeinfo2.model;
+import com.fasterxml.jackson.annotation.JsonView;
+
import java.util.List;
+import java.util.Map;
public class NodeInfo {
+ private String version;
+
private Server server;
private List<String> protocols;
private ServiceInfo services;
+ private Map<String, String> metadata;
+
+ @JsonView({XNodeInfoView.class, NodeInfoView.class})
public String getVersion() {
- return "1.0";
+ return version;
}
+ public void setVersion(String version) {
+ this.version = version;
+ }
+
+ @JsonView(XNodeInfoView.class)
public Server getServer() {
return server;
}
+ @JsonView(NodeInfoView.class)
+ public Server getSoftware() {
+ return server;
+ }
public void setServer(Server server) {
this.server = server;
}
+ @JsonView({XNodeInfoView.class, NodeInfoView.class})
public List<String> getProtocols() {
return protocols;
}
@@ -30,6 +48,7 @@ public class NodeInfo {
this.protocols = protocols;
}
+ @JsonView({XNodeInfoView.class, NodeInfoView.class})
public ServiceInfo getServices() {
return services;
}
@@ -38,12 +57,14 @@ public class NodeInfo {
this.services = services;
}
+ @JsonView({XNodeInfoView.class, NodeInfoView.class})
public boolean getOpenRegistrations() {
return true;
}
private Usage usage;
+ @JsonView({XNodeInfoView.class, NodeInfoView.class})
public Usage getUsage() {
return usage;
}
@@ -51,4 +72,16 @@ public class NodeInfo {
public void setUsage(Usage usage) {
this.usage = usage;
}
+
+ @JsonView(NodeInfoView.class)
+ public Map<String, String> getMetadata() {
+ return metadata;
+ }
+
+ public void setMetadata(Map<String, String> metadata) {
+ this.metadata = metadata;
+ }
+
+ public interface NodeInfoView {}
+ public interface XNodeInfoView {}
}
diff --git a/src/main/java/com/juick/server/api/xnodeinfo2/model/Server.java b/src/main/java/com/juick/server/api/xnodeinfo2/model/Server.java
index a772d268..08c1a696 100644
--- a/src/main/java/com/juick/server/api/xnodeinfo2/model/Server.java
+++ b/src/main/java/com/juick/server/api/xnodeinfo2/model/Server.java
@@ -1,11 +1,14 @@
package com.juick.server.api.xnodeinfo2.model;
+import com.fasterxml.jackson.annotation.JsonView;
+
public class Server {
private String baseUrl;
private String name;
private String software;
private String version;
+ @JsonView(NodeInfo.XNodeInfoView.class)
public String getBaseUrl() {
return baseUrl;
}
@@ -14,6 +17,7 @@ public class Server {
this.baseUrl = baseUrl;
}
+ @JsonView({NodeInfo.NodeInfoView.class, NodeInfo.XNodeInfoView.class})
public String getName() {
return name;
}
@@ -22,6 +26,7 @@ public class Server {
this.name = name;
}
+ @JsonView(NodeInfo.XNodeInfoView.class)
public String getSoftware() {
return software;
}
@@ -30,6 +35,7 @@ public class Server {
this.software = software;
}
+ @JsonView({NodeInfo.NodeInfoView.class, NodeInfo.XNodeInfoView.class})
public String getVersion() {
return version;
}
diff --git a/src/main/java/com/juick/server/api/xnodeinfo2/model/ServiceInfo.java b/src/main/java/com/juick/server/api/xnodeinfo2/model/ServiceInfo.java
index 5b6d2baa..0114488f 100644
--- a/src/main/java/com/juick/server/api/xnodeinfo2/model/ServiceInfo.java
+++ b/src/main/java/com/juick/server/api/xnodeinfo2/model/ServiceInfo.java
@@ -1,7 +1,10 @@
package com.juick.server.api.xnodeinfo2.model;
+import com.fasterxml.jackson.annotation.JsonView;
+
import java.util.List;
+@JsonView({NodeInfo.NodeInfoView.class, NodeInfo.XNodeInfoView.class})
public class ServiceInfo {
private List<String> inbound;
private List<String> outbound;
diff --git a/src/main/java/com/juick/server/api/xnodeinfo2/model/Usage.java b/src/main/java/com/juick/server/api/xnodeinfo2/model/Usage.java
index e04ea48b..f270296f 100644
--- a/src/main/java/com/juick/server/api/xnodeinfo2/model/Usage.java
+++ b/src/main/java/com/juick/server/api/xnodeinfo2/model/Usage.java
@@ -1,5 +1,8 @@
package com.juick.server.api.xnodeinfo2.model;
+import com.fasterxml.jackson.annotation.JsonView;
+
+@JsonView({NodeInfo.NodeInfoView.class, NodeInfo.XNodeInfoView.class})
public class Usage {
private UserStats users;
private int localPosts;
diff --git a/src/main/java/com/juick/server/api/xnodeinfo2/model/UserStats.java b/src/main/java/com/juick/server/api/xnodeinfo2/model/UserStats.java
index 515661e3..cca31be6 100644
--- a/src/main/java/com/juick/server/api/xnodeinfo2/model/UserStats.java
+++ b/src/main/java/com/juick/server/api/xnodeinfo2/model/UserStats.java
@@ -1,5 +1,8 @@
package com.juick.server.api.xnodeinfo2.model;
+import com.fasterxml.jackson.annotation.JsonView;
+
+@JsonView({NodeInfo.NodeInfoView.class, NodeInfo.XNodeInfoView.class})
public class UserStats {
private int total;
private int activeHalfyear;
diff --git a/src/main/java/com/juick/server/configuration/ActivityPubClientConfig.java b/src/main/java/com/juick/server/configuration/ActivityPubClientConfig.java
index 43b638fe..d7d49355 100644
--- a/src/main/java/com/juick/server/configuration/ActivityPubClientConfig.java
+++ b/src/main/java/com/juick/server/configuration/ActivityPubClientConfig.java
@@ -1,11 +1,13 @@
package com.juick.server.configuration;
+import com.juick.server.api.activity.model.Activity;
+import com.juick.server.helpers.HeaderRequestInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
-import org.springframework.http.client.OkHttp3ClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
import javax.inject.Inject;
+import java.util.Collections;
@Configuration
public class ActivityPubClientConfig {
@@ -13,8 +15,10 @@ public class ActivityPubClientConfig {
ActivityPubClientErrorHandler activityPubClientErrorHandler;
@Bean
public RestTemplate apClient() {
- RestTemplate restTemplate = new RestTemplate(new OkHttp3ClientHttpRequestFactory());
+ RestTemplate restTemplate = new RestTemplate();
restTemplate.setErrorHandler(activityPubClientErrorHandler);
+ restTemplate.setInterceptors(Collections.singletonList(
+ new HeaderRequestInterceptor("Accept", Activity.ACTIVITY_MEDIA_TYPE)));
return restTemplate;
}
} \ No newline at end of file
diff --git a/src/main/java/com/juick/server/configuration/ApiAppConfiguration.java b/src/main/java/com/juick/server/configuration/ApiAppConfiguration.java
index 5a5d2c7b..68b3d35f 100644
--- a/src/main/java/com/juick/server/configuration/ApiAppConfiguration.java
+++ b/src/main/java/com/juick/server/configuration/ApiAppConfiguration.java
@@ -17,25 +17,15 @@
package com.juick.server.configuration;
-import com.juick.server.WebsocketManager;
import com.juick.server.api.rss.MessagesView;
import com.juick.server.api.rss.RepliesView;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
-import org.springframework.core.Ordered;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.view.BeanNameViewResolver;
import org.springframework.web.servlet.view.feed.AbstractRssFeedView;
-import org.springframework.web.socket.config.annotation.EnableWebSocket;
-import org.springframework.web.socket.config.annotation.ServletWebSocketHandlerRegistry;
-import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
-import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
-import org.springframework.web.socket.server.standard.ServletServerContainerFactoryBean;
-
-import javax.annotation.Nonnull;
-import javax.inject.Inject;
/**
* Created by aalexeev on 11/12/16.
@@ -43,24 +33,7 @@ import javax.inject.Inject;
@Configuration
@EnableAsync(proxyTargetClass = true)
@EnableScheduling
-@EnableWebSocket
-public class ApiAppConfiguration implements WebMvcConfigurer, WebSocketConfigurer {
- @Inject
- private WebsocketManager websocketManager;
-
- @Override
- public void registerWebSocketHandlers(@Nonnull WebSocketHandlerRegistry registry) {
- ((ServletWebSocketHandlerRegistry) registry).setOrder(Ordered.HIGHEST_PRECEDENCE);
- registry.addHandler(websocketManager, "/ws/**").setAllowedOrigins("*");
- }
-
- @Bean
- public ServletServerContainerFactoryBean createWebSocketContainer() {
- ServletServerContainerFactoryBean container = new ServletServerContainerFactoryBean();
- container.setMaxTextMessageBufferSize(8192);
- container.setMaxBinaryMessageBufferSize(8192);
- return container;
- }
+public class ApiAppConfiguration implements WebMvcConfigurer {
@Bean
public BeanNameViewResolver beanNameViewResolver() {
return new BeanNameViewResolver();
diff --git a/src/main/java/com/juick/server/configuration/BaseWebConfiguration.java b/src/main/java/com/juick/server/configuration/BaseWebConfiguration.java
index 6a2a8142..c8b88cd1 100644
--- a/src/main/java/com/juick/server/configuration/BaseWebConfiguration.java
+++ b/src/main/java/com/juick/server/configuration/BaseWebConfiguration.java
@@ -17,6 +17,10 @@
package com.juick.server.configuration;
+import com.juick.server.KeystoreManager;
+import com.overzealous.remark.Options;
+import com.overzealous.remark.Remark;
+import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
@@ -36,6 +40,10 @@ import java.util.concurrent.Executors;
@Configuration
public class BaseWebConfiguration implements WebMvcConfigurer, SchedulingConfigurer {
+ @Value("${keystore:juick.p12}")
+ private String keystore;
+ @Value("${keystore_password:secret}")
+ private String keystorePassword;
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
@@ -61,4 +69,14 @@ public class BaseWebConfiguration implements WebMvcConfigurer, SchedulingConfigu
public ExecutorService executorService() {
return Executors.newCachedThreadPool();
}
+ @Bean
+ public KeystoreManager keystoreManager() {
+ return new KeystoreManager(keystore, keystorePassword);
+ }
+ @Bean
+ public Remark remarkConverter() {
+ Options options = new Options();
+ options.inlineLinks = true;
+ return new Remark(options);
+ }
}
diff --git a/src/main/java/com/juick/server/configuration/SecurityConfig.java b/src/main/java/com/juick/server/configuration/SecurityConfig.java
index f53cc531..df0da16e 100644
--- a/src/main/java/com/juick/server/configuration/SecurityConfig.java
+++ b/src/main/java/com/juick/server/configuration/SecurityConfig.java
@@ -17,7 +17,9 @@
package com.juick.server.configuration;
+import com.juick.server.SignatureManager;
import com.juick.service.UserService;
+import com.juick.service.security.HTTPSignatureAuthenticationFilter;
import com.juick.service.security.HashParamAuthenticationFilter;
import com.juick.service.security.JuickUserDetailsService;
import com.juick.service.security.deprecated.RequestParamHashRememberMeServices;
@@ -69,6 +71,20 @@ public class SecurityConfig {
public UserDetailsService userDetailsService() {
return new JuickUserDetailsService(userService);
}
+ @Bean
+ static CorsConfigurationSource corsConfigurationSource() {
+ CorsConfiguration configuration = new CorsConfiguration();
+
+ configuration.setAllowedOrigins(Collections.singletonList("*"));
+ configuration.setAllowedMethods(Arrays.asList("POST", "GET", "PUT", "OPTIONS", "DELETE"));
+ configuration.setAllowedHeaders(Collections.singletonList("*"));
+
+ UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
+ source.registerCorsConfiguration("/api/**", configuration);
+ source.registerCorsConfiguration("/u/**", configuration);
+ source.registerCorsConfiguration("/n/**", configuration);
+ return source;
+ }
@Configuration
@Order(1)
@@ -79,6 +95,8 @@ public class SecurityConfig {
private String webDomain;
@Resource
private UserService userService;
+ @Resource
+ private SignatureManager signatureManager;
ApiConfig() {
super(true);
}
@@ -95,10 +113,14 @@ public class SecurityConfig {
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/api/**")
.addFilterBefore(apiAuthenticationFilter(), BasicAuthenticationFilter.class)
+ .addFilterBefore(new HTTPSignatureAuthenticationFilter(signatureManager, userService), BasicAuthenticationFilter.class)
.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS).permitAll()
- .antMatchers("/api/", "/api/messages", "/api/messages/discussions", "/api/users", "/api/thread", "/api/tags", "/api/tlgmbtwbhk", "/api/fbwbhk",
- "/api/skypebotendpoint", "/api/_fblogin", "/api/_vklogin", "/api/_tglogin", "/api/_google", "/api/signup", "/api/inbox", "/api/u/**", "/.well-known/webfinger", "/.well-known/x-nodeinfo2", "/rss/**", "/api/events").permitAll()
+ .antMatchers("/api/", "/api/messages", "/api/avatar", "/api/messages/discussions",
+ "/api/users", "/api/thread", "/api/tags", "/api/tlgmbtwbhk", "/api/fbwbhk",
+ "/api/skypebotendpoint", "/api/_fblogin", "/api/_vklogin", "/api/_tglogin",
+ "/api/_google", "/api/signup", "/api/inbox", "/api/events", "/api/info/**",
+ "/api/nodeinfo/2.0").permitAll()
.anyRequest().hasRole("USER")
.and()
.anonymous().principal(JuickUser.ANONYMOUS_USER).authorities(JuickUser.ANONYMOUS_AUTHORITY)
@@ -122,19 +144,6 @@ public class SecurityConfig {
return new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED);
}
- @Bean
- public CorsConfigurationSource corsConfigurationSource() {
- CorsConfiguration configuration = new CorsConfiguration();
-
- configuration.setAllowedOrigins(Collections.singletonList("*"));
- configuration.setAllowedMethods(Arrays.asList("POST", "GET", "PUT", "OPTIONS", "DELETE"));
- configuration.setAllowedHeaders(Collections.singletonList("*"));
-
- UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
- source.registerCorsConfiguration("/api/**", configuration);
-
- return source;
- }
@Override
public void configure(WebSecurity web) {
web.debug(false);
@@ -182,6 +191,7 @@ public class SecurityConfig {
.anyRequest().permitAll()
.and()
.anonymous().principal(JuickUser.ANONYMOUS_USER).authorities(JuickUser.ANONYMOUS_AUTHORITY)
+ .and().cors().configurationSource(corsConfigurationSource())
.and().sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.invalidSessionUrl("/")
diff --git a/src/main/java/com/juick/server/configuration/XMPPConfig.java b/src/main/java/com/juick/server/configuration/XMPPConfig.java
index 2feef286..f9b6f092 100644
--- a/src/main/java/com/juick/server/configuration/XMPPConfig.java
+++ b/src/main/java/com/juick/server/configuration/XMPPConfig.java
@@ -1,23 +1,13 @@
package com.juick.server.configuration;
-import com.juick.server.XMPPConnection;
-import com.juick.server.XMPPServer;
+import com.juick.server.XMPPManager;
import com.juick.server.xmpp.JidConverter;
-import com.juick.server.xmpp.iq.MessageQuery;
-import com.juick.server.xmpp.router.XMPPRouter;
-import com.juick.server.xmpp.s2s.BasicXmppSession;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
-import org.springframework.context.annotation.DependsOn;
import org.springframework.core.convert.ConversionService;
import org.springframework.format.support.DefaultFormattingConversionService;
-import rocks.xmpp.core.session.Extension;
-import rocks.xmpp.core.session.XmppSessionConfiguration;
-import rocks.xmpp.core.session.debug.LogbackDebugger;
-
-import java.time.Duration;
@Configuration
@ConditionalOnProperty("xmppbot_jid")
@@ -25,31 +15,13 @@ public class XMPPConfig {
@Value("${hostname:localhost}")
private String hostname;
@Bean
- public BasicXmppSession session() {
- XmppSessionConfiguration configuration = XmppSessionConfiguration.builder()
- .extensions(Extension.of(com.juick.Message.class), Extension.of(MessageQuery.class))
- .debugger(LogbackDebugger.class)
- .defaultResponseTimeout(Duration.ofMillis(120000))
- .build();
- return BasicXmppSession.create(hostname, configuration);
- }
- @Bean
public static ConversionService conversionService() {
DefaultFormattingConversionService cs = new DefaultFormattingConversionService();
cs.addConverter(new JidConverter());
return cs;
}
@Bean
- public XMPPServer xmppServer() {
- return new XMPPServer();
- }
- @Bean
- public XMPPRouter xmppRouter() {
- return new XMPPRouter();
- }
- @Bean
- @DependsOn("xmppRouter")
- public XMPPConnection xmppConnection() {
- return new XMPPConnection();
+ public XMPPManager xmppConnection() {
+ return new XMPPManager();
}
}
diff --git a/src/main/java/com/juick/server/helpers/HeaderRequestInterceptor.java b/src/main/java/com/juick/server/helpers/HeaderRequestInterceptor.java
new file mode 100644
index 00000000..9121f5ce
--- /dev/null
+++ b/src/main/java/com/juick/server/helpers/HeaderRequestInterceptor.java
@@ -0,0 +1,26 @@
+package com.juick.server.helpers;
+
+import org.springframework.http.HttpRequest;
+import org.springframework.http.client.ClientHttpRequestExecution;
+import org.springframework.http.client.ClientHttpRequestInterceptor;
+import org.springframework.http.client.ClientHttpResponse;
+
+import java.io.IOException;
+
+public class HeaderRequestInterceptor implements ClientHttpRequestInterceptor {
+
+ private final String headerName;
+
+ private final String headerValue;
+
+ public HeaderRequestInterceptor(String headerName, String headerValue) {
+ this.headerName = headerName;
+ this.headerValue = headerValue;
+ }
+
+ @Override
+ public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
+ request.getHeaders().set(headerName, headerValue);
+ return execution.execute(request, body);
+ }
+}
diff --git a/src/main/java/com/juick/server/util/ImageUtils.java b/src/main/java/com/juick/server/util/ImageUtils.java
index d16faf8f..2f5d3292 100644
--- a/src/main/java/com/juick/server/util/ImageUtils.java
+++ b/src/main/java/com/juick/server/util/ImageUtils.java
@@ -108,7 +108,7 @@ public class ImageUtils {
}
}
}
- } catch (ImageReadException e) {
+ } catch (ImageReadException | IOException e) {
// failed to read metadata.
// nothing to do here, return image as is.
}
diff --git a/src/main/java/com/juick/server/www/VaryHandler.java b/src/main/java/com/juick/server/www/VaryHandler.java
new file mode 100644
index 00000000..5a1b86a6
--- /dev/null
+++ b/src/main/java/com/juick/server/www/VaryHandler.java
@@ -0,0 +1,14 @@
+package com.juick.server.www;
+
+import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.bind.annotation.ModelAttribute;
+
+import javax.servlet.http.HttpServletResponse;
+
+@ControllerAdvice
+public class VaryHandler {
+ @ModelAttribute
+ public void setVaryResponseHeader(HttpServletResponse response) {
+ response.setHeader("Vary", "Accept-Language");
+ }
+}
diff --git a/src/main/java/com/juick/server/www/controllers/AnythingFilter.java b/src/main/java/com/juick/server/www/controllers/AnythingFilter.java
index cdbeafc0..57b298eb 100644
--- a/src/main/java/com/juick/server/www/controllers/AnythingFilter.java
+++ b/src/main/java/com/juick/server/www/controllers/AnythingFilter.java
@@ -60,7 +60,7 @@ public class AnythingFilter extends OncePerRequestFilter {
} else {
com.juick.User user = userService.getUserByName(anything);
if (!user.isAnonymous()) {
- ((HttpServletResponse) servletResponse).sendRedirect("/" + user.getName() + "/?before=" + before);
+ servletResponse.sendRedirect("/" + user.getName() + "/?before=" + before);
} else {
filterChain.doFilter(servletRequest, servletResponse);
}
diff --git a/src/main/java/com/juick/server/www/controllers/MessagesWWW.java b/src/main/java/com/juick/server/www/controllers/MessagesWWW.java
index 1c69db32..4410f591 100644
--- a/src/main/java/com/juick/server/www/controllers/MessagesWWW.java
+++ b/src/main/java/com/juick/server/www/controllers/MessagesWWW.java
@@ -43,7 +43,9 @@ import ru.sape.Sape;
import javax.inject.Inject;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
+import java.net.URI;
import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
@@ -97,7 +99,7 @@ public class MessagesWWW {
@CookieValue(name = "sape_cookie", required = false, defaultValue = StringUtils.EMPTY) String sapeCookie,
ModelMap model) throws IOException {
if (tag != null) {
- return "redirect:/tag/" + URLEncoder.encode(tag, CharEncoding.UTF_8);
+ return "redirect:/tag/" + URLEncoder.encode(tag, StandardCharsets.UTF_8);
}
com.juick.User visitor = UserUtils.getCurrentUser();
@@ -182,10 +184,9 @@ public class MessagesWWW {
}
model.addAttribute("nextpage", nextpage);
}
- UriComponents builder = ServletUriComponentsBuilder.fromCurrentRequestUri().build();
- String queryString = builder.getQuery();
- String requestURI = builder.toUri().getPath();
- if (sape.isPresent() && visitor.isAnonymous() && queryString == null) {
+ UriComponents builder = ServletUriComponentsBuilder.fromCurrentRequest().build();
+ URI requestURI = builder.toUri();
+ if (sape.isPresent() && visitor.isAnonymous()) {
String links = sape.get().getPageLinks(requestURI, sapeCookie).render();
model.addAttribute("links", links);
}
@@ -295,17 +296,16 @@ public class MessagesWWW {
nextpage += "&amp;show=" + paramShow;
}
if (paramSearch != null) {
- nextpage += "&amp;search=" + URLEncoder.encode(paramSearch, CharEncoding.UTF_8);
+ nextpage += "&amp;search=" + URLEncoder.encode(paramSearch, StandardCharsets.UTF_8);
}
if (paramTag != null) {
- nextpage += "&amp;tag=" + URLEncoder.encode(paramTag.getName(), CharEncoding.UTF_8);
+ nextpage += "&amp;tag=" + URLEncoder.encode(paramTag.getName(), StandardCharsets.UTF_8);
}
model.addAttribute("nextpage", nextpage);
}
- UriComponents builder = ServletUriComponentsBuilder.fromCurrentRequestUri().build();
- String queryString = builder.getQuery();
- String requestURI = builder.toUri().getPath();
- if (sape.isPresent() && visitor.isAnonymous() && queryString == null) {
+ UriComponents builder = ServletUriComponentsBuilder.fromCurrentRequest().build();
+ URI requestURI = builder.toUri();
+ if (sape.isPresent() && visitor.isAnonymous()) {
String links = sape.get().getPageLinks(requestURI, sapeCookie).render();
model.addAttribute("links", links);
}
@@ -313,7 +313,7 @@ public class MessagesWWW {
}
@GetMapping("/{uname}/tags")
- protected String doGetTags(@PathVariable String uname, ModelMap model) throws IOException {
+ protected String doGetTags(@PathVariable String uname, ModelMap model) {
com.juick.User user = userService.getUserByName(uname);
com.juick.User visitor = UserUtils.getCurrentUser();
if (visitor.isBanned()) {
@@ -332,7 +332,7 @@ public class MessagesWWW {
}
@GetMapping("/{uname}/friends")
- protected String doGetFriends(@PathVariable String uname, ModelMap model) throws IOException {
+ protected String doGetFriends(@PathVariable String uname, ModelMap model) {
com.juick.User user = userService.getUserByName(uname);
com.juick.User visitor = UserUtils.getCurrentUser();
if (visitor.isBanned()) {
@@ -444,13 +444,12 @@ public class MessagesWWW {
model.addAttribute("isSubscribed", tagService.isSubscribed(visitor, paramTag));
model.addAttribute("isInBL", tagService.isInBL(visitor, paramTag));
if (mids.size() >= 20) {
- String nextpage = "/tag/" + URLEncoder.encode(paramTag.getName(), CharEncoding.UTF_8) + "?before=" + mids.get(mids.size() - 1);
+ String nextpage = "/tag/" + URLEncoder.encode(paramTag.getName(), StandardCharsets.UTF_8) + "?before=" + mids.get(mids.size() - 1);
model.addAttribute("nextpage", nextpage);
}
- UriComponents builder = ServletUriComponentsBuilder.fromCurrentRequestUri().build();
- String queryString = builder.getQuery();
- String requestURI = builder.toUri().getPath();
- if (sape.isPresent() && visitor.isAnonymous() && queryString == null) {
+ UriComponents builder = ServletUriComponentsBuilder.fromCurrentRequest().build();
+ URI requestURI = builder.toUri();
+ if (sape.isPresent() && visitor.isAnonymous()) {
String links = sape.get().getPageLinks(requestURI, sapeCookie).render();
model.addAttribute("links", links);
}
@@ -591,10 +590,9 @@ public class MessagesWWW {
}
model.addAttribute("replies", replies);
model.addAttribute("showAdv", visitor.isAnonymous());
- UriComponents builder = ServletUriComponentsBuilder.fromCurrentRequestUri().build();
- String queryString = builder.getQuery();
- String requestURI = builder.toUri().getPath();
- if (sape.isPresent() && visitor.isAnonymous() && queryString == null) {
+ UriComponents builder = ServletUriComponentsBuilder.fromCurrentRequest().build();
+ URI requestURI = builder.toUri();
+ if (sape.isPresent() && visitor.isAnonymous()) {
String links = sape.get().getPageLinks(requestURI, sapeCookie).render();
model.addAttribute("links", links);
}
diff --git a/src/main/java/com/juick/server/www/controllers/Settings.java b/src/main/java/com/juick/server/www/controllers/Settings.java
index 57984aef..d5a21d09 100644
--- a/src/main/java/com/juick/server/www/controllers/Settings.java
+++ b/src/main/java/com/juick/server/www/controllers/Settings.java
@@ -18,7 +18,6 @@ package com.juick.server.www.controllers;
import com.juick.User;
import com.juick.model.NotifyOpts;
-import com.juick.model.UserInfo;
import com.juick.server.util.HttpBadRequestException;
import com.juick.server.util.HttpUtils;
import com.juick.server.util.UserUtils;
@@ -60,8 +59,6 @@ import java.util.stream.IntStream;
public class Settings {
private static final Logger logger = LoggerFactory.getLogger(Settings.class);
- @Value("${img_path:#{systemEnvironment['TEMP'] ?: '/tmp'}}")
- private String imgDir;
@Value("${upload_tmp_dir:#{systemEnvironment['TEMP'] ?: '/tmp'}}")
private String tmpDir;
@Inject
@@ -158,16 +155,15 @@ public class Settings {
}
break;
case "about":
- UserInfo info = new UserInfo();
- info.setFullName(request.getParameter("fullname"));
- info.setCountry(request.getParameter("country"));
- info.setUrl(request.getParameter("url"));
- info.setDescription(request.getParameter("descr"));
+ visitor.setFullName(request.getParameter("fullname"));
+ visitor.setCountry(request.getParameter("country"));
+ visitor.setUrl(request.getParameter("url"));
+ visitor.setDescription(request.getParameter("descr"));
String avatarTmpPath = HttpUtils.receiveMultiPartFile(avatar, tmpDir).getHost();
if (StringUtils.isNotEmpty(avatarTmpPath)) {
imagesService.saveAvatar(avatarTmpPath, visitor.getUid());
}
- if (userService.updateUserInfo(visitor, info)) {
+ if (userService.updateUserInfo(visitor)) {
result = String.format("<p>Your info is updated.</p><p><a href='/%s/'>Back to blog</a>.</p>", visitor.getName());
}
break;
diff --git a/src/main/java/com/juick/server/www/controllers/SocialLogin.java b/src/main/java/com/juick/server/www/controllers/SocialLogin.java
index bc631a1a..59b1ec0b 100644
--- a/src/main/java/com/juick/server/www/controllers/SocialLogin.java
+++ b/src/main/java/com/juick/server/www/controllers/SocialLogin.java
@@ -79,6 +79,7 @@ public class SocialLogin {
@Inject
private ObjectMapper jsonMapper;
private ServiceBuilder facebookBuilder, twitterBuilder, vkBuilder;
+ private OAuth20Service facebookAuthService, vkAuthService;
@Value("${twitter_consumer_key:appid}")
private String twitterConsumerKey;
@@ -107,6 +108,16 @@ public class SocialLogin {
vkBuilder = new ServiceBuilder(VK_APPID);
UriComponentsBuilder facebookRedirectBuilder = UriComponentsBuilder.fromUriString(baseUri);
facebookRedirectUri = facebookRedirectBuilder.replacePath("/_fblogin").build().toUriString();
+ facebookAuthService = facebookBuilder
+ .apiSecret(FACEBOOK_SECRET)
+ .callback(facebookRedirectUri)
+ .scope("email")
+ .build(FacebookApi.instance());
+ vkAuthService = vkBuilder
+ .apiSecret(VK_SECRET)
+ .scope("friends,wall,offline")
+ .callback(VK_REDIRECT)
+ .build(VkontakteApi.instance());
}
@GetMapping("/_fblogin")
@@ -120,13 +131,7 @@ public class SocialLogin {
state = Utils.getPreviousPageByRequest(request).orElse("https://juick.com/");
}
crosspostService.addFacebookState(fbstate, state);
- OAuth20Service facebookAuthService = facebookBuilder
- .apiSecret(FACEBOOK_SECRET)
- .callback(facebookRedirectUri)
- .scope("email")
- .state(fbstate)
- .build(FacebookApi.instance());
- return "redirect:" + facebookAuthService.getAuthorizationUrl();
+ return "redirect:" + facebookAuthService.getAuthorizationUrl(fbstate);
}
String redirectUrl = crosspostService.verifyFacebookState(state);
@@ -134,31 +139,24 @@ public class SocialLogin {
logger.error("state is missing");
throw new HttpBadRequestException();
}
- OAuth20Service facebookService = facebookBuilder
- .apiKey(FACEBOOK_APPID)
- .apiSecret(FACEBOOK_SECRET)
- .callback(facebookRedirectUri)
- .scope("email")
- .state(state)
- .build(FacebookApi.instance());
- OAuth2AccessToken token = facebookService.getAccessToken(code);
- final OAuthRequest meRequest = new OAuthRequest(Verb.GET, "https://graph.facebook.com/v2.10/me?fields=id,name,link,verified,email");
- facebookService.signRequest(token, meRequest);
- String graph = facebookService.execute(meRequest).getBody();
+ OAuth2AccessToken token = facebookAuthService.getAccessToken(code);
+ final OAuthRequest meRequest = new OAuthRequest(Verb.GET, "https://graph.facebook.com/v3.2/me?fields=id,name,link,verified,email");
+ facebookAuthService.signRequest(token, meRequest);
+ String graph = facebookAuthService.execute(meRequest).getBody();
if (StringUtils.isBlank(graph)) {
logger.error("FACEBOOK GRAPH ERROR");
throw new HttpBadRequestException();
}
User fb = jsonMapper.readValue(graph, User.class);
long fbID = NumberUtils.toLong(fb.getId(), 0);
- if (fbID == 0 || StringUtils.isBlank(fb.getName()) || StringUtils.isBlank(fb.getLink())) {
- logger.error("Missing required fields, id: {}, name: {}, link: {}", fbID, fb.getName(), fb.getLink());
+ if (fbID == 0 || StringUtils.isBlank(fb.getName())) {
+ logger.error("Missing required fields, id: {}, name: {}", fbID, fb.getName());
throw new HttpBadRequestException();
}
int uid = crosspostService.getUIDbyFBID(fbID);
if (uid > 0) {
- if (!crosspostService.updateFacebookUser(fbID, token.getAccessToken(), fb.getName(), fb.getLink())) {
+ if (!crosspostService.updateFacebookUser(fbID, token.getAccessToken(), fb.getName())) {
logger.error("error updating facebook user, id: {}, token: {}", fbID, token.getAccessToken());
throw new HttpBadRequestException();
}
@@ -166,22 +164,19 @@ public class SocialLogin {
c.setMaxAge(50 * 24 * 60 * 60);
response.addCookie(c);
return "redirect:" + redirectUrl;
- } else if (fb.getVerified()) {
- if (!crosspostService.createFacebookUser(fbID, state, token.getAccessToken(), fb.getName(), fb.getLink())) {
+ } else {
+ if (!crosspostService.createFacebookUser(fbID, state, token.getAccessToken(), fb.getName())) {
if (StringUtils.isNotEmpty(fb.getEmail())) {
- logger.info("found {} for facebook user {}", fb.getEmail(), fb.getLink());
+ logger.info("found {} for facebook user {}", fb.getEmail());
Integer userId = crosspostService.getUIDbyFBID(fbID);
if (!emailService.getEmails(userId, false).contains(fb.getEmail())) {
emailService.addEmail(userId, fb.getEmail());
}
}
- logger.info("email not found for facebook user {}", fb.getLink());
+ logger.info("email not found for facebook user {}", fb.getName());
throw new HttpBadRequestException();
}
return "redirect:/signup?type=fb&hash=" + state;
- } else {
- logger.error("Facebook account is not verified, id: {}", fbID);
- throw new HttpBadRequestException();
}
}
@GetMapping("/_twitter")
@@ -243,13 +238,7 @@ public class SocialLogin {
vkstate = UUID.randomUUID().toString();
Cookie c = new Cookie("vkstate", vkstate);
response.addCookie(c);
- OAuth20Service vkAuthService = vkBuilder
- .apiSecret(VK_SECRET)
- .scope("friends,wall,offline")
- .state(vkstate)
- .callback(VK_REDIRECT)
- .build(VkontakteApi.instance());
- return "redirect:" + vkAuthService.getAuthorizationUrl();
+ return "redirect:" + vkAuthService.getAuthorizationUrl(vkstate);
}
if (StringUtils.isBlank(vkstate) || !vkstate.equals(state)) {
@@ -259,16 +248,11 @@ public class SocialLogin {
c.setMaxAge(0);
response.addCookie(c);
}
-
- OAuth20Service vkService = vkBuilder
- .apiKey(VK_APPID)
- .apiSecret(VK_SECRET)
- .build(VkontakteApi.instance());
- OAuth2AccessToken token = vkService.getAccessToken(code);
+ OAuth2AccessToken token = vkAuthService.getAccessToken(code);
OAuthRequest meRequest = new OAuthRequest(Verb.GET, "https://api.vk.com/method/users.get?fields=screen_name&v=5.73");
- vkService.signRequest(token, meRequest);
- String graph = vkService.execute(meRequest).getBody();
+ vkAuthService.signRequest(token, meRequest);
+ String graph = vkAuthService.execute(meRequest).getBody();
com.juick.model.vk.User jsonUser = jsonMapper.readValue(graph, UsersResponse.class).getUsers().get(0);
String vkName = jsonUser.getFirstName() + " " + jsonUser.getLastName();
diff --git a/src/main/java/com/juick/server/xmpp/XMPPStatusPage.java b/src/main/java/com/juick/server/xmpp/XMPPStatusPage.java
deleted file mode 100644
index 231696ec..00000000
--- a/src/main/java/com/juick/server/xmpp/XMPPStatusPage.java
+++ /dev/null
@@ -1,32 +0,0 @@
-package com.juick.server.xmpp;
-
-import com.juick.server.XMPPServer;
-import com.juick.server.xmpp.helpers.XMPPStatus;
-import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
-import org.springframework.http.MediaType;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RequestMethod;
-import org.springframework.web.bind.annotation.RestController;
-import rocks.xmpp.addr.Jid;
-import springfox.documentation.annotations.ApiIgnore;
-
-import javax.inject.Inject;
-import java.util.stream.Collectors;
-
-@RestController
-@ConditionalOnProperty("xmppbot_jid")
-public class XMPPStatusPage {
- @Inject
- private XMPPServer xmpp;
- @ApiIgnore
- @RequestMapping(method = RequestMethod.GET, value = "/api/xmpp-status", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
- public XMPPStatus xmppStatus() {
- XMPPStatus status = new XMPPStatus();
- if (xmpp != null) {
- status.setInbound(xmpp.getInConnections().stream().map(c -> c.from).flatMap(j -> j.stream().map(Jid::getDomain)).collect(Collectors.toList()));
- status.setOutbound(xmpp.getOutConnections().keySet().stream()
- .map(c -> c.to).map(Jid::getDomain).collect(Collectors.toList()));
- }
- return status;
- }
-}
diff --git a/src/main/java/com/juick/server/xmpp/helpers/XMPPStatus.java b/src/main/java/com/juick/server/xmpp/helpers/XMPPStatus.java
deleted file mode 100644
index 99d89866..00000000
--- a/src/main/java/com/juick/server/xmpp/helpers/XMPPStatus.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright (C) 2008-2017, Juick
- *
- * 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.helpers;
-
-import com.juick.server.xmpp.s2s.ConnectionIn;
-import com.juick.server.xmpp.s2s.ConnectionOut;
-
-import java.util.List;
-import java.util.Set;
-
-/**
- * Created by vitalyster on 16.02.2017.
- */
-public class XMPPStatus {
- private List<String> inbound;
- private List<String> outbound;
-
- public List<String> getInbound() {
- return inbound;
- }
-
- public void setInbound(List<String> inbound) {
- this.inbound = inbound;
- }
-
- public List<String> getOutbound() {
- return outbound;
- }
-
- public void setOutbound(List<String> outbound) {
- this.outbound = outbound;
- }
-}
diff --git a/src/main/java/com/juick/server/xmpp/router/Handshake.java b/src/main/java/com/juick/server/xmpp/router/Handshake.java
deleted file mode 100644
index 0bc501dd..00000000
--- a/src/main/java/com/juick/server/xmpp/router/Handshake.java
+++ /dev/null
@@ -1,39 +0,0 @@
-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
deleted file mode 100644
index 2154edf6..00000000
--- a/src/main/java/com/juick/server/xmpp/router/Stream.java
+++ /dev/null
@@ -1,202 +0,0 @@
-/*
- * 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
deleted file mode 100644
index a58adfc5..00000000
--- a/src/main/java/com/juick/server/xmpp/router/StreamComponentServer.java
+++ /dev/null
@@ -1,57 +0,0 @@
-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
deleted file mode 100644
index f731f039..00000000
--- a/src/main/java/com/juick/server/xmpp/router/StreamError.java
+++ /dev/null
@@ -1,57 +0,0 @@
-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
deleted file mode 100644
index e8fc324f..00000000
--- a/src/main/java/com/juick/server/xmpp/router/StreamFeatures.java
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * 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
deleted file mode 100644
index 048c61ec..00000000
--- a/src/main/java/com/juick/server/xmpp/router/StreamHandler.java
+++ /dev/null
@@ -1,13 +0,0 @@
-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
deleted file mode 100644
index 1b9b1965..00000000
--- a/src/main/java/com/juick/server/xmpp/router/StreamNamespaces.java
+++ /dev/null
@@ -1,10 +0,0 @@
-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
deleted file mode 100644
index 0cf9a3bc..00000000
--- a/src/main/java/com/juick/server/xmpp/router/XMPPError.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * 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
deleted file mode 100644
index 6d67fa9c..00000000
--- a/src/main/java/com/juick/server/xmpp/router/XMPPRouter.java
+++ /dev/null
@@ -1,220 +0,0 @@
-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
deleted file mode 100644
index 7579489f..00000000
--- a/src/main/java/com/juick/server/xmpp/router/XmlUtils.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * 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();
- }
-}
diff --git a/src/main/java/com/juick/server/xmpp/s2s/BasicXmppSession.java b/src/main/java/com/juick/server/xmpp/s2s/BasicXmppSession.java
deleted file mode 100644
index ae28f827..00000000
--- a/src/main/java/com/juick/server/xmpp/s2s/BasicXmppSession.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright (C) 2008-2017, Juick
- *
- * 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.s2s;
-
-import rocks.xmpp.addr.Jid;
-import rocks.xmpp.core.XmppException;
-import rocks.xmpp.core.session.XmppSession;
-import rocks.xmpp.core.session.XmppSessionConfiguration;
-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.server.ServerIQ;
-import rocks.xmpp.core.stanza.model.server.ServerMessage;
-import rocks.xmpp.core.stanza.model.server.ServerPresence;
-import rocks.xmpp.core.stream.model.StreamElement;
-
-/**
- * Created by vitalyster on 06.02.2017.
- */
-public class BasicXmppSession extends XmppSession {
- protected BasicXmppSession(String xmppServiceDomain, XmppSessionConfiguration configuration) {
- super(xmppServiceDomain, configuration);
- }
-
- public static BasicXmppSession create(String xmppServiceDomain, XmppSessionConfiguration configuration) {
- BasicXmppSession session = new BasicXmppSession(xmppServiceDomain, configuration);
- notifyCreationListeners(session);
- return session;
- }
-
- @Override
- public void connect(Jid from) throws XmppException {
-
- }
-
- @Override
- public Jid getConnectedResource() {
- return null;
- }
-
- @Override
- protected StreamElement prepareElement(StreamElement element) {
- if (element instanceof Message) {
- element = ServerMessage.from((Message) element);
- } else if (element instanceof Presence) {
- element = ServerPresence.from((Presence) element);
- } else if (element instanceof IQ) {
- element = ServerIQ.from((IQ) element);
- }
-
- return element;
- }
-}
diff --git a/src/main/java/com/juick/server/xmpp/s2s/CacheEntry.java b/src/main/java/com/juick/server/xmpp/s2s/CacheEntry.java
deleted file mode 100644
index 33e875bd..00000000
--- a/src/main/java/com/juick/server/xmpp/s2s/CacheEntry.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2008-2017, Juick
- *
- * 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.s2s;
-
-import rocks.xmpp.addr.Jid;
-
-import java.time.Instant;
-
-/**
- *
- * @author ugnich
- */
-public class CacheEntry {
-
- public Jid hostname;
- public Instant created;
- public Instant updated;
- public String xml;
-
- public CacheEntry(Jid hostname, String xml) {
- this.hostname = hostname;
- this.created = this.updated =Instant.now();
- this.xml = xml;
- }
-}
diff --git a/src/main/java/com/juick/server/xmpp/s2s/Connection.java b/src/main/java/com/juick/server/xmpp/s2s/Connection.java
deleted file mode 100644
index 4fa8e741..00000000
--- a/src/main/java/com/juick/server/xmpp/s2s/Connection.java
+++ /dev/null
@@ -1,158 +0,0 @@
-/*
- * Copyright (C) 2008-2017, Juick
- *
- * 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.s2s;
-
-import com.fasterxml.jackson.annotation.JsonIgnore;
-import com.juick.server.XMPPServer;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-import org.xmlpull.v1.XmlPullParserFactory;
-
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.io.OutputStreamWriter;
-import java.net.Socket;
-import java.nio.charset.StandardCharsets;
-import java.time.Instant;
-import java.util.UUID;
-
-/**
- *
- * @author ugnich
- */
-public class Connection {
-
- protected static final Logger logger = LoggerFactory.getLogger(Connection.class);
-
- public String streamID;
- public Instant created;
- public Instant updated;
- public long bytesLocal = 0;
- public long packetsLocal = 0;
- XMPPServer xmpp;
- private Socket socket;
- public static final String NS_DB = "jabber:server:dialback";
- public static final String NS_TLS = "urn:ietf:params:xml:ns:xmpp-tls";
- public static final String NS_SASL = "urn:ietf:params:xml:ns:xmpp-sasl";
- public static final String NS_STREAM = "http://etherx.jabber.org/streams";
- XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
- XmlPullParser parser = factory.newPullParser();
- OutputStreamWriter writer;
- private boolean secured = false;
- private boolean authenticated = false;
- private boolean trusted = false;
-
-
-
- public Connection(XMPPServer xmpp) throws XmlPullParserException {
- this.xmpp = xmpp;
- created = updated = Instant.now();
- }
-
- public void logParser() {
- if (streamID == null) {
- return;
- }
- String tag = "IN: <" + parser.getName();
- for (int i = 0; i < parser.getAttributeCount(); i++) {
- tag += " " + parser.getAttributeName(i) + "=\"" + parser.getAttributeValue(i) + "\"";
- }
- tag += ">...</" + parser.getName() + ">\n";
- logger.trace(tag);
- }
-
- public void sendStanza(String xml) {
- if (streamID != null) {
- logger.trace("OUT: {}\n", xml);
- }
- try {
- writer.write(xml);
- writer.flush();
- } catch (IOException e) {
- logger.error("send stanza failed", e);
- }
-
- updated = Instant.now();
- bytesLocal += xml.length();
- packetsLocal++;
- }
-
- public void closeConnection() {
- if (streamID != null) {
- logger.debug("closing stream {}", streamID);
- }
-
- try {
- writer.write("</stream:stream>");
- } catch (Exception e) {
- }
-
- try {
- writer.close();
- } catch (Exception e) {
- }
-
- try {
- socket.close();
- } catch (Exception e) {
- }
- }
-
- public boolean isSecured() {
- return secured;
- }
-
- public void setSecured(boolean secured) {
- this.secured = secured;
- }
-
- public void restartParser() throws XmlPullParserException, IOException {
- streamID = UUID.randomUUID().toString();
- parser = factory.newPullParser();
- parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
- parser.setInput(new InputStreamReader(socket.getInputStream()));
- writer = new OutputStreamWriter(socket.getOutputStream(), StandardCharsets.UTF_8);
- }
-
- @JsonIgnore
- public Socket getSocket() {
- return socket;
- }
-
- public void setSocket(Socket socket) {
- this.socket = socket;
- }
-
- public boolean isAuthenticated() {
- return authenticated;
- }
-
- public void setAuthenticated(boolean authenticated) {
- this.authenticated = authenticated;
- }
-
- public boolean isTrusted() {
- return trusted;
- }
-
- public void setTrusted(boolean trusted) {
- this.trusted = trusted;
- }
-}
diff --git a/src/main/java/com/juick/server/xmpp/s2s/ConnectionIn.java b/src/main/java/com/juick/server/xmpp/s2s/ConnectionIn.java
deleted file mode 100644
index 72c3ba8d..00000000
--- a/src/main/java/com/juick/server/xmpp/s2s/ConnectionIn.java
+++ /dev/null
@@ -1,231 +0,0 @@
-/*
- * Copyright (C) 2008-2017, Juick
- *
- * 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.s2s;
-
-import com.juick.server.XMPPServer;
-import com.juick.server.xmpp.router.StreamError;
-import com.juick.server.xmpp.router.XmlUtils;
-import org.apache.commons.lang3.StringUtils;
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-import rocks.xmpp.addr.Jid;
-
-import java.io.EOFException;
-import java.io.IOException;
-import java.net.Socket;
-import java.net.SocketException;
-import java.time.Instant;
-import java.util.Arrays;
-import java.util.List;
-import java.util.UUID;
-import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.stream.Collectors;
-
-/**
- * @author ugnich
- */
-public class ConnectionIn extends Connection implements Runnable {
-
- final public List<Jid> from = new CopyOnWriteArrayList<>();
- public Instant received;
- public long packetsRemote = 0;
- ConnectionListener listener;
-
- public ConnectionIn(XMPPServer xmpp, Socket socket) throws XmlPullParserException, IOException {
- super(xmpp);
- this.setSocket(socket);
- restartParser();
- }
-
- @Override
- public void run() {
- try {
- parser.next(); // stream:stream
- updateTsRemoteData();
- if (!parser.getName().equals("stream")
- || !parser.getNamespace("stream").equals(NS_STREAM)) {
-// || !parser.getAttributeValue(null, "version").equals("1.0")
-// || !parser.getAttributeValue(null, "to").equals(Main.HOSTNAME)) {
- throw new Exception(String.format("stream from %s invalid", getSocket().getRemoteSocketAddress()));
- }
- streamID = parser.getAttributeValue(null, "id");
- if (streamID == null) {
- streamID = UUID.randomUUID().toString();
- }
- boolean xmppversionnew = parser.getAttributeValue(null, "version") != null;
- String from = parser.getAttributeValue(null, "from");
-
- if (Arrays.asList(xmpp.bannedHosts).contains(from)) {
- closeConnection();
- return;
- }
- sendOpenStream(from, xmppversionnew);
-
- while (parser.next() != XmlPullParser.END_DOCUMENT) {
- updateTsRemoteData();
- if (parser.getEventType() != XmlPullParser.START_TAG) {
- continue;
- }
- logParser();
-
- packetsRemote++;
-
- String tag = parser.getName();
- if (tag.equals("result") && parser.getNamespace().equals(NS_DB)) {
- String dfrom = parser.getAttributeValue(null, "from");
- String to = parser.getAttributeValue(null, "to");
- logger.debug("stream from {} to {} {} asking for dialback", dfrom, to, streamID);
- if (dfrom.endsWith(xmpp.getJid().toEscapedString()) && (dfrom.equals(xmpp.getJid().toEscapedString())
- || dfrom.endsWith("." + xmpp.getJid()))) {
- logger.warn("stream from {} is invalid", dfrom);
- break;
- }
- if (to != null && to.equals(xmpp.getJid().toEscapedString())) {
- String dbKey = XmlUtils.getTagText(parser);
- updateTsRemoteData();
- xmpp.startDialback(Jid.of(dfrom), streamID, dbKey);
- } else {
- logger.warn("stream from " + dfrom + " " + streamID + " invalid to " + to);
- break;
- }
- } else if (tag.equals("verify") && parser.getNamespace().equals(NS_DB)) {
- String vfrom = parser.getAttributeValue(null, "from");
- String vto = parser.getAttributeValue(null, "to");
- String vid = parser.getAttributeValue(null, "id");
- String vkey = XmlUtils.getTagText(parser);
- updateTsRemoteData();
- final boolean[] valid = {false};
- if (vfrom != null && vto != null && vid != null && vkey != null) {
- xmpp.getConnectionOut(Jid.of(vfrom), false).ifPresent(c -> {
- String dialbackKey = c.dbKey;
- valid[0] = vkey.equals(dialbackKey);
- });
- }
- if (valid[0]) {
- sendStanza("<db:verify from='" + vto + "' to='" + vfrom + "' id='" + vid + "' type='valid'/>");
- logger.debug("stream from {} {} dialback verify valid", vfrom, streamID);
- setAuthenticated(true);
- } else {
- sendStanza("<db:verify from='" + vto + "' to='" + vfrom + "' id='" + vid + "' type='invalid'/>");
- logger.warn("stream from {} {} dialback verify invalid", vfrom, streamID);
- }
- } else if (tag.equals("presence") && checkFromTo(parser) && isAuthenticated()) {
- String xml = XmlUtils.parseToString(parser, false);
- logger.debug("stream {} presence: {}", streamID, xml);
- xmpp.onStanzaReceived(xml);
- } else if (tag.equals("message") && checkFromTo(parser)) {
- updateTsRemoteData();
- String xml = XmlUtils.parseToString(parser, false);
- logger.debug("stream {} message: {}", streamID, xml);
- xmpp.onStanzaReceived(xml);
-
- } else if (tag.equals("iq") && checkFromTo(parser) && isAuthenticated()) {
- updateTsRemoteData();
- String type = parser.getAttributeValue(null, "type");
- String xml = XmlUtils.parseToString(parser, false);
- if (type == null || !type.equals("error")) {
- logger.debug("stream {} iq: {}", streamID, xml);
- xmpp.onStanzaReceived(xml);
- }
- } else if (!isSecured() && tag.equals("starttls") && !isAuthenticated()) {
- listener.starttls(this);
- } else if (isSecured() && tag.equals("stream") && parser.getNamespace().equals(NS_STREAM)) {
- sendOpenStream(null, true);
- } else if (isSecured() && tag.equals("auth") && parser.getNamespace().equals(NS_SASL)
- && parser.getAttributeValue(null, "mechanism").equals("EXTERNAL")
- && !isAuthenticated() && isTrusted()) {
- sendStanza("<success xmlns='urn:ietf:params:xml:ns:xmpp-sasl'/>");
- logger.info("stream {} authenticated externally", streamID);
- this.from.add(Jid.of(from));
- setAuthenticated(true);
- restartParser();
- } else if (tag.equals("error")) {
- StreamError streamError = StreamError.parse(parser);
- logger.debug("Stream error {} from {}: {}", streamError.getCondition(), streamID, streamError.getText());
- xmpp.removeConnectionIn(this);
- closeConnection();
- } else {
- String unhandledStanza = XmlUtils.parseToString(parser, true);
- logger.warn("Unhandled stanza from {}: {}", streamID, unhandledStanza);
- }
- }
- logger.warn("stream {} finished", streamID);
- xmpp.removeConnectionIn(this);
- closeConnection();
- } catch (EOFException | SocketException ex) {
- logger.debug("stream {} closed (dirty)", streamID);
- xmpp.removeConnectionIn(this);
- closeConnection();
- } catch (Exception e) {
- logger.debug("stream {} error {}", streamID, e);
- xmpp.removeConnectionIn(this);
- closeConnection();
- }
- }
-
- void updateTsRemoteData() {
- received = Instant.now();
- }
-
- void sendOpenStream(String from, boolean xmppversionnew) throws IOException {
- String openStream = "<?xml version='1.0'?><stream:stream xmlns='jabber:server' " +
- "xmlns:stream='http://etherx.jabber.org/streams' xmlns:db='jabber:server:dialback' from='" +
- xmpp.getJid().toEscapedString() + "' id='" + streamID + "' version='1.0'>";
- if (xmppversionnew) {
- openStream += "<stream:features>";
- if (listener != null && listener.isTlsAvailable() && !Arrays.asList(xmpp.brokenSSLhosts).contains(from)) {
- if (!isSecured()) {
- openStream += "<starttls xmlns='" + NS_TLS + "'><optional/></starttls>";
- } else if (!isAuthenticated() && isTrusted()) {
- openStream += "<mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>" +
- "<mechanism>EXTERNAL</mechanism>" +
- "</mechanisms>";
- }
- }
- openStream += "</stream:features>";
- }
- sendStanza(openStream);
- }
-
- public void sendDialbackResult(Jid sfrom, String type) {
- sendStanza("<db:result from='" + xmpp.getJid().toEscapedString() + "' to='" + sfrom + "' type='" + type + "'/>");
- if (type.equals("valid")) {
- from.add(sfrom);
- logger.debug("stream from {} {} ready", sfrom, streamID);
- setAuthenticated(true);
- }
- }
-
- boolean checkFromTo(XmlPullParser parser) throws Exception {
- String cfrom = parser.getAttributeValue(null, "from");
- String cto = parser.getAttributeValue(null, "to");
- if (StringUtils.isNotEmpty(cfrom) && StringUtils.isNotEmpty(cto)) {
- Jid jidfrom = Jid.of(cfrom);
- for (Jid aFrom : from) {
- if (aFrom.equals(Jid.of(jidfrom.getDomain()))) {
- return true;
- }
- }
- }
- logger.warn("rejected from {}, to {}, stream {}", cfrom, cto, from.stream().collect(Collectors.joining(",")));
- return false;
- }
- public void setListener(ConnectionListener listener) {
- this.listener = listener;
- }
-}
diff --git a/src/main/java/com/juick/server/xmpp/s2s/ConnectionListener.java b/src/main/java/com/juick/server/xmpp/s2s/ConnectionListener.java
deleted file mode 100644
index 4c32b9ae..00000000
--- a/src/main/java/com/juick/server/xmpp/s2s/ConnectionListener.java
+++ /dev/null
@@ -1,16 +0,0 @@
-package com.juick.server.xmpp.s2s;
-
-
-import com.juick.server.xmpp.router.StreamError;
-
-public interface ConnectionListener {
- boolean isTlsAvailable();
- void starttls(ConnectionIn connection);
- void proceed(ConnectionOut connection);
- void verify(ConnectionOut connection, String from, String type, String sid);
- void dialbackError(ConnectionOut connection, StreamError error);
- void finished(ConnectionOut connection, boolean dirty);
- void exception(ConnectionOut connection, Exception ex);
- void ready(ConnectionOut connection);
- boolean securing(ConnectionOut connection);
-}
diff --git a/src/main/java/com/juick/server/xmpp/s2s/ConnectionOut.java b/src/main/java/com/juick/server/xmpp/s2s/ConnectionOut.java
deleted file mode 100644
index be485ab1..00000000
--- a/src/main/java/com/juick/server/xmpp/s2s/ConnectionOut.java
+++ /dev/null
@@ -1,189 +0,0 @@
-/*
- * Copyright (C) 2008-2017, Juick
- *
- * 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.s2s;
-
-import com.juick.server.xmpp.router.Stream;
-import com.juick.server.xmpp.router.StreamError;
-import com.juick.server.xmpp.router.StreamFeatures;
-import com.juick.server.xmpp.router.XmlUtils;
-import com.juick.server.xmpp.s2s.util.DialbackUtils;
-import org.apache.commons.codec.Charsets;
-import org.apache.commons.codec.binary.Base64;
-import org.apache.commons.text.RandomStringGenerator;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.xmlpull.v1.XmlPullParser;
-import rocks.xmpp.addr.Jid;
-
-import java.io.EOFException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.net.SocketException;
-import java.util.UUID;
-
-import static com.juick.server.xmpp.router.StreamNamespaces.NS_STREAM;
-import static com.juick.server.xmpp.s2s.Connection.NS_SASL;
-
-/**
- * @author ugnich
- */
-public class ConnectionOut extends Stream {
- protected static final Logger logger = LoggerFactory.getLogger(ConnectionOut.class);
- public static final String NS_TLS = "urn:ietf:params:xml:ns:xmpp-tls";
- public static final String NS_DB = "jabber:server:dialback";
- private boolean secured = false;
- private boolean trusted = false;
- public boolean streamReady = false;
- String checkSID = null;
- String dbKey = null;
- private String streamID;
- ConnectionListener listener;
- RandomStringGenerator generator = new RandomStringGenerator.Builder().withinRange('a', 'z').build();
-
- public ConnectionOut(Jid from, Jid to, InputStream is, OutputStream os, String checkSID, String dbKey) throws Exception {
- super(from, to, is, os);
- this.to = to;
- this.checkSID = checkSID;
- this.dbKey = dbKey;
- if (dbKey == null) {
- this.dbKey = DialbackUtils.generateDialbackKey(generator.generate(15), to, from, streamID);
- }
- streamID = UUID.randomUUID().toString();
- }
-
- public void sendOpenStream() throws IOException {
- send("<?xml version='1.0'?><stream:stream xmlns='jabber:server' id='" + streamID +
- "' xmlns:stream='http://etherx.jabber.org/streams' xmlns:db='jabber:server:dialback' from='" +
- from.toEscapedString() + "' to='" + to.toEscapedString() + "' version='1.0'>");
- }
-
- void processDialback() throws Exception {
- if (checkSID != null) {
- sendDialbackVerify(checkSID, dbKey);
- }
- send("<db:result from='" + from.toEscapedString() + "' to='" + to.toEscapedString() + "'>" +
- dbKey + "</db:result>");
- }
-
- @Override
- public void handshake() {
- try {
- restartStream();
-
- sendOpenStream();
-
- parser.next(); // stream:stream
- streamID = parser.getAttributeValue(null, "id");
- if (streamID == null || streamID.isEmpty()) {
- throw new Exception("stream to " + to + " invalid first packet");
- }
-
- logger.debug("stream to {} {} open", to, streamID);
- boolean xmppversionnew = parser.getAttributeValue(null, "version") != null;
- if (!xmppversionnew) {
- processDialback();
- }
-
- while (parser.next() != XmlPullParser.END_DOCUMENT) {
- if (parser.getEventType() != XmlPullParser.START_TAG) {
- continue;
- }
-
- String tag = parser.getName();
- if (tag.equals("result") && parser.getNamespace().equals(NS_DB)) {
- String type = parser.getAttributeValue(null, "type");
- if (type != null && type.equals("valid")) {
- streamReady = true;
- listener.ready(this);
- } else {
- logger.warn("stream to {} {} dialback fail", to, streamID);
- }
- XmlUtils.skip(parser);
- } else if (tag.equals("verify") && parser.getNamespace().equals(NS_DB)) {
- String from = parser.getAttributeValue(null, "from");
- String type = parser.getAttributeValue(null, "type");
- String sid = parser.getAttributeValue(null, "id");
- listener.verify(this, from, type, sid);
- XmlUtils.skip(parser);
- } else if (tag.equals("features") && parser.getNamespace().equals(NS_STREAM)) {
- StreamFeatures features = StreamFeatures.parse(parser);
- if (listener != null && !secured && features.STARTTLS >= 0
- && listener.securing(this)) {
- logger.debug("stream to {} {} securing", to.toEscapedString(), streamID);
- send("<starttls xmlns=\"" + NS_TLS + "\" />");
- } else if (secured && features.EXTERNAL >=0) {
- String authid = Base64.encodeBase64String(from.toEscapedString().getBytes(Charsets.UTF_8));
- send(String.format("<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='EXTERNAL'>%s</auth>", authid));
- } else if (secured && streamReady) {
- listener.ready(this);
- } else {
- processDialback();
- }
- } else if (tag.equals("proceed") && parser.getNamespace().equals(NS_TLS)) {
- listener.proceed(this);
- } else if (tag.equals("success") && parser.getNamespace().equals(NS_SASL)) {
- streamReady = true;
- restartStream();
- sendOpenStream();
- } else if (secured && tag.equals("stream") && parser.getNamespace().equals(NS_STREAM)) {
- streamID = parser.getAttributeValue(null, "id");
- } else if (tag.equals("error")) {
- StreamError streamError = StreamError.parse(parser);
- listener.dialbackError(this, streamError);
- } else {
- String unhandledStanza = XmlUtils.parseToString(parser, false);
- logger.warn("Unhandled stanza from {} {} : {}", to, streamID, unhandledStanza);
- }
- }
- listener.finished(this, false);
- } catch (EOFException | SocketException eofex) {
- listener.finished(this, true);
- } catch (Exception e) {
- listener.exception(this, e);
- }
- }
-
- public void sendDialbackVerify(String sid, String key) {
- send("<db:verify from='" + from.toEscapedString() + "' to='" + to + "' id='" + sid + "'>" +
- key + "</db:verify>");
- }
- public void setListener(ConnectionListener listener) {
- this.listener = listener;
- }
-
- public String getStreamID() {
- return streamID;
- }
-
- public boolean isSecured() {
- return secured;
- }
-
- public void setSecured(boolean secured) {
- this.secured = secured;
- }
-
- public boolean isTrusted() {
- return trusted;
- }
-
- public void setTrusted(boolean trusted) {
- this.trusted = trusted;
- }
-}
diff --git a/src/main/java/com/juick/server/xmpp/s2s/DNSQueries.java b/src/main/java/com/juick/server/xmpp/s2s/DNSQueries.java
deleted file mode 100644
index 1367d333..00000000
--- a/src/main/java/com/juick/server/xmpp/s2s/DNSQueries.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright (C) 2008-2017, Juick
- *
- * 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.s2s;
-
-import org.apache.commons.lang3.math.NumberUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.net.InetSocketAddress;
-import java.util.Hashtable;
-import java.util.Random;
-import javax.naming.NamingException;
-import javax.naming.directory.Attribute;
-import javax.naming.directory.DirContext;
-import javax.naming.directory.InitialDirContext;
-
-/**
- *
- * @author ugnich
- */
-public class DNSQueries {
-
- private static final Logger logger = LoggerFactory.getLogger(DNSQueries.class);
-
- private static Random rand = new Random();
-
- public static InetSocketAddress getServerAddress(String hostname) {
-
- String host = hostname;
- int port = 5269;
-
- Hashtable<String, String> env = new Hashtable<>(5);
- env.put("java.naming.factory.initial", "com.sun.jndi.dns.DnsContextFactory");
- try {
- DirContext ctx = new InitialDirContext(env);
- Attribute att = ctx.getAttributes("_xmpp-server._tcp." + hostname, new String[]{"SRV"}).get("SRV");
-
- if (att != null && att.size() > 0) {
- int i = rand.nextInt(att.size());
- String srv[] = att.get(i).toString().split(" ");
- port = NumberUtils.toInt(srv[2], 5269);
- host = srv[3];
- }
- ctx.close();
- } catch (NamingException e) {
- logger.debug("SRV record for {} is not resolved, falling back to A record", hostname);
- }
- return new InetSocketAddress(host, port);
- }
-}
diff --git a/src/main/java/com/juick/server/xmpp/s2s/StanzaListener.java b/src/main/java/com/juick/server/xmpp/s2s/StanzaListener.java
deleted file mode 100644
index 6932298f..00000000
--- a/src/main/java/com/juick/server/xmpp/s2s/StanzaListener.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 2008-2017, Juick
- *
- * 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.s2s;
-
-
-import rocks.xmpp.core.stanza.model.Stanza;
-
-/**
- * Created by vitalyster on 07.12.2016.
- */
-public interface StanzaListener {
- void stanzaReceived(Stanza xmlValue);
-}
diff --git a/src/main/java/com/juick/server/xmpp/s2s/util/DialbackUtils.java b/src/main/java/com/juick/server/xmpp/s2s/util/DialbackUtils.java
deleted file mode 100644
index d25dbad8..00000000
--- a/src/main/java/com/juick/server/xmpp/s2s/util/DialbackUtils.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2008-2017, Juick
- *
- * 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.s2s.util;
-
-import org.apache.commons.codec.digest.DigestUtils;
-import org.apache.commons.codec.digest.HmacAlgorithms;
-import org.apache.commons.codec.digest.HmacUtils;
-import rocks.xmpp.addr.Jid;
-
-/**
- * Created by vitalyster on 05.12.2016.
- */
-public class DialbackUtils {
- private DialbackUtils() {
- throw new IllegalStateException();
- }
-
- public static String generateDialbackKey(String secret, Jid to, Jid from, String id) {
- return new HmacUtils(HmacAlgorithms.HMAC_SHA_256, DigestUtils.sha256(secret))
- .hmacHex(to.toEscapedString() + " " + from.toEscapedString() + " " + id);
- }
-}
diff --git a/src/main/java/com/juick/service/CrosspostService.java b/src/main/java/com/juick/service/CrosspostService.java
index 99911250..28b9e8ab 100644
--- a/src/main/java/com/juick/service/CrosspostService.java
+++ b/src/main/java/com/juick/service/CrosspostService.java
@@ -60,9 +60,9 @@ public interface CrosspostService {
int getUIDbyFBID(long fbID);
- boolean createFacebookUser(long fbID, String loginhash, String token, String fbName, String fbLink);
+ boolean createFacebookUser(long fbID, String loginhash, String token, String fbName);
- boolean updateFacebookUser(long fbID, String token, String fbName, String fbLink);
+ boolean updateFacebookUser(long fbID, String token, String fbName);
int getUIDbyVKID(long vkID);
diff --git a/src/main/java/com/juick/service/CrosspostServiceImpl.java b/src/main/java/com/juick/service/CrosspostServiceImpl.java
index d190faba..0eeb8c78 100644
--- a/src/main/java/com/juick/service/CrosspostServiceImpl.java
+++ b/src/main/java/com/juick/service/CrosspostServiceImpl.java
@@ -180,16 +180,16 @@ public class CrosspostServiceImpl extends BaseJdbcService implements CrosspostSe
@Transactional
@Override
- public boolean createFacebookUser(long fbID, String loginhash, String token, String fbName, String fbLink) {
- return getJdbcTemplate().update("UPDATE facebook SET fb_id=?, access_token=?, fb_name=?, fb_link=? WHERE loginhash=?",
- fbID, token, fbName, fbLink, loginhash) > 0;
+ public boolean createFacebookUser(long fbID, String loginhash, String token, String fbName) {
+ return getJdbcTemplate().update("UPDATE facebook SET fb_id=?, access_token=?, fb_name=? WHERE loginhash=?",
+ fbID, token, fbName, loginhash) > 0;
}
@Transactional
@Override
- public boolean updateFacebookUser(long fbID, String token, String fbName, String fbLink) {
- return getJdbcTemplate().update("UPDATE facebook SET access_token=?,fb_name=?,fb_link=? WHERE fb_id=?",
- token, fbName, fbLink, fbID) > 0;
+ public boolean updateFacebookUser(long fbID, String token, String fbName) {
+ return getJdbcTemplate().update("UPDATE facebook SET access_token=?,fb_name=? WHERE fb_id=?",
+ token, fbName, fbID) > 0;
}
@Transactional(readOnly = true)
diff --git a/src/main/java/com/juick/service/MessagesService.java b/src/main/java/com/juick/service/MessagesService.java
index 4bcdba46..922170db 100644
--- a/src/main/java/com/juick/service/MessagesService.java
+++ b/src/main/java/com/juick/service/MessagesService.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2008-2017, Juick
+ * Copyright (C) 2008-2019, Juick
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
@@ -29,7 +29,7 @@ import java.util.*;
* Created by aalexeev on 11/13/16.
*/
public interface MessagesService {
- int createMessage(int uid, String txt, String attachment, Collection<com.juick.Tag> tags);
+ int createMessage(int uid, String txt, String attachment, List<com.juick.Tag> tags);
int createReply(int mid, int rid, User user, String txt, String attachment);
@@ -68,7 +68,7 @@ public interface MessagesService {
User getMessageAuthor(int mid);
- List<String> getMessageRecommendations(int mid);
+ List<User> getMessageRecommendations(int mid);
List<Integer> getAll(int visitorUid, int before);
diff --git a/src/main/java/com/juick/service/MessagesServiceImpl.java b/src/main/java/com/juick/service/MessagesServiceImpl.java
index 4e5dca81..7dbfe1dd 100644
--- a/src/main/java/com/juick/service/MessagesServiceImpl.java
+++ b/src/main/java/com/juick/service/MessagesServiceImpl.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2008-2017, Juick
+ * Copyright (C) 2008-2019, Juick
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
@@ -17,11 +17,14 @@
package com.juick.service;
-import com.juick.*;
+import com.juick.Message;
+import com.juick.Reaction;
+import com.juick.User;
import com.juick.model.AnonymousUser;
import com.juick.model.PrivacyOpts;
import com.juick.model.ResponseReply;
import com.juick.server.util.HttpNotFoundException;
+import com.juick.server.www.WebApp;
import com.juick.util.MessageUtils;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
@@ -29,6 +32,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.dao.IncorrectResultSizeDataAccessException;
+import org.springframework.jdbc.core.BatchPreparedStatementSetter;
import org.springframework.jdbc.core.ConnectionCallback;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
@@ -37,12 +41,24 @@ import org.springframework.jdbc.core.simple.SimpleJdbcInsert;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
+import javax.annotation.Nonnull;
import javax.inject.Inject;
import java.net.URI;
-import java.sql.*;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Timestamp;
+import java.sql.Types;
import java.time.Instant;
-import java.util.*;
+import java.util.Collections;
+import java.util.Comparator;
import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
import java.util.stream.Collectors;
/**
@@ -59,6 +75,8 @@ public class MessagesServiceImpl extends BaseJdbcService implements MessagesServ
private SearchService searchService;
@Inject
private ImagesService imagesService;
+ @Inject
+ private WebApp webApp;
@Value("${photos_url:https://i.juick.com/}")
private String baseImagesUrl;
@@ -75,7 +93,7 @@ public class MessagesServiceImpl extends BaseJdbcService implements MessagesServ
user.setBanned(rs.getBoolean(6));
user.setUri(URI.create(Optional.ofNullable(rs.getString(22)).orElse(StringUtils.EMPTY)));
msg.setUser(user);
- msg.setTimestamp(rs.getTimestamp(7).toInstant());
+ msg.setCreated(rs.getTimestamp(7).toInstant());
msg.ReadOnly = rs.getBoolean(8);
msg.setPrivacy(rs.getInt(9));
msg.FriendsOnly = msg.getPrivacy() < 0;
@@ -83,7 +101,7 @@ public class MessagesServiceImpl extends BaseJdbcService implements MessagesServ
msg.setAttachmentType(rs.getString(11));
msg.setLikes(rs.getInt(12));
msg.Hidden = rs.getBoolean(13);
- String tagsStr = rs.getString(14);
+ String tagsStr = StringUtils.defaultString(rs.getString(14));
msg.setTags(MessageUtils.parseTags(tagsStr));
msg.setRepliesBy(rs.getString(15));
msg.setText(rs.getString(16));
@@ -96,6 +114,8 @@ public class MessagesServiceImpl extends BaseJdbcService implements MessagesServ
if (quoteUid == 0) {
quoteUser.setName(AnonymousUser.INSTANCE.getName());
quoteUser.setUri(URI.create(Optional.ofNullable(rs.getString(23)).orElse(StringUtils.EMPTY)));
+ } else {
+ quoteUser.setAvatar(webApp.getAvatarUrl(quoteUser));
}
msg.setTo(quoteUser);
msg.setUpdatedAt(rs.getTimestamp(21).toInstant());
@@ -119,7 +139,7 @@ public class MessagesServiceImpl extends BaseJdbcService implements MessagesServ
*/
@Transactional
@Override
- public int createMessage(final int uid, final String txt, final String attachment, final Collection<com.juick.Tag> tags) {
+ public int createMessage(final int uid, final String txt, final String attachment, final List<com.juick.Tag> tags) {
SimpleJdbcInsert simpleJdbcInsert = new SimpleJdbcInsert(getJdbcTemplate()).withTableName("messages")
.usingColumns("user_id", "attach", "ts")
.usingGeneratedKeyColumns("message_id");
@@ -132,34 +152,25 @@ public class MessagesServiceImpl extends BaseJdbcService implements MessagesServ
}
int mid = simpleJdbcInsert.executeAndReturnKey(insertMap).intValue();
if (mid > 0) {
- String tagsNames = StringUtils.EMPTY;
-
if (CollectionUtils.isNotEmpty(tags)) {
- StringBuilder tasNamesBuilder = new StringBuilder();
- List<Object[]> params = new ArrayList<>(tags.size());
-
- boolean next = false;
-
- for (Tag tag : tags) {
- if (next) {
- tasNamesBuilder.append(" ");
- } else
- next = true;
-
- tasNamesBuilder.append(tag.getName());
- params.add(new Object[]{mid, tag.TID});
- }
- tagsNames = tasNamesBuilder.toString();
-
getJdbcTemplate().batchUpdate(
"INSERT INTO messages_tags(message_id, tag_id) VALUES (?, ?)",
- params, new int[]{Types.INTEGER, Types.INTEGER});
+ new BatchPreparedStatementSetter() {
+ @Override
+ public void setValues(@Nonnull PreparedStatement ps, int i) throws SQLException {
+ ps.setInt(1, mid);
+ ps.setInt(2, tags.get(i).TID);
+ }
+ @Override
+ public int getBatchSize() {
+ return tags.size();
+ }
+ });
}
-
getJdbcTemplate().update(
- "INSERT INTO messages_txt(message_id, tags, txt, updated_at) VALUES (?, ?, ?, ?)",
- new Object[]{mid, tagsNames, txt, Timestamp.from(now)},
- new int[]{Types.INTEGER, Types.VARCHAR, Types.VARCHAR, Types.TIMESTAMP});
+ "INSERT INTO messages_txt(message_id, txt, updated_at) VALUES (?, ?, ?)",
+ new Object[]{mid, txt, Timestamp.from(now)},
+ new int[]{Types.INTEGER, Types.VARCHAR, Types.TIMESTAMP});
getJdbcTemplate().update("UPDATE users SET lastmessage=?, last_seen=? where id=?", Timestamp.from(now), Timestamp.from(now), uid);
}
@@ -365,16 +376,18 @@ public class MessagesServiceImpl extends BaseJdbcService implements MessagesServ
+ "messages.ts,"
+ "messages.readonly, messages.privacy, messages.replies,"
+ "messages.attach, COUNT(DISTINCT favorites.user_id) as likes, messages.hidden,"
- + "txt.tags, txt.repliesby, txt.txt, '' as q, messages.updated as updated, 0 as to_uid, "
+ + "GROUP_CONCAT(tags.name SEPARATOR ' '), txt.repliesby, txt.txt, '' as q, messages.updated as updated, 0 as to_uid, "
+ "NULL as to_name, txt.updated_at, '' as user_uri, '' as to_uri, '' as reply_uri, 0 as html FROM messages "
+ "INNER JOIN users ON messages.user_id = users.id "
+ "INNER JOIN messages_txt AS txt "
+ "ON messages.message_id = txt.message_id "
+ "LEFT JOIN favorites "
+ "ON messages.message_id = favorites.message_id AND favorites.like_id=1 "
+ + "LEFT JOIN messages_tags ON messages_tags.message_id=txt.message_id "
+ + "LEFT JOIN tags ON tags.tag_id=messages_tags.tag_id "
+ "WHERE messages.message_id = ? "
+ "GROUP BY mid, rid, replyto, uid, nick, banned, messages.ts, readonly, "
- + "privacy, replies, attach, tags, repliesby, q, updated_at, user_uri, to_uri, reply_uri, html",
+ + "privacy, replies, attach, repliesby, q, updated_at, user_uri, to_uri, reply_uri, html",
new MessageMapper(),
mid);
if (!list.isEmpty()) {
@@ -418,7 +431,7 @@ public class MessagesServiceImpl extends BaseJdbcService implements MessagesServ
URI.create(Optional.ofNullable(rs.getString(11)).orElse(StringUtils.EMPTY)));
}
msg.setReplyto(rs.getInt(3));
- msg.setTimestamp(rs.getTimestamp(4).toInstant());
+ msg.setCreated(rs.getTimestamp(4).toInstant());
msg.setAttachmentType(rs.getString(5));
msg.setText(rs.getString(6));
String quote = rs.getString(7);
@@ -477,15 +490,21 @@ public class MessagesServiceImpl extends BaseJdbcService implements MessagesServ
@Transactional(readOnly = true)
@Override
- public List<String> getMessageRecommendations(final int mid) {
- return getJdbcTemplate().queryForList(
- "SELECT DISTINCT users.nick FROM favorites " +
+ public List<User> getMessageRecommendations(final int mid) {
+ return getJdbcTemplate().query(
+ "SELECT DISTINCT users.id, users.nick, favorites.user_uri FROM favorites " +
"INNER JOIN users ON (favorites.message_id = ? AND favorites.user_id = users.id) " +
"INNER JOIN messages m ON favorites.message_id=m.message_id WHERE favorites.like_id=1 " +
"AND NOT EXISTS (SELECT 1 FROM bl_users WHERE " +
"(user_id = favorites.user_id AND bl_user_id = m.user_id) " +
"OR (user_id = m.user_id AND bl_user_id = favorites.user_id))",
- String.class, mid);
+ (rs, rowNum) -> {
+ User user = new User();
+ user.setUid(rs.getInt(1));
+ user.setName(rs.getString(2));
+ user.setUri(URI.create(rs.getString(3)));
+ return user;
+ }, mid);
}
@Transactional(readOnly = true)
@@ -577,29 +596,25 @@ public class MessagesServiceImpl extends BaseJdbcService implements MessagesServ
.addValue("before", before);
List<Integer> mids = getNamedParameterJdbcTemplate().queryForList(
- "(SELECT message_id FROM messages " +
- " INNER JOIN subscr_users ON (subscr_users.suser_id = :uid AND subscr_users.user_id = messages.user_id) " +
- " WHERE " +
- (before > 0 ?
- " message_id < :before AND " : StringUtils.EMPTY) +
- " (privacy >= 0 OR (privacy >= -2 AND privacy <= -1" +
- " AND EXISTS (SELECT 1 FROM wl_users w WHERE w.wl_user_id = :uid and w.user_id = messages.user_id))) " +
- " AND NOT EXISTS (SELECT 1 FROM bl_tags bt WHERE bt.tag_id IN " +
- "(SELECT tag_id FROM messages_tags WHERE message_id = messages.message_id) and :uid = bt.user_id))" +
- " UNION " +
- " (SELECT message_id FROM messages WHERE user_id=:uid " +
- (before > 0 ?
- " AND message_id < :before " : StringUtils.EMPTY) +
+ "SELECT message_id FROM messages WHERE " +
+ "(user_id=:uid OR " +
+ "(EXISTS (SELECT 1 FROM subscr_users WHERE subscr_users.suser_id = :uid " +
+ "AND subscr_users.user_id = messages.user_id) " +
+ "AND NOT EXISTS (SELECT 1 FROM bl_tags bt WHERE bt.tag_id IN " +
+ "(SELECT tag_id FROM messages_tags WHERE message_id = messages.message_id) AND :uid = bt.user_id) " +
+ "AND (privacy >= 0 OR (privacy >= -2 AND privacy <= -1 " +
+ "AND EXISTS (SELECT 1 FROM wl_users w WHERE w.wl_user_id = :uid and w.user_id = messages.user_id)))) " +
(recommended ?
- ") UNION " +
- " (SELECT f.message_id as message_id FROM favorites f INNER JOIN messages ON " +
- "f.message_id=messages.message_id WHERE " +
- "EXISTS (SELECT 1 FROM subscr_users s WHERE s.suser_id = :uid and f.user_id = s.user_id)" +
- (before > 0 ?
- " AND f.message_id < :before " : StringUtils.EMPTY) : StringUtils.EMPTY) +
- " AND NOT EXISTS (SELECT 1 FROM bl_users b WHERE b.user_id = :uid and b.bl_user_id = messages.user_id)" +
- " AND NOT EXISTS (SELECT 1 FROM bl_tags bt WHERE bt.tag_id IN " +
- "(SELECT tag_id FROM messages_tags WHERE message_id = messages.message_id) and :uid = bt.user_id)) " +
+ "OR (EXISTS (SELECT 1 FROM favorites WHERE favorites.message_id=messages.message_id " +
+ "AND favorites.user_id IN (SELECT user_id FROM subscr_users WHERE suser_id=:uid)) " +
+ "AND NOT EXISTS (SELECT 1 FROM bl_tags bt WHERE bt.tag_id IN (SELECT tag_id FROM messages_tags " +
+ "WHERE message_id = messages.message_id) and :uid = bt.user_id) " +
+ "AND (privacy >= 0 OR (privacy >= -2 AND privacy <= -1 " +
+ "AND EXISTS (SELECT 1 FROM wl_users w " +
+ "WHERE w.wl_user_id = :uid and w.user_id = messages.user_id)))) " +
+ "AND NOT EXISTS (SELECT 1 FROM bl_users b WHERE b.user_id = :uid and b.bl_user_id = messages.user_id)" : StringUtils.EMPTY) +
+ ") " +
+ (before > 0 ? "AND message_id < :before " : StringUtils.EMPTY) +
"ORDER BY message_id DESC LIMIT 20",
sqlParameterSource,
Integer.class);
@@ -871,7 +886,7 @@ public class MessagesServiceImpl extends BaseJdbcService implements MessagesServ
+ "messages.ts,"
+ "messages.readonly,messages.privacy, messages.replies-COUNT(DISTINCT banned.reply_id) as replies,"
+ "messages.attach,COUNT(DISTINCT favorites.user_id) AS likes,messages.hidden,"
- + "messages_txt.tags,messages_txt.repliesby, messages_txt.txt, '' as q, "
+ + "GROUP_CONCAT(tags.name SEPARATOR ' '), messages_txt.repliesby, messages_txt.txt, '' as q, "
+ "messages.updated, 0 as to_uid, NULL as to_name, messages_txt.updated_at, '' as m_user_uri, "
+ "'' as to_uri, '' as msg_reply_uri, 0 as html "
+ "FROM (messages INNER JOIN messages_txt "
@@ -881,9 +896,11 @@ public class MessagesServiceImpl extends BaseJdbcService implements MessagesServ
+ "ON messages.message_id = favorites.message_id AND favorites.like_id=1 "
+ "LEFT JOIN banned "
+ "ON messages.message_id = banned.message_id "
+ + "LEFT JOIN messages_tags ON messages_tags.message_id=messages_txt.message_id "
+ + "LEFT JOIN tags ON tags.tag_id=messages_tags.tag_id "
+ "WHERE messages.message_id IN (:ids) GROUP BY "
+ "messages.message_id, rid, replyto, messages.user_id, users.nick, usr_banned, messages.ts, "
- + "messages.readonly, messages.privacy, messages.attach, messages.hidden, messages_txt.tags, "
+ + "messages.readonly, messages.privacy, messages.attach, messages.hidden, "
+ "messages_txt.repliesby, messages_txt.txt, q, messages.updated, to_uid, to_name, updated_at, "
+ "m_user_uri, msg_reply_uri, html",
new MapSqlParameterSource("ids", mids)
@@ -971,7 +988,7 @@ public class MessagesServiceImpl extends BaseJdbcService implements MessagesServ
"ORDER BY replies.reply_id ASC",
new MapSqlParameterSource("mid", mid).addValue("uid", user.getUid()),
new MessageMapper());
- if (replies.size() > 0) {
+ if (replies.size() > 0 && !user.isAnonymous()) {
setRead(user, mid);
}
replies.forEach(i -> i.setEntities(MessageUtils.getEntities(i)));
@@ -1143,7 +1160,7 @@ public class MessagesServiceImpl extends BaseJdbcService implements MessagesServ
@Override
public boolean updateReplyUri(Message reply, URI replyUri) {
- return jdbcTemplate.update("UPDATE replies SET reply_uri=?, html=1 WHERE message_id=? AND reply_id=?",
+ return jdbcTemplate.update("UPDATE replies SET reply_uri=?, html=0 WHERE message_id=? AND reply_id=?",
replyUri.toASCIIString(), reply.getMid(), reply.getRid()) > 0;
}
diff --git a/src/main/java/com/juick/service/PMQueriesServiceImpl.java b/src/main/java/com/juick/service/PMQueriesServiceImpl.java
index 712e4b0e..f1f98024 100644
--- a/src/main/java/com/juick/service/PMQueriesServiceImpl.java
+++ b/src/main/java/com/juick/service/PMQueriesServiceImpl.java
@@ -105,7 +105,7 @@ public class PMQueriesServiceImpl extends BaseJdbcService implements PMQueriesSe
user.setName(rs.getString(4));
msg.setUser(user);
msg.setText(rs.getString(2).trim());
- msg.setTimestamp(rs.getTimestamp(3).toInstant());
+ msg.setCreated(rs.getTimestamp(3).toInstant());
return msg;
});
}
@@ -122,7 +122,7 @@ public class PMQueriesServiceImpl extends BaseJdbcService implements PMQueriesSe
msg.getUser().setUid(rs.getInt(1));
msg.getUser().setName(rs.getString(2));
msg.setText(rs.getString(3).trim());
- msg.setTimestamp(rs.getTimestamp(4).toInstant());
+ msg.setCreated(rs.getTimestamp(4).toInstant());
return msg;
},
uid);
@@ -141,7 +141,7 @@ public class PMQueriesServiceImpl extends BaseJdbcService implements PMQueriesSe
msg.getUser().setUid(rs.getInt(1));
msg.getUser().setName(rs.getString(2));
msg.setText(rs.getString(3).trim());
- msg.setTimestamp(rs.getTimestamp(4).toInstant());
+ msg.setCreated(rs.getTimestamp(4).toInstant());
return msg;
},
uid);
diff --git a/src/main/java/com/juick/service/TagServiceImpl.java b/src/main/java/com/juick/service/TagServiceImpl.java
index 42159d3b..95a1a309 100644
--- a/src/main/java/com/juick/service/TagServiceImpl.java
+++ b/src/main/java/com/juick/service/TagServiceImpl.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2008-2017, Juick
+ * Copyright (C) 2008-2019, Juick
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
@@ -20,10 +20,10 @@ package com.juick.service;
import com.juick.Tag;
import com.juick.User;
import com.juick.model.TagStats;
-import com.juick.util.StreamUtils;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
+import org.springframework.jdbc.core.BatchPreparedStatementSetter;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.support.GeneratedKeyHolder;
@@ -31,6 +31,7 @@ import org.springframework.jdbc.support.KeyHolder;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
+import javax.annotation.Nonnull;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
@@ -199,21 +200,28 @@ public class TagServiceImpl extends BaseJdbcService implements TagService {
"DELETE FROM messages_tags WHERE message_id = :mid AND tag_id in (:ids)",
new MapSqlParameterSource().addValue("ids", idsForDelete).addValue("mid", mid));
- newTags.stream().filter(t -> !currentTags.contains(t))
- .forEach(t -> getJdbcTemplate().update("INSERT INTO messages_tags(message_id,tag_id) VALUES (?,?)", mid, t.TID));
-
- List<Tag> result = getMessageTags(mid).stream()
+ List<Tag> addedTags = newTags.stream().filter(t -> !currentTags.contains(t)).collect(Collectors.toList());
+ getJdbcTemplate().batchUpdate("INSERT INTO messages_tags(message_id,tag_id) VALUES (?,?)", new BatchPreparedStatementSetter() {
+ @Override
+ public void setValues(@Nonnull PreparedStatement ps, int i) throws SQLException {
+ ps.setInt(1, mid);
+ ps.setInt(2, addedTags.get(i).TID);
+ }
+ @Override
+ public int getBatchSize() {
+ return addedTags.size();
+ }
+ });
+
+ return getMessageTags(mid).stream()
.map(TagStats::getTag).collect(Collectors.toList());
- jdbcTemplate.update("UPDATE messages_txt SET tags=? WHERE message_id=?", result.stream()
- .map(Tag::getName).collect(Collectors.joining(" ")), mid);
- return result;
}
@Override
public Pair<String, List<Tag>> fromString(final String txt) {
String firstLine = txt.split("\\n", 2)[0];
- Supplier<Stream<String>> tagsStream = () -> StreamUtils.takeWhile(Arrays.stream(firstLine.split("\\ ")),
- t -> !t.equals("*") && t.startsWith("*"));
+ Supplier<Stream<String>> tagsStream = () -> Arrays.stream(firstLine.split("\\ "))
+ .takeWhile(t -> !t.equals("*") && t.startsWith("*"));
int tagsLength = tagsStream.get().collect(Collectors.joining(" ")).length();
String body = txt.substring(tagsLength);
List<Tag> tags = tagsStream.get().map(t -> getTag(t.substring(1), true))
diff --git a/src/main/java/com/juick/service/UserService.java b/src/main/java/com/juick/service/UserService.java
index 832f978a..3a51dffb 100644
--- a/src/main/java/com/juick/service/UserService.java
+++ b/src/main/java/com/juick/service/UserService.java
@@ -20,7 +20,6 @@ package com.juick.service;
import com.juick.Message;
import com.juick.User;
import com.juick.model.Auth;
-import com.juick.model.UserInfo;
import javax.annotation.Nonnull;
import java.time.Instant;
@@ -75,9 +74,9 @@ public interface UserService {
int setUserOptionInt(int uid, String option, int value);
- UserInfo getUserInfo(User user);
+ User getUserInfo(User user);
- boolean updateUserInfo(User user, UserInfo info);
+ boolean updateUserInfo(User info);
boolean getCanMedia(int uid);
diff --git a/src/main/java/com/juick/service/UserServiceImpl.java b/src/main/java/com/juick/service/UserServiceImpl.java
index 93904139..bcfb8dac 100644
--- a/src/main/java/com/juick/service/UserServiceImpl.java
+++ b/src/main/java/com/juick/service/UserServiceImpl.java
@@ -21,7 +21,6 @@ import com.juick.Message;
import com.juick.User;
import com.juick.model.AnonymousUser;
import com.juick.model.Auth;
-import com.juick.model.UserInfo;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
@@ -121,7 +120,7 @@ public class UserServiceImpl extends BaseJdbcService implements UserService {
return -1;
}
- int uid = holder.getKey().intValue();
+ int uid = holder.getKeys().size() > 1 ? (int)holder.getKeys().get("id") : holder.getKey().intValue();
getJdbcTemplate().update("INSERT INTO useroptions(user_id) VALUES (?)", uid);
getJdbcTemplate().update("INSERT INTO subscr_users(user_id, suser_id) VALUES (2, ?)", uid);
@@ -133,7 +132,7 @@ public class UserServiceImpl extends BaseJdbcService implements UserService {
@Override
public Optional<User> getUserByUID(final int uid) {
List<User> list = getJdbcTemplate().query(
- "SELECT u.id, u.nick, u.passw, u.banned, u.last_seen, " +
+ "SELECT DISTINCT u.id, u.nick, u.passw, u.banned, u.last_seen, " +
"COALESCE(f.fb_id, vk.vk_id, t.tg_id, e.user_id, 0) AS verified " +
"FROM users u LEFT JOIN facebook f ON f.user_id = u.id " +
"LEFT JOIN vk ON u.id = vk.user_id LEFT JOIN telegram t ON u.id = t.user_id " +
@@ -148,7 +147,7 @@ public class UserServiceImpl extends BaseJdbcService implements UserService {
public User getUserByName(final String username) {
if (StringUtils.isNotBlank(username)) {
List<User> list = getJdbcTemplate().query(
- "SELECT u.id, u.nick, u.passw, u.banned, u.last_seen, " +
+ "SELECT DISTINCT u.id, u.nick, u.passw, u.banned, u.last_seen, " +
"COALESCE(f.fb_id, vk.vk_id, t.tg_id, e.user_id, 0) AS verified " +
"FROM users u LEFT JOIN facebook f ON f.user_id = u.id " +
"LEFT JOIN vk ON u.id = vk.user_id LEFT JOIN telegram t ON u.id = t.user_id " +
@@ -167,7 +166,7 @@ public class UserServiceImpl extends BaseJdbcService implements UserService {
public User getUserByEmail(String email) {
if (StringUtils.isNotBlank(email)) {
List<User> list = getJdbcTemplate().query(
- "SELECT u.id, u.nick, u.passw, u.banned, u.last_seen, " +
+ "SELECT DISTINCT u.id, u.nick, u.passw, u.banned, u.last_seen, " +
"COALESCE(f.fb_id, vk.vk_id, t.tg_id, e.user_id, 0) AS verified " +
"FROM users u LEFT JOIN facebook f ON f.user_id = u.id " +
"LEFT JOIN vk ON u.id = vk.user_id LEFT JOIN telegram t ON u.id = t.user_id " +
@@ -189,7 +188,7 @@ public class UserServiceImpl extends BaseJdbcService implements UserService {
if (StringUtils.isNotBlank(jid)) {
List<User> list = getJdbcTemplate().query(
- "SELECT u.id, u.nick, u.passw, u.banned, u.last_seen," +
+ "SELECT DISTINCT u.id, u.nick, u.passw, u.banned, u.last_seen," +
"COALESCE(f.fb_id, vk.vk_id, t.tg_id, e.user_id, 0) AS verified " +
"FROM users u LEFT JOIN facebook f ON f.user_id = u.id " +
"LEFT JOIN vk ON u.id = vk.user_id LEFT JOIN telegram t ON u.id = t.user_id " +
@@ -211,7 +210,7 @@ public class UserServiceImpl extends BaseJdbcService implements UserService {
return Collections.emptyList();
return getNamedParameterJdbcTemplate().query(
- "SELECT u.id, u.nick, u.passw, u.banned, u.last_seen," +
+ "SELECT DISTINCT u.id, u.nick, u.passw, u.banned, u.last_seen," +
"COALESCE(f.fb_id, vk.vk_id, t.tg_id, e.user_id, 0) AS verified " +
"FROM users u LEFT JOIN facebook f ON f.user_id = u.id " +
"LEFT JOIN vk ON u.id = vk.user_id LEFT JOIN telegram t ON u.id = t.user_id " +
@@ -228,7 +227,7 @@ public class UserServiceImpl extends BaseJdbcService implements UserService {
return Collections.emptyList();
return getNamedParameterJdbcTemplate().query(
- "SELECT u.id, u.nick, u.passw, u.banned, u.last_seen," +
+ "SELECT DISTINCT u.id, u.nick, u.passw, u.banned, u.last_seen," +
"COALESCE(f.fb_id, vk.vk_id, t.tg_id, e.user_id, 0) AS verified " +
"FROM users u LEFT JOIN facebook f ON f.user_id = u.id " +
"LEFT JOIN vk ON u.id = vk.user_id LEFT JOIN telegram t ON u.id = t.user_id " +
@@ -288,7 +287,7 @@ public class UserServiceImpl extends BaseJdbcService implements UserService {
public com.juick.User getUserByHash(final String hash) {
if (StringUtils.isNotBlank(hash)) {
List<User> list = getJdbcTemplate().query(
- "SELECT logins.user_id, u.nick, u.passw, u.banned, u.last_seen," +
+ "SELECT DISTINCT logins.user_id, u.nick, u.passw, u.banned, u.last_seen," +
"COALESCE(f.fb_id, vk.vk_id, t.tg_id, e.user_id, 0) AS verified " +
"FROM logins INNER JOIN users u ON logins.user_id = u.id " +
"LEFT JOIN facebook f ON f.user_id = u.id " +
@@ -326,7 +325,7 @@ public class UserServiceImpl extends BaseJdbcService implements UserService {
public int checkPassword(final String username, final String password) {
if (StringUtils.isNotBlank(username)) {
List<User> list = getJdbcTemplate().query(
- "SELECT u.id, u.nick, u.passw, u.banned, u.last_seen," +
+ "SELECT DISTINCT u.id, u.nick, u.passw, u.banned, u.last_seen," +
"COALESCE(f.fb_id, vk.vk_id, t.tg_id, e.user_id, 0) AS verified " +
"FROM users u LEFT JOIN facebook f ON f.user_id = u.id " +
"LEFT JOIN vk ON u.id = vk.user_id LEFT JOIN telegram t ON u.id = t.user_id " +
@@ -375,29 +374,31 @@ public class UserServiceImpl extends BaseJdbcService implements UserService {
@Transactional(readOnly = true)
@Override
- public UserInfo getUserInfo(final User user) {
- List<UserInfo> list = getJdbcTemplate().query(
- "SELECT fullname, country, url, descr FROM usersinfo WHERE user_id = ?",
- ((rs, rowNum) -> {
- UserInfo info = new UserInfo();
- info.setFullName(rs.getString(1));
- info.setCountry(rs.getString(2));
- info.setUrl(rs.getString(3));
- info.setDescription(rs.getString(4));
- return info;
- }),
- user.getUid());
-
- return list.isEmpty() ? new UserInfo() : list.get(0);
+ public User getUserInfo(final User user) {
+ try {
+ getJdbcTemplate().queryForObject(
+ "SELECT fullname, country, url, descr FROM usersinfo WHERE user_id = ?",
+ ((rs, rowNum) -> {
+ user.setFullName(rs.getString(1));
+ user.setCountry(rs.getString(2));
+ user.setUrl(rs.getString(3));
+ user.setDescription(rs.getString(4));
+ return user;
+ }),
+ user.getUid());
+ } catch (EmptyResultDataAccessException e) {
+ return user;
+ }
+ return user;
}
@Transactional
@Override
- public boolean updateUserInfo(final User user, final UserInfo info) {
+ public boolean updateUserInfo(final User info) {
try {
return getJdbcTemplate().update(
"INSERT INTO usersinfo(user_id, fullname, country, url, descr) VALUES (?, ?, ?, ?, ?)",
- user.getUid(),
+ info.getUid(),
info.getFullName(),
info.getCountry(),
info.getUrl(),
@@ -408,7 +409,7 @@ public class UserServiceImpl extends BaseJdbcService implements UserService {
info.getCountry(),
info.getUrl(),
info.getDescription(),
- user.getUid()) > 0;
+ info.getUid()) > 0;
}
}
diff --git a/src/main/java/com/juick/service/security/HTTPSignatureAuthenticationFilter.java b/src/main/java/com/juick/service/security/HTTPSignatureAuthenticationFilter.java
new file mode 100644
index 00000000..44d97207
--- /dev/null
+++ b/src/main/java/com/juick/service/security/HTTPSignatureAuthenticationFilter.java
@@ -0,0 +1,71 @@
+package com.juick.service.security;
+
+import com.juick.User;
+import com.juick.server.SignatureManager;
+import com.juick.service.UserService;
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.security.authentication.AnonymousAuthenticationToken;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.web.filter.OncePerRequestFilter;
+
+import javax.annotation.Nonnull;
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.Collections;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+public class HTTPSignatureAuthenticationFilter extends OncePerRequestFilter {
+
+ private final SignatureManager signatureManager;
+ private final UserService userService;
+
+
+ public HTTPSignatureAuthenticationFilter(
+ final SignatureManager signatureManager,
+ final UserService userService) {
+ this.signatureManager = signatureManager;
+ this.userService = userService;
+ }
+ @Override
+ protected void doFilterInternal(@Nonnull HttpServletRequest request, @Nonnull HttpServletResponse response, @Nonnull FilterChain filterChain) throws IOException, ServletException {
+ if (authenticationIsRequired()) {
+ Map<String, String> headers = Collections.list(request.getHeaderNames())
+ .stream()
+ .collect(Collectors.toMap(String::toLowerCase, request::getHeader));
+ if (StringUtils.isNotEmpty(headers.get("signature"))) {
+ User user = signatureManager.verifySignature(request.getMethod(), request.getRequestURI(), headers);
+ String userUri = user.getUri().toString();
+ if (!user.isAnonymous() || userUri.length() > 0) {
+ if (userUri.length() == 0) {
+ User userWithPassword = userService.getUserByName(user.getName());
+ userWithPassword.setAuthHash(userService.getHashByUID(userWithPassword.getUid()));
+ Authentication authentication = new UsernamePasswordAuthenticationToken(userWithPassword.getName(), userWithPassword.getCredentials());
+ SecurityContextHolder.getContext().setAuthentication(authentication);
+ } else {
+ Authentication authentication = new AnonymousAuthenticationToken(userUri, user, Collections.singletonList(new SimpleGrantedAuthority("ROLE_ANONYMOUS")));
+ SecurityContextHolder.getContext().setAuthentication(authentication);
+ }
+ }
+ }
+ }
+
+ filterChain.doFilter(request, response);
+ }
+
+ private boolean authenticationIsRequired() {
+ Authentication existingAuth = SecurityContextHolder.getContext().getAuthentication();
+
+ return existingAuth == null ||
+ !existingAuth.isAuthenticated() ||
+ existingAuth instanceof AnonymousAuthenticationToken;
+ }
+}
diff --git a/src/main/java/com/juick/service/security/HashParamAuthenticationFilter.java b/src/main/java/com/juick/service/security/HashParamAuthenticationFilter.java
index 9215d09a..2fd5a2a7 100644
--- a/src/main/java/com/juick/service/security/HashParamAuthenticationFilter.java
+++ b/src/main/java/com/juick/service/security/HashParamAuthenticationFilter.java
@@ -30,6 +30,7 @@ import org.springframework.util.Assert;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.util.WebUtils;
+import javax.annotation.Nonnull;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
@@ -59,9 +60,9 @@ public class HashParamAuthenticationFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(
- HttpServletRequest request,
- HttpServletResponse response,
- FilterChain filterChain) throws ServletException, IOException {
+ @Nonnull HttpServletRequest request,
+ @Nonnull HttpServletResponse response,
+ @Nonnull FilterChain filterChain) throws ServletException, IOException {
String hash = getHashFromRequest(request);
diff --git a/src/main/java/com/juick/util/MessageUtils.java b/src/main/java/com/juick/util/MessageUtils.java
index cc0d7b12..fa94e978 100644
--- a/src/main/java/com/juick/util/MessageUtils.java
+++ b/src/main/java/com/juick/util/MessageUtils.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2008-2017, Juick
+ * Copyright (C) 2008-2019, Juick
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
@@ -28,7 +28,13 @@ import org.springframework.web.util.UriComponentsBuilder;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URLEncoder;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -87,36 +93,36 @@ public class MessageUtils {
private final static String underlineRegex = "((?<=\\s)|(?<=\\A))_([^\\_\\n<>]+)_((?=\\s)|(?=\\Z)|(?=\\p{Punct}))";
- private final static String citateRegex = "(?:(?<=\\n)|(?<=\\A))\\> *(.*)?(\\n|(?=\\Z))";
+ private final static String citateRegex = "(?:(?<=\\n)|(?<=\\A))(?:&gt;|>) *(.*)?(\\n|(?=\\Z))";
public static List<Entity> getEntities(Message msg) {
String txt = msg.getText();
// http://juick.com/last?page=2
- List<Entity> result = new ArrayList<>(entitiesForType("a", txt, urlWithWhitespacesRegex, matcher -> matcher.group(3), matcher -> Optional.of(matcher.group(2))));
+ List<Entity> result = new ArrayList<>(entitiesForType("a", txt, urlWithWhitespacesRegex, matcher -> matcher.group(3), matcher -> Optional.of(matcher.group(2)), 0));
// [link text][http://juick.com/last?page=2]
- result.addAll(entitiesForType("a", txt, textUrlRegex, matcher -> matcher.group(1), matcher -> Optional.of(matcher.group(2))));
+ result.addAll(entitiesForType("a", txt, textUrlRegex, matcher -> matcher.group(1), matcher -> Optional.of(matcher.group(2)), 0));
// [link text](http://juick.com/last?page=2)
- result.addAll(entitiesForType("a", txt, textUrlRegex2, matcher -> matcher.group(1), matcher -> Optional.of(matcher.group(2))));
+ result.addAll(entitiesForType("a", txt, textUrlRegex2, matcher -> matcher.group(1), matcher -> Optional.of(matcher.group(2)), 0));
// #12345
- result.addAll(entitiesForType("a", txt, midRegex, matcher -> String.format("#%s", matcher.group(2)), matcher -> Optional.of("https://juick.com/m/" + matcher.group(2))));
+ result.addAll(entitiesForType("a", txt, midRegex, matcher -> String.format("#%s", matcher.group(2)), matcher -> Optional.of("https://juick.com/m/" + matcher.group(2)), 0));
// #12345/65
- result.addAll(entitiesForType("a", txt, ridRegex, matcher -> String.format("#%s/%s", matcher.group(2), matcher.group(3)), matcher -> Optional.of(String.format("https://juick.com/m/%s#%s", matcher.group(2), matcher.group(3)))));
+ result.addAll(entitiesForType("a", txt, ridRegex, matcher -> String.format("#%s/%s", matcher.group(2), matcher.group(3)), matcher -> Optional.of(String.format("https://juick.com/m/%s#%s", matcher.group(2), matcher.group(3))), 0));
// /12
- result.addAll(entitiesForType("a", txt, replyNumberRegex, matcher -> "/" + matcher.group(2), matcher -> Optional.of(String.format("https://juick.com/m/%d#%s", msg.getMid(), matcher.group(2)))));
+ result.addAll(entitiesForType("a", txt, replyNumberRegex, matcher -> "/" + matcher.group(2), matcher -> Optional.of(String.format("https://juick.com/m/%d#%s", msg.getMid(), matcher.group(2))), 0));
// @username@jabber.org
- result.addAll(entitiesForType("a", txt, jidRegex, matcher -> matcher.group(2), matcher -> Optional.of(String.format("https://juick.com/%s", matcher.group(2)))));
+ result.addAll(entitiesForType("a", txt, jidRegex, matcher -> matcher.group(2), matcher -> Optional.of(String.format("https://juick.com/%s", matcher.group(2))), 0));
// @username
- result.addAll(entitiesForType("a", txt, usernameRegex, matcher -> matcher.group(2), matcher -> Optional.of(String.format("https://juick.com/%s", matcher.group(2)))));
+ result.addAll(entitiesForType("a", txt, usernameRegex, matcher -> matcher.group(2), matcher -> Optional.of(String.format("https://juick.com/%s", matcher.group(2))), 0));
// *bold*
- result.addAll(entitiesForType("b", txt, boldRegex, matcher -> matcher.group(2), matcher -> Optional.empty()));
+ result.addAll(entitiesForType("b", txt, boldRegex, matcher -> matcher.group(2), matcher -> Optional.empty(), 0));
// /italic/
- result.addAll(entitiesForType("i", txt, italicRegex, matcher -> matcher.group(2), matcher -> Optional.empty()));
+ result.addAll(entitiesForType("i", txt, italicRegex, matcher -> matcher.group(2), matcher -> Optional.empty(), 0));
// _underline_
- result.addAll(entitiesForType("u", txt, underlineRegex, matcher -> matcher.group(2), matcher -> Optional.empty()));
+ result.addAll(entitiesForType("u", txt, underlineRegex, matcher -> matcher.group(2), matcher -> Optional.empty(), 0));
// > citate
- result.addAll(entitiesForType("q", txt, citateRegex, matcher -> matcher.group(1), matcher -> Optional.empty()));
+ result.addAll(entitiesForType("q", txt, citateRegex, matcher -> matcher.group(1), matcher -> Optional.empty(), 1));
return result;
}
@@ -144,6 +150,11 @@ public class MessageUtils {
}
public static String formatMessage(String msg) {
+
+ msg = msg.replaceAll("&", "&amp;");
+ msg = msg.replaceAll("<", "&lt;");
+ msg = msg.replaceAll(">", "&gt;");
+
// --
// &mdash;
msg = msg.replaceAll("((?<=\\s)|(?<=\\A))\\-\\-?((?=\\s)|(?=\\Z))", "$1&mdash;$2");
@@ -294,23 +305,15 @@ public class MessageUtils {
public static boolean replyStartsWithQuote(Message msg) {
return msg.getRid() > 0 && StringUtils.defaultString(msg.getText()).startsWith(">");
}
- public static List<Tag> parseTags(String strTags) {
- List<Tag> tags = new ArrayList<>();
- if (StringUtils.isNotEmpty(strTags)) {
- Set<Tag> tagSet = new TreeSet<>(tags);
- for (String str : strTags.split(" ")) {
- Tag tag = new Tag(str);
- if (!tagSet.contains(tag)) {
- tags.add(tag);
- tagSet.add(tag);
- }
- }
- }
- return tags;
+ public static Set<Tag> parseTags(String strTags) {
+ return StringUtils.isEmpty(strTags) ? Collections.emptySet()
+ : Arrays.stream(strTags.split(" ")).map(Tag::new)
+ .collect(Collectors.toCollection(LinkedHashSet::new));
}
public static String getTagsString(Message msg) {
StringBuilder builder = new StringBuilder();
- List<Tag> tags = msg.getTags();
+ Set<Tag> tags = msg.getTags();
+
if (!tags.isEmpty()) {
for (Tag Tag : tags)
builder.append(" *").append(Tag.getName());
@@ -369,7 +372,20 @@ public class MessageUtils {
return collectMatches(jidPattern, msg.getText());
}
- private static List<Entity> entitiesForType(String type, String input, String patternText, Function<Matcher, String> textGroup, Function<Matcher, Optional<String>> linkGroup) {
+ /**
+ *
+ * @param type Name of the entity
+ * @param input data to find matches
+ * @param patternText pattern to match
+ * @param textGroup function which return text representation
+ * @param linkGroup function which return link address
+ * @param endGroupId group id used to set end of entity (e.g. do not count linebreak as part of quote entity)
+ * @return list of entities
+ */
+ private static List<Entity> entitiesForType(String type, String input, String patternText,
+ Function<Matcher, String> textGroup,
+ Function<Matcher, Optional<String>> linkGroup,
+ int endGroupId) {
List<Entity> result = new ArrayList<>();
Pattern pattern = Pattern.compile(patternText);
Matcher matcher = pattern.matcher(input);
@@ -380,7 +396,7 @@ public class MessageUtils {
Optional<String> link = linkGroup.apply(matcher);
link.ifPresent(entity::setUrl);
entity.setStart(matcher.start());
- entity.setEnd(matcher.end());
+ entity.setEnd(matcher.end(endGroupId));
result.add(entity);
}
return result;
diff --git a/src/main/java/com/juick/util/StreamUtils.java b/src/main/java/com/juick/util/StreamUtils.java
deleted file mode 100644
index 576107af..00000000
--- a/src/main/java/com/juick/util/StreamUtils.java
+++ /dev/null
@@ -1,38 +0,0 @@
-package com.juick.util;
-
-import java.util.Spliterator;
-import java.util.Spliterators;
-import java.util.function.Consumer;
-import java.util.function.Predicate;
-import java.util.stream.Stream;
-import java.util.stream.StreamSupport;
-
-/**
- * @deprecated switch to JDK9+ Stream API when possible
- */
-@Deprecated
-public class StreamUtils {
- private static <T> Spliterator<T> takeWhile(
- Spliterator<T> splitr, Predicate<? super T> predicate) {
- return new Spliterators.AbstractSpliterator<T>(splitr.estimateSize(), 0) {
- boolean stillGoing = true;
- @Override public boolean tryAdvance(Consumer<? super T> consumer) {
- if (stillGoing) {
- boolean hadNext = splitr.tryAdvance(elem -> {
- if (predicate.test(elem)) {
- consumer.accept(elem);
- } else {
- stillGoing = false;
- }
- });
- return hadNext && stillGoing;
- }
- return false;
- }
- };
- }
-
- public static <T> Stream<T> takeWhile(Stream<T> stream, Predicate<? super T> predicate) {
- return StreamSupport.stream(takeWhile(stream.spliterator(), predicate), false);
- }
-}
diff --git a/src/main/java/com/mitchellbosecke/pebble/extension/filters/TagsListFilter.java b/src/main/java/com/mitchellbosecke/pebble/extension/filters/TagsListFilter.java
index c7b00ea3..c83c3a45 100644
--- a/src/main/java/com/mitchellbosecke/pebble/extension/filters/TagsListFilter.java
+++ b/src/main/java/com/mitchellbosecke/pebble/extension/filters/TagsListFilter.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2008-2017, Juick
+ * Copyright (C) 2008-2019, Juick
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
@@ -22,6 +22,7 @@ import com.mitchellbosecke.pebble.extension.Filter;
import com.mitchellbosecke.pebble.template.EvaluationContext;
import com.mitchellbosecke.pebble.template.PebbleTemplate;
+import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@@ -33,7 +34,7 @@ public class TagsListFilter implements Filter {
@SuppressWarnings("unchecked")
@Override
public Object apply(Object input, Map<String, Object> args, PebbleTemplate self, EvaluationContext context, int lineNumber) {
- return ((List<Tag>) input).stream().map(Tag::getName).collect(Collectors.toList());
+ return ((Collection<Tag>) input).stream().map(Tag::getName).collect(Collectors.toList());
}
@Override
diff --git a/src/main/java/ru/sape/Sape.java b/src/main/java/ru/sape/Sape.java
index 38577c45..a94bcc62 100644
--- a/src/main/java/ru/sape/Sape.java
+++ b/src/main/java/ru/sape/Sape.java
@@ -3,6 +3,8 @@
*/
package ru.sape;
+import java.net.URI;
+
public class Sape {
private final String sapeUser;
@@ -17,7 +19,7 @@ public class Sape {
}
public boolean debug = false;
- public SapePageLinks getPageLinks(String requestUri, String cookie) {
+ public SapePageLinks getPageLinks(URI requestUri, String cookie) {
return new SapePageLinks(sapePageLinkConnection, sapeUser, requestUri, cookie, debug);
}
}
diff --git a/src/main/java/ru/sape/SapePageLinks.java b/src/main/java/ru/sape/SapePageLinks.java
index e89b4e71..77715aea 100644
--- a/src/main/java/ru/sape/SapePageLinks.java
+++ b/src/main/java/ru/sape/SapePageLinks.java
@@ -1,17 +1,18 @@
package ru.sape;
+import org.apache.commons.lang3.StringUtils;
+
+import java.net.URI;
import java.util.*;
public class SapePageLinks {
private boolean showCode;
- public SapePageLinks(SapeConnection sapeConnection, String sapeUser, String requestUri, String sapeCookie) {
- this(sapeConnection, sapeUser, requestUri, sapeCookie, false);
- }
-
@SuppressWarnings("unchecked")
- public SapePageLinks(SapeConnection sapeConnection, String sapeUser, String requestUri, String sapeCookie, boolean showCode) {
+ public SapePageLinks(SapeConnection sapeConnection, String sapeUser, URI request, String sapeCookie, boolean showCode) {
+ String req = StringUtils.isNotEmpty(request.getQuery()) ? request.getPath() + "?" + request.getQuery()
+ : request.getPath();
if (sapeUser.equals(sapeCookie)) {
showCode = true;
}
@@ -22,8 +23,8 @@ public class SapePageLinks {
linkDelimiter = (String) data.get("__sape_delimiter__");
}
- if (data.containsKey(requestUri)) {
- pageLinks = new ArrayList<>(((Map<Object, String>) data.get(requestUri)).values());
+ if (data.containsKey(req)) {
+ pageLinks = new ArrayList<>(((Map<Object, String>) data.get(req)).values());
}
if (data.containsKey("__sape_new_url__")) {
diff --git a/src/main/resources/banner.txt b/src/main/resources/banner.txt
new file mode 100644
index 00000000..c81c89f7
--- /dev/null
+++ b/src/main/resources/banner.txt
@@ -0,0 +1,13 @@
+${AnsiColor.BRIGHT_RED}
+ ___ ___ ___ ___
+ /\ \ /\__\ ___ /\ \ /\__\
+ \:\ \ /:/ / /\ \ /::\ \ /:/ /
+ ___ /::\__\ /:/ / \:\ \ /:/\:\ \ /:/__/
+ /\ /:/\/__/ /:/ / ___ /::\__\ /:/ \:\ \ /::\__\____
+ \:\/:/ / /:/__/ /\__\ __/:/\/__/ /:/__/ \:\__\ /:/\:::::\__\
+ \::/ / \:\ \ /:/ / /\/:/ / \:\ \ \/__/ \/_|:|~~|~
+ \/__/ \:\ /:/ / \::/__/ \:\ \ |:| |
+ \:\/:/ / \:\__\ \:\ \ |:| |
+ \::/ / \/__/ \:\__\ |:| |
+ \/__/ \/__/ \|__|
+${AnsiColor.DEFAULT}
diff --git a/src/main/resources/db/migration/V1.17__drop tags column.sql b/src/main/resources/db/migration/V1.17__drop tags column.sql
new file mode 100644
index 00000000..ebb2d9a6
--- /dev/null
+++ b/src/main/resources/db/migration/V1.17__drop tags column.sql
@@ -0,0 +1 @@
+ALTER TABLE messages_txt DROP COLUMN tags; \ No newline at end of file
diff --git a/src/main/resources/db/migration/V1.18__increase messages and replies timestamp precision.sql b/src/main/resources/db/migration/V1.18__increase messages and replies timestamp precision.sql
new file mode 100644
index 00000000..5b298c46
--- /dev/null
+++ b/src/main/resources/db/migration/V1.18__increase messages and replies timestamp precision.sql
@@ -0,0 +1,5 @@
+ALTER TABLE replies MODIFY COLUMN ts timestamp(6) NOT NULL DEFAULT CURRENT_TIMESTAMP;
+ALTER TABLE messages MODIFY COLUMN ts timestamp(6) NOT NULL DEFAULT CURRENT_TIMESTAMP;
+ALTER TABLE messages MODIFY COLUMN updated timestamp(6) NOT NULL DEFAULT CURRENT_TIMESTAMP;
+ALTER TABLE messages_txt MODIFY COLUMN updated_at timestamp(6) NOT NULL DEFAULT CURRENT_TIMESTAMP;
+ALTER TABLE users MODIFY COLUMN lastmessage timestamp(6) NOT NULL DEFAULT CURRENT_TIMESTAMP;
diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql
index 2e8fad9b..423fc375 100644
--- a/src/main/resources/schema.sql
+++ b/src/main/resources/schema.sql
@@ -258,7 +258,7 @@ CREATE TABLE IF NOT EXISTS `useroptions` (
);
CREATE TABLE IF NOT EXISTS `users` (
- `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
+ `id` int(10) unsigned NOT NULL AUTO_INCREMENT(0),
`nick` char(64) NOT NULL,
`passw` char(32) NOT NULL,
`lang` enum('en','ru','fr','fa','__') NOT NULL DEFAULT '__',
diff --git a/src/main/resources/templates/layouts/note.html b/src/main/resources/templates/layouts/note.html
index 42b939c0..e832dc63 100644
--- a/src/main/resources/templates/layouts/note.html
+++ b/src/main/resources/templates/layouts/note.html
@@ -1,5 +1,5 @@
{% import "views/macros/tags" %}
<p>{{ msg | formatMessage }}</p>
-{% if msg.tags.size > 0 %}
+{% if msg.tags | length > 0 %}
<div class="msg-tags">{{ allTags(baseUri, msg.tags | tagsList) }}</div>
{% endif %} \ No newline at end of file
diff --git a/src/main/resources/templates/views/login.html b/src/main/resources/templates/views/login.html
index a538cb26..11900c8d 100644
--- a/src/main/resources/templates/views/login.html
+++ b/src/main/resources/templates/views/login.html
@@ -117,7 +117,7 @@
<li><a href="/tag/учеба" style="left: 616px; top: 84px; width: 43px; height: 17px">учеба</a></li>
</ul>
-<div id="bottom1">juick.com &copy; 2008-2018 &nbsp; <a href="/help/ru/contacts" rel="nofollow">Контакты</a> &middot; <a href="/help/" rel="nofollow">Помощь</a></div>
+<div id="bottom1">juick.com &copy; 2008-2019 &nbsp; <a href="/help/ru/contacts" rel="nofollow">Контакты</a> &middot; <a href="/help/" rel="nofollow">Помощь</a></div>
<div id="signup">
{{ i18n("messages","label.register") }}:
diff --git a/src/main/resources/templates/views/partial/footer.html b/src/main/resources/templates/views/partial/footer.html
index 35972254..bb6ccb97 100644
--- a/src/main/resources/templates/views/partial/footer.html
+++ b/src/main/resources/templates/views/partial/footer.html
@@ -8,7 +8,7 @@
<a href="https://vk.com/juick" rel="nofollow"><i data-icon="ei-sc-vk" data-size="m"></i></a>
<a href="https://www.facebook.com/JuickCom" rel="nofollow"><i data-icon="ei-sc-facebook" data-size="m"></i></a>
</div>
- <div id="footer-left">juick.com &copy; 2008-2018
+ <div id="footer-left">juick.com &copy; 2008-2019
{% if links | default ('') is not empty %}
<br/>{{ i18n("messages","label.sponsors") }}: {{ links | raw }}
{% endif %}
diff --git a/src/main/resources/templates/views/partial/message.html b/src/main/resources/templates/views/partial/message.html
index b1d27ae5..bb36b25e 100644
--- a/src/main/resources/templates/views/partial/message.html
+++ b/src/main/resources/templates/views/partial/message.html
@@ -8,9 +8,9 @@
</div>
<div class="msg-ts">
<a href="/{{ msg.user.name }}/{{ msg.mid }}">
- <time datetime="{{ msg.timestamp | timestamp | date('yyyy-MM-dd HH:mm:ss') }}Z"
- title="{{ msg.timestamp | timestamp | date('yyyy-MM-dd HH:mm:ss') }} GMT">
- {{ msg.timestamp | prettyTime }}
+ <time datetime="{{ msg.created | timestamp | date('yyyy-MM-dd HH:mm:ss') }}Z"
+ title="{{ msg.created | timestamp | date('yyyy-MM-dd HH:mm:ss') }} GMT">
+ {{ msg.created | prettyTime }}
</time>
</a>
</div>
diff --git a/src/main/resources/templates/views/pm_inbox.html b/src/main/resources/templates/views/pm_inbox.html
index 4f97bc86..f89b2923 100644
--- a/src/main/resources/templates/views/pm_inbox.html
+++ b/src/main/resources/templates/views/pm_inbox.html
@@ -12,7 +12,7 @@
<img src="{{ msg.user.avatar }}" alt="{{ msg.user.name }}"/>
</a>
</div>
- <div class="msg-ts">{{ msg.timestamp | prettyTime }}</div>
+ <div class="msg-ts">{{ msg.created | prettyTime }}</div>
</div>
<div class="msg-txt">{{ msg | formatMessage }}</div>
diff --git a/src/main/resources/templates/views/pm_sent.html b/src/main/resources/templates/views/pm_sent.html
index ace25301..f0af71d3 100644
--- a/src/main/resources/templates/views/pm_sent.html
+++ b/src/main/resources/templates/views/pm_sent.html
@@ -19,7 +19,7 @@
<img src="{{ msg.user.avatar }}" alt="{{ msg.user.name }}"/>
</a>
</div>
- <div class="msg-ts">{{ msg.timestamp | prettyTime }}</div>
+ <div class="msg-ts">{{ msg.created | prettyTime }}</div>
</div>
<div class="msg-txt">{{ msg | formatMessage }}</div>
</div>
diff --git a/src/main/resources/templates/views/thread.html b/src/main/resources/templates/views/thread.html
index 47dfd000..2092cc1b 100644
--- a/src/main/resources/templates/views/thread.html
+++ b/src/main/resources/templates/views/thread.html
@@ -13,9 +13,9 @@
</span>
<div class="msg-ts">
<a href="/{{ msg.user.name }}/{{ msg.mid }}">
- <time datetime="{{ msg.timestamp | timestamp | date('yyyy-MM-dd HH:mm:ss') }}Z"
- title="{{ msg.timestamp | timestamp | date('yyyy-MM-dd HH:mm:ss') }} GMT">
- {{ msg.timestamp | prettyTime }}
+ <time datetime="{{ msg.created | timestamp | date('yyyy-MM-dd HH:mm:ss') }}Z"
+ title="{{ msg.created | timestamp | date('yyyy-MM-dd HH:mm:ss') }} GMT">
+ {{ msg.created | prettyTime }}
</time>
</a>
</div>
@@ -99,7 +99,11 @@
{% if recomm is not empty %}
<div class="msg-recomms">{{ i18n("messages","message.recommendedBy") }}
{% for rec in recomm %}
- <a href="/{{ rec }}/">@{{ rec }}</a>{% if loop.index < (loop.length - 1) %}, {% endif %}
+ {% if rec.uri.toString() is empty %}
+ <a href="/{{ rec.name }}/">@{{ rec.name }}</a>{% if loop.index < (loop.length - 1) %}, {% endif %}
+ {% else %}
+ <a href="{{ rec.uri }}" data-user-uri="1">@{{ rec.name }}</a>{% if loop.index < (loop.length - 1) %}, {% endif %}
+ {% endif %}
{% endfor %}
{% if msg.likes > recomm.size() %}
&nbsp;{{ i18n("messages","message.recommendedOthers", msg.likes - recomm.size()) }}
@@ -136,9 +140,9 @@
{% endif %}
<div class="msg-ts">
<a href="/{{ msg.mid }}#{{ msg.rid }}">
- <time datetime="{{ msg.timestamp | timestamp | date('yyyy-MM-dd HH:mm:ss') }}Z"
- title="{{ msg.timestamp | timestamp | date('yyyy-MM-dd HH:mm:ss') }} GMT">
- {{ msg.timestamp | prettyTime }}
+ <time datetime="{{ msg.created | timestamp | date('yyyy-MM-dd HH:mm:ss') }}Z"
+ title="{{ msg.created | timestamp | date('yyyy-MM-dd HH:mm:ss') }} GMT">
+ {{ msg.created | prettyTime }}
</time>
</a>
</div>