/* * Copyright 1998-2017 Linux.org.ru * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package ru.org.linux.util; import org.apache.commons.httpclient.HttpURL; import org.apache.commons.httpclient.HttpsURL; import org.apache.commons.httpclient.URI; import org.apache.commons.httpclient.URIException; import ru.org.linux.group.Group; import ru.org.linux.site.MessageNotFoundException; import ru.org.linux.topic.Topic; import ru.org.linux.topic.TopicDao; import java.util.Arrays; import java.util.regex.Matcher; import java.util.regex.Pattern; import static ru.org.linux.util.StringUtil.isUnsignedPositiveNumber; public class LorURL { private static final Pattern requestMessagePattern = Pattern.compile("^/[\\w-]+/[\\w-]+/(\\d+)"); private static final Pattern requestCommentPattern = Pattern.compile("^comment-(\\d+)"); private static final Pattern requestCommentPatternNew = Pattern.compile("cid=(\\d+)"); private static final Pattern requestOldJumpPathPattern = Pattern.compile("^/jump-message.jsp$"); private static final Pattern requestOldJumpQueryPattern = Pattern.compile("^msgid=(\\d+)&cid=(\\d+)"); private boolean _true_lor_url = false; private int _topic_id = -1; private int _comment_id = -1; private final URI parsed; private static class Impl extends URI { Impl(String url) throws URIException { protocolCharset = "UTF-8"; try { parseUriReference(url, true); /* * Пытаемся вычислить, что fragment таки не encode */ if (_fragment != null) { String fragmentStr = new String(_fragment); String asciiFragment = fragmentStr.replaceAll("[^\\p{ASCII}]", ""); if (fragmentStr.length() != asciiFragment.length()) { throw new URIException("error fragment?"); } } getQuery(); // check if we can decode it } catch (URIException ex) { parseUriReference(url, false); } if (_host == null) { throw new URIException("no host"); } } } public LorURL(URI mainURI, String url) throws URIException { parsed = new Impl(url); char[] _main_host = mainURI.getRawHost(); int _main_port = mainURI.getPort(); char[] _https_scheme = "https".toCharArray(); char[] _http_scheme = "http".toCharArray(); _true_lor_url = Arrays.equals(_main_host, parsed.getRawHost()) && _main_port == parsed.getPort() && (Arrays.equals(_http_scheme, parsed.getRawScheme()) || Arrays.equals(_https_scheme, parsed.getRawScheme())); findURLIds(); } private void findURLIds() throws URIException { if(_true_lor_url) { // find message id in lor url String path = parsed.getPath(); String query = parsed.getQuery(); String fragment = parsed.getFragment(); if (path != null && query != null) { Matcher oldJumpPathMatcher = requestOldJumpPathPattern.matcher(path); Matcher oldJumpQueryMatcher = requestOldJumpQueryPattern.matcher(query); if (oldJumpPathMatcher.find() && oldJumpQueryMatcher.find()) { if (isUnsignedPositiveNumber(oldJumpQueryMatcher.group(1)) && isUnsignedPositiveNumber(oldJumpQueryMatcher.group(2))) { _topic_id = Integer.parseInt(oldJumpQueryMatcher.group(1)); _comment_id = Integer.parseInt(oldJumpQueryMatcher.group(2)); } } } if (path != null && _topic_id == -1) { Matcher messageMatcher = requestMessagePattern.matcher(path); if (messageMatcher.find()) { if(isUnsignedPositiveNumber(messageMatcher.group(1))) { try { _topic_id = Integer.parseInt(messageMatcher.group(1)); } catch (NumberFormatException ex) { } } } if(path.endsWith("/history") || path.endsWith("/comments")) { _topic_id = -1; } } if (fragment != null && _topic_id != -1) { Matcher commentMatcher = requestCommentPattern.matcher(fragment); if (commentMatcher.find()) { if(isUnsignedPositiveNumber(commentMatcher.group(1))) { try { _comment_id = Integer.parseInt(commentMatcher.group(1)); } catch (NumberFormatException ex) { } } } } if (query != null && _topic_id != -1) { Matcher commentMatcher = requestCommentPatternNew.matcher(query); if (commentMatcher.find()) { if(isUnsignedPositiveNumber(commentMatcher.group(1))) { _comment_id = Integer.parseInt(commentMatcher.group(1)); } } } } } /** * Возвращает escaped URL * @return url */ @Override public String toString() { return parsed.getEscapedURIReference(); } /** * Ссылка является ссылкой на внтренности lorsource * @return true если lorsource ссылка */ public boolean isTrueLorUrl() { return _true_lor_url; } /** * Ссылка является ссылкой на топик или комментарий в топике * @return true если ссылка на топик или комментарий */ public boolean isMessageUrl() { return _topic_id != -1; } /** * Вовзращает id топика ссылки или 0 если ссылка не на топик или комментарий * @return id топика */ public int getMessageId() { return _topic_id; } /** * Ссылка является комментарием в топике * @return true если ссылка на комментарий */ public boolean isCommentUrl() { return _comment_id != -1; } /** * Возвращает id комментария из ссылки или 0 если ссылка не на комментарий * @return id комментария */ public int getCommentId() { return _comment_id; } /** * Ищет в стороке символ который полчается если строка однобайтовая вместо предполагаемого utf8 * @param str строка для проверки * @return флажок */ private boolean isContainReplacementCharset(String str) { for(char c : str.toCharArray()) { if(c == 65533) { return true; } } return false; } public String formatUrlBody(int maxLength) throws URIException { String all = parsed.getURIReference(); // Костыль для однобайтовых неудачников if (isContainReplacementCharset(all)) { all = parsed.getEscapedURIReference(); } String scheme = parsed.getScheme(); String uriWithoutScheme = all.substring(scheme.length()+3); int trueMaxLength = maxLength - 3; // '...' if(_true_lor_url) { if(uriWithoutScheme.length() < maxLength + 1) { return uriWithoutScheme; } else { String hostPort = parsed.getHost(); if(parsed.getPort() != -1) { hostPort += ":" + parsed.getPort(); } if(hostPort.length() > maxLength) { return hostPort+"/..."; } else { return uriWithoutScheme.substring(0, trueMaxLength) + "..."; } } } else { if(all.length() < maxLength + 1) { return all; } else { return all.substring(0, trueMaxLength) + "..."; } } } /** * Исправляет scheme url http или https в зависимости от флага secure * предполагалось только для lor ссылок, но будет работать с любыми, только зачем? * @param canonical канонический URL сайта * @return исправленный url * @throws URIException неправильный url */ public String canonize(URI canonical) throws URIException { if(!_true_lor_url) { return toString(); } String host = canonical.getHost(); int port = canonical.getPort(); String path = parsed.getPath(); String query = parsed.getQuery(); String fragment = parsed.getFragment(); if (canonical.getScheme().equals("http")) { return (new HttpURL(null, host, port, path, query, fragment)).getEscapedURIReference(); } else { return (new HttpsURL(null, host, port, path, query, fragment)).getEscapedURIReference(); } } /** * Создает url для редиректа на текущее сообщение\комментарий * @param messageDao доступ к базе сообщений * @param canonical канонический URL сайта * @return url для редиректа или пустая строка * @throws MessageNotFoundException если нет сообещния * @throws URIException если url неправильный */ public String formatJump(TopicDao messageDao, URI canonical) throws MessageNotFoundException, URIException { if(_topic_id != -1) { Topic message = messageDao.getById(_topic_id); Group group = messageDao.getGroup(message); String scheme = canonical.getScheme(); String host = canonical.getHost(); int port = canonical.getPort(); String path = group.getUrl() + _topic_id; String query = ""; if(_comment_id != -1) { query = "cid=" + _comment_id; } URI jumpUri = new URI(scheme, null , host, port, path, query); return jumpUri.getEscapedURI(); } return ""; } }