readers = ImageIO.getImageReaders(iis);
-
- String format = StringUtils.EMPTY;
- while (readers.hasNext()) {
- ImageReader read = readers.next();
- format = read.getFormatName();
- }
- URLConnection urlConn;
- try {
- urlConn = url.openConnection();
- } catch (IOException e) {
- logger.error(String.format("Failed open url: %s", url.toString()));
- throw e;
- }
-
- try (InputStream is = new BufferedInputStream(urlConn.getInputStream())) {
- String attachmentType = attachmentTypeFromFormat(format);
-
- String attachmentFName = DigestUtils.md5Hex(UUID.randomUUID().toString()) + "." + attachmentType;
- Files.copy(is, Paths.get(tmpDir, attachmentFName));
- return URI.create(String.format("juick://%s", attachmentFName));
- } catch (IOException e) {
- logger.error(String.format("Failed download image by url: %s", url.toString()), e);
- throw e;
- }
- }
-}
diff --git a/src/main/java/com/juick/server/util/ImageUtils.java b/src/main/java/com/juick/server/util/ImageUtils.java
deleted file mode 100644
index e06339ba..00000000
--- a/src/main/java/com/juick/server/util/ImageUtils.java
+++ /dev/null
@@ -1,173 +0,0 @@
-/*
- * 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
- * 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.server.util;
-
-import com.juick.model.Attachment;
-import org.apache.commons.imaging.ImageReadException;
-import org.apache.commons.imaging.Imaging;
-import org.apache.commons.imaging.common.ImageMetadata;
-import org.apache.commons.imaging.formats.jpeg.JpegImageMetadata;
-import org.apache.commons.imaging.formats.tiff.TiffField;
-import org.apache.commons.imaging.formats.tiff.constants.TiffTagConstants;
-import org.apache.commons.io.FileUtils;
-import org.apache.commons.io.FilenameUtils;
-import org.imgscalr.Scalr;
-import org.imgscalr.Scalr.Rotation;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import javax.imageio.ImageIO;
-import javax.imageio.ImageReader;
-import javax.imageio.stream.ImageInputStream;
-import java.awt.image.BufferedImage;
-import java.io.File;
-import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.nio.file.StandardCopyOption;
-import java.util.Iterator;
-
-public class ImageUtils {
- private static final Logger logger = LoggerFactory.getLogger(ImageUtils.class);
-
- private String imgDir;
- private String tmpDir;
-
- public ImageUtils(String imgDir, String tmpDir) {
- this.imgDir = imgDir;
- this.tmpDir = tmpDir;
- }
-/**
- * Returns BufferedImage
, same as ImageIO.read()
does.
- *
- * JPEG images with EXIF metadata are rotated according to Orientation tag.
- *
- * @param imageFile a File
to read from.
- */
- private static BufferedImage readImageWithOrientation(File imageFile)
- throws IOException {
-
- BufferedImage image = ImageIO.read(imageFile);
- if (!FilenameUtils.getExtension(imageFile.getName()).equals("jpg")) {
- return image;
- }
-
- try {
- ImageMetadata metadata = Imaging.getMetadata(imageFile);
-
- if (metadata instanceof JpegImageMetadata) {
- JpegImageMetadata jpegMetadata = (JpegImageMetadata) metadata;
- TiffField orientationField = jpegMetadata.findEXIFValue(TiffTagConstants.TIFF_TAG_ORIENTATION);
-
- if (orientationField != null) {
- int orientation = orientationField.getIntValue();
- switch (orientation) {
- case TiffTagConstants.ORIENTATION_VALUE_ROTATE_90_CW:
- image = Scalr.rotate(image, Rotation.CW_90);
- break;
- case TiffTagConstants.ORIENTATION_VALUE_ROTATE_180:
- image = Scalr.rotate(image, Rotation.CW_180);
- break;
- case TiffTagConstants.ORIENTATION_VALUE_ROTATE_270_CW:
- image = Scalr.rotate(image, Rotation.CW_270);
- break;
- case TiffTagConstants.ORIENTATION_VALUE_MIRROR_HORIZONTAL:
- image = Scalr.rotate(image, Rotation.FLIP_HORZ);
- break;
- case TiffTagConstants.ORIENTATION_VALUE_MIRROR_VERTICAL:
- image = Scalr.rotate(image, Rotation.FLIP_VERT);
- break;
- case TiffTagConstants.ORIENTATION_VALUE_MIRROR_HORIZONTAL_AND_ROTATE_90_CW:
- image = Scalr.rotate(Scalr.rotate(image, Rotation.FLIP_HORZ), Rotation.CW_90);
- break;
- case TiffTagConstants.ORIENTATION_VALUE_MIRROR_HORIZONTAL_AND_ROTATE_270_CW:
- image = Scalr.rotate(Scalr.rotate(image, Rotation.FLIP_HORZ), Rotation.CW_270);
- break;
- case TiffTagConstants.ORIENTATION_VALUE_HORIZONTAL_NORMAL:
- default:
- // do nothing
- break;
- }
- }
- }
- } catch (ImageReadException | IOException e) {
- // failed to read metadata.
- // nothing to do here, return image as is.
- }
-
- return image;
- }
-
- public void saveImageWithPreviews(String tempFilename, String outputFilename)
- throws IOException {
- String ext = FilenameUtils.getExtension(outputFilename);
-
- Path outputImagePath = Paths.get(imgDir, "p", outputFilename);
- // this throws strange exceptions
- // Files.move(Paths.get(tmpDir, tempFilename), outputImagePath);
- FileUtils.moveFile(Paths.get(tmpDir, tempFilename).toFile(), outputImagePath.toFile());
- BufferedImage originalImage = readImageWithOrientation(outputImagePath.toFile());
-
- int width = originalImage.getWidth();
- int height = originalImage.getHeight();
- int maxDimension = Math.max(width, height);
- BufferedImage image1024 = (maxDimension > 1024) ? Scalr.resize(originalImage, 1024) : originalImage;
- BufferedImage image0512 = (maxDimension > 512) ? Scalr.resize(originalImage, 512) : originalImage;
- BufferedImage image0160 = (maxDimension > 160) ? Scalr.resize(originalImage, 160) : originalImage;
- ImageIO.write(image1024, ext, Paths.get(imgDir, "photos-1024", outputFilename).toFile());
- ImageIO.write(image0512, ext, Paths.get(imgDir, "photos-512", outputFilename).toFile());
- ImageIO.write(image0160, ext, Paths.get(imgDir, "ps", outputFilename).toFile());
- }
-
- public void saveAvatar(String tempFilename, int uid)
- throws IOException {
- String ext = FilenameUtils.getExtension(tempFilename);
- String originalName = String.format("%s.%s", uid, ext);
- Path originalPath = Paths.get(imgDir, "ao", originalName);
- Files.move(Paths.get(tmpDir, tempFilename), originalPath, StandardCopyOption.REPLACE_EXISTING);
- BufferedImage originalImage = ImageIO.read(originalPath.toFile());
-
- String targetExt = "png";
- String targetName = String.format("%s.%s", uid, targetExt);
- ImageIO.write(Scalr.resize(originalImage, 96), targetExt, Paths.get(imgDir, "a", targetName).toFile());
- ImageIO.write(Scalr.resize(originalImage, 32), targetExt, Paths.get(imgDir, "as", targetName).toFile());
- }
- public Attachment getAttachment(File imgFile) throws IOException {
- Attachment attachment = new Attachment();
- try (ImageInputStream stream = ImageIO.createImageInputStream(imgFile)) {
- Iterator iter = ImageIO.getImageReaders(stream);
- while (iter.hasNext()) {
- ImageReader reader = iter.next();
- try {
- reader.setInput(stream);
- attachment.setWidth(reader.getWidth(reader.getMinIndex()));
- attachment.setHeight(reader.getHeight(reader.getMinIndex()));
- return attachment;
- } catch (Exception e) {
- logger.debug("Error reading {}, trying next reader", imgFile.getAbsolutePath());
- } finally {
- reader.dispose();
- }
- }
- }
-
- logger.warn("Not a known image file {}", imgFile.getAbsolutePath());
- return attachment;
- }
-}
\ No newline at end of file
diff --git a/src/main/java/com/juick/server/util/TagUtils.java b/src/main/java/com/juick/server/util/TagUtils.java
deleted file mode 100644
index 754d8020..00000000
--- a/src/main/java/com/juick/server/util/TagUtils.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * 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
- * 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.server.util;
-
-import com.juick.model.Tag;
-import org.apache.commons.collections4.CollectionUtils;
-import org.apache.commons.lang3.StringUtils;
-
-import java.util.List;
-import java.util.stream.Collectors;
-
-/**
- * Created by aalexeev on 11/13/16.
- */
-public class TagUtils {
- private TagUtils() {
- throw new IllegalStateException();
- }
-
- public static String toString(final List tags) {
- if (CollectionUtils.isEmpty(tags))
- return StringUtils.EMPTY;
-
- return tags.stream().map(t -> "*" + t.getName())
- .collect(Collectors.joining(" "));
- }
-}
diff --git a/src/main/java/com/juick/server/util/WebUtils.java b/src/main/java/com/juick/server/util/WebUtils.java
deleted file mode 100644
index bc3ac63a..00000000
--- a/src/main/java/com/juick/server/util/WebUtils.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * 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
- * 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.server.util;
-
-import java.util.regex.Pattern;
-
-/**
- * Created by aalexeev on 11/28/16.
- */
-public class WebUtils {
- private WebUtils() {
- throw new IllegalStateException();
- }
-
- private static final Pattern USER_NAME_PATTERN = Pattern.compile("[a-zA-Z-_\\d]{2,16}");
-
- private static final Pattern POST_NUMBER_PATTERN = Pattern.compile("-?\\d+");
-
- private static final Pattern JID_PATTERN = Pattern.compile("^[a-zA-Z0-9\\\\-\\\\_\\\\@\\\\.]{6,64}$");
-
-
- public static boolean isPostNumber(final String aString) {
- return aString != null && POST_NUMBER_PATTERN.matcher(aString).matches();
- }
-
- public static boolean isNotPostNumber(final String aString) {
- return !isPostNumber(aString);
- }
-
- public static boolean isUserName(final String aString) {
- return aString != null && USER_NAME_PATTERN.matcher(aString).matches();
- }
-
- public static boolean isNotUserName(final String aString) {
- return !isUserName(aString);
- }
-
- public static boolean isJid(final String aString) {
- return aString != null && JID_PATTERN.matcher(aString).matches();
- }
-
- public static boolean isNotJid(final String aString) {
- return !isJid(aString);
- }
-
-
-}
diff --git a/src/main/java/com/juick/server/xmpp/JidConverter.java b/src/main/java/com/juick/server/xmpp/JidConverter.java
deleted file mode 100644
index fdf80108..00000000
--- a/src/main/java/com/juick/server/xmpp/JidConverter.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * 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
- * 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.server.xmpp;
-
-import org.springframework.core.convert.converter.Converter;
-import org.springframework.lang.Nullable;
-import rocks.xmpp.addr.Jid;
-
-import javax.annotation.Nonnull;
-
-public class JidConverter implements Converter {
- @Nullable
- @Override
- public Jid convert(@Nonnull String jidStr) {
- return Jid.of(jidStr);
- }
-}
diff --git a/src/main/java/com/juick/server/xmpp/iq/MessageQuery.java b/src/main/java/com/juick/server/xmpp/iq/MessageQuery.java
deleted file mode 100644
index c973b624..00000000
--- a/src/main/java/com/juick/server/xmpp/iq/MessageQuery.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * 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
- * 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.server.xmpp.iq;
-
-import javax.xml.bind.annotation.XmlRootElement;
-
-@XmlRootElement(name = "query")
-public class MessageQuery {
- private MessageQuery() {
-
- }
-}
diff --git a/src/main/java/com/juick/server/xmpp/iq/package-info.java b/src/main/java/com/juick/server/xmpp/iq/package-info.java
deleted file mode 100644
index 822fb8c4..00000000
--- a/src/main/java/com/juick/server/xmpp/iq/package-info.java
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * 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
- * 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 .
- */
-
-@XmlAccessorType(XmlAccessType.FIELD)
-@XmlSchema(namespace = "http://juick.com/query#messages", elementFormDefault = XmlNsForm.QUALIFIED)
-package com.juick.server.xmpp.iq;
-
-import javax.xml.bind.annotation.XmlAccessType;
-import javax.xml.bind.annotation.XmlAccessorType;
-import javax.xml.bind.annotation.XmlNsForm;
-import javax.xml.bind.annotation.XmlSchema;
\ No newline at end of file
diff --git a/src/main/java/com/juick/service/ImagesServiceImpl.java b/src/main/java/com/juick/service/ImagesServiceImpl.java
index abaec940..5884b0d0 100644
--- a/src/main/java/com/juick/service/ImagesServiceImpl.java
+++ b/src/main/java/com/juick/service/ImagesServiceImpl.java
@@ -20,7 +20,7 @@ package com.juick.service;
import com.juick.model.Attachment;
import com.juick.model.Message;
import com.juick.model.Photo;
-import com.juick.server.util.ImageUtils;
+import com.juick.util.ImageUtils;
import org.springframework.util.StringUtils;
import java.io.File;
diff --git a/src/main/java/com/juick/service/UserService.java b/src/main/java/com/juick/service/UserService.java
index 4bd5486d..fbbab0ad 100644
--- a/src/main/java/com/juick/service/UserService.java
+++ b/src/main/java/com/juick/service/UserService.java
@@ -55,12 +55,8 @@ public interface UserService {
List getJIDsbyUID(int uid);
- int getUIDbyJID(String jid);
-
int getUIDbyName(String uname);
- int getUIDbyHash(String hash);
-
@Nonnull
User getUserByHash(String hash);
diff --git a/src/main/java/com/juick/service/UserServiceImpl.java b/src/main/java/com/juick/service/UserServiceImpl.java
index 23c55bbe..84ff1ff5 100644
--- a/src/main/java/com/juick/service/UserServiceImpl.java
+++ b/src/main/java/com/juick/service/UserServiceImpl.java
@@ -248,19 +248,6 @@ public class UserServiceImpl extends BaseJdbcService implements UserService {
return getJdbcTemplate().queryForList("SELECT jid FROM jids WHERE user_id = ? AND active = 1", String.class, uid);
}
- @Transactional(readOnly = true)
- @Override
- public int getUIDbyJID(final String jid) {
- if (StringUtils.isNotBlank(jid)) {
- List list = getJdbcTemplate().queryForList(
- "SELECT user_id FROM jids WHERE jid = ?", Integer.class, jid);
-
- if (!list.isEmpty())
- return list.get(0);
- }
- return 0;
- }
-
@Transactional(readOnly = true)
@Override
public int getUIDbyName(final String uname) {
@@ -274,19 +261,6 @@ public class UserServiceImpl extends BaseJdbcService implements UserService {
return 0;
}
- @Transactional(readOnly = true)
- @Override
- public int getUIDbyHash(final String hash) {
- if (StringUtils.isNotBlank(hash)) {
- List list = getJdbcTemplate().queryForList(
- "SELECT user_id FROM logins WHERE hash = ?", Integer.class, hash);
-
- if (!list.isEmpty())
- return list.get(0);
- }
- return 0;
- }
-
@Transactional(readOnly = true)
@Override
public User getUserByHash(final String hash) {
diff --git a/src/main/java/com/juick/service/security/HTTPSignatureAuthenticationFilter.java b/src/main/java/com/juick/service/security/HTTPSignatureAuthenticationFilter.java
index 1b3cb936..9878df82 100644
--- a/src/main/java/com/juick/service/security/HTTPSignatureAuthenticationFilter.java
+++ b/src/main/java/com/juick/service/security/HTTPSignatureAuthenticationFilter.java
@@ -18,7 +18,7 @@
package com.juick.service.security;
import com.juick.model.User;
-import com.juick.server.SignatureManager;
+import com.juick.SignatureManager;
import com.juick.service.UserService;
import com.juick.service.security.entities.JuickUser;
import org.apache.commons.lang3.StringUtils;
diff --git a/src/main/java/com/juick/util/HeaderRequestInterceptor.java b/src/main/java/com/juick/util/HeaderRequestInterceptor.java
new file mode 100644
index 00000000..22a3de06
--- /dev/null
+++ b/src/main/java/com/juick/util/HeaderRequestInterceptor.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2008-2020, 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 .
+ */
+
+package com.juick.util;
+
+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/util/HttpBadRequestException.java b/src/main/java/com/juick/util/HttpBadRequestException.java
new file mode 100644
index 00000000..386b52df
--- /dev/null
+++ b/src/main/java/com/juick/util/HttpBadRequestException.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2008-2020, 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 .
+ */
+
+package com.juick.util;
+
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.ResponseStatus;
+
+/**
+ * Created by vt on 24/11/2016.
+ */
+@ResponseStatus(value = HttpStatus.BAD_REQUEST)
+public class HttpBadRequestException extends RuntimeException {
+ public HttpBadRequestException() {
+ super("the request was bad", null, false, false);
+ }
+}
diff --git a/src/main/java/com/juick/util/HttpForbiddenException.java b/src/main/java/com/juick/util/HttpForbiddenException.java
new file mode 100644
index 00000000..e2211574
--- /dev/null
+++ b/src/main/java/com/juick/util/HttpForbiddenException.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2008-2020, 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 .
+ */
+
+package com.juick.util;
+
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.ResponseStatus;
+
+/**
+ * Created by vt on 24/11/2016.
+ */
+@ResponseStatus(value = HttpStatus.FORBIDDEN)
+public class HttpForbiddenException extends RuntimeException {
+ public HttpForbiddenException() {
+ super(StringUtils.EMPTY, null, false, false);
+ }
+
+}
diff --git a/src/main/java/com/juick/util/HttpNotFoundException.java b/src/main/java/com/juick/util/HttpNotFoundException.java
new file mode 100644
index 00000000..f9d1ffbe
--- /dev/null
+++ b/src/main/java/com/juick/util/HttpNotFoundException.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2008-2020, 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 .
+ */
+
+package com.juick.util;
+
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.ResponseStatus;
+
+/**
+ * Created by vt on 24/11/2016.
+ */
+@ResponseStatus(value = HttpStatus.NOT_FOUND)
+public class HttpNotFoundException extends RuntimeException {
+ public HttpNotFoundException() {
+ super(StringUtils.EMPTY, null, false, false);
+ }
+}
diff --git a/src/main/java/com/juick/util/HttpUtils.java b/src/main/java/com/juick/util/HttpUtils.java
new file mode 100644
index 00000000..46bb3e2d
--- /dev/null
+++ b/src/main/java/com/juick/util/HttpUtils.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2008-2020, 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 .
+ */
+package com.juick.util;
+
+import org.apache.commons.codec.digest.DigestUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.MediaType;
+import org.springframework.web.multipart.MultipartFile;
+
+import javax.imageio.ImageIO;
+import javax.imageio.ImageReader;
+import javax.imageio.stream.ImageInputStream;
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.net.URL;
+import java.net.URLConnection;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.Iterator;
+import java.util.UUID;
+
+/**
+ *
+ * @author Ugnich Anton
+ */
+public class HttpUtils {
+ private static final Logger logger = LoggerFactory.getLogger(HttpUtils.class);
+
+ public static URI receiveMultiPartFile(MultipartFile attach, String tmpDir) throws IOException {
+ if (attach != null && !attach.isEmpty()) {
+ ImageInputStream iis = ImageIO.createImageInputStream(attach.getInputStream());
+ Iterator readers = ImageIO.getImageReaders(iis);
+
+ String format = StringUtils.EMPTY;
+ while (readers.hasNext()) {
+ ImageReader read = readers.next();
+ format = read.getFormatName();
+ }
+ String attachmentType = attachmentTypeFromFormat(format);
+ if (attachmentType.equals("jpg") || attachmentType.equals("png")) {
+ String attachmentFName = DigestUtils.md5Hex(UUID.randomUUID().toString()) + "." + attachmentType;
+ try {
+ Files.write(Paths.get(tmpDir, attachmentFName),
+ attach.getBytes());
+ return URI.create(String.format("juick://%s", attachmentFName));
+ } catch (IOException e) {
+ logger.warn("file receive error", e);
+ }
+ }
+ logger.warn("file type is unknown: {}", attach.getOriginalFilename());
+ }
+ return URI.create(StringUtils.EMPTY);
+ }
+
+ private static String attachmentTypeFromFormat(String format) throws IOException {
+ if (format != null && format.equals("JPEG")) {
+ return "jpg";
+ } else if (format != null && format.equals("png")) {
+ return "png";
+ } else {
+ throw new IOException("Wrong file type: " + format);
+ }
+ }
+
+ public static String mediaType(String attachmentType) {
+ return attachmentType.equals("jpg") ? MediaType.IMAGE_JPEG_VALUE : MediaType.IMAGE_PNG_VALUE;
+ }
+
+ public static URI downloadImage(URL url, String tmpDir) throws IOException {
+ ImageInputStream iis = ImageIO.createImageInputStream(url.openStream());
+ Iterator readers = ImageIO.getImageReaders(iis);
+
+ String format = StringUtils.EMPTY;
+ while (readers.hasNext()) {
+ ImageReader read = readers.next();
+ format = read.getFormatName();
+ }
+ URLConnection urlConn;
+ try {
+ urlConn = url.openConnection();
+ } catch (IOException e) {
+ logger.error(String.format("Failed open url: %s", url.toString()));
+ throw e;
+ }
+
+ try (InputStream is = new BufferedInputStream(urlConn.getInputStream())) {
+ String attachmentType = attachmentTypeFromFormat(format);
+
+ String attachmentFName = DigestUtils.md5Hex(UUID.randomUUID().toString()) + "." + attachmentType;
+ Files.copy(is, Paths.get(tmpDir, attachmentFName));
+ return URI.create(String.format("juick://%s", attachmentFName));
+ } catch (IOException e) {
+ logger.error(String.format("Failed download image by url: %s", url.toString()), e);
+ throw e;
+ }
+ }
+}
diff --git a/src/main/java/com/juick/util/ImageUtils.java b/src/main/java/com/juick/util/ImageUtils.java
new file mode 100644
index 00000000..38e1de08
--- /dev/null
+++ b/src/main/java/com/juick/util/ImageUtils.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2008-2020, 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 .
+ */
+
+package com.juick.util;
+
+import com.juick.model.Attachment;
+import org.apache.commons.imaging.ImageReadException;
+import org.apache.commons.imaging.Imaging;
+import org.apache.commons.imaging.common.ImageMetadata;
+import org.apache.commons.imaging.formats.jpeg.JpegImageMetadata;
+import org.apache.commons.imaging.formats.tiff.TiffField;
+import org.apache.commons.imaging.formats.tiff.constants.TiffTagConstants;
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.FilenameUtils;
+import org.imgscalr.Scalr;
+import org.imgscalr.Scalr.Rotation;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.imageio.ImageIO;
+import javax.imageio.ImageReader;
+import javax.imageio.stream.ImageInputStream;
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardCopyOption;
+import java.util.Iterator;
+
+public class ImageUtils {
+ private static final Logger logger = LoggerFactory.getLogger(ImageUtils.class);
+
+ private String imgDir;
+ private String tmpDir;
+
+ public ImageUtils(String imgDir, String tmpDir) {
+ this.imgDir = imgDir;
+ this.tmpDir = tmpDir;
+ }
+/**
+ * Returns BufferedImage
, same as ImageIO.read()
does.
+ *
+ * JPEG images with EXIF metadata are rotated according to Orientation tag.
+ *
+ * @param imageFile a File
to read from.
+ */
+ private static BufferedImage readImageWithOrientation(File imageFile)
+ throws IOException {
+
+ BufferedImage image = ImageIO.read(imageFile);
+ if (!FilenameUtils.getExtension(imageFile.getName()).equals("jpg")) {
+ return image;
+ }
+
+ try {
+ ImageMetadata metadata = Imaging.getMetadata(imageFile);
+
+ if (metadata instanceof JpegImageMetadata) {
+ JpegImageMetadata jpegMetadata = (JpegImageMetadata) metadata;
+ TiffField orientationField = jpegMetadata.findEXIFValue(TiffTagConstants.TIFF_TAG_ORIENTATION);
+
+ if (orientationField != null) {
+ int orientation = orientationField.getIntValue();
+ switch (orientation) {
+ case TiffTagConstants.ORIENTATION_VALUE_ROTATE_90_CW:
+ image = Scalr.rotate(image, Rotation.CW_90);
+ break;
+ case TiffTagConstants.ORIENTATION_VALUE_ROTATE_180:
+ image = Scalr.rotate(image, Rotation.CW_180);
+ break;
+ case TiffTagConstants.ORIENTATION_VALUE_ROTATE_270_CW:
+ image = Scalr.rotate(image, Rotation.CW_270);
+ break;
+ case TiffTagConstants.ORIENTATION_VALUE_MIRROR_HORIZONTAL:
+ image = Scalr.rotate(image, Rotation.FLIP_HORZ);
+ break;
+ case TiffTagConstants.ORIENTATION_VALUE_MIRROR_VERTICAL:
+ image = Scalr.rotate(image, Rotation.FLIP_VERT);
+ break;
+ case TiffTagConstants.ORIENTATION_VALUE_MIRROR_HORIZONTAL_AND_ROTATE_90_CW:
+ image = Scalr.rotate(Scalr.rotate(image, Rotation.FLIP_HORZ), Rotation.CW_90);
+ break;
+ case TiffTagConstants.ORIENTATION_VALUE_MIRROR_HORIZONTAL_AND_ROTATE_270_CW:
+ image = Scalr.rotate(Scalr.rotate(image, Rotation.FLIP_HORZ), Rotation.CW_270);
+ break;
+ case TiffTagConstants.ORIENTATION_VALUE_HORIZONTAL_NORMAL:
+ default:
+ // do nothing
+ break;
+ }
+ }
+ }
+ } catch (ImageReadException | IOException e) {
+ // failed to read metadata.
+ // nothing to do here, return image as is.
+ }
+
+ return image;
+ }
+
+ public void saveImageWithPreviews(String tempFilename, String outputFilename)
+ throws IOException {
+ String ext = FilenameUtils.getExtension(outputFilename);
+
+ Path outputImagePath = Paths.get(imgDir, "p", outputFilename);
+ // this throws strange exceptions
+ // Files.move(Paths.get(tmpDir, tempFilename), outputImagePath);
+ FileUtils.moveFile(Paths.get(tmpDir, tempFilename).toFile(), outputImagePath.toFile());
+ BufferedImage originalImage = readImageWithOrientation(outputImagePath.toFile());
+
+ int width = originalImage.getWidth();
+ int height = originalImage.getHeight();
+ int maxDimension = Math.max(width, height);
+ BufferedImage image1024 = (maxDimension > 1024) ? Scalr.resize(originalImage, 1024) : originalImage;
+ BufferedImage image0512 = (maxDimension > 512) ? Scalr.resize(originalImage, 512) : originalImage;
+ BufferedImage image0160 = (maxDimension > 160) ? Scalr.resize(originalImage, 160) : originalImage;
+ ImageIO.write(image1024, ext, Paths.get(imgDir, "photos-1024", outputFilename).toFile());
+ ImageIO.write(image0512, ext, Paths.get(imgDir, "photos-512", outputFilename).toFile());
+ ImageIO.write(image0160, ext, Paths.get(imgDir, "ps", outputFilename).toFile());
+ }
+
+ public void saveAvatar(String tempFilename, int uid)
+ throws IOException {
+ String ext = FilenameUtils.getExtension(tempFilename);
+ String originalName = String.format("%s.%s", uid, ext);
+ Path originalPath = Paths.get(imgDir, "ao", originalName);
+ Files.move(Paths.get(tmpDir, tempFilename), originalPath, StandardCopyOption.REPLACE_EXISTING);
+ BufferedImage originalImage = ImageIO.read(originalPath.toFile());
+
+ String targetExt = "png";
+ String targetName = String.format("%s.%s", uid, targetExt);
+ ImageIO.write(Scalr.resize(originalImage, 96), targetExt, Paths.get(imgDir, "a", targetName).toFile());
+ ImageIO.write(Scalr.resize(originalImage, 32), targetExt, Paths.get(imgDir, "as", targetName).toFile());
+ }
+ public Attachment getAttachment(File imgFile) throws IOException {
+ Attachment attachment = new Attachment();
+ try (ImageInputStream stream = ImageIO.createImageInputStream(imgFile)) {
+ Iterator iter = ImageIO.getImageReaders(stream);
+ while (iter.hasNext()) {
+ ImageReader reader = iter.next();
+ try {
+ reader.setInput(stream);
+ attachment.setWidth(reader.getWidth(reader.getMinIndex()));
+ attachment.setHeight(reader.getHeight(reader.getMinIndex()));
+ return attachment;
+ } catch (Exception e) {
+ logger.debug("Error reading {}, trying next reader", imgFile.getAbsolutePath());
+ } finally {
+ reader.dispose();
+ }
+ }
+ }
+
+ logger.warn("Not a known image file {}", imgFile.getAbsolutePath());
+ return attachment;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/juick/util/TagUtils.java b/src/main/java/com/juick/util/TagUtils.java
new file mode 100644
index 00000000..2ec03e48
--- /dev/null
+++ b/src/main/java/com/juick/util/TagUtils.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2008-2020, 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 .
+ */
+
+package com.juick.util;
+
+import com.juick.model.Tag;
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * Created by aalexeev on 11/13/16.
+ */
+public class TagUtils {
+ private TagUtils() {
+ throw new IllegalStateException();
+ }
+
+ public static String toString(final List tags) {
+ if (CollectionUtils.isEmpty(tags))
+ return StringUtils.EMPTY;
+
+ return tags.stream().map(t -> "*" + t.getName())
+ .collect(Collectors.joining(" "));
+ }
+}
diff --git a/src/main/java/com/juick/util/WebUtils.java b/src/main/java/com/juick/util/WebUtils.java
new file mode 100644
index 00000000..3a8c7620
--- /dev/null
+++ b/src/main/java/com/juick/util/WebUtils.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2008-2020, 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 .
+ */
+
+package com.juick.util;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.Optional;
+import java.util.regex.Pattern;
+
+/**
+ * Created by aalexeev on 11/28/16.
+ */
+public class WebUtils {
+ private WebUtils() {
+ throw new IllegalStateException();
+ }
+
+ private static final Pattern USER_NAME_PATTERN = Pattern.compile("[a-zA-Z-_\\d]{2,16}");
+
+ private static final Pattern POST_NUMBER_PATTERN = Pattern.compile("-?\\d+");
+
+ private static final Pattern JID_PATTERN = Pattern.compile("^[a-zA-Z0-9\\\\-\\\\_\\\\@\\\\.]{6,64}$");
+
+
+ public static boolean isPostNumber(final String aString) {
+ return aString != null && POST_NUMBER_PATTERN.matcher(aString).matches();
+ }
+
+ public static boolean isNotPostNumber(final String aString) {
+ return !isPostNumber(aString);
+ }
+
+ public static boolean isUserName(final String aString) {
+ return aString != null && USER_NAME_PATTERN.matcher(aString).matches();
+ }
+
+ public static boolean isNotUserName(final String aString) {
+ return !isUserName(aString);
+ }
+
+ public static boolean isJid(final String aString) {
+ return aString != null && JID_PATTERN.matcher(aString).matches();
+ }
+
+ public static boolean isNotJid(final String aString) {
+ return !isJid(aString);
+ }
+
+ public static String encodeSphinx(String str) {
+ return str.replaceAll("@", "\\\\@")
+ .replaceAll("\\'", "\\\\'")
+ .replaceAll("=", "\\\\\\\\=");
+ }
+ /**
+ * Returns the viewName to return for coming back to the sender url
+ *
+ * @param request Instance of {@link HttpServletRequest} or use an injected instance
+ * @return Optional with the view name. Recomended to use an alternativa url with
+ * {@link Optional#orElse(java.lang.Object)}
+ */
+ public static Optional getPreviousPageByRequest(HttpServletRequest request)
+ {
+ return Optional.ofNullable(request.getHeader("Referer"));
+ }
+}
diff --git a/src/main/java/com/juick/util/adapters/SimpleDateAdapter.java b/src/main/java/com/juick/util/adapters/SimpleDateAdapter.java
new file mode 100644
index 00000000..fd5f3b5a
--- /dev/null
+++ b/src/main/java/com/juick/util/adapters/SimpleDateAdapter.java
@@ -0,0 +1,40 @@
+/*
+ * 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
+ * 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.util.adapters;
+
+import com.juick.util.DateFormattersHolder;
+
+import javax.xml.bind.annotation.adapters.XmlAdapter;
+import java.time.Instant;
+
+/**
+ * Created by vitalyster on 15.11.2016.
+ */
+
+public class SimpleDateAdapter extends XmlAdapter {
+
+ @Override
+ public String marshal(Instant v) {
+ return DateFormattersHolder.getMessageFormatterInstance().format(v);
+ }
+
+ @Override
+ public Instant unmarshal(String v) {
+ return DateFormattersHolder.getMessageFormatterInstance().parse(v);
+ }
+}
diff --git a/src/main/java/com/juick/util/annotation/UserCommand.java b/src/main/java/com/juick/util/annotation/UserCommand.java
new file mode 100644
index 00000000..29e40ca2
--- /dev/null
+++ b/src/main/java/com/juick/util/annotation/UserCommand.java
@@ -0,0 +1,50 @@
+/*
+ * 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
+ * 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.util.annotation;
+
+import org.apache.commons.lang3.StringUtils;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Created by oxpa on 22.03.16.
+ */
+@Target({ElementType.TYPE, ElementType.METHOD})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface UserCommand {
+ /**
+ *
+ * @return a command pattern
+ */
+ String pattern() default StringUtils.EMPTY;
+
+ /**
+ *
+ * @return pattern flags
+ */
+ int patternFlags() default 0;
+
+ /**
+ *
+ * @return a string used in HELP command output. Basically, only 1 string
+ */
+ String help() default StringUtils.EMPTY;
+}
diff --git a/src/main/java/com/juick/util/formatters/PlainTextFormatter.java b/src/main/java/com/juick/util/formatters/PlainTextFormatter.java
new file mode 100644
index 00000000..b5d67030
--- /dev/null
+++ b/src/main/java/com/juick/util/formatters/PlainTextFormatter.java
@@ -0,0 +1,105 @@
+/*
+ * 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
+ * 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.util.formatters;
+
+import com.juick.model.Message;
+import com.juick.util.MessageUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.ocpsoft.prettytime.PrettyTime;
+
+import java.util.Date;
+import java.util.Locale;
+
+/**
+ * Created by vitalyster on 12.10.2016.
+ */
+public class PlainTextFormatter {
+ static PrettyTime pt = new PrettyTime(new Locale("en"));
+
+ public static String formatPost(Message jmsg) {
+ return formatPost(jmsg, false);
+ }
+
+ public static String formatPost(Message jmsg, boolean markdown) {
+ StringBuilder sb = new StringBuilder();
+ String title = MessageUtils.isReply(jmsg) ? "Reply by @" : MessageUtils.isPM(jmsg) ? "Private message from @" : "@";
+ String subtitle = MessageUtils.isReply(jmsg) ? markdown ? MessageUtils.escapeMarkdown(StringUtils.defaultString(jmsg.getReplyQuote()))
+ : jmsg.getReplyQuote()
+ : markdown ? MessageUtils.getMessageHashTags(jmsg) : MessageUtils.getTagsString(jmsg);
+ sb.append(title).append(markdown ? MessageUtils.getMarkdownUser(jmsg.getUser()) : jmsg.getUser().getName()).append(":\n")
+ .append(subtitle).append("\n");
+ if (markdown) {
+ sb.append(MessageUtils.formatMarkdownText(jmsg));
+ } else {
+ sb.append(StringUtils.defaultString(jmsg.getText()));
+ }
+ sb.append("\n");
+ if (!markdown && StringUtils.isNotEmpty(jmsg.getAttachmentType())) {
+ sb.append(MessageUtils.attachmentUrl(jmsg));
+ }
+ return sb.toString();
+ }
+
+ public static String formatPostSummary(Message m) {
+ int cropLength = 384;
+ 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();
+ String txt = StringUtils.defaultString(m.getText());
+ String attachmentUrl = MessageUtils.attachmentUrl(m);
+ if (StringUtils.isNotEmpty(attachmentUrl)) {
+ sb.append(attachmentUrl).append("\n");
+ }
+ if (txt.length() >= cropLength) {
+ sb.append(StringUtils.substring(txt, 0, cropLength)).append(" [...]");
+ } else {
+ sb.append(txt);
+ }
+ return String.format("@%s:%s\n%s\n#%s (%s%s) %s",
+ m.getUser().getName(), MessageUtils.getTagsString(m), sb.toString(), formatPostNumber(m), timeAgo, repliesCount, formatUrl(m));
+ }
+
+ public static String formatUrl(Message jmsg) {
+ if (MessageUtils.isReply(jmsg)) {
+ return String.format("https://juick.com/m/%d#%d", jmsg.getMid(), jmsg.getRid());
+ } else if (MessageUtils.isPM(jmsg)) {
+ return "https://juick.com/pm/inbox";
+ }
+ 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(Message jmsg) {
+ if (jmsg.getRid() > 0) {
+ return String.format("%d/%d", jmsg.getMid(), jmsg.getRid());
+ }
+ return String.format("%d", jmsg.getMid());
+ }
+
+ public static String formatTwitterCard(Message jmsg) {
+ return MessageUtils.getMessageHashTags(jmsg) + StringUtils.defaultString(jmsg.getText());
+ }
+}
diff --git a/src/main/java/com/juick/util/xmpp/JidConverter.java b/src/main/java/com/juick/util/xmpp/JidConverter.java
new file mode 100644
index 00000000..118c6711
--- /dev/null
+++ b/src/main/java/com/juick/util/xmpp/JidConverter.java
@@ -0,0 +1,32 @@
+/*
+ * 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
+ * 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.util.xmpp;
+
+import org.springframework.core.convert.converter.Converter;
+import org.springframework.lang.Nullable;
+import rocks.xmpp.addr.Jid;
+
+import javax.annotation.Nonnull;
+
+public class JidConverter implements Converter {
+ @Nullable
+ @Override
+ public Jid convert(@Nonnull String jidStr) {
+ return Jid.of(jidStr);
+ }
+}
diff --git a/src/main/java/com/juick/util/xmpp/iq/MessageQuery.java b/src/main/java/com/juick/util/xmpp/iq/MessageQuery.java
new file mode 100644
index 00000000..c1096e8e
--- /dev/null
+++ b/src/main/java/com/juick/util/xmpp/iq/MessageQuery.java
@@ -0,0 +1,27 @@
+/*
+ * 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
+ * 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.util.xmpp.iq;
+
+import javax.xml.bind.annotation.XmlRootElement;
+
+@XmlRootElement(name = "query")
+public class MessageQuery {
+ private MessageQuery() {
+
+ }
+}
diff --git a/src/main/java/com/juick/util/xmpp/iq/package-info.java b/src/main/java/com/juick/util/xmpp/iq/package-info.java
new file mode 100644
index 00000000..7a1694a5
--- /dev/null
+++ b/src/main/java/com/juick/util/xmpp/iq/package-info.java
@@ -0,0 +1,25 @@
+/*
+ * 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
+ * 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 .
+ */
+
+@XmlAccessorType(XmlAccessType.FIELD)
+@XmlSchema(namespace = "http://juick.com/query#messages", elementFormDefault = XmlNsForm.QUALIFIED)
+package com.juick.util.xmpp.iq;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlNsForm;
+import javax.xml.bind.annotation.XmlSchema;
\ No newline at end of file
diff --git a/src/main/java/com/juick/www/api/ApiSocialLogin.java b/src/main/java/com/juick/www/api/ApiSocialLogin.java
index 6499b507..101f6b1f 100644
--- a/src/main/java/com/juick/www/api/ApiSocialLogin.java
+++ b/src/main/java/com/juick/www/api/ApiSocialLogin.java
@@ -34,7 +34,7 @@ import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.jackson2.JacksonFactory;
import com.juick.model.AuthResponse;
import com.juick.model.ext.facebook.User;
-import com.juick.server.util.HttpBadRequestException;
+import com.juick.util.HttpBadRequestException;
import com.juick.service.CrosspostService;
import com.juick.service.EmailService;
import com.juick.service.UserService;
diff --git a/src/main/java/com/juick/www/api/Messages.java b/src/main/java/com/juick/www/api/Messages.java
index 59ed7c8f..de29c78c 100644
--- a/src/main/java/com/juick/www/api/Messages.java
+++ b/src/main/java/com/juick/www/api/Messages.java
@@ -20,10 +20,10 @@ package com.juick.www.api;
import com.juick.model.Message;
import com.juick.model.Tag;
import com.juick.model.User;
-import com.juick.server.Utils;
+import com.juick.util.WebUtils;
import com.juick.www.WebApp;
import com.juick.model.CommandResult;
-import com.juick.server.util.HttpBadRequestException;
+import com.juick.util.HttpBadRequestException;
import com.juick.service.MessagesService;
import com.juick.service.TagService;
import com.juick.service.UserService;
@@ -119,7 +119,7 @@ public class Messages {
} else if (daysback > 0) {
mids = messagesService.getUserBlogAtDay(user.getUid(), 0, daysback);
} else if (!StringUtils.isEmpty(search)) {
- mids = messagesService.getUserSearch(visitor, user.getUid(), Utils.encodeSphinx(search), 0, page);
+ mids = messagesService.getUserSearch(visitor, user.getUid(), WebUtils.encodeSphinx(search), 0, page);
} else {
mids = messagesService.getUserBlog(user.getUid(), 0, before);
}
@@ -139,7 +139,7 @@ public class Messages {
return NOT_FOUND;
}
} else if (!StringUtils.isEmpty(search)) {
- mids = messagesService.getSearch(visitor, Utils.encodeSphinx(search), page);
+ mids = messagesService.getSearch(visitor, WebUtils.encodeSphinx(search), page);
} else {
mids = messagesService.getAll(visitor.getUid(), before);
}
diff --git a/src/main/java/com/juick/www/api/Notifications.java b/src/main/java/com/juick/www/api/Notifications.java
index ca382246..4f6096ce 100644
--- a/src/main/java/com/juick/www/api/Notifications.java
+++ b/src/main/java/com/juick/www/api/Notifications.java
@@ -22,7 +22,7 @@ import com.juick.model.Message;
import com.juick.model.Status;
import com.juick.model.User;
import com.juick.model.AnonymousUser;
-import com.juick.server.util.HttpBadRequestException;
+import com.juick.util.HttpBadRequestException;
import com.juick.service.MessagesService;
import com.juick.service.PushQueriesService;
import com.juick.service.SubscriptionService;
diff --git a/src/main/java/com/juick/www/api/PM.java b/src/main/java/com/juick/www/api/PM.java
index b81dcc78..863a1055 100644
--- a/src/main/java/com/juick/www/api/PM.java
+++ b/src/main/java/com/juick/www/api/PM.java
@@ -22,9 +22,9 @@ import com.juick.model.Message;
import com.juick.model.User;
import com.juick.model.AnonymousUser;
import com.juick.model.PrivateChats;
-import com.juick.server.util.HttpBadRequestException;
-import com.juick.server.util.HttpForbiddenException;
-import com.juick.server.util.WebUtils;
+import com.juick.util.HttpBadRequestException;
+import com.juick.util.HttpForbiddenException;
+import com.juick.util.WebUtils;
import com.juick.www.WebApp;
import com.juick.service.PMQueriesService;
import com.juick.service.UserService;
diff --git a/src/main/java/com/juick/www/api/Post.java b/src/main/java/com/juick/www/api/Post.java
index 3c1fbf6b..205c8c90 100644
--- a/src/main/java/com/juick/www/api/Post.java
+++ b/src/main/java/com/juick/www/api/Post.java
@@ -22,12 +22,12 @@ import com.juick.model.Reaction;
import com.juick.model.Status;
import com.juick.model.User;
import com.juick.model.CommandResult;
-import com.juick.server.ActivityPubManager;
-import com.juick.server.CommandsManager;
-import com.juick.server.util.HttpBadRequestException;
-import com.juick.server.util.HttpForbiddenException;
-import com.juick.server.util.HttpNotFoundException;
-import com.juick.server.util.HttpUtils;
+import com.juick.ActivityPubManager;
+import com.juick.CommandsManager;
+import com.juick.util.HttpBadRequestException;
+import com.juick.util.HttpForbiddenException;
+import com.juick.util.HttpNotFoundException;
+import com.juick.util.HttpUtils;
import com.juick.service.MessagesService;
import com.juick.service.UserService;
import com.juick.service.activities.UpdateEvent;
diff --git a/src/main/java/com/juick/www/api/Service.java b/src/main/java/com/juick/www/api/Service.java
index cb918682..850acb9d 100644
--- a/src/main/java/com/juick/www/api/Service.java
+++ b/src/main/java/com/juick/www/api/Service.java
@@ -20,11 +20,11 @@ package com.juick.www.api;
import com.juick.model.Message;
import com.juick.model.User;
import com.juick.model.CommandResult;
-import com.juick.server.CommandsManager;
-import com.juick.server.EmailManager;
-import com.juick.server.ServerManager;
-import com.juick.server.util.HttpBadRequestException;
-import com.juick.server.util.HttpForbiddenException;
+import com.juick.CommandsManager;
+import com.juick.EmailManager;
+import com.juick.ServerManager;
+import com.juick.util.HttpBadRequestException;
+import com.juick.util.HttpForbiddenException;
import com.juick.service.EmailService;
import com.juick.service.MessagesService;
import com.juick.service.UserService;
diff --git a/src/main/java/com/juick/www/api/Users.java b/src/main/java/com/juick/www/api/Users.java
index 06467b7d..f4c3a4c1 100644
--- a/src/main/java/com/juick/www/api/Users.java
+++ b/src/main/java/com/juick/www/api/Users.java
@@ -20,10 +20,10 @@ package com.juick.www.api;
import com.juick.model.User;
import com.juick.model.AnonymousUser;
import com.juick.model.ApplicationStatus;
-import com.juick.server.util.HttpBadRequestException;
-import com.juick.server.util.HttpNotFoundException;
-import com.juick.server.util.HttpUtils;
-import com.juick.server.util.WebUtils;
+import com.juick.util.HttpBadRequestException;
+import com.juick.util.HttpNotFoundException;
+import com.juick.util.HttpUtils;
+import com.juick.util.WebUtils;
import com.juick.www.WebApp;
import com.juick.service.*;
import com.juick.service.component.MailVerificationEvent;
diff --git a/src/main/java/com/juick/www/api/activity/Profile.java b/src/main/java/com/juick/www/api/activity/Profile.java
index bdd7cab2..ef9b342f 100644
--- a/src/main/java/com/juick/www/api/activity/Profile.java
+++ b/src/main/java/com/juick/www/api/activity/Profile.java
@@ -20,12 +20,12 @@ package com.juick.www.api.activity;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.juick.model.Message;
import com.juick.model.User;
-import com.juick.formatters.PlainTextFormatter;
+import com.juick.util.formatters.PlainTextFormatter;
import com.juick.model.CommandResult;
-import com.juick.server.ActivityPubManager;
-import com.juick.server.CommandsManager;
-import com.juick.server.KeystoreManager;
-import com.juick.server.SignatureManager;
+import com.juick.ActivityPubManager;
+import com.juick.CommandsManager;
+import com.juick.KeystoreManager;
+import com.juick.SignatureManager;
import com.juick.www.api.activity.model.Activity;
import com.juick.www.api.activity.model.Context;
import com.juick.www.api.activity.model.activities.Announce;
@@ -40,7 +40,7 @@ import com.juick.www.api.activity.model.objects.Note;
import com.juick.www.api.activity.model.objects.OrderedCollection;
import com.juick.www.api.activity.model.objects.OrderedCollectionPage;
import com.juick.www.api.activity.model.objects.Person;
-import com.juick.server.util.HttpNotFoundException;
+import com.juick.util.HttpNotFoundException;
import com.juick.www.WebApp;
import com.juick.service.MessagesService;
import com.juick.service.UserService;
diff --git a/src/main/java/com/juick/www/api/webfinger/Resource.java b/src/main/java/com/juick/www/api/webfinger/Resource.java
index 1529ea09..1e8b45e5 100644
--- a/src/main/java/com/juick/www/api/webfinger/Resource.java
+++ b/src/main/java/com/juick/www/api/webfinger/Resource.java
@@ -20,7 +20,7 @@ package com.juick.www.api.webfinger;
import com.juick.model.User;
import com.juick.www.api.webfinger.model.Account;
import com.juick.www.api.webfinger.model.Link;
-import com.juick.server.util.HttpNotFoundException;
+import com.juick.util.HttpNotFoundException;
import com.juick.service.UserService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
diff --git a/src/main/java/com/juick/www/api/webhooks/TelegramWebhook.java b/src/main/java/com/juick/www/api/webhooks/TelegramWebhook.java
index 32218b3a..8090f388 100644
--- a/src/main/java/com/juick/www/api/webhooks/TelegramWebhook.java
+++ b/src/main/java/com/juick/www/api/webhooks/TelegramWebhook.java
@@ -17,7 +17,7 @@
package com.juick.www.api.webhooks;
-import com.juick.server.TelegramBotManager;
+import com.juick.TelegramBotManager;
import com.pengrad.telegrambot.BotUtils;
import com.pengrad.telegrambot.model.Update;
import org.apache.commons.io.IOUtils;
diff --git a/src/main/java/com/juick/www/controllers/Help.java b/src/main/java/com/juick/www/controllers/Help.java
index 7fc84060..f65e254a 100644
--- a/src/main/java/com/juick/www/controllers/Help.java
+++ b/src/main/java/com/juick/www/controllers/Help.java
@@ -18,7 +18,7 @@
package com.juick.www.controllers;
import com.juick.model.User;
-import com.juick.server.util.HttpNotFoundException;
+import com.juick.util.HttpNotFoundException;
import com.juick.service.HelpService;
import com.juick.service.security.annotation.Visitor;
import com.juick.www.WebApp;
diff --git a/src/main/java/com/juick/www/controllers/Settings.java b/src/main/java/com/juick/www/controllers/Settings.java
index 851e576b..69ddcd2f 100644
--- a/src/main/java/com/juick/www/controllers/Settings.java
+++ b/src/main/java/com/juick/www/controllers/Settings.java
@@ -18,8 +18,8 @@ package com.juick.www.controllers;
import com.juick.model.User;
import com.juick.model.NotifyOpts;
-import com.juick.server.util.HttpBadRequestException;
-import com.juick.server.util.HttpUtils;
+import com.juick.util.HttpBadRequestException;
+import com.juick.util.HttpUtils;
import com.juick.www.WebApp;
import com.juick.service.*;
import com.juick.service.security.annotation.Visitor;
diff --git a/src/main/java/com/juick/www/controllers/SignUp.java b/src/main/java/com/juick/www/controllers/SignUp.java
index 4e74d4c4..3b052e18 100644
--- a/src/main/java/com/juick/www/controllers/SignUp.java
+++ b/src/main/java/com/juick/www/controllers/SignUp.java
@@ -17,8 +17,8 @@
package com.juick.www.controllers;
import com.juick.model.User;
-import com.juick.server.util.HttpBadRequestException;
-import com.juick.server.util.HttpForbiddenException;
+import com.juick.util.HttpBadRequestException;
+import com.juick.util.HttpForbiddenException;
import com.juick.www.WebApp;
import com.juick.service.CrosspostService;
import com.juick.service.EmailService;
diff --git a/src/main/java/com/juick/www/controllers/Site.java b/src/main/java/com/juick/www/controllers/Site.java
index 61b84f71..a8cbc9bd 100644
--- a/src/main/java/com/juick/www/controllers/Site.java
+++ b/src/main/java/com/juick/www/controllers/Site.java
@@ -19,11 +19,10 @@ package com.juick.www.controllers;
import com.juick.model.Message;
import com.juick.model.Tag;
import com.juick.model.User;
-import com.juick.formatters.PlainTextFormatter;
-import com.juick.server.Utils;
-import com.juick.server.util.HttpForbiddenException;
-import com.juick.server.util.HttpNotFoundException;
-import com.juick.server.util.WebUtils;
+import com.juick.util.formatters.PlainTextFormatter;
+import com.juick.util.HttpForbiddenException;
+import com.juick.util.HttpNotFoundException;
+import com.juick.util.WebUtils;
import com.juick.www.WebApp;
import com.juick.service.*;
import com.juick.service.security.annotation.Visitor;
@@ -109,7 +108,7 @@ public class Site {
if (paramSearch != null) {
title = "Поиск: " + StringEscapeUtils.escapeHtml4(paramSearch);
- mids = messagesService.getSearch(visitor, Utils.encodeSphinx(paramSearch), page);
+ mids = messagesService.getSearch(visitor, WebUtils.encodeSphinx(paramSearch), page);
} else if (paramShow == null) {
title = "Обсуждения";
mids = messagesService.getDiscussions(visitor.getUid(), paramTo);
@@ -228,7 +227,7 @@ public class Site {
mids = messagesService.getUserTag(user.getUid(), paramTag.TID, privacy, before);
} else if (paramSearch != null) {
title = "Блог " + user.getName() + ": " + StringEscapeUtils.escapeHtml4(paramSearch);
- mids = messagesService.getUserSearch(visitor, user.getUid(), Utils.encodeSphinx(paramSearch), privacy, page);
+ mids = messagesService.getUserSearch(visitor, user.getUid(), WebUtils.encodeSphinx(paramSearch), privacy, page);
} else {
title = "Блог " + user.getName();
mids = messagesService.getUserBlog(user.getUid(), privacy, before);
diff --git a/src/main/java/com/juick/www/controllers/SocialLogin.java b/src/main/java/com/juick/www/controllers/SocialLogin.java
index 2bd89587..ffd785b7 100644
--- a/src/main/java/com/juick/www/controllers/SocialLogin.java
+++ b/src/main/java/com/juick/www/controllers/SocialLogin.java
@@ -24,13 +24,13 @@ import com.github.scribejava.core.oauth.OAuth10aService;
import com.github.scribejava.core.oauth.OAuth20Service;
import com.juick.model.ext.facebook.User;
import com.juick.model.ext.vk.UsersResponse;
-import com.juick.server.Utils;
-import com.juick.server.util.HttpBadRequestException;
+import com.juick.util.HttpBadRequestException;
import com.juick.service.CrosspostService;
import com.juick.service.EmailService;
import com.juick.service.TelegramService;
import com.juick.service.UserService;
import com.juick.service.security.annotation.Visitor;
+import com.juick.util.WebUtils;
import com.nimbusds.jose.JOSEException;
import com.nimbusds.jose.proc.BadJOSEException;
import org.apache.commons.codec.digest.DigestUtils;
@@ -140,7 +140,7 @@ public class SocialLogin {
if (StringUtils.isBlank(code)) {
String fbstate = UUID.randomUUID().toString();
if (StringUtils.isBlank(state)) {
- state = Utils.getPreviousPageByRequest(request).orElse("https://juick.com/");
+ state = WebUtils.getPreviousPageByRequest(request).orElse("https://juick.com/");
}
crosspostService.addFacebookState(fbstate, state);
return "redirect:" + facebookAuthService.getAuthorizationUrl(fbstate);
@@ -282,7 +282,7 @@ public class SocialLogin {
Cookie c = new Cookie("hash", userService.getHashByUID(uid));
c.setMaxAge(50 * 24 * 60 * 60);
response.addCookie(c);
- return "redirect:/" + Utils.getPreviousPageByRequest(request).orElse(StringUtils.EMPTY);
+ return "redirect:/" + WebUtils.getPreviousPageByRequest(request).orElse(StringUtils.EMPTY);
} else {
String loginhash = UUID.randomUUID().toString();
if (!crosspostService.createVKUser(vkID, loginhash, token.getAccessToken(), vkName, vkLink)) {
@@ -312,7 +312,7 @@ public class SocialLogin {
Cookie c = new Cookie("hash", userService.getHashByUID(uid));
c.setMaxAge(50 * 24 * 60 * 60);
response.addCookie(c);
- return "redirect:/" + Utils.getPreviousPageByRequest(request).orElse(StringUtils.EMPTY);
+ return "redirect:/" + WebUtils.getPreviousPageByRequest(request).orElse(StringUtils.EMPTY);
} else {
String username = StringUtils.defaultString(params.get("username"), params.get("first_name"));
List chats = telegramService.getAnonymous();
diff --git a/src/main/java/com/juick/www/filters/AnythingFilter.java b/src/main/java/com/juick/www/filters/AnythingFilter.java
index 8f9af7f6..461a2888 100644
--- a/src/main/java/com/juick/www/filters/AnythingFilter.java
+++ b/src/main/java/com/juick/www/filters/AnythingFilter.java
@@ -18,7 +18,7 @@
package com.juick.www.filters;
import com.juick.model.User;
-import com.juick.server.util.WebUtils;
+import com.juick.util.WebUtils;
import com.juick.service.MessagesService;
import com.juick.service.UserService;
import org.apache.commons.lang3.math.NumberUtils;
diff --git a/src/main/java/com/juick/www/rss/Feeds.java b/src/main/java/com/juick/www/rss/Feeds.java
index 42cadc5d..1817301e 100644
--- a/src/main/java/com/juick/www/rss/Feeds.java
+++ b/src/main/java/com/juick/www/rss/Feeds.java
@@ -18,7 +18,7 @@
package com.juick.www.rss;
import com.juick.model.User;
-import com.juick.server.util.HttpNotFoundException;
+import com.juick.util.HttpNotFoundException;
import com.juick.service.MessagesService;
import com.juick.service.UserService;
import com.juick.service.security.annotation.Visitor;
--
cgit v1.2.3