1 // Copyright (C) 2009 Red Hat, Inc. 2 // 3 // This library is free software; you can redistribute it and/or 4 // modify it under the terms of the GNU Lesser General Public 5 // License as published by the Free Software Foundation; either 6 // version 2.1 of the License, or (at your option) any later version. 7 // 8 // This library is distributed in the hope that it will be useful, 9 // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 11 // Lesser General Public License for more details. 12 // 13 // You should have received a copy of the GNU Lesser General Public 14 // License along with this library; if not, write to the Free Software 15 // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 16 17 package net.sourceforge.jnlp.util; 18 19 import net.sourceforge.jnlp.util.logging.OutputController; 20 import java.io.BufferedReader; 21 import java.io.File; 22 import java.io.FileNotFoundException; 23 import java.io.FileOutputStream; 24 import java.io.FileReader; 25 import java.io.IOException; 26 import java.io.OutputStreamWriter; 27 import java.io.Reader; 28 import java.io.StringReader; 29 import java.net.URL; 30 import java.nio.charset.Charset; 31 import java.nio.file.Files; 32 import java.nio.file.StandardCopyOption; 33 import java.util.ArrayList; 34 import java.util.Arrays; 35 import java.util.Collections; 36 import java.util.Comparator; 37 import java.util.List; 38 import java.util.Map; 39 import java.util.Map.Entry; 40 41 import net.sourceforge.jnlp.IconDesc; 42 import net.sourceforge.jnlp.JNLPFile; 43 import net.sourceforge.jnlp.OptionsDefinitions; 44 import net.sourceforge.jnlp.PluginBridge; 45 import net.sourceforge.jnlp.StreamEater; 46 import net.sourceforge.jnlp.cache.CacheUtil; 47 import net.sourceforge.jnlp.cache.UpdatePolicy; 48 import net.sourceforge.jnlp.config.PathsAndFiles; 49 import net.sourceforge.jnlp.runtime.JNLPRuntime; 50 import net.sourceforge.jnlp.security.dialogs.AccessWarningPaneComplexReturn; 51 52 /** 53 * This class builds a (freedesktop.org) desktop entry out of a {@link JNLPFile} 54 * . This entry can be used to install desktop shortcuts. See xdg-desktop-icon 55 * (1) and http://standards.freedesktop.org/desktop-entry-spec/latest/ for more 56 * information 57 * 58 * @author Omair Majid 59 * 60 * 61 * This class builds also (freedesktop.org) menu entry out of a {@link JNLPFile} 62 * Few notes valid November 2014: 63 * Mate/gnome 2/xfce - no meter of exec or icon put icon to defined/"others" Category 64 * - name is as expected Name's value 65 * - if removed, xfce kept icon until login/logout 66 * kde 4 - unknown Cathegory is sorted to Lost & Found -thats bad 67 * - if icon is not found, nothing shows 68 * - name is GENERIC NAME and then little name 69 * Gnome 3 shell - exec must be valid program! 70 * - also had issues with icon 71 * 72 * conclusion: 73 * - backup icon to .config 74 * - use "Network" category 75 * - force valid launcher 76 77 * @author (not so proudly) Jiri Vanek 78 */ 79 public class XDesktopEntry { 80 81 public static final String JAVA_ICON_NAME = "itweb-javaws"; 82 83 private JNLPFile file = null; 84 private int iconSize = -1; 85 private String iconLocation = null; 86 87 //in pixels 88 private static final int[] VALID_ICON_SIZES = new int[] { 16, 22, 32, 48, 64, 128 }; 89 //browsers we try to find on path for html shortcut 90 public static final String[] BROWSERS = new String[]{"firefox", "midori", "epiphany", "opera", "chromium", "chrome", "konqueror"}; 91 public static final String FAVICON = "favicon.ico"; 92 93 /** 94 * Create a XDesktopEntry for the given JNLP file 95 * 96 * @param file a {@link JNLPFile} that indicates the application to launch 97 */ XDesktopEntry(JNLPFile file)98 public XDesktopEntry(JNLPFile file) { 99 this.file = file; 100 101 /* looks like a good initial value */ 102 iconSize = VALID_ICON_SIZES[2]; 103 } 104 105 /** 106 * Returns the contents of the {@link XDesktopEntry} through the 107 * {@link Reader} interface. 108 * @param menu whether to create this icon to menu 109 * @param info result of user's interference 110 * @param isSigned whether the app is signed 111 * @return reader with desktop shortcut specification 112 */ getContentsAsReader(boolean menu, AccessWarningPaneComplexReturn.ShortcutResult info, boolean isSigned)113 public Reader getContentsAsReader(boolean menu, AccessWarningPaneComplexReturn.ShortcutResult info, boolean isSigned) { 114 115 File generatedJnlp = null; 116 if (file instanceof PluginBridge && (info.getShortcutType() == AccessWarningPaneComplexReturn.ShortcutResult.Shortcut.GENERATED_JNLP || info.getShortcutType() == AccessWarningPaneComplexReturn.ShortcutResult.Shortcut.JNLP_HREF)) { 117 try { 118 String content = ((PluginBridge) file).toJnlp(isSigned, info.getShortcutType() == AccessWarningPaneComplexReturn.ShortcutResult.Shortcut.JNLP_HREF, info.isFixHref()); 119 generatedJnlp = getGeneratedJnlpFileName(); 120 FileUtils.saveFile(content, generatedJnlp); 121 } catch (Exception ex) { 122 OutputController.getLogger().log(ex); 123 } 124 } 125 126 String fileContents = "[Desktop Entry]\n"; 127 fileContents += "Version=1.0\n"; 128 fileContents += "Name=" + getDesktopIconName() + "\n"; 129 fileContents += "GenericName=Java Web Start Application\n"; 130 fileContents += "Comment=" + sanitize(file.getInformation().getDescription()) + "\n"; 131 if (menu) { 132 //keeping the default category because of KDE 133 String menuString = "Categories=Network;"; 134 if (file.getInformation().getShortcut() != null 135 && file.getInformation().getShortcut().getMenu() != null 136 && file.getInformation().getShortcut().getMenu().getSubMenu() != null 137 && !file.getInformation().getShortcut().getMenu().getSubMenu().trim().isEmpty()) { 138 menuString += file.getInformation().getShortcut().getMenu().getSubMenu().trim() + ";"; 139 } 140 menuString += "Java;Javaws;"; 141 fileContents += menuString + "\n"; 142 } 143 fileContents += "Type=Application\n"; 144 if (iconLocation != null) { 145 fileContents += "Icon=" + iconLocation + "\n"; 146 } else { 147 fileContents += "Icon=" + JAVA_ICON_NAME + "\n"; 148 149 } 150 if (file.getInformation().getVendor() != null) { 151 fileContents += "X-Vendor=" + sanitize(file.getInformation().getVendor()) + "\n"; 152 } 153 154 if (JNLPRuntime.isWebstartApplication()) { 155 String htmlSwitch = ""; 156 if (JNLPRuntime.isHtml()){ 157 htmlSwitch = " "+OptionsDefinitions.OPTIONS.HTML.option; 158 } 159 fileContents += "Exec=" 160 + getJavaWsBin() + htmlSwitch + " \"" + file.getSourceLocation() + "\"\n"; 161 OutputController.getLogger().log("Using " + getJavaWsBin() + htmlSwitch + " as binary for " + file.getSourceLocation()); 162 } else { 163 if (info.getShortcutType() == AccessWarningPaneComplexReturn.ShortcutResult.Shortcut.BROWSER) { 164 String browser = info.getBrowser(); 165 if (browser == null) { 166 browser = getBrowserBin(); 167 } 168 fileContents += "Exec=" 169 + browser + " \"" + file.getSourceLocation() + "\"\n"; 170 OutputController.getLogger().log("Using " + browser + " as binary for " + file.getSourceLocation()); 171 } else if ((info.getShortcutType() == AccessWarningPaneComplexReturn.ShortcutResult.Shortcut.GENERATED_JNLP 172 || info.getShortcutType() == AccessWarningPaneComplexReturn.ShortcutResult.Shortcut.JNLP_HREF) && generatedJnlp != null) { 173 fileContents += "Exec=" 174 + getJavaWsBin() + " \"" + generatedJnlp.getAbsolutePath() + "\"\n"; 175 OutputController.getLogger().log("Using " + getJavaWsBin() + " (generated) as binary for " + file.getSourceLocation() + " to " + generatedJnlp.getAbsolutePath()); 176 } else if (info.getShortcutType() == AccessWarningPaneComplexReturn.ShortcutResult.Shortcut.JAVAWS_HTML) { 177 fileContents += "Exec=" 178 + getJavaWsBin() + " -html \"" + file.getSourceLocation() + "\"\n"; 179 OutputController.getLogger().log("Using " + getJavaWsBin() + " -html as binary for " + file.getSourceLocation()); 180 } else { 181 fileContents += "Exec=" 182 + getBrowserBin() + " \"" + file.getSourceLocation() + "\"\n"; 183 OutputController.getLogger().log("Using " + getBrowserBin() + " as binary for " + file.getSourceLocation()); 184 } 185 } 186 187 return new StringReader(fileContents); 188 189 } 190 getBrowserBin()191 public static String getBrowserBin() { 192 String pathResult = findOnPath(BROWSERS); 193 if (pathResult != null) { 194 return pathResult; 195 } else { 196 return "browser_not_found"; 197 } 198 199 } 200 getJavaWsBin()201 private String getJavaWsBin() { 202 //Shortcut executes the jnlp as it was with system preferred java. It should work fine offline 203 //absolute - works in case of self built 204 String exec = System.getProperty("icedtea-web.bin.location"); 205 String pathResult = findOnPath(new String[]{"itweb-javaws", System.getProperty("icedtea-web.bin.name")}); 206 if (pathResult != null) { 207 return pathResult; 208 } 209 if (exec != null) { 210 return exec; 211 } 212 return "itweb-javaws"; 213 } 214 215 findOnPath(String[] bins)216 private static String findOnPath(String[] bins) { 217 String exec = null; 218 //find if one of binaries is on path 219 String path = System.getenv().get("PATH"); 220 if (path == null || path.trim().isEmpty()) { 221 path = System.getenv().get("path"); 222 } 223 if (path == null || path.trim().isEmpty()) { 224 path = System.getenv().get("Path"); 225 } 226 if (path != null && !path.trim().isEmpty()) { 227 //relative - works with alternatives 228 String[] paths = path.split(File.pathSeparator); 229 outerloop: 230 for (String bin : bins) { 231 //when property is not set 232 if (bin == null) { 233 continue; 234 } 235 for (String p : paths) { 236 if (new File(p, bin).exists()) { 237 exec = bin; 238 break outerloop; 239 } 240 } 241 242 } 243 } 244 return exec; 245 } 246 247 /** 248 * Sanitizes a string so that it can be used safely in a key=value pair in a 249 * desktop entry file. 250 * 251 * @param input a String to sanitize 252 * @return a string safe to use as either the key or the value in the 253 * key=value pair in a desktop entry file 254 */ sanitize(String input)255 private static String sanitize(String input) { 256 if (input == null) { 257 return ""; 258 } 259 /* key=value pairs must be a single line */ 260 input = FileUtils.sanitizeFileName(input, '-'); 261 //return first line or replace new lines by space? 262 return input.split("\n")[0]; 263 } 264 265 /** 266 * @return the size of the icon (in pixels) for the desktop shortcut 267 */ getIconSize()268 public int getIconSize() { 269 return iconSize; 270 } 271 getShortcutTmpFile()272 public File getShortcutTmpFile() { 273 String userTmp = PathsAndFiles.TMP_DIR.getFullPath(); 274 File shortcutFile = new File(userTmp + File.separator + getDesktopIconFileName()); 275 return shortcutFile; 276 } 277 278 /** 279 * Set the icon size to use for the desktop shortcut 280 * 281 * @param size the size (in pixels) of the icon to use. Commonly used sizes 282 * are of 16, 22, 32, 48, 64 and 128 283 */ setIconSize(int size)284 public void setIconSize(int size) { 285 iconSize = size; 286 } 287 288 /** 289 * Create a desktop shortcut for this desktop entry 290 * @param menu how to create in menu 291 * @param desktop how to create on desktop 292 * @param isSigned if it is signed 293 */ createDesktopShortcuts(AccessWarningPaneComplexReturn.ShortcutResult menu, AccessWarningPaneComplexReturn.ShortcutResult desktop, boolean isSigned)294 public void createDesktopShortcuts(AccessWarningPaneComplexReturn.ShortcutResult menu, AccessWarningPaneComplexReturn.ShortcutResult desktop, boolean isSigned) { 295 boolean isDesktop = false; 296 if (desktop != null && desktop.isCreate()) { 297 isDesktop = true; 298 } 299 boolean isMenu = false; 300 if (menu != null && menu.isCreate()) { 301 isMenu = true; 302 } 303 try { 304 if (isMenu || isDesktop) { 305 try { 306 cacheIcon(); 307 } catch (NonFileProtocolException ex) { 308 OutputController.getLogger().log(ex); 309 //default icon will be used later 310 } 311 } 312 if (isDesktop) { 313 installDesktopLauncher(desktop, isSigned); 314 } 315 if (isMenu) { 316 installMenuLauncher(menu, isSigned); 317 } 318 } catch (Exception e) { 319 OutputController.getLogger().log(OutputController.Level.ERROR_ALL, e); 320 } 321 } 322 323 /** 324 * Install this XDesktopEntry into the user's menu. 325 */ installMenuLauncher(AccessWarningPaneComplexReturn.ShortcutResult info, boolean isSigned)326 private void installMenuLauncher(AccessWarningPaneComplexReturn.ShortcutResult info, boolean isSigned) { 327 //TODO add itweb-settings tab which alows to remove inidividual items/icons 328 try { 329 File f = getLinuxMenuIconFile(); 330 try (OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(f), 331 Charset.forName("UTF-8")); Reader reader = getContentsAsReader(true, info, isSigned)) { 332 333 char[] buffer = new char[1024]; 334 int ret = 0; 335 while (-1 != (ret = reader.read(buffer))) { 336 writer.write(buffer, 0, ret); 337 } 338 339 } 340 OutputController.getLogger().log("Menu item created: " + f.getAbsolutePath()); 341 } catch (FileNotFoundException e) { 342 OutputController.getLogger().log(OutputController.Level.ERROR_ALL, e); 343 } catch (IOException e) { 344 OutputController.getLogger().log(OutputController.Level.ERROR_ALL, e); 345 } 346 } 347 348 /** 349 * Install this XDesktopEntry into the user's desktop as a launcher. 350 */ installDesktopLauncher(AccessWarningPaneComplexReturn.ShortcutResult info, boolean isSigned)351 private void installDesktopLauncher(AccessWarningPaneComplexReturn.ShortcutResult info, boolean isSigned) { 352 File shortcutFile = getShortcutTmpFile(); 353 try { 354 355 if (!shortcutFile.getParentFile().isDirectory() && !shortcutFile.getParentFile().mkdirs()) { 356 throw new IOException(shortcutFile.getParentFile().toString()); 357 } 358 359 FileUtils.createRestrictedFile(shortcutFile, true); 360 361 try ( /* 362 * Write out a Java String (UTF-16) as a UTF-8 file 363 */ OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(shortcutFile), 364 Charset.forName("UTF-8")); Reader reader = getContentsAsReader(false, info, isSigned)) { 365 366 char[] buffer = new char[1024]; 367 int ret = 0; 368 while (-1 != (ret = reader.read(buffer))) { 369 writer.write(buffer, 0, ret); 370 } 371 372 } 373 374 /* 375 * Install the desktop entry 376 */ 377 378 String[] execString = new String[] { "xdg-desktop-icon", "install", "--novendor", 379 shortcutFile.getCanonicalPath() }; 380 OutputController.getLogger().log(OutputController.Level.ERROR_DEBUG, "Execing: " + Arrays.toString(execString)); 381 Process installer = Runtime.getRuntime().exec(execString); 382 new StreamEater(installer.getInputStream()).start(); 383 new StreamEater(installer.getErrorStream()).start(); 384 385 try { 386 installer.waitFor(); 387 } catch (InterruptedException e) { 388 OutputController.getLogger().log(OutputController.Level.ERROR_ALL, e); 389 } 390 391 if (!shortcutFile.delete()) { 392 throw new IOException("Unable to delete temporary file:" + shortcutFile); 393 } 394 395 } catch (FileNotFoundException e) { 396 OutputController.getLogger().log(OutputController.Level.ERROR_ALL, e); 397 } catch (IOException e) { 398 OutputController.getLogger().log(OutputController.Level.ERROR_ALL, e); 399 } 400 } 401 402 refreshExistingShortcuts(boolean desktop, boolean menu)403 public void refreshExistingShortcuts(boolean desktop, boolean menu) { 404 //TODO TODO TODO TODO TODO TODO TODO TODO 405 //check existing jnlp files 406 //check luncher 407 //get where it poiints 408 //try all supported shortcuts methods 409 //choose the one which have most similar result to exisitng ones 410 411 } 412 getGeneratedJnlpFileName()413 public File getGeneratedJnlpFileName() { 414 String name = FileUtils.sanitizeFileName(file.createJnlpTitle()); 415 while (name.endsWith(".jnlp")) { 416 name = name.substring(0, name.length() - 5); 417 } 418 name += ".jnlp"; 419 return new File(findAndVerifyGeneratedJnlpDir(), name); 420 } 421 422 private class NonFileProtocolException extends Exception { 423 NonFileProtocolException(String unable_to_cache_icon)424 private NonFileProtocolException(String unable_to_cache_icon) { 425 super(unable_to_cache_icon); 426 } 427 428 } 429 430 /** 431 * Cache the icon for the desktop entry 432 */ cacheIcon()433 private void cacheIcon() throws IOException, NonFileProtocolException { 434 435 URL uiconLocation = file.getInformation().getIconLocation(IconDesc.SHORTCUT, iconSize, 436 iconSize); 437 438 if (uiconLocation == null) { 439 uiconLocation = file.getInformation().getIconLocation(IconDesc.DEFAULT, iconSize, 440 iconSize); 441 } 442 443 String location = null; 444 if (uiconLocation != null) { 445 //this throws npe, if url (specified in jnlp) points to 404 446 URL urlLocation = CacheUtil.getCachedResourceURL(uiconLocation, null, UpdatePolicy.SESSION); 447 if (urlLocation == null) { 448 cantCache(); 449 } 450 location = urlLocation.toString(); 451 if (!location.startsWith("file:")) { 452 cantCache(); 453 } 454 } else { 455 //try favicon.ico 456 try { 457 URL favico = new URL( 458 file.getCodeBase().getProtocol(), 459 file.getCodeBase().getHost(), 460 file.getCodeBase().getPort(), 461 "/" + FAVICON); 462 JNLPFile.openURL(favico, null, UpdatePolicy.ALWAYS); 463 //this MAY throw npe, if url (specified in jnlp) points to 404 464 URL urlLocation = CacheUtil.getCachedResourceURL(favico, null, UpdatePolicy.SESSION); 465 if (urlLocation == null) { 466 cantCache(); 467 } 468 location = urlLocation.toString(); 469 if (!location.startsWith("file:")) { 470 cantCache(); 471 } 472 } catch (IOException ex) { 473 //favicon 404 or similar 474 OutputController.getLogger().log(ex); 475 } 476 } 477 if (location != null) { 478 String origLocation = location.substring("file:".length()); 479 this.iconLocation = origLocation; 480 // icons are never unisntalled by itw. however, system clears them on its own.. soemtimes. 481 // once the -Xcelarcache is run, system MAY clean it later, and so image wil be lost. 482 // copy icon somewhere where -Xclearcache can not 483 PathsAndFiles.ICONS_DIR.getFile().mkdirs(); 484 File source = new File(origLocation); 485 String targetName = source.getName(); 486 if (targetName.equals(FAVICON)) { 487 targetName = file.getCodeBase().getHost() + ".ico"; 488 } 489 File target = new File(PathsAndFiles.ICONS_DIR.getFile(), targetName); 490 Files.copy(source.toPath(), target.toPath(), StandardCopyOption.REPLACE_EXISTING); 491 this.iconLocation = target.getAbsolutePath(); 492 OutputController.getLogger().log(OutputController.Level.ERROR_DEBUG, "Cached desktop shortcut icon: " + target + " , With source from: " + origLocation); 493 } 494 } 495 cantCache()496 private void cantCache() throws NonFileProtocolException { 497 throw new NonFileProtocolException("Unable to cache icon"); 498 } 499 getDesktopIconName()500 private String getDesktopIconName() { 501 return sanitize(file.createJnlpTitle()); 502 } 503 getLinuxDesktopIconFile()504 public File getLinuxDesktopIconFile() { 505 return new File(findFreedesktopOrgDesktopPathCatch() + "/" + getDesktopIconFileName()); 506 } 507 getLinuxMenuIconFile()508 public File getLinuxMenuIconFile() { 509 return new File(findAndVerifyJavawsMenuDir() + "/" + getDesktopIconFileName()); 510 } 511 getDesktopIconFileName()512 private String getDesktopIconFileName() { 513 return getDesktopIconName() + ".desktop"; 514 } 515 findAndVerifyGeneratedJnlpDir()516 private static String findAndVerifyGeneratedJnlpDir() { 517 return findAndVerifyBasicDir(PathsAndFiles.GEN_JNLPS_DIR.getFile(), " directroy for stroing generated jnlps cannot be created. You may expect failure"); 518 } 519 findAndVerifyJavawsMenuDir()520 private static String findAndVerifyJavawsMenuDir() { 521 return findAndVerifyBasicDir(PathsAndFiles.MENUS_DIR.getFile(), " directroy for stroing menu entry cannot be created. You may expect failure"); 522 } 523 findAndVerifyBasicDir(File f, String message)524 private static String findAndVerifyBasicDir(File f, String message) { 525 String fPath = f.getAbsolutePath(); 526 if (!f.exists()) { 527 if (!f.mkdirs()) { 528 OutputController.getLogger().log(OutputController.Level.ERROR_ALL, fPath + message); 529 } 530 } 531 return fPath; 532 } 533 findFreedesktopOrgDesktopPathCatch()534 public static String findFreedesktopOrgDesktopPathCatch() { 535 try { 536 return findFreedesktopOrgDesktopPath(); 537 } catch (Exception ex) { 538 OutputController.getLogger().log(OutputController.Level.ERROR_ALL, ex); 539 return System.getProperty("user.home") + "/Desktop"; 540 } 541 } 542 543 /** 544 * Instead of having all this parsing of user-dirs.dirs and replacing 545 * variables we can execute `echo $(xdg-user-dir DESKTOP)` and it will do 546 * all the job in case approaches below become failing 547 * 548 * @return variables (if declared) and quotation marks (unless escaped) free 549 * path 550 * @throws IOException if no file do not exists or key with desktop do not 551 * exists 552 */ findFreedesktopOrgDesktopPath()553 private static String findFreedesktopOrgDesktopPath() throws IOException { 554 File userDirs = new File(System.getProperty("user.home") + "/.config/user-dirs.dirs"); 555 if (!userDirs.exists()) { 556 return System.getProperty("user.home") + "/Desktop/"; 557 } 558 return getFreedesktopOrgDesktopPathFrom(userDirs); 559 } 560 getFreedesktopOrgDesktopPathFrom(File userDirs)561 private static String getFreedesktopOrgDesktopPathFrom(File userDirs) throws IOException { 562 try (BufferedReader r = new BufferedReader(new FileReader(userDirs))) { 563 return getFreedesktopOrgDesktopPathFrom(r); 564 } 565 566 } 567 static final String XDG_DESKTOP_DIR = "XDG_DESKTOP_DIR"; 568 getFreedesktopOrgDesktopPathFrom(BufferedReader r)569 static String getFreedesktopOrgDesktopPathFrom(BufferedReader r) throws IOException { 570 while (true) { 571 String s = r.readLine(); 572 if (s == null) { 573 throw new IOException("End of user-dirs found, but no " + XDG_DESKTOP_DIR + " key found"); 574 } 575 s = s.trim(); 576 if (s.startsWith(XDG_DESKTOP_DIR)) { 577 if (!s.contains("=")) { 578 throw new IOException(XDG_DESKTOP_DIR + " has no value"); 579 } 580 String[] keyAndValue = s.split("="); 581 keyAndValue[1] = keyAndValue[1].trim(); 582 String filteredQuotes = filterQuotes(keyAndValue[1]); 583 return evaluateLinuxVariables(filteredQuotes); 584 } 585 } 586 } 587 private static final String MIC = "MAGIC_QUOTIN_ITW_CONSTANT_FOR_DUMMIES"; 588 filterQuotes(String string)589 private static String filterQuotes(String string) { 590 //get rid of " but not of 591 String s = string.replaceAll("\\\\\"", MIC); 592 s = s.replaceAll("\"", ""); 593 s = s.replaceAll(MIC, "\\\""); 594 return s; 595 } 596 evaluateLinuxVariables(String orig)597 private static String evaluateLinuxVariables(String orig) { 598 return evaluateLinuxVariables(orig, System.getenv()); 599 } 600 evaluateLinuxVariables(String orig, Map<String, String> variables)601 private static String evaluateLinuxVariables(String orig, Map<String, String> variables) { 602 List<Entry<String, String>> envVariables = new ArrayList<>(variables.entrySet()); 603 Collections.sort(envVariables, new Comparator<Entry<String, String>>() { 604 @Override 605 public int compare(Entry<String, String> o1, Entry<String, String> o2) { 606 return o2.getKey().length() - o1.getKey().length(); 607 } 608 }); 609 while (true) { 610 String before = orig; 611 for (Entry<String, String> entry : envVariables) { 612 orig = orig.replaceAll("\\$" + entry.getKey(), entry.getValue()); 613 } 614 if (before.equals(orig)) { 615 return orig; 616 } 617 } 618 619 } 620 } 621