/*
* 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 .
*/
package com.juick.api;
import com.juick.Tag;
import com.juick.json.MessageSerializer;
import com.juick.server.MessagesQueries;
import com.juick.server.SubscriptionsQueries;
import com.juick.server.TagQueries;
import com.juick.server.UserQueries;
import com.juick.xmpp.JID;
import com.juick.xmpp.Message;
import com.juick.xmpp.Stream;
import com.juick.xmpp.StreamComponent;
import com.juick.xmpp.extensions.JuickMessage;
import com.juick.xmpp.extensions.Nickname;
import com.juick.xmpp.extensions.XOOB;
import net.coobird.thumbnailator.Thumbnails;
import org.apache.commons.dbcp2.BasicDataSource;
import org.apache.commons.lang3.math.NumberUtils;
import org.springframework.jdbc.core.JdbcTemplate;
import javax.servlet.ServletException;
import javax.servlet.annotation.MultipartConfig;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.InvocationTargetException;
import java.net.Socket;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
*
* @author Ugnich Anton
*/
@WebServlet(name = "Main", urlPatterns = {"/"})
@MultipartConfig
public class Main extends HttpServlet implements Stream.StreamListener {
JdbcTemplate jdbc;
Stream xmpp;
Messages messages;
Users users;
PM pm;
Others others;
Subscriptions subscriptions;
Notifications notifications;
TelegramBotHook tgb;
SkypeEndpoint sep;
String tmpDir, imgDir;
@Override
public void init() throws ServletException {
super.init();
try {
Properties conf = new Properties();
conf.load(getServletContext().getResourceAsStream("/WEB-INF/juick.conf"));
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName(conf.getProperty("datasource_driver", "com.mysql.jdbc.Driver"));
dataSource.setUrl(conf.getProperty("datasource_url"));
jdbc = new JdbcTemplate(dataSource);
messages = new Messages(jdbc);
users = new Users(jdbc);
pm = new PM(jdbc);
others = new Others(jdbc);
subscriptions = new Subscriptions(jdbc);
notifications = new Notifications(jdbc);
tgb = new TelegramBotHook(jdbc, conf.getProperty("telegram_token", ""));
sep = new SkypeEndpoint();
setupXmppComponent(conf.getProperty("xmpp_host", "localhost"), Integer.parseInt(conf.getProperty("xmpp_port", "5347")),
conf.getProperty("xmpp_jid", "api.localhost"), conf.getProperty("xmpp_password"));
tmpDir = conf.getProperty("upload_tmp_dir", "/var/www/juick.com/i/tmp/");
imgDir = conf.getProperty("img_path", "/var/www/juick.com/i/");
} catch (IOException e) {
log("API initialization error", e);
}
}
public void setupXmppComponent(final String host, final int port, final String jid, final String password) {
ExecutorService executorService = Executors.newSingleThreadExecutor();
executorService.submit(() -> {
try {
Socket socket = new Socket(host, port);
xmpp = new StreamComponent(new JID(jid), socket.getInputStream(), socket.getOutputStream(), password);
xmpp.addListener(Main.this);
xmpp.startParsing();
} catch (IOException e) {
log("XMPP exception", e);
}
});
}
@Override
public void onStreamFail(Exception e) {
log("XMPP failed", e);
}
@Override
public void onStreamReady() {
log("XMPP STREAM READY");
}
/**
* Handles the HTTP GET
method.
* @param request servlet request
* @param response servlet response
* @throws ServletException if a servlet-specific error occurs
* @throws IOException if an I/O error occurs
*/
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
if (request.getCharacterEncoding() == null) {
request.setCharacterEncoding("UTF-8");
}
String uri = request.getRequestURI();
int vuid = Utils.getHttpAuthUID(jdbc, request);
if (vuid == 0) {
vuid = Utils.getVisitorQueryStringUID(jdbc, request);
}
if (uri.equals("/home")) {
if (vuid > 0) {
messages.doGetHome(request, response, vuid);
} else {
response.sendError(401);
}
} else if (uri.equals("/messages")) {
messages.doGet(request, response, vuid);
} else if (uri.equals("/thread")) {
messages.doThreadGet(request, response, vuid);
} else if (uri.equals("/users")) {
users.doGetUsers(request, response, vuid);
} else if (uri.equals("/users/read")) {
users.doGetUserRead(request, response, vuid);
} else if (uri.equals("/users/readers")) {
users.doGetUserReaders(request, response, vuid);
} else if (uri.equals("/pm")) {
if (vuid > 0) {
pm.doGetPM(request, response, vuid);
} else {
response.sendError(401);
}
} else if (uri.equals("/groups_pms")) {
if (vuid > 0) {
others.doGetGroupsPMs(request, response, vuid);
} else {
response.sendError(401);
}
} else if (uri.equals("/messages/recommended")) {
if (vuid > 0) {
messages.doGetRecommended(request, response, vuid);
} else {
response.sendError(401);
}
} else if (uri.equals("/messages/set_popular") && vuid == 3694) {
messages.doSetPopular(request, response, xmpp);
} else if (uri.equals("/messages/set_privacy") && vuid > 0) {
messages.doSetPrivacy(request, response, xmpp, vuid);
} else if (uri.equals("/subscriptions")) {
subscriptions.doGet(request, response, vuid);
} else if (uri.equals("/notifications")) {
notifications.doGet(request, response, vuid);
} else {
response.sendError(404);
}
}
/**
* Handles the HTTP POST
method.
* @param request servlet request
* @param response servlet response
* @throws ServletException if a servlet-specific error occurs
* @throws IOException if an I/O error occurs
*/
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
if (request.getCharacterEncoding() == null) {
request.setCharacterEncoding("UTF-8");
}
String uri = request.getRequestURI();
if (uri.equals("/tlgmbtwbhk")) {
try {
tgb.doPost(request);
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
log("telegram error", e);
}
return;
}
if (uri.equals("/skypebotendpoint")) {
sep.doPost(request);
return;
}
int vuid = Utils.getHttpAuthUID(jdbc, request);
if (vuid == 0) {
vuid = Utils.getVisitorQueryStringUID(jdbc, request);
}
if (vuid == 0) {
response.sendError(401);
return;
}
switch (uri) {
case "/post":
int mid = NumberUtils.toInt(request.getParameter("mid"), 0);
if (mid == 0) {
doPostMessage(jdbc, request, response, xmpp, vuid);
} else {
doPostComment(jdbc, request, response, xmpp, vuid);
}
break;
case "/pm":
pm.doPostPM(request, response, xmpp, vuid);
break;
default:
response.sendError(405);
break;
}
}
public void doPostMessage(JdbcTemplate sql, HttpServletRequest request, HttpServletResponse response, Stream xmpp, int vuid)
throws ServletException, IOException {
String body = request.getParameter("body");
if (body == null || body.length() < 1 || body.length() > 4096) {
response.sendError(400);
return;
}
body = body.replace("\r", "");
String tagsStr = request.getParameter("tags");
List tags = new ArrayList<>();
String tagsArr[] = new String[1];
if (tagsStr != null && !tagsStr.isEmpty()) {
tagsArr = tagsStr.split("[ \\,]");
for (int i = 0; i < tagsArr.length; i++) {
if (tagsArr[i].startsWith("*")) {
tagsArr[i] = tagsArr[i].substring(1);
}
if (tagsArr[i].length() > 64) {
tagsArr[i] = tagsArr[i].substring(0, 64);
}
}
tags = TagQueries.getTags(sql, tagsArr, true);
while (tags.size() > 5) {
tags.remove(5);
}
}
String attachmentFName = null;
try {
attachmentFName = Utils.receiveMultiPartFile(request, "attach");
} catch (Exception e) {
log("MULTIPART ERROR", e);
response.sendError(400);
return;
}
String paramImg = request.getParameter("img");
if (attachmentFName == null && paramImg != null && paramImg.length() > 10 ) {
try {
URL imgUrl = new URL(paramImg);
attachmentFName = Utils.downloadImage(imgUrl);
} catch (Exception e) {
log("DOWNLOAD ERROR", e);
response.sendError(500);
return;
}
}
String attachmentType = attachmentFName != null ? attachmentFName.substring(attachmentFName.length() - 3) : null;
int mid = MessagesQueries.createMessage(sql, vuid, body, attachmentType, tags);
SubscriptionsQueries.subscribeMessage(sql, mid, vuid);
JuickMessage jmsg = new JuickMessage(MessagesQueries.getMessage(sql, mid));
if (xmpp != null) {
Message xmsg = new Message();
xmsg.from = new JID("juick", "juick.com", null);
xmsg.type = Message.Type.chat;
xmsg.thread = "juick-" + mid;
xmsg.addChild(jmsg);
Nickname nick = new Nickname();
nick.Nickname = "@" + jmsg.getUser().getUName();
xmsg.addChild(nick);
if (attachmentFName != null) {
String fname = mid + "." + attachmentType;
String attachmentURL = "http://i.juick.com/photos-1024/" + fname;
Path origName = Paths.get(imgDir, "p", fname);
Files.move(Paths.get(tmpDir, attachmentFName), origName);
Thumbnails.of(origName.toFile()).size(1024, 1024).outputQuality(0.9)
.toFile(Paths.get(imgDir, "photos-1024", fname).toFile());
Thumbnails.of(origName.toFile()).size(512, 512).outputQuality(0.9)
.toFile(Paths.get(imgDir, "photos-512", fname).toFile());
Thumbnails.of(origName.toFile()).size(160, 120).outputQuality(0.9)
.toFile(Paths.get(imgDir, "ps", fname).toFile());
body = attachmentURL + "\n" + body;
XOOB xoob = new XOOB();
xoob.URL = attachmentURL;
xmsg.addChild(xoob);
}
String tagsStr2 = "";
for (String tag : tagsArr) {
tagsStr2 += " *" + tag;
}
xmsg.body = "@" + jmsg.getUser().getUName() + ":" + tagsStr2 + "\n" + body + "\n\n#" + mid + " http://juick.com/" + mid;
xmsg.to = new JID("juick", "s2s.juick.com", null);
xmpp.send(xmsg);
xmsg.to.Host = "ws.juick.com";
xmpp.send(xmsg);
xmsg.to.Host = "push.juick.com";
xmpp.send(xmsg);
xmsg.to.Host = "crosspost.juick.com";
xmsg.to.Username = "twitter";
xmpp.send(xmsg);
xmsg.to.Username = "fb";
xmpp.send(xmsg);
xmsg.to.Host = "nologin.ru";
xmsg.to.Username = "jubo";
xmpp.send(xmsg);
} else {
log("XMPP unavailable");
}
MessageSerializer serializer = new MessageSerializer();
Main.replyJSON(request, response, serializer.serialize(jmsg).toString());
}
public void doPostComment(JdbcTemplate sql, HttpServletRequest request, HttpServletResponse response, Stream xmpp, int vuid)
throws ServletException, IOException {
int mid = NumberUtils.toInt(request.getParameter("mid"), 0);
if (mid == 0) {
response.sendError(400);
return;
}
com.juick.Message msg = MessagesQueries.getMessage(sql, mid);
if (msg == null) {
response.sendError(404);
return;
}
int rid = NumberUtils.toInt(request.getParameter("rid"), 0);
com.juick.Message reply = null;
if (rid > 0) {
reply = MessagesQueries.getReply(sql, mid, rid);
if (reply == null) {
response.sendError(404);
return;
}
}
String body = request.getParameter("body");
if (body == null || body.length() < 1 || body.length() > 4096) {
response.sendError(400);
return;
}
body = body.replace("\r", "");
if ((msg.ReadOnly && msg.getUser().getUID() != vuid) || UserQueries.isInBLAny(sql, msg.getUser().getUID(), vuid)
|| (reply != null && UserQueries.isInBLAny(sql, reply.getUser().getUID(), vuid))) {
response.sendError(403);
return;
}
String attachmentFName = null;
try {
attachmentFName = Utils.receiveMultiPartFile(request, "attach");
} catch (Exception e) {
log("MULTIPART ERROR", e);
response.sendError(400);
return;
}
String paramImg = request.getParameter("img");
if (attachmentFName == null && paramImg != null && paramImg.length() > 10) {
try {
attachmentFName = Utils.downloadImage(new URL(paramImg));
} catch (Exception e) {
log("DOWNLOAD ERROR", e);
response.sendError(500);
return;
}
}
String attachmentType = attachmentFName != null ? attachmentFName.substring(attachmentFName.length() - 3) : null;
int ridnew = MessagesQueries.createReply(sql, mid, rid, vuid, body, attachmentType);
SubscriptionsQueries.subscribeMessage(sql, mid, vuid);
JuickMessage jmsg = new JuickMessage(MessagesQueries.getReply(sql, mid, ridnew));
if (xmpp != null) {
Message xmsg = new Message();
xmsg.from = new JID("juick", "juick.com", null);
xmsg.type = Message.Type.chat;
xmsg.thread = "juick-" + mid;
xmsg.addChild(jmsg);
String quote = reply != null ? reply.getText() : msg.getText();
if (quote.length() >= 50) {
quote = quote.substring(0, 47) + "...";
}
Nickname nick = new Nickname();
nick.Nickname = "@" + jmsg.getUser().getUName();
xmsg.addChild(nick);
if (attachmentFName != null) {
String fname = mid + "-" + ridnew + "." + attachmentType;
String attachmentURL = "http://i.juick.com/photos-1024/" + fname;
Path origName = Paths.get(imgDir, "p", fname);
Files.move(Paths.get(tmpDir, attachmentFName), origName);
Thumbnails.of(origName.toFile()).size(1024, 1024).outputQuality(0.9)
.toFile(Paths.get(imgDir, "photos-1024", fname).toFile());
Thumbnails.of(origName.toFile()).size(512, 512).outputQuality(0.9)
.toFile(Paths.get(imgDir, "photos-512", fname).toFile());
Thumbnails.of(origName.toFile()).size(160, 120).outputQuality(0.9)
.toFile(Paths.get(imgDir, "ps", fname).toFile());
body = attachmentURL + "\n" + body;
XOOB xoob = new XOOB();
xoob.URL = attachmentURL;
xmsg.addChild(xoob);
}
xmsg.body = "Reply by @" + jmsg.getUser().getUName() + ":\n>" + quote + "\n" + body + "\n\n#" + mid + "/" + ridnew + " http://juick.com/" + mid + "#" + ridnew;
xmsg.to = new JID("juick", "s2s.juick.com", null);
xmpp.send(xmsg);
xmsg.to.Host = "ws.juick.com";
xmpp.send(xmsg);
xmsg.to.Host = "push.juick.com";
xmpp.send(xmsg);
} else {
log("XMPP unavailable");
}
MessageSerializer serializer = new MessageSerializer();
Main.replyJSON(request, response, serializer.serialize(jmsg).toString());
}
public static void replyJSON(HttpServletRequest request, HttpServletResponse response, String json) throws IOException {
response.setContentType("application/json; charset=UTF-8");
response.setHeader("Access-Control-Allow-Origin", "*");
String callback = request.getParameter("callback");
if (callback != null && (callback.length() > 64 || !callback.matches("[a-zA-Z0-9\\-\\_]+"))) {
callback = null;
}
try (PrintWriter out = response.getWriter()) {
if (callback != null) {
out.print(callback + "(");
out.print(json);
out.print(")");
} else {
out.print(json);
}
}
}
}