/* * 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 net.coobird.thumbnailator.Thumbnails; import org.apache.commons.dbcp2.BasicDataSource; import org.apache.commons.lang3.math.NumberUtils; import org.springframework.jdbc.core.JdbcTemplate; 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.stanza.model.Message; import rocks.xmpp.extensions.component.accept.ExternalComponent; import rocks.xmpp.extensions.nick.model.Nickname; import rocks.xmpp.extensions.oob.model.x.OobX; 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.URI; import java.net.URISyntaxException; 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; /** * @author Ugnich Anton */ @WebServlet(name = "Main", urlPatterns = {"/"}) @MultipartConfig public class Main extends HttpServlet { JdbcTemplate jdbc; ExternalComponent xmpp; Messages messages; Users users; PM pm; Others others; Subscriptions subscriptions; Notifications notifications; TGBot 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 TGBot(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); } } @Override public void destroy() { try { if (xmpp != null) xmpp.close(); log("ExternalComponent on juick-api destroyed"); } catch (Exception e) { log("Exception occurs on juick-api destroy", e); } } public void setupXmppComponent(final String host, final int port, final String jid, final String password) { XmppSessionConfiguration configuration = XmppSessionConfiguration.builder() .extensions(Extension.of(com.juick.Message.class)) .build(); xmpp = ExternalComponent.create(jid, password, configuration, host, port); try { xmpp.connect(); } catch (XmppException e) { log("xmpp extension", e); } } /** * 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, XmppSession 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); com.juick.Message jmsg = MessagesQueries.getMessage(sql, mid); if (xmpp != null) { Message xmsg = new Message(); xmsg.setFrom(Jid.of("juick@juick.com")); xmsg.setType(Message.Type.CHAT); xmsg.setThread("juick-" + mid); xmsg.addExtension(jmsg); xmsg.addExtension(new Nickname("@" + jmsg.getUser().getName())); 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; try { OobX xoob = new OobX(new URI(attachmentURL)); xmsg.addExtension(xoob); } catch (URISyntaxException e) { log("invalid uri: " + attachmentURL, e); } } String tagsStr2 = ""; for (String tag : tagsArr) { tagsStr2 += " *" + tag; } xmsg.setBody("@" + jmsg.getUser().getName() + ":" + tagsStr2 + "\n" + body + "\n\n#" + mid + " http://juick.com/" + mid); xmsg.setTo(Jid.of("juick@s2s.juick.com")); xmpp.send(xmsg); xmsg.setTo(Jid.of("juick@ws.juick.com")); xmpp.send(xmsg); xmsg.setTo(Jid.of("juick@push.juick.com")); xmpp.send(xmsg); xmsg.setTo(Jid.of("twitter@crosspost.juick.com")); xmpp.send(xmsg); xmsg.setTo(Jid.of("fb@crosspost.juick.com")); xmpp.send(xmsg); xmsg.setTo(Jid.of("jubo@nologin.ru")); 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, XmppSession 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); com.juick.Message jmsg = MessagesQueries.getReply(sql, mid, ridnew); if (xmpp != null) { Message xmsg = new Message(); xmsg.setFrom(Jid.of("juick@juick.com")); xmsg.setType(Message.Type.CHAT); xmsg.setThread("juick-" + mid); xmsg.addExtension(jmsg); String quote = reply != null ? reply.getText() : msg.getText(); if (quote.length() >= 50) { quote = quote.substring(0, 47) + "..."; } xmsg.addExtension(new Nickname("@" + jmsg.getUser().getName())); 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; try { xmsg.addExtension(new OobX(new URI(attachmentURL))); } catch (URISyntaxException e) { log("invalid uri: " + attachmentURL, e); } } xmsg.setBody("Reply by @" + jmsg.getUser().getName() + ":\n>" + quote + "\n" + body + "\n\n#" + mid + "/" + ridnew + " http://juick.com/" + mid + "#" + ridnew); xmsg.setTo(Jid.of("juick@s2s.juick.com")); xmpp.send(xmsg); xmsg.setTo(Jid.of("juick@ws.juick.com")); xmpp.send(xmsg); xmsg.setTo(Jid.of("juick@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); } } } @Override public void destroy() { try { if (xmpp != null) { xmpp.close(); } } catch (Exception e) { log("exception on destroy", e); } } }