/* * 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.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.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; import java.util.logging.LogManager; /** * * @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; TelegramBotHook tgb; SkypeEndpoint sep; String tmpDir, imgDir; @Override public void init() throws ServletException { super.init(); try { LogManager.getLogManager().readConfiguration(getServletContext().getResourceAsStream("/WEB-INF/logging.properties")); 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); 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 <code>GET</code> 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 { response.sendError(404); } } /** * Handles the HTTP <code>POST</code> 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 = Utils.parseInt(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<Tag> 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 = Utils.parseInt(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 = Utils.parseInt(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); } } } }