1b5840353SAdam Hornáček /* 2b5840353SAdam Hornáček * CDDL HEADER START 3b5840353SAdam Hornáček * 4b5840353SAdam Hornáček * The contents of this file are subject to the terms of the 5b5840353SAdam Hornáček * Common Development and Distribution License (the "License"). 6b5840353SAdam Hornáček * You may not use this file except in compliance with the License. 7b5840353SAdam Hornáček * 8b5840353SAdam Hornáček * See LICENSE.txt included in this distribution for the specific 9b5840353SAdam Hornáček * language governing permissions and limitations under the License. 10b5840353SAdam Hornáček * 11b5840353SAdam Hornáček * When distributing Covered Code, include this CDDL HEADER in each 12b5840353SAdam Hornáček * file and include the License file at LICENSE.txt. 13b5840353SAdam Hornáček * If applicable, add the following below this CDDL HEADER, with the 14b5840353SAdam Hornáček * fields enclosed by brackets "[]" replaced with your own identifying 15b5840353SAdam Hornáček * information: Portions Copyright [yyyy] [name of copyright owner] 16b5840353SAdam Hornáček * 17b5840353SAdam Hornáček * CDDL HEADER END 18b5840353SAdam Hornáček */ 19b5840353SAdam Hornáček 20b5840353SAdam Hornáček /* 21b13c5a0eSAdam Hornacek * Copyright (c) 2005, 2021, Oracle and/or its affiliates. All rights reserved. 2251e20d51SAdam Hornáček * Portions Copyright (c) 2011, Jens Elkner. 2351e20d51SAdam Hornáček * Portions Copyright (c) 2017, 2020, Chris Fraire <cfraire@me.com>. 24e87b8f8cSKryštof Tulinger * Portions Copyright (c) 2019, Krystof Tulinger <k.tulinger@seznam.cz>. 25b5840353SAdam Hornáček */ 269805b761SAdam Hornáček package org.opengrok.indexer.web; 27b5840353SAdam Hornáček 28cf51835aSKryštof Tulinger import static org.opengrok.indexer.index.Indexer.PATH_SEPARATOR; 29cf51835aSKryštof Tulinger 30b5840353SAdam Hornáček import java.io.BufferedInputStream; 31b5840353SAdam Hornáček import java.io.File; 32b5840353SAdam Hornáček import java.io.FileInputStream; 33b5840353SAdam Hornáček import java.io.IOException; 34b5840353SAdam Hornáček import java.io.InputStream; 35b5840353SAdam Hornáček import java.io.InputStreamReader; 36b5840353SAdam Hornáček import java.io.Reader; 37b5840353SAdam Hornáček import java.io.Writer; 38b5840353SAdam Hornáček import java.net.MalformedURLException; 39b5840353SAdam Hornáček import java.net.URI; 40b5840353SAdam Hornáček import java.net.URISyntaxException; 41b5840353SAdam Hornáček import java.net.URL; 42911e8af0SAdam Hornáček import java.net.URLDecoder; 43b5840353SAdam Hornáček import java.net.URLEncoder; 44b5840353SAdam Hornáček import java.nio.charset.StandardCharsets; 45b5840353SAdam Hornáček import java.text.DecimalFormat; 46b5840353SAdam Hornáček import java.text.NumberFormat; 47b5840353SAdam Hornáček import java.util.Collection; 48911e8af0SAdam Hornáček import java.util.HashMap; 49b5840353SAdam Hornáček import java.util.LinkedList; 50b5840353SAdam Hornáček import java.util.List; 51b5840353SAdam Hornáček import java.util.Locale; 52b5840353SAdam Hornáček import java.util.Map; 53b5840353SAdam Hornáček import java.util.Map.Entry; 54b5840353SAdam Hornáček import java.util.TreeMap; 55b5840353SAdam Hornáček import java.util.function.Function; 56b5840353SAdam Hornáček import java.util.logging.Level; 57b5840353SAdam Hornáček import java.util.logging.Logger; 58b5840353SAdam Hornáček import java.util.regex.Matcher; 59b5840353SAdam Hornáček import java.util.regex.Pattern; 60b5840353SAdam Hornáček import java.util.zip.GZIPInputStream; 619a4e74f4SAdam Hornacek 629a4e74f4SAdam Hornacek import jakarta.servlet.http.HttpServletRequest; 636c62ede9SAdam Hornacek import org.apache.commons.lang3.SystemUtils; 64a5cf78b2SChris Fraire import org.apache.lucene.queryparser.classic.QueryParser; 659805b761SAdam Hornáček import org.opengrok.indexer.configuration.RuntimeEnvironment; 669805b761SAdam Hornáček import org.opengrok.indexer.history.Annotation; 679805b761SAdam Hornáček import org.opengrok.indexer.history.HistoryException; 689805b761SAdam Hornáček import org.opengrok.indexer.history.HistoryGuru; 699805b761SAdam Hornáček import org.opengrok.indexer.logger.LoggerFactory; 70b5840353SAdam Hornáček 71b5840353SAdam Hornáček /** 72b5840353SAdam Hornáček * Class for useful functions. 73b5840353SAdam Hornáček */ 74b5840353SAdam Hornáček public final class Util { 75b5840353SAdam Hornáček 76b5840353SAdam Hornáček private static final Logger LOGGER = LoggerFactory.getLogger(Util.class); 77b5840353SAdam Hornáček 78b5840353SAdam Hornáček private static final int BOLD_COUNT_THRESHOLD = 1000; 79b5840353SAdam Hornáček 80807ead8fSLubos Kosco private static final String anchorLinkStart = "<a href=\""; 81807ead8fSLubos Kosco private static final String anchorClassStart = "<a class=\""; 82807ead8fSLubos Kosco private static final String anchorEnd = "</a>"; 83807ead8fSLubos Kosco private static final String closeQuotedTag = "\">"; 84807ead8fSLubos Kosco 851c830032SChris Fraire private static final String RE_Q_ESC_AMP_AMP = "\\?|&|&"; 861c830032SChris Fraire private static final String RE_Q_E_A_A_COUNT_EQ_VAL = "(" + RE_Q_ESC_AMP_AMP + "|\\b)" + 871c830032SChris Fraire QueryParameters.COUNT_PARAM_EQ + "\\d+"; 881c830032SChris Fraire private static final String RE_Q_E_A_A_START_EQ_VAL = "(" + RE_Q_ESC_AMP_AMP + "|\\b)" + 891c830032SChris Fraire QueryParameters.START_PARAM_EQ + "\\d+"; 901c830032SChris Fraire private static final String RE_A_ANCHOR_Q_E_A_A = "^(" + RE_Q_ESC_AMP_AMP + ")"; 911c830032SChris Fraire 92a5cf78b2SChris Fraire /** Private to enforce static. */ Util()93b5840353SAdam Hornáček private Util() { 94b5840353SAdam Hornáček } 95b5840353SAdam Hornáček 96b5840353SAdam Hornáček /** 97b5840353SAdam Hornáček * Return a string that represents <code>s</code> in HTML by calling 98b5840353SAdam Hornáček * {@link #htmlize(java.lang.CharSequence, java.lang.Appendable, boolean)} 99b5840353SAdam Hornáček * with {@code s}, a transient {@link StringBuilder}, and {@code true}. 100b5840353SAdam Hornáček * <p> 101b5840353SAdam Hornáček * (N.b. if no special characters are present, {@code s} is returned as is, 102b5840353SAdam Hornáček * without the expensive call.) 103b5840353SAdam Hornáček * 104b5840353SAdam Hornáček * @param s a defined string 105b5840353SAdam Hornáček * @return a string representing the character sequence in HTML 106b5840353SAdam Hornáček */ prehtmlize(String s)107b5840353SAdam Hornáček public static String prehtmlize(String s) { 108a72324b1SAdam Hornáček if (!needsHtmlize(s, true)) { 109a72324b1SAdam Hornáček return s; 110a72324b1SAdam Hornáček } 111b5840353SAdam Hornáček 112b5840353SAdam Hornáček StringBuilder sb = new StringBuilder(s.length() * 2); 113b5840353SAdam Hornáček try { 114b5840353SAdam Hornáček htmlize(s, sb, true); 115b5840353SAdam Hornáček } catch (IOException ioe) { 116b5840353SAdam Hornáček // IOException cannot happen when the destination is a 117b5840353SAdam Hornáček // StringBuilder. Wrap in an AssertionError so that callers 118b5840353SAdam Hornáček // don't have to check for an IOException that should never 119b5840353SAdam Hornáček // happen. 120b5840353SAdam Hornáček throw new AssertionError("StringBuilder threw IOException", ioe); 121b5840353SAdam Hornáček } 122b5840353SAdam Hornáček return sb.toString(); 123b5840353SAdam Hornáček } 124b5840353SAdam Hornáček 125b5840353SAdam Hornáček /** 126b5840353SAdam Hornáček * Calls 127b5840353SAdam Hornáček * {@link #htmlize(java.lang.CharSequence, java.lang.Appendable, boolean)} 128b5840353SAdam Hornáček * with {@code q}, a transient {@link StringBuilder}, and {@code true}. 129b5840353SAdam Hornáček * @param q a character sequence 130b5840353SAdam Hornáček * @return a string representing the character sequence in HTML 131b5840353SAdam Hornáček */ prehtmlize(CharSequence q)132b5840353SAdam Hornáček public static String prehtmlize(CharSequence q) { 133b5840353SAdam Hornáček StringBuilder sb = new StringBuilder(q.length() * 2); 134b5840353SAdam Hornáček try { 135b5840353SAdam Hornáček htmlize(q, sb, true); 136b5840353SAdam Hornáček } catch (IOException ioe) { 137b5840353SAdam Hornáček // IOException cannot happen when the destination is a 138b5840353SAdam Hornáček // StringBuilder. Wrap in an AssertionError so that callers 139b5840353SAdam Hornáček // don't have to check for an IOException that should never 140b5840353SAdam Hornáček // happen. 141b5840353SAdam Hornáček throw new AssertionError("StringBuilder threw IOException", ioe); 142b5840353SAdam Hornáček } 143b5840353SAdam Hornáček return sb.toString(); 144b5840353SAdam Hornáček } 145b5840353SAdam Hornáček 146b5840353SAdam Hornáček /** 147a5cf78b2SChris Fraire * Append to {@code dest} the UTF-8 URL-encoded representation of the 148a5cf78b2SChris Fraire * Lucene-escaped version of {@code str}. 149b5840353SAdam Hornáček * @param str a defined instance 150b5840353SAdam Hornáček * @param dest a defined target 15181b586e6SVladimir Kotal * @throws IOException I/O exception 152b5840353SAdam Hornáček */ qurlencode(String str, Appendable dest)153*d6df19e1SAdam Hornacek public static void qurlencode(String str, Appendable dest) throws IOException { 154*d6df19e1SAdam Hornacek uriEncode(QueryParser.escape(str), dest); 155b5840353SAdam Hornáček } 156b5840353SAdam Hornáček 157b5840353SAdam Hornáček /** 158b5840353SAdam Hornáček * Return a string that represents a <code>CharSequence</code> in HTML by 159b5840353SAdam Hornáček * calling 160b5840353SAdam Hornáček * {@link #htmlize(java.lang.CharSequence, java.lang.Appendable, boolean)} 161b5840353SAdam Hornáček * with {@code s}, a transient {@link StringBuilder}, and {@code false}. 162b5840353SAdam Hornáček * <p> 163b5840353SAdam Hornáček * (N.b. if no special characters are present, {@code s} is returned as is, 164b5840353SAdam Hornáček * without the expensive call.) 165b5840353SAdam Hornáček * 166b5840353SAdam Hornáček * @param s a defined string 167b5840353SAdam Hornáček * @return a string representing the character sequence in HTML 168b5840353SAdam Hornáček */ htmlize(String s)169b5840353SAdam Hornáček public static String htmlize(String s) { 170a72324b1SAdam Hornáček if (!needsHtmlize(s, false)) { 171a72324b1SAdam Hornáček return s; 172a72324b1SAdam Hornáček } 173b5840353SAdam Hornáček 174b5840353SAdam Hornáček StringBuilder sb = new StringBuilder(s.length() * 2); 175b5840353SAdam Hornáček try { 176b5840353SAdam Hornáček htmlize(s, sb, false); 177b5840353SAdam Hornáček } catch (IOException ioe) { 178b5840353SAdam Hornáček // IOException cannot happen when the destination is a 179b5840353SAdam Hornáček // StringBuilder. Wrap in an AssertionError so that callers 180b5840353SAdam Hornáček // don't have to check for an IOException that should never 181b5840353SAdam Hornáček // happen. 182b5840353SAdam Hornáček throw new AssertionError("StringBuilder threw IOException", ioe); 183b5840353SAdam Hornáček } 184b5840353SAdam Hornáček return sb.toString(); 185b5840353SAdam Hornáček } 186b5840353SAdam Hornáček 187b5840353SAdam Hornáček /** 188b5840353SAdam Hornáček * Return a string which represents a <code>CharSequence</code> in HTML by 189b5840353SAdam Hornáček * calling 190b5840353SAdam Hornáček * {@link #htmlize(java.lang.CharSequence, java.lang.Appendable, boolean)} 191b5840353SAdam Hornáček * with {@code q}, a transient {@link StringBuilder}, and {@code false}. 192b5840353SAdam Hornáček * 193b5840353SAdam Hornáček * @param q a character sequence 194b5840353SAdam Hornáček * @return a string representing the character sequence in HTML 195b5840353SAdam Hornáček */ htmlize(CharSequence q)196b5840353SAdam Hornáček public static String htmlize(CharSequence q) { 197b5840353SAdam Hornáček StringBuilder sb = new StringBuilder(q.length() * 2); 198b5840353SAdam Hornáček try { 199b5840353SAdam Hornáček htmlize(q, sb, false); 200b5840353SAdam Hornáček } catch (IOException ioe) { 201b5840353SAdam Hornáček // IOException cannot happen when the destination is a 202b5840353SAdam Hornáček // StringBuilder. Wrap in an AssertionError so that callers 203b5840353SAdam Hornáček // don't have to check for an IOException that should never 204b5840353SAdam Hornáček // happen. 205b5840353SAdam Hornáček throw new AssertionError("StringBuilder threw IOException", ioe); 206b5840353SAdam Hornáček } 207b5840353SAdam Hornáček return sb.toString(); 208b5840353SAdam Hornáček } 209b5840353SAdam Hornáček 210b5840353SAdam Hornáček /** 211b5840353SAdam Hornáček * Append a character sequence to the given destination whereby special 212b5840353SAdam Hornáček * characters for HTML or characters that are not printable ASCII are 213b5840353SAdam Hornáček * escaped accordingly. 214b5840353SAdam Hornáček * 215b5840353SAdam Hornáček * @param q a character sequence to escape 216b5840353SAdam Hornáček * @param dest where to append the character sequence to 217b5840353SAdam Hornáček * @param pre a value indicating whether the output is pre-formatted -- if 218b5840353SAdam Hornáček * true then LFs will not be converted to <br> elements 219b5840353SAdam Hornáček * @throws IOException if an error occurred when writing to {@code dest} 220b5840353SAdam Hornáček */ htmlize(CharSequence q, Appendable dest, boolean pre)221b5840353SAdam Hornáček public static void htmlize(CharSequence q, Appendable dest, boolean pre) 222b5840353SAdam Hornáček throws IOException { 223b5840353SAdam Hornáček for (int i = 0; i < q.length(); i++) { 224b5840353SAdam Hornáček htmlize(q.charAt(i), dest, pre); 225b5840353SAdam Hornáček } 226b5840353SAdam Hornáček } 227b5840353SAdam Hornáček 228b5840353SAdam Hornáček /** 229b5840353SAdam Hornáček * Calls 230b5840353SAdam Hornáček * {@link #htmlize(java.lang.CharSequence, java.lang.Appendable, boolean)} 231b5840353SAdam Hornáček * with {@code q}, {@code dest}, and {@code false}. 232b5840353SAdam Hornáček * 233b5840353SAdam Hornáček * @param q a character sequence to escape 234b5840353SAdam Hornáček * @param dest where to append the character sequence to 235b5840353SAdam Hornáček * @throws IOException if an error occurred when writing to {@code dest} 236b5840353SAdam Hornáček */ htmlize(CharSequence q, Appendable dest)237b5840353SAdam Hornáček public static void htmlize(CharSequence q, Appendable dest) 238b5840353SAdam Hornáček throws IOException { 239b5840353SAdam Hornáček htmlize(q, dest, false); 240b5840353SAdam Hornáček } 241b5840353SAdam Hornáček 242b5840353SAdam Hornáček /** 243b5840353SAdam Hornáček * Append a character array to the given destination whereby special 244b5840353SAdam Hornáček * characters for HTML or characters that are not printable ASCII are 245b5840353SAdam Hornáček * escaped accordingly. 246b5840353SAdam Hornáček * 247b5840353SAdam Hornáček * @param cs characters to escape 248b5840353SAdam Hornáček * @param length max. number of characters to append, starting from index 0. 249b5840353SAdam Hornáček * @param dest where to append the character sequence to 250b5840353SAdam Hornáček * @throws IOException if an error occurred when writing to {@code dest} 251b5840353SAdam Hornáček */ htmlize(char[] cs, int length, Appendable dest)252b5840353SAdam Hornáček public static void htmlize(char[] cs, int length, Appendable dest) 253b5840353SAdam Hornáček throws IOException { 254b5840353SAdam Hornáček int len = length; 255b5840353SAdam Hornáček if (cs.length < length) { 256b5840353SAdam Hornáček len = cs.length; 257b5840353SAdam Hornáček } 258b5840353SAdam Hornáček for (int i = 0; i < len; i++) { 259b5840353SAdam Hornáček htmlize(cs[i], dest, false); 260b5840353SAdam Hornáček } 261b5840353SAdam Hornáček } 262b5840353SAdam Hornáček 263b5840353SAdam Hornáček /** 264b5840353SAdam Hornáček * Append a character to the given destination whereby special characters 265b5840353SAdam Hornáček * special for HTML or characters that are not printable ASCII are 266b5840353SAdam Hornáček * escaped accordingly. 267b5840353SAdam Hornáček * 268b5840353SAdam Hornáček * @param c the character to append 269b5840353SAdam Hornáček * @param dest where to append the character to 270b5840353SAdam Hornáček * @param pre a value indicating whether the output is pre-formatted -- if 271b5840353SAdam Hornáček * true then LFs will not be converted to <br> elements 272b5840353SAdam Hornáček * @throws IOException if an error occurred when writing to {@code dest} 273b5840353SAdam Hornáček * @see #needsHtmlize(char, boolean) 274b5840353SAdam Hornáček */ htmlize(char c, Appendable dest, boolean pre)275b5840353SAdam Hornáček private static void htmlize(char c, Appendable dest, boolean pre) 276b5840353SAdam Hornáček throws IOException { 277b5840353SAdam Hornáček switch (c) { 278b5840353SAdam Hornáček case '\'': 279b5840353SAdam Hornáček dest.append("'"); 280b5840353SAdam Hornáček break; 281b5840353SAdam Hornáček case '"': 282b5840353SAdam Hornáček dest.append("""); 283b5840353SAdam Hornáček break; 284b5840353SAdam Hornáček case '&': 285b5840353SAdam Hornáček dest.append("&"); 286b5840353SAdam Hornáček break; 287b5840353SAdam Hornáček case '>': 288b5840353SAdam Hornáček dest.append(">"); 289b5840353SAdam Hornáček break; 290b5840353SAdam Hornáček case '<': 291b5840353SAdam Hornáček dest.append("<"); 292b5840353SAdam Hornáček break; 293b5840353SAdam Hornáček case '\n': 294b5840353SAdam Hornáček if (pre) { 295b5840353SAdam Hornáček dest.append(c); 296b5840353SAdam Hornáček } else { 297b5840353SAdam Hornáček dest.append("<br/>"); 298b5840353SAdam Hornáček } 299b5840353SAdam Hornáček break; 300b5840353SAdam Hornáček default: 301b5840353SAdam Hornáček if ((c >= ' ' && c <= '~') || (c < ' ' && 302b5840353SAdam Hornáček Character.isWhitespace(c))) { 303b5840353SAdam Hornáček dest.append(c); 304b5840353SAdam Hornáček } else { 305b5840353SAdam Hornáček dest.append("&#").append(Integer.toString(c)).append(';'); 306b5840353SAdam Hornáček } 307b5840353SAdam Hornáček break; 308b5840353SAdam Hornáček } 309b5840353SAdam Hornáček } 310b5840353SAdam Hornáček 311b5840353SAdam Hornáček /** 312b5840353SAdam Hornáček * Determine if a character is a special character needing HTML escaping or 313b5840353SAdam Hornáček * is a character that is not printable ASCII. 314b5840353SAdam Hornáček * @param c the character to examine 315b5840353SAdam Hornáček * @param pre a value indicating whether the output is pre-formatted -- if 316b5840353SAdam Hornáček * true then LFs will not be converted to <br> elements 317b5840353SAdam Hornáček * @see #htmlize(char, java.lang.Appendable, boolean) 318b5840353SAdam Hornáček */ needsHtmlize(char c, boolean pre)319b5840353SAdam Hornáček private static boolean needsHtmlize(char c, boolean pre) { 320b5840353SAdam Hornáček switch (c) { 321b5840353SAdam Hornáček case '\'': 322b5840353SAdam Hornáček case '"': 323b5840353SAdam Hornáček case '&': 324b5840353SAdam Hornáček case '>': 325b5840353SAdam Hornáček case '<': 326b5840353SAdam Hornáček return true; 327b5840353SAdam Hornáček case '\n': 328a72324b1SAdam Hornáček if (!pre) { 329a72324b1SAdam Hornáček return true; 330a72324b1SAdam Hornáček } 331b5840353SAdam Hornáček default: 332b13c5a0eSAdam Hornacek return (c < ' ' || c > '~') && (c >= ' ' || !Character.isWhitespace(c)); 333b5840353SAdam Hornáček } 334b5840353SAdam Hornáček } 335b5840353SAdam Hornáček needsHtmlize(CharSequence q, boolean pre)336b5840353SAdam Hornáček private static boolean needsHtmlize(CharSequence q, boolean pre) { 337b5840353SAdam Hornáček for (int i = 0; i < q.length(); ++i) { 338a72324b1SAdam Hornáček if (needsHtmlize(q.charAt(i), pre)) { 339a72324b1SAdam Hornáček return true; 340a72324b1SAdam Hornáček } 341b5840353SAdam Hornáček } 342b5840353SAdam Hornáček return false; 343b5840353SAdam Hornáček } 344b5840353SAdam Hornáček 345b5840353SAdam Hornáček /** 346807ead8fSLubos Kosco * Convenience method for {@code breadcrumbPath(urlPrefix, path, PATH_SEPARATOR)}. 347b5840353SAdam Hornáček * 348b5840353SAdam Hornáček * @param urlPrefix prefix to add to each url 349b5840353SAdam Hornáček * @param path path to crack 350379f387bSVladimir Kotal * @return HTML markup for the breadcrumb or the path itself. 351b5840353SAdam Hornáček * 352b5840353SAdam Hornáček * @see #breadcrumbPath(String, String, char) 353b5840353SAdam Hornáček */ breadcrumbPath(String urlPrefix, String path)354b5840353SAdam Hornáček public static String breadcrumbPath(String urlPrefix, String path) { 355807ead8fSLubos Kosco return breadcrumbPath(urlPrefix, path, PATH_SEPARATOR); 356b5840353SAdam Hornáček } 357b5840353SAdam Hornáček 358b5840353SAdam Hornáček /** 359b5840353SAdam Hornáček * Convenience method for 360b5840353SAdam Hornáček * {@code breadcrumbPath(urlPrefix, path, sep, "", false)}. 361b5840353SAdam Hornáček * 362b5840353SAdam Hornáček * @param urlPrefix prefix to add to each url 363b5840353SAdam Hornáček * @param path path to crack 364b5840353SAdam Hornáček * @param sep separator to use to crack the given path 365b5840353SAdam Hornáček * 366b5840353SAdam Hornáček * @return HTML markup fro the breadcrumb or the path itself. 367b5840353SAdam Hornáček * @see #breadcrumbPath(String, String, char, String, boolean, boolean) 368b5840353SAdam Hornáček */ breadcrumbPath(String urlPrefix, String path, char sep)369b5840353SAdam Hornáček public static String breadcrumbPath(String urlPrefix, String path, char sep) { 370b5840353SAdam Hornáček return breadcrumbPath(urlPrefix, path, sep, "", false); 371b5840353SAdam Hornáček } 372b5840353SAdam Hornáček 373b5840353SAdam Hornáček /** 374b5840353SAdam Hornáček * Convenience method for 375b5840353SAdam Hornáček * {@code breadcrumbPath(urlPrefix, path, sep, "", false, path.endsWith(sep)}. 376b5840353SAdam Hornáček * 377b5840353SAdam Hornáček * @param urlPrefix prefix to add to each url 378b5840353SAdam Hornáček * @param path path to crack 379b5840353SAdam Hornáček * @param sep separator to use to crack the given path 380b5840353SAdam Hornáček * @param urlPostfix suffix to add to each url 381b5840353SAdam Hornáček * @param compact if {@code true} the given path gets transformed into its 382b5840353SAdam Hornáček * canonical form (.i.e. all '.' and '..' and double separators removed, but 383b5840353SAdam Hornáček * not always resolves to an absolute path) before processing starts. 384b5840353SAdam Hornáček * @return HTML markup fro the breadcrumb or the path itself. 385b5840353SAdam Hornáček * @see #breadcrumbPath(String, String, char, String, boolean, boolean) 386b5840353SAdam Hornáček * @see #getCanonicalPath(String, char) 387b5840353SAdam Hornáček */ breadcrumbPath(String urlPrefix, String path, char sep, String urlPostfix, boolean compact)388b5840353SAdam Hornáček public static String breadcrumbPath(String urlPrefix, String path, 389b5840353SAdam Hornáček char sep, String urlPostfix, boolean compact) { 390b5840353SAdam Hornáček if (path == null || path.length() == 0) { 391b5840353SAdam Hornáček return path; 392b5840353SAdam Hornáček } 393b5840353SAdam Hornáček return breadcrumbPath(urlPrefix, path, sep, urlPostfix, compact, 394b5840353SAdam Hornáček path.charAt(path.length() - 1) == sep); 395b5840353SAdam Hornáček } 396b5840353SAdam Hornáček 397b5840353SAdam Hornáček /** 398b5840353SAdam Hornáček * Create a breadcrumb path to allow navigation to each element of a path. 399b5840353SAdam Hornáček * Consecutive separators (<var>sep</var>) in the given <var>path</var> are 400b5840353SAdam Hornáček * always collapsed into a single separator automatically. If 401b5840353SAdam Hornáček * <var>compact</var> is {@code true} path gets translated into a canonical 402b5840353SAdam Hornáček * path similar to {@link File#getCanonicalPath()}, however the current 403b5840353SAdam Hornáček * working directory is assumed to be "/" and no checks are done (e.g. 404b5840353SAdam Hornáček * neither whether the path [component] exists nor which type it is). 405b5840353SAdam Hornáček * 406b5840353SAdam Hornáček * @param urlPrefix what should be prepend to the constructed URL 407b5840353SAdam Hornáček * @param path the full path from which the breadcrumb path is built. 408b5840353SAdam Hornáček * @param sep the character that separates the path components in 409b5840353SAdam Hornáček * <var>path</var> 410b5840353SAdam Hornáček * @param urlPostfix what should be append to the constructed URL 411b5840353SAdam Hornáček * @param compact if {@code true}, a canonical path gets constructed before 412b5840353SAdam Hornáček * processing. 413b5840353SAdam Hornáček * @param isDir if {@code true} a "/" gets append to the last path 414b5840353SAdam Hornáček * component's link and <var>sep</var> to its name 415b5840353SAdam Hornáček * @return <var>path</var> if it resolves to an empty or "/" or {@code null} 416b5840353SAdam Hornáček * path, the HTML markup for the breadcrumb path otherwise. 417b5840353SAdam Hornáček */ breadcrumbPath(String urlPrefix, String path, char sep, String urlPostfix, boolean compact, boolean isDir)418b5840353SAdam Hornáček public static String breadcrumbPath(String urlPrefix, String path, 419b5840353SAdam Hornáček char sep, String urlPostfix, boolean compact, boolean isDir) { 420b5840353SAdam Hornáček if (path == null || path.length() == 0) { 421b5840353SAdam Hornáček return path; 422b5840353SAdam Hornáček } 423b5840353SAdam Hornáček String[] pnames = normalize(path.split(escapeForRegex(sep)), compact); 424b5840353SAdam Hornáček if (pnames.length == 0) { 425b5840353SAdam Hornáček return path; 426b5840353SAdam Hornáček } 427b5840353SAdam Hornáček String prefix = urlPrefix == null ? "" : urlPrefix; 428b5840353SAdam Hornáček String postfix = urlPostfix == null ? "" : urlPostfix; 429b5840353SAdam Hornáček StringBuilder pwd = new StringBuilder(path.length() + pnames.length); 430b5840353SAdam Hornáček StringBuilder markup 431b5840353SAdam Hornáček = new StringBuilder((pnames.length + 3 >> 1) * path.length() 432b5840353SAdam Hornáček + pnames.length 433b5840353SAdam Hornáček * (17 + prefix.length() + postfix.length())); 434b5840353SAdam Hornáček int k = path.indexOf(pnames[0]); 435b5840353SAdam Hornáček if (path.lastIndexOf(sep, k) != -1) { 436807ead8fSLubos Kosco pwd.append(PATH_SEPARATOR); 437b5840353SAdam Hornáček markup.append(sep); 438b5840353SAdam Hornáček } 439b5840353SAdam Hornáček for (int i = 0; i < pnames.length; i++) { 440*d6df19e1SAdam Hornacek pwd.append(uriEncodePath(pnames[i])); 441b5840353SAdam Hornáček if (isDir || i < pnames.length - 1) { 442807ead8fSLubos Kosco pwd.append(PATH_SEPARATOR); 443b5840353SAdam Hornáček } 444b5840353SAdam Hornáček markup.append(anchorLinkStart).append(prefix).append(pwd) 445b5840353SAdam Hornáček .append(postfix).append(closeQuotedTag).append(pnames[i]) 446b5840353SAdam Hornáček .append(anchorEnd); 447b5840353SAdam Hornáček if (isDir || i < pnames.length - 1) { 448b5840353SAdam Hornáček markup.append(sep); 449b5840353SAdam Hornáček } 450b5840353SAdam Hornáček } 451b5840353SAdam Hornáček return markup.toString(); 452b5840353SAdam Hornáček } 453b5840353SAdam Hornáček 454b5840353SAdam Hornáček /** 455b5840353SAdam Hornáček * Normalize the given <var>path</var> to its canonical form. I.e. all 456b5840353SAdam Hornáček * separators (<var>sep</var>) are replaced with a slash ('/'), all double 457b5840353SAdam Hornáček * slashes are replaced by a single slash, all single dot path components 458b5840353SAdam Hornáček * (".") of the formed path are removed and all double dot path components 459b5840353SAdam Hornáček * (".." ) of the formed path are replaced with its parent or '/' if there 460b5840353SAdam Hornáček * is no parent. 461b5840353SAdam Hornáček * <p> 462b5840353SAdam Hornáček * So the difference to {@link File#getCanonicalPath()} is, that this method 463b5840353SAdam Hornáček * does not hit the disk (just string manipulation), resolves 464b5840353SAdam Hornáček * <var>path</var> 465b5840353SAdam Hornáček * always against '/' and thus always returns an absolute path, which may 466b5840353SAdam Hornáček * actually not exist, and which has a single trailing '/' if the given 467b5840353SAdam Hornáček * <var>path</var> ends with the given <var>sep</var>. 468b5840353SAdam Hornáček * 469b5840353SAdam Hornáček * @param path path to mangle. If not absolute or {@code null}, the current 470b5840353SAdam Hornáček * working directory is assumed to be '/'. 471b5840353SAdam Hornáček * @param sep file separator to use to crack <var>path</var> into path 472b5840353SAdam Hornáček * components 473b5840353SAdam Hornáček * @return always a canonical path which starts with a '/'. 474b5840353SAdam Hornáček */ getCanonicalPath(String path, char sep)475b5840353SAdam Hornáček public static String getCanonicalPath(String path, char sep) { 476b5840353SAdam Hornáček if (path == null || path.length() == 0) { 477b5840353SAdam Hornáček return "/"; 478b5840353SAdam Hornáček } 479b5840353SAdam Hornáček String[] pnames = normalize(path.split(escapeForRegex(sep)), true); 480b5840353SAdam Hornáček if (pnames.length == 0) { 481b5840353SAdam Hornáček return "/"; 482b5840353SAdam Hornáček } 483b5840353SAdam Hornáček StringBuilder buf = new StringBuilder(path.length()); 484b5840353SAdam Hornáček buf.append('/'); 485b13c5a0eSAdam Hornacek for (String pname : pnames) { 486b13c5a0eSAdam Hornacek buf.append(pname).append('/'); 487b5840353SAdam Hornáček } 488b5840353SAdam Hornáček if (path.charAt(path.length() - 1) != sep) { 489b5840353SAdam Hornáček // since is not a general purpose method. So we waive to handle 490b5840353SAdam Hornáček // cases like: 491b5840353SAdam Hornáček // || path.endsWith("/..") || path.endsWith("/.") 492b5840353SAdam Hornáček buf.setLength(buf.length() - 1); 493b5840353SAdam Hornáček } 494b5840353SAdam Hornáček return buf.toString(); 495b5840353SAdam Hornáček } 496b5840353SAdam Hornáček 497d1e826faSAdam Hornáček private static final Pattern EMAIL_PATTERN 498b5840353SAdam Hornáček = Pattern.compile("([^<\\s]+@[^>\\s]+)"); 499b5840353SAdam Hornáček 500b5840353SAdam Hornáček /** 501b5840353SAdam Hornáček * Get email address of the author. 502b5840353SAdam Hornáček * 503b5840353SAdam Hornáček * @param author string containing author and possibly email address. 504b5840353SAdam Hornáček * @return email address of the author or full author string if the author 505b5840353SAdam Hornáček * string does not contain an email address. 506b5840353SAdam Hornáček */ getEmail(String author)507b5840353SAdam Hornáček public static String getEmail(String author) { 508b5840353SAdam Hornáček Matcher emailMatcher = EMAIL_PATTERN.matcher(author); 509b5840353SAdam Hornáček String email = author; 510b5840353SAdam Hornáček if (emailMatcher.find()) { 511b5840353SAdam Hornáček email = emailMatcher.group(1).trim(); 512b5840353SAdam Hornáček } 513b5840353SAdam Hornáček 514b5840353SAdam Hornáček return email; 515b5840353SAdam Hornáček } 516b5840353SAdam Hornáček 517b5840353SAdam Hornáček /** 518b5840353SAdam Hornáček * Remove all empty and {@code null} string elements from the given 519b5840353SAdam Hornáček * <var>names</var> and optionally all redundant information like "." and 520b5840353SAdam Hornáček * "..". 521b5840353SAdam Hornáček * 522b5840353SAdam Hornáček * @param names names to check 523b5840353SAdam Hornáček * @param canonical if {@code true}, remove redundant elements as well. 524b5840353SAdam Hornáček * @return a possible empty array of names all with a length > 0. 525b5840353SAdam Hornáček */ normalize(String[] names, boolean canonical)526b5840353SAdam Hornáček private static String[] normalize(String[] names, boolean canonical) { 527b5840353SAdam Hornáček LinkedList<String> res = new LinkedList<>(); 528b5840353SAdam Hornáček if (names == null || names.length == 0) { 529b5840353SAdam Hornáček return new String[0]; 530b5840353SAdam Hornáček } 531b5840353SAdam Hornáček for (String name : names) { 532b5840353SAdam Hornáček if (name == null || name.length() == 0) { 533b5840353SAdam Hornáček continue; 534b5840353SAdam Hornáček } 535b5840353SAdam Hornáček if (canonical) { 536b5840353SAdam Hornáček if (name.equals("..")) { 537b5840353SAdam Hornáček if (!res.isEmpty()) { 538b5840353SAdam Hornáček res.removeLast(); 539b5840353SAdam Hornáček } 540ff44f24aSAdam Hornáček } else if (!name.equals(".")) { 541b5840353SAdam Hornáček res.add(name); 542b5840353SAdam Hornáček } 543b5840353SAdam Hornáček } else { 544b5840353SAdam Hornáček res.add(name); 545b5840353SAdam Hornáček } 546b5840353SAdam Hornáček } 547b13c5a0eSAdam Hornacek return res.size() == names.length ? names : res.toArray(new String[0]); 548b5840353SAdam Hornáček } 549b5840353SAdam Hornáček 550b5840353SAdam Hornáček /** 551b5840353SAdam Hornáček * Generate a regexp that matches the specified character. Escape it in case 552b5840353SAdam Hornáček * it is a character that has a special meaning in a regexp. 553b5840353SAdam Hornáček * 554b5840353SAdam Hornáček * @param c the character that the regexp should match 555b5840353SAdam Hornáček * @return a six-character string in the form of <code>\u</code><i>hhhh</i> 556b5840353SAdam Hornáček */ escapeForRegex(char c)557b5840353SAdam Hornáček private static String escapeForRegex(char c) { 558b5840353SAdam Hornáček StringBuilder sb = new StringBuilder(6); 559b5840353SAdam Hornáček sb.append("\\u"); 560b5840353SAdam Hornáček String hex = Integer.toHexString(c); 561b13c5a0eSAdam Hornacek sb.append("0".repeat(4 - hex.length())); 562b5840353SAdam Hornáček sb.append(hex); 563b5840353SAdam Hornáček return sb.toString(); 564b5840353SAdam Hornáček } 565b5840353SAdam Hornáček 566b13c5a0eSAdam Hornacek private static final NumberFormat FORMATTER = new DecimalFormat("#,###,###,###.#"); 567b5840353SAdam Hornáček 568b13c5a0eSAdam Hornacek private static final NumberFormat COUNT_FORMATTER = new DecimalFormat("#,###,###,###"); 569b5840353SAdam Hornáček 570b5840353SAdam Hornáček /** 571b5840353SAdam Hornáček * Convert the given size into a human readable string. 572b5840353SAdam Hornáček * 573b5840353SAdam Hornáček * NOTE: when changing the output of this function make sure to adapt the 574b5840353SAdam Hornáček * jQuery tablesorter custom parsers in web/httpheader.jspf 575b5840353SAdam Hornáček * 576b5840353SAdam Hornáček * @param num size to convert. 577b5840353SAdam Hornáček * @return a readable string 578b5840353SAdam Hornáček */ readableSize(long num)579b5840353SAdam Hornáček public static String readableSize(long num) { 580b5840353SAdam Hornáček float l = num; 581b5840353SAdam Hornáček NumberFormat formatter = (NumberFormat) FORMATTER.clone(); 582b5840353SAdam Hornáček if (l < 1024) { 583b5840353SAdam Hornáček return formatter.format(l) + ' '; // for none-dirs append 'B'? ... 584b5840353SAdam Hornáček } else if (l < 1048576) { 585b5840353SAdam Hornáček return (formatter.format(l / 1024) + " KiB"); 586b5840353SAdam Hornáček } else if (l < 1073741824) { 587b5840353SAdam Hornáček return ("<b>" + formatter.format(l / 1048576) + " MiB</b>"); 588b5840353SAdam Hornáček } else { 589b5840353SAdam Hornáček return ("<b>" + formatter.format(l / 1073741824) + " GiB</b>"); 590b5840353SAdam Hornáček } 591b5840353SAdam Hornáček } 592b5840353SAdam Hornáček 593b5840353SAdam Hornáček /** 594b5840353SAdam Hornáček * Convert the specified {@code count} into a human readable string. 595b5840353SAdam Hornáček * @param count value to convert. 596b5840353SAdam Hornáček * @return a readable string 597b5840353SAdam Hornáček */ readableCount(long count)598b5840353SAdam Hornáček public static String readableCount(long count) { 5990402c7e4SChris Fraire return readableCount(count, false); 6000402c7e4SChris Fraire } 6010402c7e4SChris Fraire 6020402c7e4SChris Fraire /** 6030402c7e4SChris Fraire * Convert the specified {@code count} into a human readable string. 6040402c7e4SChris Fraire * @param isKnownDirectory a value indicating if {@code count} is known to 6050402c7e4SChris Fraire * be for a directory 6060402c7e4SChris Fraire * @param count value to convert. 6070402c7e4SChris Fraire * @return a readable string 6080402c7e4SChris Fraire */ readableCount(long count, boolean isKnownDirectory)6090402c7e4SChris Fraire public static String readableCount(long count, boolean isKnownDirectory) { 610b5840353SAdam Hornáček NumberFormat formatter = (NumberFormat) COUNT_FORMATTER.clone(); 6110402c7e4SChris Fraire if (isKnownDirectory || count < BOLD_COUNT_THRESHOLD) { 612b5840353SAdam Hornáček return formatter.format(count); 613b5840353SAdam Hornáček } else { 614b5840353SAdam Hornáček return "<b>" + formatter.format(count) + "</b>"; 615b5840353SAdam Hornáček } 616b5840353SAdam Hornáček } 617b5840353SAdam Hornáček 618b5840353SAdam Hornáček /** 619b5840353SAdam Hornáček * Converts different HTML special characters into their encodings used in 620b5840353SAdam Hornáček * html. 621b5840353SAdam Hornáček * 622b5840353SAdam Hornáček * @param s input text 623b5840353SAdam Hornáček * @return encoded text for use in <a title=""> tag 624b5840353SAdam Hornáček */ encode(String s)625b5840353SAdam Hornáček public static String encode(String s) { 626b5840353SAdam Hornáček /** 627b5840353SAdam Hornáček * Make sure that the buffer is long enough to contain the whole string 628b5840353SAdam Hornáček * with the expanded special characters. We use 1.5*length as a 629b5840353SAdam Hornáček * heuristic. 630b5840353SAdam Hornáček */ 631b5840353SAdam Hornáček StringBuilder sb = new StringBuilder((int) Math.max(10, s.length() * 1.5)); 632b5840353SAdam Hornáček try { 633b5840353SAdam Hornáček encode(s, sb); 634b5840353SAdam Hornáček } catch (IOException ex) { 635b5840353SAdam Hornáček // IOException cannot happen when the destination is a 636b5840353SAdam Hornáček // StringBuilder. Wrap in an AssertionError so that callers 637b5840353SAdam Hornáček // don't have to check for an IOException that should never 638b5840353SAdam Hornáček // happen. 639b5840353SAdam Hornáček throw new AssertionError("StringBuilder threw IOException", ex); 640b5840353SAdam Hornáček } 641b5840353SAdam Hornáček return sb.toString(); 642b5840353SAdam Hornáček } 643b5840353SAdam Hornáček 644b5840353SAdam Hornáček /** 645b5840353SAdam Hornáček * Converts different HTML special characters into their encodings used in 646b5840353SAdam Hornáček * html. 647b5840353SAdam Hornáček * 648b5840353SAdam Hornáček * @param s input text 649b5840353SAdam Hornáček * @param dest appendable destination for appending the encoded characters 65081b586e6SVladimir Kotal * @throws java.io.IOException I/O exception 651b5840353SAdam Hornáček */ encode(String s, Appendable dest)652b5840353SAdam Hornáček public static void encode(String s, Appendable dest) throws IOException { 653b5840353SAdam Hornáček for (int i = 0; i < s.length(); i++) { 654b5840353SAdam Hornáček char c = s.charAt(i); 655b5840353SAdam Hornáček if (c > 127 || c == '"' || c == '<' || c == '>' || c == '&' || c == '\'') { 656b5840353SAdam Hornáček // special html characters 657b5840353SAdam Hornáček dest.append("&#").append("" + (int) c).append(";"); 658b5840353SAdam Hornáček } else if (c == ' ') { 659b5840353SAdam Hornáček // non breaking space 660b5840353SAdam Hornáček dest.append(" "); 661b5840353SAdam Hornáček } else if (c == '\t') { 662b5840353SAdam Hornáček dest.append(" "); 663b5840353SAdam Hornáček } else if (c == '\n') { 664b5840353SAdam Hornáček // <br/> 665b5840353SAdam Hornáček dest.append("<br/>"); 666b5840353SAdam Hornáček } else { 667b5840353SAdam Hornáček dest.append(c); 668b5840353SAdam Hornáček } 669b5840353SAdam Hornáček } 670b5840353SAdam Hornáček } 671b5840353SAdam Hornáček 672b5840353SAdam Hornáček /** 673ff44f24aSAdam Hornáček * Encode URL. 674b5840353SAdam Hornáček * 675b5840353SAdam Hornáček * @param urlStr string URL 676b5840353SAdam Hornáček * @return the encoded URL 67781b586e6SVladimir Kotal * @throws URISyntaxException URI syntax 67881b586e6SVladimir Kotal * @throws MalformedURLException URL malformed 679b5840353SAdam Hornáček */ encodeURL(String urlStr)680b5840353SAdam Hornáček public static String encodeURL(String urlStr) throws URISyntaxException, MalformedURLException { 681b5840353SAdam Hornáček URL url = new URL(urlStr); 682b5840353SAdam Hornáček URI constructed = new URI(url.getProtocol(), url.getUserInfo(), 683b5840353SAdam Hornáček url.getHost(), url.getPort(), 684b5840353SAdam Hornáček url.getPath(), url.getQuery(), url.getRef()); 685b5840353SAdam Hornáček return constructed.toString(); 686b5840353SAdam Hornáček } 687b5840353SAdam Hornáček 688b5840353SAdam Hornáček /** 689b5840353SAdam Hornáček * Write out line information wrt. to the given annotation in the format: 690b5840353SAdam Hornáček * {@code Linenumber Blame Author} incl. appropriate links. 691b5840353SAdam Hornáček * 692b5840353SAdam Hornáček * @param num linenumber to print 693b5840353SAdam Hornáček * @param out print destination 694b5840353SAdam Hornáček * @param annotation annotation to use. If {@code null} only the linenumber 695b5840353SAdam Hornáček * gets printed. 696b5840353SAdam Hornáček * @param userPageLink see {@link RuntimeEnvironment#getUserPage()} 697b5840353SAdam Hornáček * @param userPageSuffix see {@link RuntimeEnvironment#getUserPageSuffix()} 698b5840353SAdam Hornáček * @param project project that is used 699b5840353SAdam Hornáček * @throws IOException depends on the destination (<var>out</var>). 700b5840353SAdam Hornáček */ readableLine(int num, Writer out, Annotation annotation, String userPageLink, String userPageSuffix, String project)701b5840353SAdam Hornáček public static void readableLine(int num, Writer out, Annotation annotation, 702b5840353SAdam Hornáček String userPageLink, String userPageSuffix, String project) 703ff44f24aSAdam Hornáček throws IOException { 704b5840353SAdam Hornáček readableLine(num, out, annotation, userPageLink, userPageSuffix, project, false); 705b5840353SAdam Hornáček } 706b5840353SAdam Hornáček readableLine(int num, Writer out, Annotation annotation, String userPageLink, String userPageSuffix, String project, boolean skipNewline)707ff44f24aSAdam Hornáček public static void readableLine(int num, Writer out, Annotation annotation, String userPageLink, 708ff44f24aSAdam Hornáček String userPageSuffix, String project, boolean skipNewline) 709ff44f24aSAdam Hornáček throws IOException { 710b5840353SAdam Hornáček // this method should go to JFlexXref 711b5840353SAdam Hornáček String snum = String.valueOf(num); 712b5840353SAdam Hornáček if (num > 1 && !skipNewline) { 713b5840353SAdam Hornáček out.write("\n"); 714b5840353SAdam Hornáček } 715b5840353SAdam Hornáček out.write(anchorClassStart); 716b5840353SAdam Hornáček out.write(num % 10 == 0 ? "hl" : "l"); 717b5840353SAdam Hornáček out.write("\" name=\""); 718b5840353SAdam Hornáček out.write(snum); 719b5840353SAdam Hornáček out.write("\" href=\"#"); 720b5840353SAdam Hornáček out.write(snum); 721b5840353SAdam Hornáček out.write(closeQuotedTag); 722b5840353SAdam Hornáček out.write(snum); 723b5840353SAdam Hornáček out.write(anchorEnd); 724754a7a39SVladimir Kotal 725b5840353SAdam Hornáček if (annotation != null) { 726754a7a39SVladimir Kotal writeAnnotation(num, out, annotation, userPageLink, userPageSuffix, project); 727754a7a39SVladimir Kotal } 728754a7a39SVladimir Kotal } 729754a7a39SVladimir Kotal writeAnnotation(int num, Writer out, Annotation annotation, String userPageLink, String userPageSuffix, String project)730754a7a39SVladimir Kotal private static void writeAnnotation(int num, Writer out, Annotation annotation, String userPageLink, 731754a7a39SVladimir Kotal String userPageSuffix, String project) throws IOException { 732b5840353SAdam Hornáček String r = annotation.getRevision(num); 733b5840353SAdam Hornáček boolean enabled = annotation.isEnabled(num); 734b5840353SAdam Hornáček out.write("<span class=\"blame\">"); 735b5840353SAdam Hornáček if (enabled) { 736b5840353SAdam Hornáček out.write(anchorClassStart); 737b5840353SAdam Hornáček out.write("r"); 738e87b8f8cSKryštof Tulinger out.write("\" style=\"background-color: "); 739e87b8f8cSKryštof Tulinger out.write(annotation.getColors().getOrDefault(r, "inherit")); 740b5840353SAdam Hornáček out.write("\" href=\""); 741*d6df19e1SAdam Hornacek out.write(uriEncode(annotation.getFilename())); 7421c830032SChris Fraire out.write("?"); 7431c830032SChris Fraire out.write(QueryParameters.ANNOTATION_PARAM_EQ_TRUE); 7441c830032SChris Fraire out.write("&"); 7451c830032SChris Fraire out.write(QueryParameters.REVISION_PARAM_EQ); 746*d6df19e1SAdam Hornacek out.write(uriEncode(r)); 747b5840353SAdam Hornáček String msg = annotation.getDesc(r); 748b5840353SAdam Hornáček out.write("\" title=\""); 749b5840353SAdam Hornáček if (msg != null) { 750754a7a39SVladimir Kotal out.write(Util.encode(msg)); 751b5840353SAdam Hornáček } 752b5840353SAdam Hornáček if (annotation.getFileVersion(r) != 0) { 753b5840353SAdam Hornáček out.write("<br/>version: " + annotation.getFileVersion(r) + "/" 754b5840353SAdam Hornáček + annotation.getRevisions().size()); 755b5840353SAdam Hornáček } 756b5840353SAdam Hornáček out.write(closeQuotedTag); 757b5840353SAdam Hornáček } 758b5840353SAdam Hornáček StringBuilder buf = new StringBuilder(); 759e87b8f8cSKryštof Tulinger final boolean most_recent_revision = annotation.getFileVersion(r) == annotation.getRevisions().size(); 760e87b8f8cSKryštof Tulinger // print an asterisk for the most recent revision 761e87b8f8cSKryštof Tulinger if (most_recent_revision) { 762e87b8f8cSKryštof Tulinger buf.append("<span class=\"most_recent_revision\">"); 763e87b8f8cSKryštof Tulinger buf.append('*'); 764e87b8f8cSKryštof Tulinger } 765b5840353SAdam Hornáček htmlize(r, buf); 766e87b8f8cSKryštof Tulinger if (most_recent_revision) { 767e87b8f8cSKryštof Tulinger buf.append("</span>"); // recent revision span 768e87b8f8cSKryštof Tulinger } 769b5840353SAdam Hornáček out.write(buf.toString()); 770b5840353SAdam Hornáček buf.setLength(0); 771b5840353SAdam Hornáček if (enabled) { 772b5840353SAdam Hornáček RuntimeEnvironment env = RuntimeEnvironment.getInstance(); 773b5840353SAdam Hornáček 774b5840353SAdam Hornáček out.write(anchorEnd); 775b5840353SAdam Hornáček 776b5840353SAdam Hornáček // Write link to search the revision in current project. 777b5840353SAdam Hornáček out.write(anchorClassStart); 778b5840353SAdam Hornáček out.write("search\" href=\"" + env.getUrlPrefix()); 7791c830032SChris Fraire out.write(QueryParameters.DEFS_SEARCH_PARAM_EQ); 7801c830032SChris Fraire out.write("&"); 7811c830032SChris Fraire out.write(QueryParameters.REFS_SEARCH_PARAM_EQ); 7821c830032SChris Fraire out.write("&"); 7831c830032SChris Fraire out.write(QueryParameters.PATH_SEARCH_PARAM_EQ); 784b5840353SAdam Hornáček out.write(project); 7851c830032SChris Fraire out.write("&"); 7861c830032SChris Fraire out.write(QueryParameters.HIST_SEARCH_PARAM_EQ); 7871c830032SChris Fraire out.write("""); 788*d6df19e1SAdam Hornacek out.write(uriEncode(r)); 7891c830032SChris Fraire out.write(""&"); 7901c830032SChris Fraire out.write(QueryParameters.TYPE_SEARCH_PARAM_EQ); 7911c830032SChris Fraire out.write("\" title=\"Search history for this revision"); 792b5840353SAdam Hornáček out.write(closeQuotedTag); 793b5840353SAdam Hornáček out.write("S"); 794b5840353SAdam Hornáček out.write(anchorEnd); 795b5840353SAdam Hornáček } 796b5840353SAdam Hornáček String a = annotation.getAuthor(num); 797b5840353SAdam Hornáček if (userPageLink == null) { 798b5840353SAdam Hornáček out.write(HtmlConsts.SPAN_A); 799b5840353SAdam Hornáček htmlize(a, buf); 800b5840353SAdam Hornáček out.write(buf.toString()); 801b5840353SAdam Hornáček out.write(HtmlConsts.ZSPAN); 802b5840353SAdam Hornáček buf.setLength(0); 803b5840353SAdam Hornáček } else { 804b5840353SAdam Hornáček out.write(anchorClassStart); 805b5840353SAdam Hornáček out.write("a\" href=\""); 806b5840353SAdam Hornáček out.write(userPageLink); 807*d6df19e1SAdam Hornacek out.write(uriEncode(a)); 808b5840353SAdam Hornáček if (userPageSuffix != null) { 809b5840353SAdam Hornáček out.write(userPageSuffix); 810b5840353SAdam Hornáček } 811b5840353SAdam Hornáček out.write(closeQuotedTag); 812b5840353SAdam Hornáček htmlize(a, buf); 813b5840353SAdam Hornáček out.write(buf.toString()); 814b5840353SAdam Hornáček buf.setLength(0); 815b5840353SAdam Hornáček out.write(anchorEnd); 816b5840353SAdam Hornáček } 817b5840353SAdam Hornáček out.write("</span>"); 818b5840353SAdam Hornáček } 819b5840353SAdam Hornáček 820b5840353SAdam Hornáček /** 821b5840353SAdam Hornáček * Generate a string from the given path and date in a way that allows 822b5840353SAdam Hornáček * stable lexicographic sorting (i.e. gives always the same results) as a 823b5840353SAdam Hornáček * walk of the file hierarchy. Thus null character (\u0000) is used both to 824b5840353SAdam Hornáček * separate directory components and to separate the path from the date. 825b5840353SAdam Hornáček * 826b5840353SAdam Hornáček * @param path path to mangle. 827b5840353SAdam Hornáček * @param date date string to use. 828b5840353SAdam Hornáček * @return the mangled path. 829b5840353SAdam Hornáček */ path2uid(String path, String date)830b5840353SAdam Hornáček public static String path2uid(String path, String date) { 831b5840353SAdam Hornáček return path.replace('/', '\u0000') + "\u0000" + date; 832b5840353SAdam Hornáček } 833b5840353SAdam Hornáček 834b5840353SAdam Hornáček /** 835b5840353SAdam Hornáček * The reverse operation for {@link #path2uid(String, String)} - re-creates 836b5840353SAdam Hornáček * the unmangled path from the given uid. 837b5840353SAdam Hornáček * 838b5840353SAdam Hornáček * @param uid uid to unmangle. 839b5840353SAdam Hornáček * @return the original path. 840b5840353SAdam Hornáček */ uid2url(String uid)841b5840353SAdam Hornáček public static String uid2url(String uid) { 842807ead8fSLubos Kosco String url = uid.replace('\u0000', PATH_SEPARATOR); 843807ead8fSLubos Kosco return url.substring(0, url.lastIndexOf(PATH_SEPARATOR)); // remove date from end 844807ead8fSLubos Kosco } 845807ead8fSLubos Kosco 8460241b5b5SChris Fraire /** 8476c62ede9SAdam Hornacek * Sanitizes Windows path delimiters (if {@link SystemUtils#IS_OS_WINDOWS} 8480241b5b5SChris Fraire * is {@code true}) as 8490241b5b5SChris Fraire * {@link org.opengrok.indexer.index.Indexer#PATH_SEPARATOR} in order not 8500241b5b5SChris Fraire * to conflict with the Lucene escape character and also so {@code path} 8510241b5b5SChris Fraire * appears as a correctly formed URI in search results. 8520241b5b5SChris Fraire */ fixPathIfWindows(String path)853807ead8fSLubos Kosco public static String fixPathIfWindows(String path) { 8546c62ede9SAdam Hornacek if (path != null && SystemUtils.IS_OS_WINDOWS) { 855807ead8fSLubos Kosco return path.replace(File.separatorChar, PATH_SEPARATOR); 856807ead8fSLubos Kosco } 857807ead8fSLubos Kosco return path; 858807ead8fSLubos Kosco } 859807ead8fSLubos Kosco 860cf51835aSKryštof Tulinger /** 861b5840353SAdam Hornáček * Write the 'H A D' links. This is used for search results and directory 862b5840353SAdam Hornáček * listings. 863b5840353SAdam Hornáček * 864b5840353SAdam Hornáček * @param out writer for producing output 865b5840353SAdam Hornáček * @param ctxE URI encoded prefix 866b5840353SAdam Hornáček * @param entry file/directory name to write 8670ec550ccSAdam Hornacek * @param isDir is directory 868b5840353SAdam Hornáček * @throws IOException depends on the destination (<var>out</var>). 869b5840353SAdam Hornáček */ writeHAD(Writer out, String ctxE, String entry, boolean isDir)8700ec550ccSAdam Hornacek public static void writeHAD(Writer out, String ctxE, String entry, boolean isDir) throws IOException { 871b5840353SAdam Hornáček 872b5840353SAdam Hornáček String downloadPrefixE = ctxE + Prefix.DOWNLOAD_P; 873b5840353SAdam Hornáček String xrefPrefixE = ctxE + Prefix.XREF_P; 874b5840353SAdam Hornáček 875b5840353SAdam Hornáček out.write("<td class=\"q\">"); 876b5840353SAdam Hornáček if (RuntimeEnvironment.getInstance().isHistoryEnabled()) { 877b5840353SAdam Hornáček String histPrefixE = ctxE + Prefix.HIST_L; 878b5840353SAdam Hornáček 879b5840353SAdam Hornáček out.write("<a href=\""); 880b5840353SAdam Hornáček out.write(histPrefixE); 881b5840353SAdam Hornáček if (!entry.startsWith("/")) { 882b5840353SAdam Hornáček entry = "/" + entry; 883b5840353SAdam Hornáček } 884b5840353SAdam Hornáček out.write(entry); 885b5840353SAdam Hornáček out.write("\" title=\"History\">H</a>"); 886b5840353SAdam Hornáček } 887b5840353SAdam Hornáček 8880ec550ccSAdam Hornacek if (!isDir) { 889b5840353SAdam Hornáček out.write(" <a href=\""); 890b5840353SAdam Hornáček out.write(xrefPrefixE); 891b5840353SAdam Hornáček out.write(entry); 8921c830032SChris Fraire out.write("?"); 8931c830032SChris Fraire out.write(QueryParameters.ANNOTATION_PARAM_EQ_TRUE); 8941c830032SChris Fraire out.write("\" title=\"Annotate\">A</a> "); 895b5840353SAdam Hornáček out.write("<a href=\""); 896b5840353SAdam Hornáček out.write(downloadPrefixE); 897b5840353SAdam Hornáček out.write(entry); 898b5840353SAdam Hornáček out.write("\" title=\"Download\">D</a>"); 899b5840353SAdam Hornáček } 900b5840353SAdam Hornáček 901b5840353SAdam Hornáček out.write("</td>"); 902b5840353SAdam Hornáček } 903b5840353SAdam Hornáček 904b5840353SAdam Hornáček /** 905ff44f24aSAdam Hornáček * Wrapper around UTF-8 URL encoding of a string. 906b5840353SAdam Hornáček * 907379f387bSVladimir Kotal * @param q query to be encoded. If {@code null}, an empty string will be used instead. 908379f387bSVladimir Kotal * @return null if failed, otherwise the encoded string 909b5840353SAdam Hornáček * @see URLEncoder#encode(String, String) 910b5840353SAdam Hornáček */ uriEncode(String q)911*d6df19e1SAdam Hornacek public static String uriEncode(String q) { 912f22e1845SAdam Hornacek return q == null ? "" : URLEncoder.encode(q, StandardCharsets.UTF_8); 913b5840353SAdam Hornáček } 914b5840353SAdam Hornáček 915b5840353SAdam Hornáček /** 916b5840353SAdam Hornáček * Append to {@code dest} the UTF-8 URL-encoded representation of 917b5840353SAdam Hornáček * {@code str}. 918b5840353SAdam Hornáček * @param str a defined instance 919b5840353SAdam Hornáček * @param dest a defined target 92081b586e6SVladimir Kotal * @throws IOException I/O 921b5840353SAdam Hornáček */ uriEncode(String str, Appendable dest)922*d6df19e1SAdam Hornacek public static void uriEncode(String str, Appendable dest) 923b5840353SAdam Hornáček throws IOException { 924*d6df19e1SAdam Hornacek String uenc = uriEncode(str); 925b5840353SAdam Hornáček dest.append(uenc); 926b5840353SAdam Hornáček } 927b5840353SAdam Hornáček 928b5840353SAdam Hornáček /** 929b5840353SAdam Hornáček * Append '&name=value" to the given buffer. If the given 930b5840353SAdam Hornáček * <var>value</var> 931b5840353SAdam Hornáček * is {@code null}, this method does nothing. 932b5840353SAdam Hornáček * 933b5840353SAdam Hornáček * @param buf where to append the query string 934b5840353SAdam Hornáček * @param key the name of the parameter to add. Append as is! 935b5840353SAdam Hornáček * @param value the value for the given parameter. Gets automatically UTF-8 936b5840353SAdam Hornáček * URL encoded. 937b5840353SAdam Hornáček * @throws NullPointerException if the given buffer is {@code null}. 938*d6df19e1SAdam Hornacek * @see #uriEncode(String) 939b5840353SAdam Hornáček */ appendQuery(StringBuilder buf, String key, String value)940b5840353SAdam Hornáček public static void appendQuery(StringBuilder buf, String key, 941b5840353SAdam Hornáček String value) { 942b5840353SAdam Hornáček 943b5840353SAdam Hornáček if (value != null) { 944*d6df19e1SAdam Hornacek buf.append("&").append(key).append('=').append(uriEncode(value)); 945b5840353SAdam Hornáček } 946b5840353SAdam Hornáček } 947b5840353SAdam Hornáček 948b5840353SAdam Hornáček /** 949b5840353SAdam Hornáček * URI encode the given path. 950b5840353SAdam Hornáček * 951b5840353SAdam Hornáček * @param path path to encode. 952b5840353SAdam Hornáček * @return the encoded path. 953b5840353SAdam Hornáček * @throws NullPointerException if a parameter is {@code null} 954b5840353SAdam Hornáček */ uriEncodePath(String path)955*d6df19e1SAdam Hornacek public static String uriEncodePath(String path) { 956b5840353SAdam Hornáček // Bug #19188: Ideally, we'd like to use the standard class library to 957b5840353SAdam Hornáček // encode the paths. We're aware of the following two methods: 958b5840353SAdam Hornáček // 959b5840353SAdam Hornáček // 1) URLEncoder.encode() - this method however transforms space to + 960b5840353SAdam Hornáček // instead of %20 (which is right for HTML form data, but not for 961b5840353SAdam Hornáček // paths), and it also does not preserve the separator chars (/). 962b5840353SAdam Hornáček // 963b5840353SAdam Hornáček // 2) URI.getRawPath() - transforms space and / as expected, but gets 964b5840353SAdam Hornáček // confused when the path name contains a colon character (it thinks 965b5840353SAdam Hornáček // parts of the path is schema in that case) 966b5840353SAdam Hornáček // 967b5840353SAdam Hornáček // For now, encode manually the way we want it. 968b5840353SAdam Hornáček StringBuilder sb = new StringBuilder(path.length()); 969b5840353SAdam Hornáček for (byte b : path.getBytes(StandardCharsets.UTF_8)) { 970b5840353SAdam Hornáček // URLEncoder's javadoc says a-z, A-Z, ., -, _ and * are safe 971b5840353SAdam Hornáček // characters, so we preserve those to make the encoded string 972b5840353SAdam Hornáček // shorter and easier to read. We also preserve the separator 973b5840353SAdam Hornáček // chars (/). All other characters are encoded (as UTF-8 byte 974b5840353SAdam Hornáček // sequences). 975b5840353SAdam Hornáček if ((b == '/') || (b >= 'a' && b <= 'z') 976b5840353SAdam Hornáček || (b >= 'A' && b <= 'Z') || (b >= '0' && b <= '9') 977b5840353SAdam Hornáček || (b == '.') || (b == '-') || (b == '_') || (b == '*')) { 978b5840353SAdam Hornáček sb.append((char) b); 979b5840353SAdam Hornáček } else { 980b5840353SAdam Hornáček sb.append('%'); 981b5840353SAdam Hornáček int u = b & 0xFF; // Make the byte value unsigned. 982b5840353SAdam Hornáček if (u <= 0x0F) { 983b5840353SAdam Hornáček // Add leading zero if required. 984b5840353SAdam Hornáček sb.append('0'); 985b5840353SAdam Hornáček } 98652dccac1SChris Fraire sb.append(Integer.toHexString(u).toUpperCase(Locale.ROOT)); 987b5840353SAdam Hornáček } 988b5840353SAdam Hornáček } 989b5840353SAdam Hornáček return sb.toString(); 990b5840353SAdam Hornáček } 991b5840353SAdam Hornáček 992b5840353SAdam Hornáček /** 993b5840353SAdam Hornáček * Escape a string for use as in an HTML attribute value. The returned value 994b5840353SAdam Hornáček * is not enclosed in double quotes. The caller needs to add those. 995b5840353SAdam Hornáček * 996b5840353SAdam Hornáček * @param q string to escape. 997b5840353SAdam Hornáček * @return an empty string if a parameter is {@code null}, the mangled 998b5840353SAdam Hornáček * string otherwise. 999b5840353SAdam Hornáček */ formQuoteEscape(String q)1000b5840353SAdam Hornáček public static String formQuoteEscape(String q) { 1001b5840353SAdam Hornáček if (q == null || q.isEmpty()) { 1002b5840353SAdam Hornáček return ""; 1003b5840353SAdam Hornáček } 1004b5840353SAdam Hornáček StringBuilder sb = new StringBuilder(); 1005b5840353SAdam Hornáček char c; 1006b5840353SAdam Hornáček for (int i = 0; i < q.length(); i++) { 1007b5840353SAdam Hornáček c = q.charAt(i); 1008b5840353SAdam Hornáček switch (c) { 1009b5840353SAdam Hornáček case '"': 1010b5840353SAdam Hornáček sb.append("""); 1011b5840353SAdam Hornáček break; 1012b5840353SAdam Hornáček case '&': 1013b5840353SAdam Hornáček sb.append("&"); 1014b5840353SAdam Hornáček break; 1015b5840353SAdam Hornáček default: 1016b5840353SAdam Hornáček sb.append(c); 1017b5840353SAdam Hornáček break; 1018b5840353SAdam Hornáček } 1019b5840353SAdam Hornáček } 1020b5840353SAdam Hornáček return sb.toString(); 1021b5840353SAdam Hornáček } 1022b5840353SAdam Hornáček 1023b5840353SAdam Hornáček /** 1024b5840353SAdam Hornáček * Tag changes in the given <var>line1</var> and <var>line2</var> 1025b5840353SAdam Hornáček * for highlighting. Removed parts are tagged with CSS class {@code d}, new 1026b5840353SAdam Hornáček * parts are tagged with CSS class {@code a} using a {@code span} element. 1027b5840353SAdam Hornáček * The input parameters must not have any HTML escapes in them. 1028b5840353SAdam Hornáček * 1029b5840353SAdam Hornáček * @param line1 line of the original file 1030b5840353SAdam Hornáček * @param line2 line of the changed/new file 1031b5840353SAdam Hornáček * @return the tagged lines (field[0] ~= line1, field[1] ~= line2). 1032b5840353SAdam Hornáček * @throws NullPointerException if one of the given parameters is 1033b5840353SAdam Hornáček * {@code null}. 1034b5840353SAdam Hornáček */ diffline(StringBuilder line1, StringBuilder line2)1035b5840353SAdam Hornáček public static String[] diffline(StringBuilder line1, StringBuilder line2) { 1036b5840353SAdam Hornáček String[] ret = new String[2]; 1037b5840353SAdam Hornáček int s = 0; 1038b5840353SAdam Hornáček int m = line1.length() - 1; 1039b5840353SAdam Hornáček int n = line2.length() - 1; 1040b5840353SAdam Hornáček while (s <= m && s <= n && (line1.charAt(s) == line2.charAt(s))) { 1041b5840353SAdam Hornáček s++; 1042b5840353SAdam Hornáček } 1043b5840353SAdam Hornáček 1044b5840353SAdam Hornáček while (s <= m && s <= n && (line1.charAt(m) == line2.charAt(n))) { 1045b5840353SAdam Hornáček m--; 1046b5840353SAdam Hornáček n--; 1047b5840353SAdam Hornáček } 1048b5840353SAdam Hornáček 1049b5840353SAdam Hornáček // deleted 1050b5840353SAdam Hornáček if (s <= m) { 1051b13c5a0eSAdam Hornacek String sb = Util.htmlize(line1.substring(0, s)) + 1052b13c5a0eSAdam Hornacek HtmlConsts.SPAN_D + 1053b13c5a0eSAdam Hornacek Util.htmlize(line1.substring(s, m + 1)) + 1054b13c5a0eSAdam Hornacek HtmlConsts.ZSPAN + 1055b13c5a0eSAdam Hornacek Util.htmlize(line1.substring(m + 1, line1.length())); 1056b13c5a0eSAdam Hornacek ret[0] = sb; 1057b5840353SAdam Hornáček } else { 1058b5840353SAdam Hornáček ret[0] = Util.htmlize(line1.toString()); // no change 1059b5840353SAdam Hornáček } 1060b5840353SAdam Hornáček 1061b5840353SAdam Hornáček // added 1062b5840353SAdam Hornáček if (s <= n) { 1063b13c5a0eSAdam Hornacek String sb = Util.htmlize(line2.substring(0, s)) + 1064b13c5a0eSAdam Hornacek HtmlConsts.SPAN_A + 1065b13c5a0eSAdam Hornacek Util.htmlize(line2.substring(s, n + 1)) + 1066b13c5a0eSAdam Hornacek HtmlConsts.ZSPAN + 1067b13c5a0eSAdam Hornacek Util.htmlize(line2.substring(n + 1, line2.length())); 1068b13c5a0eSAdam Hornacek ret[1] = sb; 1069b5840353SAdam Hornáček } else { 1070b5840353SAdam Hornáček ret[1] = Util.htmlize(line2.toString()); // no change 1071b5840353SAdam Hornáček } 1072b5840353SAdam Hornáček 1073b5840353SAdam Hornáček return ret; 1074b5840353SAdam Hornáček } 1075b5840353SAdam Hornáček 1076b5840353SAdam Hornáček /** 1077b5840353SAdam Hornáček * Dump the configuration as an HTML table. 1078b5840353SAdam Hornáček * 1079b5840353SAdam Hornáček * @param out destination for the HTML output 1080b5840353SAdam Hornáček * @throws IOException if an error happens while writing to {@code out} 1081b5840353SAdam Hornáček * @throws HistoryException if the history guru cannot be accesses 1082b5840353SAdam Hornáček */ 1083b5840353SAdam Hornáček @SuppressWarnings("boxing") dumpConfiguration(Appendable out)1084b5840353SAdam Hornáček public static void dumpConfiguration(Appendable out) throws IOException, 1085b5840353SAdam Hornáček HistoryException { 1086b5840353SAdam Hornáček out.append("<table border=\"1\" width=\"100%\">"); 1087b5840353SAdam Hornáček out.append("<tr><th>Variable</th><th>Value</th></tr>"); 1088b5840353SAdam Hornáček RuntimeEnvironment env = RuntimeEnvironment.getInstance(); 1089b5840353SAdam Hornáček printTableRow(out, "Source root", env.getSourceRootPath()); 1090b5840353SAdam Hornáček printTableRow(out, "Data root", env.getDataRootPath()); 1091b5840353SAdam Hornáček printTableRow(out, "CTags", env.getCtags()); 1092b5840353SAdam Hornáček printTableRow(out, "Bug page", env.getBugPage()); 1093b5840353SAdam Hornáček printTableRow(out, "Bug pattern", env.getBugPattern()); 1094b5840353SAdam Hornáček printTableRow(out, "User page", env.getUserPage()); 1095b5840353SAdam Hornáček printTableRow(out, "User page suffix", env.getUserPageSuffix()); 1096b5840353SAdam Hornáček printTableRow(out, "Review page", env.getReviewPage()); 1097b5840353SAdam Hornáček printTableRow(out, "Review pattern", env.getReviewPattern()); 1098b5840353SAdam Hornáček printTableRow(out, "Using projects", env.isProjectsEnabled()); 1099b5840353SAdam Hornáček out.append("<tr><td>Ignored files</td><td>"); 1100b5840353SAdam Hornáček printUnorderedList(out, env.getIgnoredNames().getItems()); 1101b5840353SAdam Hornáček out.append("</td></tr>"); 1102b5840353SAdam Hornáček printTableRow(out, "lucene RAM_BUFFER_SIZE_MB", env.getRamBufferSize()); 1103b5840353SAdam Hornáček printTableRow(out, "Allow leading wildcard in search", 1104b5840353SAdam Hornáček env.isAllowLeadingWildcard()); 1105b5840353SAdam Hornáček printTableRow(out, "History cache", HistoryGuru.getInstance() 1106b5840353SAdam Hornáček .getCacheInfo()); 1107b5840353SAdam Hornáček printTableRow(out, "Authorization plugin directory", env.getPluginDirectory()); 1108b5840353SAdam Hornáček printTableRow(out, "Authorization watchdog directory", env.getPluginDirectory()); 1109b5840353SAdam Hornáček printTableRow(out, "Authorization watchdog enabled", env.isAuthorizationWatchdog()); 1110b5840353SAdam Hornáček printTableRow(out, "Authorization stack", "<pre>" + env.getAuthorizationFramework().getStack().hierarchyToString() + "</pre>"); 1111b5840353SAdam Hornáček out.append("</table>"); 1112b5840353SAdam Hornáček } 1113b5840353SAdam Hornáček 1114b5840353SAdam Hornáček /** 1115b5840353SAdam Hornáček * Just read the given source and dump as is to the given destination. Does 1116b5840353SAdam Hornáček * nothing, if one or more of the parameters is {@code null}. 1117b5840353SAdam Hornáček * 1118b5840353SAdam Hornáček * @param out write destination 1119b5840353SAdam Hornáček * @param in source to read 1120b5840353SAdam Hornáček * @throws IOException as defined by the given reader/writer 1121b5840353SAdam Hornáček * @throws NullPointerException if a parameter is {@code null}. 1122b5840353SAdam Hornáček */ dump(Writer out, Reader in)1123b5840353SAdam Hornáček public static void dump(Writer out, Reader in) throws IOException { 1124b5840353SAdam Hornáček if (in == null || out == null) { 1125b5840353SAdam Hornáček return; 1126b5840353SAdam Hornáček } 1127b5840353SAdam Hornáček char[] buf = new char[8192]; 1128b5840353SAdam Hornáček int len = 0; 1129b5840353SAdam Hornáček while ((len = in.read(buf)) >= 0) { 1130b5840353SAdam Hornáček out.write(buf, 0, len); 1131b5840353SAdam Hornáček } 1132b5840353SAdam Hornáček } 1133b5840353SAdam Hornáček 1134b5840353SAdam Hornáček /** 1135b5840353SAdam Hornáček * Silently dump a file to the given destination. All {@link IOException}s 1136b5840353SAdam Hornáček * gets caught and logged, but not re-thrown. 1137b5840353SAdam Hornáček * 1138b5840353SAdam Hornáček * @param out dump destination 1139b5840353SAdam Hornáček * @param dir directory, which should contains the file. 1140b5840353SAdam Hornáček * @param filename the basename of the file to dump. 1141b5840353SAdam Hornáček * @param compressed if {@code true} the denoted file is assumed to be 1142b5840353SAdam Hornáček * gzipped. 1143b5840353SAdam Hornáček * @return {@code true} on success (everything read and written). 1144b5840353SAdam Hornáček * @throws NullPointerException if a parameter is {@code null}. 1145b5840353SAdam Hornáček */ dump(Writer out, File dir, String filename, boolean compressed)1146b5840353SAdam Hornáček public static boolean dump(Writer out, File dir, String filename, 1147b5840353SAdam Hornáček boolean compressed) { 1148b5840353SAdam Hornáček return dump(out, new File(dir, filename), compressed); 1149b5840353SAdam Hornáček } 1150b5840353SAdam Hornáček 1151b5840353SAdam Hornáček /** 1152b5840353SAdam Hornáček * Silently dump a file to the given destination. All {@link IOException}s 1153b5840353SAdam Hornáček * gets caught and logged, but not re-thrown. 1154b5840353SAdam Hornáček * 1155b5840353SAdam Hornáček * @param out dump destination 1156b5840353SAdam Hornáček * @param file file to dump. 1157b5840353SAdam Hornáček * @param compressed if {@code true} the denoted file is assumed to be 1158b5840353SAdam Hornáček * gzipped. 1159b5840353SAdam Hornáček * @return {@code true} on success (everything read and written). 1160b5840353SAdam Hornáček * @throws NullPointerException if a parameter is {@code null}. 1161b5840353SAdam Hornáček */ dump(Writer out, File file, boolean compressed)1162b5840353SAdam Hornáček public static boolean dump(Writer out, File file, boolean compressed) { 1163b5840353SAdam Hornáček if (!file.exists()) { 1164b5840353SAdam Hornáček return false; 1165b5840353SAdam Hornáček } 1166b5840353SAdam Hornáček /** 1167b5840353SAdam Hornáček * For backward compatibility, read the OpenGrok-produced document 1168b5840353SAdam Hornáček * using the system default charset. 1169b5840353SAdam Hornáček */ 11704b613dedSAdam Hornacek try (InputStream iss = new BufferedInputStream(new FileInputStream(file))) { 11714b613dedSAdam Hornacek try (Reader in = compressed ? new InputStreamReader(new GZIPInputStream(iss)) : new InputStreamReader(iss)) { 1172b5840353SAdam Hornáček dump(out, in); 11734b613dedSAdam Hornacek } 1174b5840353SAdam Hornáček return true; 1175b5840353SAdam Hornáček } catch (IOException e) { 1176b5840353SAdam Hornáček LOGGER.log(Level.WARNING, 1177b5840353SAdam Hornáček "An error occurred while piping file " + file + ": ", e); 1178b5840353SAdam Hornáček } 1179b5840353SAdam Hornáček return false; 1180b5840353SAdam Hornáček } 1181b5840353SAdam Hornáček 1182b5840353SAdam Hornáček /** 1183b5840353SAdam Hornáček * Silently dump an xref file to the given destination. All 1184b5840353SAdam Hornáček * {@link IOException}s get caught and logged, but not re-thrown. 1185b5840353SAdam Hornáček * @param out dump destination 1186b5840353SAdam Hornáček * @param file file to dump 1187ddad4e6fSVladimir Kotal * @param compressed if {@code true} the denoted file is assumed to be gzipped 1188b5840353SAdam Hornáček * @param contextPath an optional override of "/source/" as the context path 1189b5840353SAdam Hornáček * @return {@code true} on success (everything read and written) 1190b5840353SAdam Hornáček * @throws NullPointerException if a parameter is {@code null}. 1191b5840353SAdam Hornáček */ dumpXref(Writer out, File file, boolean compressed, String contextPath)1192ddad4e6fSVladimir Kotal public static boolean dumpXref(Writer out, File file, boolean compressed, String contextPath) { 1193ddad4e6fSVladimir Kotal 1194b5840353SAdam Hornáček if (!file.exists()) { 1195b5840353SAdam Hornáček return false; 1196b5840353SAdam Hornáček } 1197ddad4e6fSVladimir Kotal 1198ddad4e6fSVladimir Kotal /* 1199b5840353SAdam Hornáček * For backward compatibility, read the OpenGrok-produced document 1200b5840353SAdam Hornáček * using the system default charset. 1201b5840353SAdam Hornáček */ 1202a20d6924SVladimir Kotal try (InputStream iss = new BufferedInputStream(new FileInputStream(file)); 1203a20d6924SVladimir Kotal Reader in = compressed ? new InputStreamReader(new GZIPInputStream(iss)) : new InputStreamReader(iss)) { 1204b5840353SAdam Hornáček dumpXref(out, in, contextPath); 1205b5840353SAdam Hornáček return true; 1206ddad4e6fSVladimir Kotal } catch (IOException e) { 1207ddad4e6fSVladimir Kotal LOGGER.log(Level.WARNING, "An error occurred while piping file " + file, e); 1208ddad4e6fSVladimir Kotal } 1209ddad4e6fSVladimir Kotal 1210b5840353SAdam Hornáček return false; 1211b5840353SAdam Hornáček } 1212b5840353SAdam Hornáček 1213b5840353SAdam Hornáček /** 1214b5840353SAdam Hornáček * Silently dump an xref file to the given destination. All 1215b5840353SAdam Hornáček * {@link IOException}s get caught and logged, but not re-thrown. 1216b5840353SAdam Hornáček * @param out dump destination 1217b5840353SAdam Hornáček * @param in source to read 1218b5840353SAdam Hornáček * @param contextPath an optional override of "/source/" as the context path 1219b5840353SAdam Hornáček * @throws IOException as defined by the given reader/writer 1220b5840353SAdam Hornáček * @throws NullPointerException if a parameter is {@code null}. 1221b5840353SAdam Hornáček */ dumpXref(Writer out, Reader in, String contextPath)1222b5840353SAdam Hornáček public static void dumpXref(Writer out, Reader in, String contextPath) 1223b5840353SAdam Hornáček throws IOException { 1224b5840353SAdam Hornáček if (in == null || out == null) { 1225b5840353SAdam Hornáček return; 1226b5840353SAdam Hornáček } 1227b5840353SAdam Hornáček XrefSourceTransformer xform = new XrefSourceTransformer(in); 1228b5840353SAdam Hornáček xform.setWriter(out); 1229b5840353SAdam Hornáček xform.setContextPath(contextPath); 1230b5840353SAdam Hornáček while (xform.yylex()) { 1231b5840353SAdam Hornáček // Nothing else to do. 1232b5840353SAdam Hornáček } 1233b5840353SAdam Hornáček } 1234b5840353SAdam Hornáček 1235b5840353SAdam Hornáček /** 1236b5840353SAdam Hornáček * Print a row in an HTML table. 1237b5840353SAdam Hornáček * 1238b5840353SAdam Hornáček * @param out destination for the HTML output 1239b5840353SAdam Hornáček * @param cells the values to print in the cells of the row 1240b5840353SAdam Hornáček * @throws IOException if an error happens while writing to {@code out} 1241b5840353SAdam Hornáček */ printTableRow(Appendable out, Object... cells)1242b5840353SAdam Hornáček private static void printTableRow(Appendable out, Object... cells) 1243b5840353SAdam Hornáček throws IOException { 1244b5840353SAdam Hornáček out.append("<tr>"); 1245b5840353SAdam Hornáček StringBuilder buf = new StringBuilder(256); 1246b5840353SAdam Hornáček for (Object cell : cells) { 1247b5840353SAdam Hornáček out.append("<td>"); 1248b5840353SAdam Hornáček String str = (cell == null) ? "null" : cell.toString(); 1249b5840353SAdam Hornáček htmlize(str, buf); 1250b5840353SAdam Hornáček out.append(str); 1251b5840353SAdam Hornáček buf.setLength(0); 1252b5840353SAdam Hornáček out.append("</td>"); 1253b5840353SAdam Hornáček } 1254b5840353SAdam Hornáček out.append("</tr>"); 1255b5840353SAdam Hornáček } 1256b5840353SAdam Hornáček 1257b5840353SAdam Hornáček /** 1258b5840353SAdam Hornáček * Print an unordered list (HTML). 1259b5840353SAdam Hornáček * 1260b5840353SAdam Hornáček * @param out destination for the HTML output 1261b5840353SAdam Hornáček * @param items the list items 1262b5840353SAdam Hornáček * @throws IOException if an error happens while writing to {@code out} 1263b5840353SAdam Hornáček */ printUnorderedList(Appendable out, Collection<String> items)1264b5840353SAdam Hornáček private static void printUnorderedList(Appendable out, 1265b5840353SAdam Hornáček Collection<String> items) throws IOException { 1266b5840353SAdam Hornáček out.append("<ul>"); 1267b5840353SAdam Hornáček StringBuilder buf = new StringBuilder(256); 1268b5840353SAdam Hornáček for (String item : items) { 1269b5840353SAdam Hornáček out.append("<li>"); 1270b5840353SAdam Hornáček htmlize(item, buf); 1271b5840353SAdam Hornáček out.append(buf); 1272b5840353SAdam Hornáček buf.setLength(0); 1273b5840353SAdam Hornáček out.append("</li>"); 1274b5840353SAdam Hornáček } 1275b5840353SAdam Hornáček out.append("</ul>"); 1276b5840353SAdam Hornáček } 1277b5840353SAdam Hornáček 1278b5840353SAdam Hornáček /** 1279b5840353SAdam Hornáček * Create a string literal for use in JavaScript functions. 1280b5840353SAdam Hornáček * 1281b5840353SAdam Hornáček * @param str the string to be represented by the literal 1282b5840353SAdam Hornáček * @return a JavaScript string literal 1283b5840353SAdam Hornáček */ jsStringLiteral(String str)1284b5840353SAdam Hornáček public static String jsStringLiteral(String str) { 1285b5840353SAdam Hornáček StringBuilder sb = new StringBuilder(); 1286b5840353SAdam Hornáček sb.append('"'); 1287b5840353SAdam Hornáček for (int i = 0; i < str.length(); i++) { 1288b5840353SAdam Hornáček char c = str.charAt(i); 1289b5840353SAdam Hornáček switch (c) { 1290b5840353SAdam Hornáček case '"': 1291b5840353SAdam Hornáček sb.append("\\\""); 1292b5840353SAdam Hornáček break; 1293b5840353SAdam Hornáček case '\\': 1294b5840353SAdam Hornáček sb.append("\\\\"); 1295b5840353SAdam Hornáček break; 1296b5840353SAdam Hornáček case '\n': 1297b5840353SAdam Hornáček sb.append("\\n"); 1298b5840353SAdam Hornáček break; 1299b5840353SAdam Hornáček case '\r': 1300b5840353SAdam Hornáček sb.append("\\r"); 1301b5840353SAdam Hornáček break; 1302b5840353SAdam Hornáček default: 1303b5840353SAdam Hornáček sb.append(c); 1304b5840353SAdam Hornáček } 1305b5840353SAdam Hornáček } 1306b5840353SAdam Hornáček sb.append('"'); 1307b5840353SAdam Hornáček return sb.toString(); 1308b5840353SAdam Hornáček } 1309b5840353SAdam Hornáček 1310b5840353SAdam Hornáček /** 1311b5840353SAdam Hornáček * Make a path relative by stripping off a prefix. If the path does not have 1312b5840353SAdam Hornáček * the given prefix, return the full path unchanged. 1313b5840353SAdam Hornáček * 1314b5840353SAdam Hornáček * @param prefix the prefix to strip off 1315b5840353SAdam Hornáček * @param fullPath the path from which to remove the prefix 1316b5840353SAdam Hornáček * @return a path relative to {@code prefix} if {@code prefix} is a parent 1317b5840353SAdam Hornáček * directory of {@code fullPath}; otherwise, {@code fullPath} 1318b5840353SAdam Hornáček */ stripPathPrefix(String prefix, String fullPath)1319b5840353SAdam Hornáček public static String stripPathPrefix(String prefix, String fullPath) { 1320b5840353SAdam Hornáček // Find the length of the prefix to strip off. The prefix should 1321b5840353SAdam Hornáček // represent a directory, so it could end with a slash. In case it 1322b5840353SAdam Hornáček // doesn't end with a slash, increase the length by one so that we 1323b5840353SAdam Hornáček // strip off the leading slash from the relative path. 1324b5840353SAdam Hornáček int prefixLength = prefix.length(); 1325b5840353SAdam Hornáček if (!prefix.endsWith("/")) { 1326b5840353SAdam Hornáček prefixLength++; 1327b5840353SAdam Hornáček } 1328b5840353SAdam Hornáček 1329b5840353SAdam Hornáček // If the full path starts with the prefix, strip off the prefix. 1330b5840353SAdam Hornáček if (fullPath.length() > prefixLength && fullPath.startsWith(prefix) 1331b5840353SAdam Hornáček && fullPath.charAt(prefixLength - 1) == '/') { 1332b5840353SAdam Hornáček return fullPath.substring(prefixLength); 1333b5840353SAdam Hornáček } 1334b5840353SAdam Hornáček 1335b5840353SAdam Hornáček // Otherwise, return the full path. 1336b5840353SAdam Hornáček return fullPath; 1337b5840353SAdam Hornáček } 1338b5840353SAdam Hornáček 1339b5840353SAdam Hornáček /** 134091712be4SVladimir Kotal * Creates a HTML slider for pagination. This has the same effect as 1341b5840353SAdam Hornáček * invoking <code>createSlider(offset, limit, size, null)</code>. 1342b5840353SAdam Hornáček * 1343b5840353SAdam Hornáček * @param offset start of the current page 1344b5840353SAdam Hornáček * @param limit max number of items per page 1345b5840353SAdam Hornáček * @param size number of total hits to paginate 1346b5840353SAdam Hornáček * @return string containing slider html 1347b5840353SAdam Hornáček */ createSlider(int offset, int limit, int size)1348b5840353SAdam Hornáček public static String createSlider(int offset, int limit, int size) { 1349b5840353SAdam Hornáček return createSlider(offset, limit, size, null); 1350b5840353SAdam Hornáček } 1351b5840353SAdam Hornáček 1352b5840353SAdam Hornáček /** 135391712be4SVladimir Kotal * Creates a HTML slider for pagination. 1354b5840353SAdam Hornáček * 1355b5840353SAdam Hornáček * @param offset start of the current page 1356b5840353SAdam Hornáček * @param limit max number of items per page 1357b5840353SAdam Hornáček * @param size number of total hits to paginate 1358b5840353SAdam Hornáček * @param request request containing URL parameters which should be appended 1359b5840353SAdam Hornáček * to the page URL 1360b5840353SAdam Hornáček * @return string containing slider html 1361b5840353SAdam Hornáček */ createSlider(int offset, int limit, long size, HttpServletRequest request)1362b5840353SAdam Hornáček public static String createSlider(int offset, int limit, long size, HttpServletRequest request) { 1363b5840353SAdam Hornáček String slider = ""; 1364b5840353SAdam Hornáček if (limit < size) { 1365b5840353SAdam Hornáček final StringBuilder buf = new StringBuilder(4096); 1366b5840353SAdam Hornáček int lastPage = (int) Math.ceil((double) size / limit); 1367b5840353SAdam Hornáček // startingResult is the number of a first result on the current page 1368b5840353SAdam Hornáček int startingResult = offset - limit * (offset / limit % 10 + 1); 1369b5840353SAdam Hornáček int myFirstPage = startingResult < 0 ? 1 : startingResult / limit + 1; 1370b5840353SAdam Hornáček int myLastPage = Math.min(lastPage, myFirstPage + 10 + (myFirstPage == 1 ? 0 : 1)); 1371b5840353SAdam Hornáček 1372b5840353SAdam Hornáček // function taking the page number and appending the desired content into the final buffer 1373b13c5a0eSAdam Hornacek Function<Integer, Void> generatePageLink = page -> { 1374b5840353SAdam Hornáček int myOffset = Math.max(0, (page - 1) * limit); 1375b5840353SAdam Hornáček if (myOffset <= offset && offset < myOffset + limit) { 1376b5840353SAdam Hornáček // do not generate anchor for current page 1377b5840353SAdam Hornáček buf.append("<span class=\"sel\">").append(page).append("</span>"); 1378b5840353SAdam Hornáček } else { 1379b5840353SAdam Hornáček buf.append("<a class=\"more\" href=\"?"); 1380b5840353SAdam Hornáček // append request parameters 1381b5840353SAdam Hornáček if (request != null && request.getQueryString() != null) { 1382b5840353SAdam Hornáček String query = request.getQueryString(); 13831c830032SChris Fraire query = query.replaceFirst(RE_Q_E_A_A_COUNT_EQ_VAL, ""); 13841c830032SChris Fraire query = query.replaceFirst(RE_Q_E_A_A_START_EQ_VAL, ""); 13851c830032SChris Fraire query = query.replaceFirst(RE_A_ANCHOR_Q_E_A_A, ""); 1386b5840353SAdam Hornáček if (!query.isEmpty()) { 1387b5840353SAdam Hornáček buf.append(query); 1388b5840353SAdam Hornáček buf.append("&"); 1389b5840353SAdam Hornáček } 1390b5840353SAdam Hornáček } 13911c830032SChris Fraire buf.append(QueryParameters.COUNT_PARAM_EQ).append(limit); 1392b5840353SAdam Hornáček if (myOffset != 0) { 13931c830032SChris Fraire buf.append("&").append(QueryParameters.START_PARAM_EQ). 13941c830032SChris Fraire append(myOffset); 1395b5840353SAdam Hornáček } 1396b5840353SAdam Hornáček buf.append("\">"); 1397b5840353SAdam Hornáček // add << or >> if this link would lead to another section 1398b5840353SAdam Hornáček if (page == myFirstPage && page != 1) { 1399b5840353SAdam Hornáček buf.append("<<"); 1400b5840353SAdam Hornáček } else if (page == myLastPage && myOffset + limit < size) { 1401b5840353SAdam Hornáček buf.append(">>"); 1402b5840353SAdam Hornáček } else { 1403b5840353SAdam Hornáček buf.append(page); 1404b5840353SAdam Hornáček } 1405b5840353SAdam Hornáček buf.append("</a>"); 1406b5840353SAdam Hornáček } 1407b5840353SAdam Hornáček return null; 1408b5840353SAdam Hornáček }; 1409b5840353SAdam Hornáček 1410b5840353SAdam Hornáček // slider composition 1411b5840353SAdam Hornáček if (myFirstPage != 1) { 1412b5840353SAdam Hornáček generatePageLink.apply(1); 1413b5840353SAdam Hornáček buf.append("<span>...</span>"); 1414b5840353SAdam Hornáček } 1415b5840353SAdam Hornáček for (int page = myFirstPage; page <= myLastPage; page++) { 1416b5840353SAdam Hornáček generatePageLink.apply(page); 1417b5840353SAdam Hornáček } 1418b5840353SAdam Hornáček if (myLastPage != lastPage) { 1419b5840353SAdam Hornáček buf.append("<span>...</span>"); 1420b5840353SAdam Hornáček generatePageLink.apply(lastPage); 1421b5840353SAdam Hornáček } 1422b5840353SAdam Hornáček return buf.toString(); 1423b5840353SAdam Hornáček } 1424b5840353SAdam Hornáček return slider; 1425b5840353SAdam Hornáček } 1426b5840353SAdam Hornáček 1427b5840353SAdam Hornáček /** 142891712be4SVladimir Kotal * Check if the string is a HTTP URL. 1429b5840353SAdam Hornáček * 1430b5840353SAdam Hornáček * @param string the string to check 1431b5840353SAdam Hornáček * @return true if it is http URL, false otherwise 1432b5840353SAdam Hornáček */ isHttpUri(String string)1433b5840353SAdam Hornáček public static boolean isHttpUri(String string) { 1434b5840353SAdam Hornáček URL url; 1435b5840353SAdam Hornáček try { 1436b5840353SAdam Hornáček url = new URL(string); 1437b5840353SAdam Hornáček } catch (MalformedURLException ex) { 1438b5840353SAdam Hornáček return false; 1439b5840353SAdam Hornáček } 1440b5840353SAdam Hornáček return url.getProtocol().equals("http") || url.getProtocol().equals("https"); 1441b5840353SAdam Hornáček } 1442b5840353SAdam Hornáček 1443e03b934bSAdam Hornáček protected static final String REDACTED_USER_INFO = "redacted_by_OpenGrok"; 144454aac5b5SVladimir Kotal 1445b5840353SAdam Hornáček /** 144654aac5b5SVladimir Kotal * If given path is a URL, return the string representation with the user-info part filtered out. 144754aac5b5SVladimir Kotal * @param path path to object 144854aac5b5SVladimir Kotal * @return either the original string or string representation of URL with the user-info part removed 144954aac5b5SVladimir Kotal */ redactUrl(String path)145054aac5b5SVladimir Kotal public static String redactUrl(String path) { 145154aac5b5SVladimir Kotal URL url; 145254aac5b5SVladimir Kotal try { 145354aac5b5SVladimir Kotal url = new URL(path); 145454aac5b5SVladimir Kotal } catch (MalformedURLException e) { 145554aac5b5SVladimir Kotal // not an URL 145654aac5b5SVladimir Kotal return path; 145754aac5b5SVladimir Kotal } 145854aac5b5SVladimir Kotal if (url.getUserInfo() != null) { 145954aac5b5SVladimir Kotal return url.toString().replace(url.getUserInfo(), 146054aac5b5SVladimir Kotal REDACTED_USER_INFO); 146154aac5b5SVladimir Kotal } else { 1462a86ae970SVladimir Kotal return path; 146354aac5b5SVladimir Kotal } 146454aac5b5SVladimir Kotal } 146554aac5b5SVladimir Kotal 146654aac5b5SVladimir Kotal /** 146754aac5b5SVladimir Kotal * Build a HTML link to the given HTTP URL. If the URL is not an http URL 1468b5840353SAdam Hornáček * then it is returned as it was received. This has the same effect as 1469b5840353SAdam Hornáček * invoking <code>linkify(url, true)</code>. 1470b5840353SAdam Hornáček * 1471b5840353SAdam Hornáček * @param url the text to be linkified 1472b5840353SAdam Hornáček * @return the linkified string 1473b5840353SAdam Hornáček * 1474b5840353SAdam Hornáček * @see #linkify(java.lang.String, boolean) 1475b5840353SAdam Hornáček */ linkify(String url)1476b5840353SAdam Hornáček public static String linkify(String url) { 1477b5840353SAdam Hornáček return linkify(url, true); 1478b5840353SAdam Hornáček } 1479b5840353SAdam Hornáček 1480b5840353SAdam Hornáček /** 1481b5840353SAdam Hornáček * Build a html link to the given http URL. If the URL is not an http URL 1482b5840353SAdam Hornáček * then it is returned as it was received. 1483b5840353SAdam Hornáček * 148491712be4SVladimir Kotal * @param url the HTTP URL 1485b5840353SAdam Hornáček * @param newTab if the link should open in a new tab 148691712be4SVladimir Kotal * @return HTML code containing the link <a>...</a> 1487b5840353SAdam Hornáček */ linkify(String url, boolean newTab)1488b5840353SAdam Hornáček public static String linkify(String url, boolean newTab) { 1489b5840353SAdam Hornáček if (isHttpUri(url)) { 1490b5840353SAdam Hornáček try { 1491b5840353SAdam Hornáček Map<String, String> attrs = new TreeMap<>(); 1492b5840353SAdam Hornáček attrs.put("href", url); 1493b5840353SAdam Hornáček attrs.put("title", String.format("Link to %s", Util.encode(url))); 1494b5840353SAdam Hornáček if (newTab) { 1495b5840353SAdam Hornáček attrs.put("target", "_blank"); 14967b94bd45SAdam Hornacek attrs.put("rel", "noreferrer"); 1497b5840353SAdam Hornáček } 1498b5840353SAdam Hornáček return buildLink(url, attrs); 1499b5840353SAdam Hornáček } catch (URISyntaxException | MalformedURLException ex) { 1500b5840353SAdam Hornáček return url; 1501b5840353SAdam Hornáček } 1502b5840353SAdam Hornáček } 1503b5840353SAdam Hornáček return url; 1504b5840353SAdam Hornáček } 1505b5840353SAdam Hornáček 1506b5840353SAdam Hornáček /** 1507b5840353SAdam Hornáček * Build an anchor with given name and a pack of attributes. Automatically 1508b5840353SAdam Hornáček * escapes href attributes and automatically escapes the name into HTML 1509b5840353SAdam Hornáček * entities. 1510b5840353SAdam Hornáček * 1511b5840353SAdam Hornáček * @param name displayed name of the anchor 1512b5840353SAdam Hornáček * @param attrs map of attributes for the html element 1513b5840353SAdam Hornáček * @return string containing the result 1514b5840353SAdam Hornáček * 151581b586e6SVladimir Kotal * @throws URISyntaxException URI syntax 151681b586e6SVladimir Kotal * @throws MalformedURLException malformed URL 1517b5840353SAdam Hornáček */ buildLink(String name, Map<String, String> attrs)1518b5840353SAdam Hornáček public static String buildLink(String name, Map<String, String> attrs) 1519b5840353SAdam Hornáček throws URISyntaxException, MalformedURLException { 1520b5840353SAdam Hornáček StringBuilder buffer = new StringBuilder(); 1521b5840353SAdam Hornáček buffer.append("<a"); 1522b5840353SAdam Hornáček for (Entry<String, String> attr : attrs.entrySet()) { 1523b5840353SAdam Hornáček buffer.append(" "); 1524b5840353SAdam Hornáček buffer.append(attr.getKey()); 1525b5840353SAdam Hornáček buffer.append("=\""); 1526b5840353SAdam Hornáček String value = attr.getValue(); 1527b5840353SAdam Hornáček if (attr.getKey().equals("href")) { 1528b5840353SAdam Hornáček value = Util.encodeURL(value); 1529b5840353SAdam Hornáček } 1530b5840353SAdam Hornáček buffer.append(value); 1531b5840353SAdam Hornáček buffer.append("\""); 1532b5840353SAdam Hornáček } 1533b5840353SAdam Hornáček buffer.append(">"); 1534b5840353SAdam Hornáček buffer.append(Util.htmlize(name)); 1535b5840353SAdam Hornáček buffer.append("</a>"); 1536b5840353SAdam Hornáček return buffer.toString(); 1537b5840353SAdam Hornáček } 1538b5840353SAdam Hornáček 1539b5840353SAdam Hornáček /** 1540b5840353SAdam Hornáček * Build an anchor with given name and a pack of attributes. Automatically 1541b5840353SAdam Hornáček * escapes href attributes and automatically escapes the name into HTML 1542b5840353SAdam Hornáček * entities. 1543b5840353SAdam Hornáček * 1544b5840353SAdam Hornáček * @param name displayed name of the anchor 1545b5840353SAdam Hornáček * @param url anchor's URL 1546b5840353SAdam Hornáček * @return string containing the result 1547b5840353SAdam Hornáček * 1548b5840353SAdam Hornáček * @throws URISyntaxException URI syntax 1549b5840353SAdam Hornáček * @throws MalformedURLException bad URL 1550b5840353SAdam Hornáček */ buildLink(String name, String url)1551b5840353SAdam Hornáček public static String buildLink(String name, String url) 1552b5840353SAdam Hornáček throws URISyntaxException, MalformedURLException { 1553b5840353SAdam Hornáček Map<String, String> attrs = new TreeMap<>(); 1554b5840353SAdam Hornáček attrs.put("href", url); 1555b5840353SAdam Hornáček return buildLink(name, attrs); 1556b5840353SAdam Hornáček } 1557b5840353SAdam Hornáček 1558b5840353SAdam Hornáček /** 1559b5840353SAdam Hornáček * Build an anchor with given name and a pack of attributes. Automatically 1560b5840353SAdam Hornáček * escapes href attributes and automatically escapes the name into HTML 1561b5840353SAdam Hornáček * entities. 1562b5840353SAdam Hornáček * 1563b5840353SAdam Hornáček * @param name displayed name of the anchor 1564b5840353SAdam Hornáček * @param url anchor's URL 1565b5840353SAdam Hornáček * @param newTab a flag if the link should be opened in a new tab 1566b5840353SAdam Hornáček * @return string containing the result 1567b5840353SAdam Hornáček * 1568b5840353SAdam Hornáček * @throws URISyntaxException URI syntax 1569b5840353SAdam Hornáček * @throws MalformedURLException bad URL 1570b5840353SAdam Hornáček */ buildLink(String name, String url, boolean newTab)1571b5840353SAdam Hornáček public static String buildLink(String name, String url, boolean newTab) 1572b5840353SAdam Hornáček throws URISyntaxException, MalformedURLException { 1573b5840353SAdam Hornáček Map<String, String> attrs = new TreeMap<>(); 1574b5840353SAdam Hornáček attrs.put("href", url); 1575b5840353SAdam Hornáček if (newTab) { 1576b5840353SAdam Hornáček attrs.put("target", "_blank"); 15777b94bd45SAdam Hornacek attrs.put("rel", "noreferrer"); 1578b5840353SAdam Hornáček } 1579b5840353SAdam Hornáček return buildLink(name, attrs); 1580b5840353SAdam Hornáček } 1581b5840353SAdam Hornáček 1582b5840353SAdam Hornáček /** 1583b5840353SAdam Hornáček * Replace all occurrences of pattern in the incoming text with the link 1584b5840353SAdam Hornáček * named name pointing to an URL. It is possible to use the regexp pattern 1585b5840353SAdam Hornáček * groups in name and URL when they are specified in the pattern. 1586b5840353SAdam Hornáček * 1587b5840353SAdam Hornáček * @param text text to replace all patterns 1588b5840353SAdam Hornáček * @param pattern the pattern to match 1589b5840353SAdam Hornáček * @param name link display name 1590b5840353SAdam Hornáček * @param url link URL 1591b5840353SAdam Hornáček * @return the text with replaced links 1592b5840353SAdam Hornáček */ linkifyPattern(String text, Pattern pattern, String name, String url)1593b5840353SAdam Hornáček public static String linkifyPattern(String text, Pattern pattern, String name, String url) { 1594b5840353SAdam Hornáček try { 1595b5840353SAdam Hornáček String buildLink = buildLink(name, url, true); 1596b5840353SAdam Hornáček return pattern.matcher(text).replaceAll(buildLink); 1597b5840353SAdam Hornáček } catch (URISyntaxException | MalformedURLException ex) { 1598b5840353SAdam Hornáček LOGGER.log(Level.WARNING, "The given URL ''{0}'' is not valid", url); 1599b5840353SAdam Hornáček return text; 1600b5840353SAdam Hornáček } 1601b5840353SAdam Hornáček } 1602b5840353SAdam Hornáček 1603b5840353SAdam Hornáček /** 1604b5840353SAdam Hornáček * Try to complete the given URL part into full URL with server name, port, 1605b5840353SAdam Hornáček * scheme, ... 1606b5840353SAdam Hornáček * <dl> 1607b5840353SAdam Hornáček * <dt>for request http://localhost:8080/source/xref/xxx and part 1608b5840353SAdam Hornáček * /cgi-bin/user=</dt> 1609b5840353SAdam Hornáček * <dd>http://localhost:8080/cgi-bin/user=</dd> 1610b5840353SAdam Hornáček * <dt>for request http://localhost:8080/source/xref/xxx and part 1611b5840353SAdam Hornáček * cgi-bin/user=</dt> 1612b5840353SAdam Hornáček * <dd>http://localhost:8080/source/xref/xxx/cgi-bin/user=</dd> 1613b5840353SAdam Hornáček * <dt>for request http://localhost:8080/source/xref/xxx and part 1614b5840353SAdam Hornáček * http://users.com/user=</dt> 1615b5840353SAdam Hornáček * <dd>http://users.com/user=</dd> 1616b5840353SAdam Hornáček * </dl> 1617b5840353SAdam Hornáček * 1618b5840353SAdam Hornáček * @param url the given URL part, may be already full URL 1619b5840353SAdam Hornáček * @param req the request containing the information about the server 1620b5840353SAdam Hornáček * @return the converted URL or the input parameter if there was an error 1621b5840353SAdam Hornáček */ completeUrl(String url, HttpServletRequest req)1622b5840353SAdam Hornáček public static String completeUrl(String url, HttpServletRequest req) { 1623b5840353SAdam Hornáček try { 1624b5840353SAdam Hornáček if (!isHttpUri(url)) { 1625b5840353SAdam Hornáček if (url.startsWith("/")) { 1626b5840353SAdam Hornáček return new URI(req.getScheme(), null, req.getServerName(), req.getServerPort(), url, null, null).toString(); 1627b5840353SAdam Hornáček } 1628b5840353SAdam Hornáček StringBuffer prepUrl = req.getRequestURL(); 1629b5840353SAdam Hornáček if (!url.isEmpty()) { 1630b5840353SAdam Hornáček prepUrl.append('/').append(url); 1631b5840353SAdam Hornáček } 1632b5840353SAdam Hornáček return new URI(prepUrl.toString()).toString(); 1633b5840353SAdam Hornáček } 1634b5840353SAdam Hornáček return url; 1635b5840353SAdam Hornáček } catch (URISyntaxException ex) { 1636b5840353SAdam Hornáček LOGGER.log(Level.INFO, 1637b5840353SAdam Hornáček String.format("Unable to convert given URL part '%s' to complete URL", url), 1638b5840353SAdam Hornáček ex); 1639b5840353SAdam Hornáček return url; 1640b5840353SAdam Hornáček } 1641b5840353SAdam Hornáček } 1642911e8af0SAdam Hornáček 1643911e8af0SAdam Hornáček /** 164491712be4SVladimir Kotal * Parses the specified URL and returns its query params. 164591712be4SVladimir Kotal * @param url URL to retrieve the query params from 1646911e8af0SAdam Hornáček * @return query params of {@code url} 1647911e8af0SAdam Hornáček */ getQueryParams(final URL url)1648911e8af0SAdam Hornáček public static Map<String, List<String>> getQueryParams(final URL url) { 1649911e8af0SAdam Hornáček if (url == null) { 1650911e8af0SAdam Hornáček throw new IllegalArgumentException("Cannot get query params from the null url"); 1651911e8af0SAdam Hornáček } 1652911e8af0SAdam Hornáček Map<String, List<String>> returnValue = new HashMap<>(); 1653911e8af0SAdam Hornáček 1654911e8af0SAdam Hornáček if (url.getQuery() == null) { 1655911e8af0SAdam Hornáček return returnValue; 1656911e8af0SAdam Hornáček } 1657911e8af0SAdam Hornáček 1658911e8af0SAdam Hornáček String[] pairs = url.getQuery().split("&"); 1659911e8af0SAdam Hornáček 1660911e8af0SAdam Hornáček for (String pair : pairs) { 1661911e8af0SAdam Hornáček if (pair.isEmpty()) { 1662911e8af0SAdam Hornáček continue; 1663911e8af0SAdam Hornáček } 1664911e8af0SAdam Hornáček 1665911e8af0SAdam Hornáček int idx = pair.indexOf('='); 1666911e8af0SAdam Hornáček if (idx == -1) { 1667911e8af0SAdam Hornáček returnValue.computeIfAbsent(pair, k -> new LinkedList<>()); 1668911e8af0SAdam Hornáček continue; 1669911e8af0SAdam Hornáček } 1670911e8af0SAdam Hornáček 1671f22e1845SAdam Hornacek String key = URLDecoder.decode(pair.substring(0, idx), StandardCharsets.UTF_8); 1672f22e1845SAdam Hornacek String value = URLDecoder.decode(pair.substring(idx + 1), StandardCharsets.UTF_8); 1673911e8af0SAdam Hornáček 1674911e8af0SAdam Hornáček List<String> paramValues = returnValue.computeIfAbsent(key, k -> new LinkedList<>()); 1675911e8af0SAdam Hornáček paramValues.add(value); 1676911e8af0SAdam Hornáček } 1677911e8af0SAdam Hornáček return returnValue; 1678911e8af0SAdam Hornáček } 1679911e8af0SAdam Hornáček 1680b5840353SAdam Hornáček } 1681