1 /* 2 * Zed Attack Proxy (ZAP) and its related class files. 3 * 4 * ZAP is an HTTP/HTTPS proxy for assessing web application security. 5 * 6 * Copyright 2012 The ZAP Development Team 7 * 8 * Licensed under the Apache License, Version 2.0 (the "License"); 9 * you may not use this file except in compliance with the License. 10 * You may obtain a copy of the License at 11 * 12 * http://www.apache.org/licenses/LICENSE-2.0 13 * 14 * Unless required by applicable law or agreed to in writing, software 15 * distributed under the License is distributed on an "AS IS" BASIS, 16 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 * See the License for the specific language governing permissions and 18 * limitations under the License. 19 */ 20 package org.zaproxy.zap.control; 21 22 import java.io.File; 23 import java.io.IOException; 24 import java.io.InputStream; 25 import java.net.MalformedURLException; 26 import java.net.URL; 27 import java.nio.file.Files; 28 import java.nio.file.Path; 29 import java.util.ArrayList; 30 import java.util.Arrays; 31 import java.util.Collection; 32 import java.util.Collections; 33 import java.util.HashSet; 34 import java.util.List; 35 import java.util.Locale; 36 import java.util.Objects; 37 import java.util.Optional; 38 import java.util.ResourceBundle; 39 import java.util.Set; 40 import java.util.stream.Collectors; 41 import java.util.zip.ZipEntry; 42 import java.util.zip.ZipException; 43 import java.util.zip.ZipFile; 44 import org.apache.commons.configuration.SubnodeConfiguration; 45 import org.apache.commons.lang.ArrayUtils; 46 import org.apache.commons.lang.StringUtils; 47 import org.apache.commons.lang.SystemUtils; 48 import org.apache.commons.lang3.exception.ExceptionUtils; 49 import org.apache.logging.log4j.LogManager; 50 import org.apache.logging.log4j.Logger; 51 import org.jgrapht.DirectedGraph; 52 import org.jgrapht.alg.CycleDetector; 53 import org.jgrapht.graph.DefaultDirectedGraph; 54 import org.jgrapht.graph.DefaultEdge; 55 import org.jgrapht.traverse.TopologicalOrderIterator; 56 import org.parosproxy.paros.Constant; 57 import org.parosproxy.paros.core.scanner.AbstractPlugin; 58 import org.parosproxy.paros.extension.Extension; 59 import org.zaproxy.zap.Version; 60 import org.zaproxy.zap.control.BaseZapAddOnXmlData.AddOnDep; 61 import org.zaproxy.zap.control.BaseZapAddOnXmlData.Dependencies; 62 import org.zaproxy.zap.control.BaseZapAddOnXmlData.ExtensionWithDeps; 63 import org.zaproxy.zap.extension.pscan.PluginPassiveScanner; 64 65 public class AddOn { 66 67 /** 68 * The name of the manifest file, contained in the add-ons. 69 * 70 * <p>The manifest is expected to be in the root of the ZIP file. 71 * 72 * @since 2.6.0 73 */ 74 public static final String MANIFEST_FILE_NAME = "ZapAddOn.xml"; 75 76 public enum Status { 77 unknown, 78 example, 79 alpha, 80 beta, 81 weekly, 82 release 83 } 84 85 private static ZapRelease v2_4 = new ZapRelease("2.4.0"); 86 87 /** 88 * The installation status of the add-on. 89 * 90 * @since 2.4.0 91 */ 92 public enum InstallationStatus { 93 94 /** 95 * The add-on is available for installation, for example, an add-on in the marketplace (even 96 * if it requires previous actions, in this case, download the file). 97 */ 98 AVAILABLE, 99 100 /** 101 * The add-on was not (yet) installed. For example, the add-on is available in the 'plugin' 102 * directory but it's missing a dependency or requires a greater Java version. It's also in 103 * this status while a dependency is being updated. 104 */ 105 NOT_INSTALLED, 106 107 /** The add-on is installed. */ 108 INSTALLED, 109 110 /** The add-on is being downloaded. */ 111 DOWNLOADING, 112 113 /** 114 * The uninstallation of the add-on failed. For example, when the add-on is not dynamically 115 * installable or when an {@code Exception} is thrown during the uninstallation. 116 */ 117 UNINSTALLATION_FAILED, 118 119 /** 120 * The soft uninstallation of the add-on failed. It's in this status when the uninstallation 121 * failed for an update of a dependency. 122 */ 123 SOFT_UNINSTALLATION_FAILED 124 } 125 126 /** 127 * The result of checking if a file is a valid add-on. 128 * 129 * @since 2.8.0 130 * @see AddOn#isValidAddOn(Path) 131 */ 132 public static class ValidationResult { 133 134 /** The validity of the add-on. */ 135 public enum Validity { 136 /** 137 * The add-on is valid. 138 * 139 * <p>The result contains the {@link ValidationResult#getManifest() manifest} of the 140 * add-on. 141 */ 142 VALID, 143 /** The file path is not valid. */ 144 INVALID_PATH, 145 /** The file does not have the expected extension {@value #FILE_EXTENSION}. */ 146 INVALID_FILE_NAME, 147 /** The file is not a regular file or is not readable. */ 148 FILE_NOT_READABLE, 149 /** 150 * There was an error reading the ZIP file. 151 * 152 * <p>The result contains the {@link ValidationResult#getException() exception}. 153 */ 154 UNREADABLE_ZIP_FILE, 155 /** 156 * There was an error reading the file. 157 * 158 * <p>The result contains the {@link ValidationResult#getException() exception}. 159 */ 160 IO_ERROR_FILE, 161 /** The ZIP file does not have the add-on manifest, {@value #MANIFEST_FILE_NAME}. */ 162 MISSING_MANIFEST, 163 /** 164 * The manifest is not valid. 165 * 166 * <p>The result contains the {@link ValidationResult#getException() exception}. 167 */ 168 INVALID_MANIFEST, 169 /** The add-on declared a missing/invalid library. */ 170 INVALID_LIB, 171 } 172 173 private final Validity validity; 174 private final Exception exception; 175 private final ZapAddOnXmlFile manifest; 176 ValidationResult(Validity validity)177 private ValidationResult(Validity validity) { 178 this(validity, null); 179 } 180 ValidationResult(ZapAddOnXmlFile manifest)181 private ValidationResult(ZapAddOnXmlFile manifest) { 182 this(Validity.VALID, null, manifest); 183 } 184 ValidationResult(Validity validity, Exception exception)185 private ValidationResult(Validity validity, Exception exception) { 186 this(validity, exception, null); 187 } 188 ValidationResult(Validity validity, Exception exception, ZapAddOnXmlFile manifest)189 private ValidationResult(Validity validity, Exception exception, ZapAddOnXmlFile manifest) { 190 this.validity = validity; 191 this.exception = exception; 192 this.manifest = manifest; 193 } 194 195 /** 196 * Gets the validity of the add-on. 197 * 198 * @return the validity of the add-on. 199 */ getValidity()200 public Validity getValidity() { 201 return validity; 202 } 203 204 /** 205 * Gets the exception that occurred while validating the file. 206 * 207 * @return the exception, or {@code null} if none. 208 */ getException()209 public Exception getException() { 210 return exception; 211 } 212 213 /** 214 * Gets the manifest of the add-on. 215 * 216 * @return the manifest of the add-on, if valid, {@code null} otherwise. 217 */ getManifest()218 public ZapAddOnXmlFile getManifest() { 219 return manifest; 220 } 221 } 222 223 /** 224 * The file extension of ZAP add-ons. 225 * 226 * @since 2.6.0 227 */ 228 public static final String FILE_EXTENSION = ".zap"; 229 230 private String id; 231 private String name; 232 private String description = ""; 233 private String author = ""; 234 /** 235 * The version declared in the manifest file. 236 * 237 * <p>Never {@code null}. 238 */ 239 private Version version; 240 /** 241 * The (semantic) version declared in the manifest file, to be replaced by {@link #version}. 242 * 243 * <p>Might be {@code null}. 244 */ 245 private Version semVer; 246 247 private Status status; 248 private String changes = ""; 249 private File file = null; 250 private URL url = null; 251 private URL info = null; 252 private URL repo; 253 private long size = 0; 254 private boolean hasZapAddOnEntry = false; 255 256 /** 257 * Flag that indicates if the manifest was read (or attempted to). Allows to prevent reading the 258 * manifest a second time when the add-on file is corrupt. 259 */ 260 private boolean manifestRead; 261 262 private String notBeforeVersion = null; 263 private String notFromVersion = null; 264 private String hash = null; 265 private String releaseDate; 266 267 /** 268 * The installation status of the add-on. 269 * 270 * <p>Default is {@code NOT_INSTALLED}. 271 * 272 * @see InstallationStatus#NOT_INSTALLED 273 */ 274 private InstallationStatus installationStatus = InstallationStatus.NOT_INSTALLED; 275 276 private List<String> extensions = Collections.emptyList(); 277 private List<ExtensionWithDeps> extensionsWithDeps = Collections.emptyList(); 278 279 /** 280 * The extensions of the add-on that were loaded. 281 * 282 * <p>This instance variable is lazy initialised. 283 * 284 * @see #addLoadedExtension(Extension) 285 * @see #removeLoadedExtension(Extension) 286 */ 287 private List<Extension> loadedExtensions; 288 289 private List<String> ascanrules = Collections.emptyList(); 290 private List<AbstractPlugin> loadedAscanrules = Collections.emptyList(); 291 private boolean loadedAscanRulesSet; 292 private List<String> pscanrules = Collections.emptyList(); 293 private List<PluginPassiveScanner> loadedPscanrules = Collections.emptyList(); 294 private boolean loadedPscanRulesSet; 295 private List<String> files = Collections.emptyList(); 296 private List<Lib> libs = Collections.emptyList(); 297 298 private AddOnClassnames addOnClassnames = AddOnClassnames.ALL_ALLOWED; 299 300 private ClassLoader classLoader; 301 302 private Dependencies dependencies; 303 304 /** 305 * The data of the bundle declared in the manifest. 306 * 307 * <p>Never {@code null}. 308 */ 309 private BundleData bundleData = BundleData.EMPTY_BUNDLE; 310 311 /** 312 * The resource bundle from the declaration in the manifest. 313 * 314 * <p>Might be {@code null}, if not declared. 315 */ 316 private ResourceBundle resourceBundle; 317 318 /** 319 * The data for the HelpSet, declared in the manifest. 320 * 321 * <p>Never {@code null}. 322 */ 323 private HelpSetData helpSetData = HelpSetData.EMPTY_HELP_SET; 324 325 private static final Logger logger = LogManager.getLogger(AddOn.class); 326 327 /** 328 * Tells whether or not the given file name matches the name of a ZAP add-on. 329 * 330 * <p>The file name must have the format "{@code <id>-<status>-<version>.zap}". The {@code id} 331 * is a string, the {@code status} must be a value from {@link Status} and the {@code version} 332 * must be an integer. 333 * 334 * @param fileName the name of the file to check 335 * @return {@code true} if the given file name is the name of an add-on, {@code false} 336 * otherwise. 337 * @deprecated (2.6.0) Use {@link #isAddOnFileName(String)} instead, the checks done in this 338 * method are more strict than it needs to. 339 * @see #isAddOnFileName(String) 340 */ 341 @Deprecated isAddOn(String fileName)342 public static boolean isAddOn(String fileName) { 343 if (!isAddOnFileName(fileName)) { 344 return false; 345 } 346 if (fileName.substring(0, fileName.indexOf(".")).split("-").length < 3) { 347 return false; 348 } 349 String[] strArray = fileName.substring(0, fileName.indexOf(".")).split("-"); 350 try { 351 Status.valueOf(strArray[1]); 352 Integer.parseInt(strArray[2]); 353 } catch (Exception e) { 354 return false; 355 } 356 357 return true; 358 } 359 360 /** 361 * Tells whether or not the given file name matches the name of a ZAP add-on. 362 * 363 * <p>The file name must have as file extension {@link #FILE_EXTENSION}. 364 * 365 * @param fileName the name of the file to check 366 * @return {@code true} if the given file name is the name of an add-on, {@code false} 367 * otherwise. 368 * @since 2.6.0 369 */ isAddOnFileName(String fileName)370 public static boolean isAddOnFileName(String fileName) { 371 if (fileName == null) { 372 return false; 373 } 374 return fileName.toLowerCase(Locale.ROOT).endsWith(FILE_EXTENSION); 375 } 376 377 /** 378 * Tells whether or not the given file is an add-on. 379 * 380 * @param f the file to be checked 381 * @return {@code true} if the given file is an add-on, {@code false} otherwise. 382 * @deprecated (2.6.0) Use {@link #isAddOn(Path)} instead. 383 */ 384 @Deprecated isAddOn(File f)385 public static boolean isAddOn(File f) { 386 return isAddOn(f.toPath()); 387 } 388 389 /** 390 * Tells whether or not the given file is a ZAP add-on. 391 * 392 * <p>An add-on is a ZIP file that contains, at least, a {@value #MANIFEST_FILE_NAME} file. 393 * 394 * @param file the file to be checked 395 * @return {@code true} if the given file is an add-on, {@code false} otherwise. 396 * @since 2.6.0 397 * @see #isAddOnFileName(String) 398 * @see #isValidAddOn(Path) 399 */ isAddOn(Path file)400 public static boolean isAddOn(Path file) { 401 return isValidAddOn(file).getValidity() == ValidationResult.Validity.VALID; 402 } 403 404 /** 405 * Tells whether or not the given file is a ZAP add-on. 406 * 407 * @param file the file to be checked. 408 * @return the result of the validation. 409 * @since 2.8.0 410 * @see #isAddOn(Path) 411 */ isValidAddOn(Path file)412 public static ValidationResult isValidAddOn(Path file) { 413 if (file == null || file.getNameCount() == 0) { 414 return new ValidationResult(ValidationResult.Validity.INVALID_PATH); 415 } 416 417 if (!isAddOnFileName(file.getFileName().toString())) { 418 return new ValidationResult(ValidationResult.Validity.INVALID_FILE_NAME); 419 } 420 421 if (!Files.isRegularFile(file) || !Files.isReadable(file)) { 422 return new ValidationResult(ValidationResult.Validity.FILE_NOT_READABLE); 423 } 424 425 try (ZipFile zip = new ZipFile(file.toFile())) { 426 ZipEntry manifestEntry = zip.getEntry(MANIFEST_FILE_NAME); 427 if (manifestEntry == null) { 428 return new ValidationResult(ValidationResult.Validity.MISSING_MANIFEST); 429 } 430 431 try (InputStream zis = zip.getInputStream(manifestEntry)) { 432 ZapAddOnXmlFile manifest; 433 try { 434 manifest = new ZapAddOnXmlFile(zis); 435 } catch (IOException e) { 436 return new ValidationResult(ValidationResult.Validity.INVALID_MANIFEST, e); 437 } 438 439 if (hasInvalidLibs(file, manifest, zip)) { 440 return new ValidationResult(ValidationResult.Validity.INVALID_LIB); 441 } 442 return new ValidationResult(manifest); 443 } 444 } catch (ZipException e) { 445 return new ValidationResult(ValidationResult.Validity.UNREADABLE_ZIP_FILE, e); 446 } catch (Exception e) { 447 return new ValidationResult(ValidationResult.Validity.IO_ERROR_FILE, e); 448 } 449 } 450 hasInvalidLibs(Path file, ZapAddOnXmlFile manifest, ZipFile zip)451 private static boolean hasInvalidLibs(Path file, ZapAddOnXmlFile manifest, ZipFile zip) { 452 return manifest.getLibs().stream() 453 .anyMatch( 454 e -> { 455 ZipEntry libEntry = zip.getEntry(e); 456 if (libEntry == null) { 457 logger.warn("The add-on " + file + " does not have the lib: " + e); 458 return true; 459 } 460 if (libEntry.isDirectory()) { 461 logger.warn( 462 "The add-on " + file + " does not have a file lib: " + e); 463 return true; 464 } 465 return false; 466 }); 467 } 468 469 /** 470 * Convenience method that attempts to create an {@code AddOn} from the given file. 471 * 472 * <p>A warning is logged if the add-on is invalid and an error if an error occurred while 473 * creating it. 474 * 475 * @param file the file of the add-on. 476 * @return a container with the {@code AddOn}, or empty if not valid or an error occurred while 477 * creating it. 478 * @since 2.8.0 479 */ createAddOn(Path file)480 public static Optional<AddOn> createAddOn(Path file) { 481 try { 482 return Optional.of(new AddOn(file)); 483 } catch (AddOn.InvalidAddOnException e) { 484 String logMessage = "Invalid add-on: " + file.toString() + "."; 485 if (logger.isDebugEnabled() || Constant.isDevMode()) { 486 logger.warn(logMessage, e); 487 } else { 488 logger.warn(logMessage + " " + e.getMessage()); 489 } 490 } catch (Exception e) { 491 logger.error("Failed to create an add-on from: " + file.toString(), e); 492 } 493 return Optional.empty(); 494 } 495 496 /** 497 * Constructs an {@code AddOn} with the given file name. 498 * 499 * @param fileName the file name of the add-on 500 * @throws Exception if the file name is not valid. 501 * @deprecated (2.6.0) Use {@link #AddOn(Path)} instead. 502 */ 503 @Deprecated AddOn(String fileName)504 public AddOn(String fileName) throws Exception { 505 // Format is <name>-<status>-<version>.zap 506 if (!isAddOn(fileName)) { 507 throw new Exception("Invalid ZAP add-on file " + fileName); 508 } 509 String[] strArray = fileName.substring(0, fileName.indexOf(".")).split("-"); 510 this.id = strArray[0]; 511 this.name = this.id; // Will be overridden if theres a ZapAddOn.xml file 512 this.status = Status.valueOf(strArray[1]); 513 this.version = new Version(Integer.parseInt(strArray[2]) + ".0.0"); 514 } 515 516 /** 517 * Constructs an {@code AddOn} from the given {@code file}. 518 * 519 * <p>The {@value #MANIFEST_FILE_NAME} ZIP file entry is read after validating that the add-on 520 * has a valid add-on file name. 521 * 522 * <p>The installation status of the add-on is 'not installed'. 523 * 524 * @param file the file of the add-on 525 * @throws Exception if the given {@code file} does not exist, does not have a valid add-on file 526 * name or an error occurred while reading the {@code value #ZAP_ADD_ON_XML} ZIP file entry 527 * @deprecated (2.6.0) Use {@link #AddOn(Path)} instead. 528 */ 529 @Deprecated AddOn(File file)530 public AddOn(File file) throws Exception { 531 this(file.toPath()); 532 } 533 534 /** 535 * Constructs an {@code AddOn} from the given {@code file}. 536 * 537 * <p>The {@value #MANIFEST_FILE_NAME} ZIP file entry is read after validating that the add-on 538 * is a valid add-on. 539 * 540 * <p>The installation status of the add-on is 'not installed'. 541 * 542 * @param file the file of the add-on 543 * @throws InvalidAddOnException (since 2.8.0) if the given {@code file} does not exist, does 544 * not have a valid add-on file name, or an error occurred while reading the add-on manifest 545 * ({@value #MANIFEST_FILE_NAME}). 546 * @throws IOException if an error occurred while reading/validating the file. 547 * @see #isAddOn(Path) 548 */ AddOn(Path file)549 public AddOn(Path file) throws IOException { 550 ValidationResult result = isValidAddOn(file); 551 if (result.getValidity() != ValidationResult.Validity.VALID) { 552 throw new InvalidAddOnException(result); 553 } 554 this.id = extractAddOnId(file.getFileName().toString()); 555 this.file = file.toFile(); 556 readZapAddOnXmlFile(result.getManifest()); 557 } 558 extractAddOnId(String fileName)559 private static String extractAddOnId(String fileName) { 560 return fileName.substring(0, fileName.lastIndexOf('.')).split("-")[0]; 561 } 562 loadManifestFile()563 private void loadManifestFile() throws IOException { 564 manifestRead = true; 565 if (file.exists()) { 566 // Might not exist in the tests 567 try (ZipFile zip = new ZipFile(file)) { 568 ZipEntry zapAddOnEntry = zip.getEntry(MANIFEST_FILE_NAME); 569 if (zapAddOnEntry == null) { 570 throw new IOException( 571 "Add-on does not have the " + MANIFEST_FILE_NAME + " file."); 572 } 573 574 try (InputStream zis = zip.getInputStream(zapAddOnEntry)) { 575 ZapAddOnXmlFile zapAddOnXml = new ZapAddOnXmlFile(zis); 576 readZapAddOnXmlFile(zapAddOnXml); 577 } 578 } 579 } 580 } 581 readZapAddOnXmlFile(ZapAddOnXmlFile zapAddOnXml)582 private void readZapAddOnXmlFile(ZapAddOnXmlFile zapAddOnXml) { 583 this.name = zapAddOnXml.getName(); 584 this.version = zapAddOnXml.getVersion(); 585 this.semVer = zapAddOnXml.getSemVer(); 586 this.status = AddOn.Status.valueOf(zapAddOnXml.getStatus()); 587 this.description = zapAddOnXml.getDescription(); 588 this.changes = zapAddOnXml.getChanges(); 589 this.author = zapAddOnXml.getAuthor(); 590 this.notBeforeVersion = zapAddOnXml.getNotBeforeVersion(); 591 this.notFromVersion = zapAddOnXml.getNotFromVersion(); 592 this.dependencies = zapAddOnXml.getDependencies(); 593 this.info = createUrl(zapAddOnXml.getUrl()); 594 this.repo = createUrl(zapAddOnXml.getRepo()); 595 596 this.ascanrules = zapAddOnXml.getAscanrules(); 597 this.extensions = zapAddOnXml.getExtensions(); 598 this.extensionsWithDeps = zapAddOnXml.getExtensionsWithDeps(); 599 this.files = zapAddOnXml.getFiles(); 600 this.libs = createLibs(zapAddOnXml.getLibs()); 601 this.pscanrules = zapAddOnXml.getPscanrules(); 602 603 this.addOnClassnames = zapAddOnXml.getAddOnClassnames(); 604 605 String bundleBaseName = zapAddOnXml.getBundleBaseName(); 606 if (!bundleBaseName.isEmpty()) { 607 bundleData = new BundleData(bundleBaseName, zapAddOnXml.getBundlePrefix()); 608 } 609 610 String helpSetBaseName = zapAddOnXml.getHelpSetBaseName(); 611 if (!helpSetBaseName.isEmpty()) { 612 this.helpSetData = 613 new HelpSetData(helpSetBaseName, zapAddOnXml.getHelpSetLocaleToken()); 614 } 615 616 hasZapAddOnEntry = true; 617 } 618 619 /** 620 * Constructs an {@code AddOn} from an add-on entry of {@code ZapVersions.xml} file. The 621 * installation status of the add-on is 'not installed'. 622 * 623 * <p>The given {@code SubnodeConfiguration} must have a {@code XPathExpressionEngine} 624 * installed. 625 * 626 * <p>The {@value #MANIFEST_FILE_NAME} ZIP file entry is read, if the add-on file exists 627 * locally. 628 * 629 * @param id the id of the add-on 630 * @param baseDir the base directory where the add-on is located 631 * @param xmlData the source of add-on entry of {@code ZapVersions.xml} file 632 * @throws MalformedURLException if the {@code URL} of the add-on is malformed 633 * @throws IOException if an error occurs while reading the XML data 634 * @see org.apache.commons.configuration.tree.xpath.XPathExpressionEngine 635 */ AddOn(String id, File baseDir, SubnodeConfiguration xmlData)636 public AddOn(String id, File baseDir, SubnodeConfiguration xmlData) 637 throws MalformedURLException, IOException { 638 this.id = id; 639 ZapVersionsAddOnEntry addOnData = new ZapVersionsAddOnEntry(xmlData); 640 this.name = addOnData.getName(); 641 this.description = addOnData.getDescription(); 642 this.author = addOnData.getAuthor(); 643 this.dependencies = addOnData.getDependencies(); 644 this.extensionsWithDeps = addOnData.getExtensionsWithDeps(); 645 this.version = addOnData.getVersion(); 646 this.semVer = addOnData.getSemVer(); 647 this.status = AddOn.Status.valueOf(addOnData.getStatus()); 648 this.changes = addOnData.getChanges(); 649 this.url = new URL(addOnData.getUrl()); 650 this.file = new File(baseDir, addOnData.getFile()); 651 this.size = addOnData.getSize(); 652 this.notBeforeVersion = addOnData.getNotBeforeVersion(); 653 this.notFromVersion = addOnData.getNotFromVersion(); 654 this.info = createUrl(addOnData.getInfo()); 655 this.repo = createUrl(addOnData.getRepo()); 656 this.releaseDate = addOnData.getDate(); 657 this.hash = addOnData.getHash(); 658 659 loadManifestFile(); 660 } 661 createUrl(String url)662 private URL createUrl(String url) { 663 if (url != null && !url.isEmpty()) { 664 try { 665 return new URL(url); 666 } catch (Exception e) { 667 logger.warn("Invalid URL for add-on \"" + id + "\": " + url, e); 668 } 669 } 670 return null; 671 } 672 createLibs(List<String> libPaths)673 private static List<Lib> createLibs(List<String> libPaths) { 674 if (libPaths.isEmpty()) { 675 return Collections.emptyList(); 676 } 677 return libPaths.stream().map(Lib::new).collect(Collectors.toList()); 678 } 679 getId()680 public String getId() { 681 return id; 682 } 683 setId(String id)684 public void setId(String id) { 685 this.id = id; 686 } 687 getName()688 public String getName() { 689 return name; 690 } 691 setName(String name)692 public void setName(String name) { 693 this.name = name; 694 } 695 getDescription()696 public String getDescription() { 697 return description; 698 } 699 setDescription(String description)700 public void setDescription(String description) { 701 this.description = description; 702 } 703 704 /** 705 * Gets the file version of the add-on. 706 * 707 * @return the file version. 708 * @deprecated (2.7.0) Use {@link #getVersion()} instead. 709 */ 710 @Deprecated getFileVersion()711 public int getFileVersion() { 712 return getVersion().getMajorVersion(); 713 } 714 715 /** 716 * Gets the semantic version of this add-on. 717 * 718 * <p>Since 2.7.0, for add-ons that use just an integer as the version it's appended ".0.0", for 719 * example, for version {@literal 14} it returns the version {@literal 14.0.0}. 720 * 721 * @return the semantic version of the add-on, since 2.7.0, never {@code null}. 722 * @since 2.4.0 723 */ getVersion()724 public Version getVersion() { 725 return version; 726 } 727 728 /** 729 * Gets the semantic version declared in the manifest file. 730 * 731 * <p>To be replaced by {@link #getVersion()}. 732 * 733 * @return the semantic version declared in the manifest file, might be {@code null}. 734 */ getSemVer()735 Version getSemVer() { 736 return semVer; 737 } 738 getStatus()739 public Status getStatus() { 740 return status; 741 } 742 setStatus(Status status)743 public void setStatus(Status status) { 744 this.status = status; 745 } 746 getChanges()747 public String getChanges() { 748 return changes; 749 } 750 setChanges(String changes)751 public void setChanges(String changes) { 752 this.changes = changes; 753 } 754 755 /** 756 * Gets the normalised file name of the add-on. 757 * 758 * <p>Should be used when copying the file from an "unknown" source (e.g. manually installed). 759 * 760 * @return the normalised file name. 761 * @since 2.6.0 762 * @see #getFile() 763 */ getNormalisedFileName()764 public String getNormalisedFileName() { 765 return getId() + "-" + getVersion() + FILE_EXTENSION; 766 } 767 768 /** 769 * Gets the file of the add-on. 770 * 771 * @return the file of the add-on. 772 * @see #getNormalisedFileName() 773 */ getFile()774 public File getFile() { 775 return file; 776 } 777 setFile(File file)778 public void setFile(File file) { 779 this.file = file; 780 } 781 getUrl()782 public URL getUrl() { 783 return url; 784 } 785 setUrl(URL url)786 public void setUrl(URL url) { 787 this.url = url; 788 } 789 getSize()790 public long getSize() { 791 return size; 792 } 793 setSize(long size)794 public void setSize(long size) { 795 this.size = size; 796 } 797 getAuthor()798 public String getAuthor() { 799 return author; 800 } 801 setAuthor(String author)802 public void setAuthor(String author) { 803 this.author = author; 804 } 805 806 /** 807 * Gets the class loader of the add-on. 808 * 809 * @return the class loader of the add-on, {@code null} if the add-on is not installed. 810 * @since 2.8.0 811 */ getClassLoader()812 public ClassLoader getClassLoader() { 813 return classLoader; 814 } 815 816 /** 817 * Sets the class loader of the add-on. 818 * 819 * <p><strong>Note:</strong> This method should be called only by bootstrap classes. 820 * 821 * @param classLoader the class loader of the add-on, might be {@code null}. 822 * @since 2.8.0 823 */ setClassLoader(ClassLoader classLoader)824 public void setClassLoader(ClassLoader classLoader) { 825 this.classLoader = classLoader; 826 } 827 828 /** 829 * Sets the installation status of the add-on. 830 * 831 * @param installationStatus the new installation status 832 * @throws IllegalArgumentException if the given {@code installationStatus} is {@code null}. 833 * @since 2.4.0 834 */ setInstallationStatus(InstallationStatus installationStatus)835 public void setInstallationStatus(InstallationStatus installationStatus) { 836 if (installationStatus == null) { 837 throw new IllegalArgumentException("Parameter installationStatus must not be null."); 838 } 839 840 this.installationStatus = installationStatus; 841 } 842 843 /** 844 * Gets installations status of the add-on. 845 * 846 * @return the installation status, never {@code null} 847 * @since 2.4.0 848 */ getInstallationStatus()849 public InstallationStatus getInstallationStatus() { 850 return installationStatus; 851 } 852 hasZapAddOnEntry()853 public boolean hasZapAddOnEntry() { 854 if (!hasZapAddOnEntry) { 855 if (!manifestRead) { 856 // Worth trying, as it depends which constructor has been used 857 try { 858 this.loadManifestFile(); 859 } catch (IOException e) { 860 if (logger.isDebugEnabled()) { 861 logger.debug( 862 "Failed to read the " 863 + AddOn.MANIFEST_FILE_NAME 864 + " file of " 865 + id 866 + ":", 867 e); 868 } 869 } 870 } 871 } 872 return hasZapAddOnEntry; 873 } 874 875 /** 876 * Gets the classnames that can be loaded for the add-on. 877 * 878 * @return the classnames that can be loaded 879 * @since 2.4.3 880 */ getAddOnClassnames()881 public AddOnClassnames getAddOnClassnames() { 882 return addOnClassnames; 883 } 884 getExtensions()885 public List<String> getExtensions() { 886 return extensions; 887 } 888 889 /** 890 * Returns the classnames of {@code Extension}sthat have dependencies on add-ons. 891 * 892 * @return the classnames of the extensions with dependencies on add-ons. 893 * @see #hasExtensionsWithDeps() 894 */ getExtensionsWithDeps()895 public List<String> getExtensionsWithDeps() { 896 if (extensionsWithDeps.isEmpty()) { 897 return Collections.emptyList(); 898 } 899 900 List<String> extensionClassnames = new ArrayList<>(extensionsWithDeps.size()); 901 for (ExtensionWithDeps extension : extensionsWithDeps) { 902 extensionClassnames.add(extension.getClassname()); 903 } 904 return extensionClassnames; 905 } 906 907 /** 908 * Returns the classnames that can be loaded for the given {@code Extension} (with 909 * dependencies). 910 * 911 * @param classname the classname of the extension 912 * @return the classnames that can be loaded 913 * @since 2.4.3 914 * @see #hasExtensionsWithDeps() 915 */ getExtensionAddOnClassnames(String classname)916 public AddOnClassnames getExtensionAddOnClassnames(String classname) { 917 if (extensionsWithDeps.isEmpty() || classname == null || classname.isEmpty()) { 918 return AddOnClassnames.ALL_ALLOWED; 919 } 920 921 for (ExtensionWithDeps extension : extensionsWithDeps) { 922 if (classname.equals(extension.getClassname())) { 923 return extension.getAddOnClassnames(); 924 } 925 } 926 return AddOnClassnames.ALL_ALLOWED; 927 } 928 929 /** 930 * Tells whether or not this add-on has at least one extension with dependencies. 931 * 932 * @return {@code true} if the add-on has at leas one extension with dependencies, {@code false} 933 * otherwise 934 * @see #getExtensionsWithDeps() 935 */ hasExtensionsWithDeps()936 public boolean hasExtensionsWithDeps() { 937 return !extensionsWithDeps.isEmpty(); 938 } 939 940 /** 941 * Gets the extensions of this add-on that have dependencies and were loaded. 942 * 943 * @return an unmodifiable {@code List} with the extensions of this add-on that have 944 * dependencies and were loaded 945 * @since 2.4.0 946 */ getLoadedExtensionsWithDeps()947 public List<Extension> getLoadedExtensionsWithDeps() { 948 List<String> classnames = getExtensionsWithDeps(); 949 ArrayList<Extension> loadedExtensions = new ArrayList<>(extensionsWithDeps.size()); 950 for (Extension extension : getLoadedExtensions()) { 951 if (classnames.contains(extension.getClass().getCanonicalName())) { 952 loadedExtensions.add(extension); 953 } 954 } 955 loadedExtensions.trimToSize(); 956 return loadedExtensions; 957 } 958 959 /** 960 * Gets the extensions of this add-on that were loaded. 961 * 962 * @return an unmodifiable {@code List} with the extensions of this add-on that were loaded 963 * @since 2.4.0 964 */ getLoadedExtensions()965 public List<Extension> getLoadedExtensions() { 966 if (loadedExtensions == null) { 967 return Collections.emptyList(); 968 } 969 return Collections.unmodifiableList(loadedExtensions); 970 } 971 972 /** 973 * Adds the given {@code extension} to the list of loaded extensions of this add-on. 974 * 975 * <p>This add-on is set to the given {@code extension}. 976 * 977 * @param extension the extension of this add-on that was loaded 978 * @throws IllegalArgumentException if extension is {@code null} 979 * @since 2.4.0 980 * @see #removeLoadedExtension(Extension) 981 * @see Extension#setAddOn(AddOn) 982 */ addLoadedExtension(Extension extension)983 public void addLoadedExtension(Extension extension) { 984 if (extension == null) { 985 throw new IllegalArgumentException("Parameter extension must not be null."); 986 } 987 988 if (loadedExtensions == null) { 989 loadedExtensions = new ArrayList<>(1); 990 } 991 992 if (!loadedExtensions.contains(extension)) { 993 loadedExtensions.add(extension); 994 extension.setAddOn(this); 995 } 996 } 997 998 /** 999 * Removes the given {@code extension} from the list of loaded extensions of this add-on. 1000 * 1001 * <p>The add-on of the given {@code extension} is set to {@code null}. 1002 * 1003 * <p>The call to this method has no effect if the given {@code extension} does not belong to 1004 * this add-on. 1005 * 1006 * @param extension the loaded extension of this add-on that should be removed 1007 * @throws IllegalArgumentException if extension is {@code null} 1008 * @since 2.4.0 1009 * @see #addLoadedExtension(Extension) 1010 * @see Extension#setAddOn(AddOn) 1011 */ removeLoadedExtension(Extension extension)1012 public void removeLoadedExtension(Extension extension) { 1013 if (extension == null) { 1014 throw new IllegalArgumentException("Parameter extension must not be null."); 1015 } 1016 1017 if (loadedExtensions != null && loadedExtensions.contains(extension)) { 1018 loadedExtensions.remove(extension); 1019 extension.setAddOn(null); 1020 } 1021 } 1022 getAscanrules()1023 public List<String> getAscanrules() { 1024 return ascanrules; 1025 } 1026 1027 /** 1028 * Gets the active scan rules of this add-on that were loaded. 1029 * 1030 * @return an unmodifiable {@code List} with the active scan rules of this add-on that were 1031 * loaded, never {@code null} 1032 * @since 2.4.3 1033 * @see #setLoadedAscanrules(List) 1034 */ getLoadedAscanrules()1035 public List<AbstractPlugin> getLoadedAscanrules() { 1036 return loadedAscanrules; 1037 } 1038 1039 /** 1040 * Sets the loaded active scan rules of the add-on, allowing to set the status of the active 1041 * scan rules appropriately and to keep track of the active scan rules loaded so that they can 1042 * be removed during uninstallation. 1043 * 1044 * <p><strong>Note:</strong> Helper method to be used (only) by/during (un)installation process 1045 * and loading of the add-on. Should be called when installing/loading the add-on, by setting 1046 * the loaded active scan rules, and when uninstalling by setting an empty list. The method 1047 * {@code setLoadedAscanrulesSet(boolean)} should also be called. 1048 * 1049 * @param ascanrules the active scan rules loaded, might be empty if none were actually loaded 1050 * @throws IllegalArgumentException if {@code ascanrules} is {@code null}. 1051 * @since 2.4.3 1052 * @see #setLoadedAscanrulesSet(boolean) 1053 * @see AbstractPlugin#setStatus(Status) 1054 */ setLoadedAscanrules(List<AbstractPlugin> ascanrules)1055 void setLoadedAscanrules(List<AbstractPlugin> ascanrules) { 1056 if (ascanrules == null) { 1057 throw new IllegalArgumentException("Parameter ascanrules must not be null."); 1058 } 1059 1060 if (ascanrules.isEmpty()) { 1061 loadedAscanrules = Collections.emptyList(); 1062 return; 1063 } 1064 1065 for (AbstractPlugin ascanrule : ascanrules) { 1066 ascanrule.setStatus(getStatus()); 1067 } 1068 loadedAscanrules = Collections.unmodifiableList(new ArrayList<>(ascanrules)); 1069 } 1070 1071 /** 1072 * Tells whether or not the loaded active scan rules of the add-on, if any, were already set to 1073 * the add-on. 1074 * 1075 * <p><strong>Note:</strong> Helper method to be used (only) by/during (un)installation process 1076 * and loading of the add-on. 1077 * 1078 * @return {@code true} if the loaded active scan rules were already set, {@code false} 1079 * otherwise 1080 * @since 2.4.3 1081 * @see #setLoadedAscanrules(List) 1082 * @see #setLoadedAscanrulesSet(boolean) 1083 */ isLoadedAscanrulesSet()1084 boolean isLoadedAscanrulesSet() { 1085 return loadedAscanRulesSet; 1086 } 1087 1088 /** 1089 * Sets whether or not the loaded active scan rules, if any, where already set to the add-on. 1090 * 1091 * <p><strong>Note:</strong> Helper method to be used (only) by/during (un)installation process 1092 * and loading of the add-on. The method should be called, with {@code true} during 1093 * installation/loading and {@code false} during uninstallation, after calling the method {@code 1094 * setLoadedAscanrules(List)}. 1095 * 1096 * @param ascanrulesSet {@code true} if the loaded active scan rules were already set, {@code 1097 * false} otherwise 1098 * @since 2.4.3 1099 * @see #setLoadedAscanrules(List) 1100 */ setLoadedAscanrulesSet(boolean ascanrulesSet)1101 void setLoadedAscanrulesSet(boolean ascanrulesSet) { 1102 loadedAscanRulesSet = ascanrulesSet; 1103 } 1104 getPscanrules()1105 public List<String> getPscanrules() { 1106 return pscanrules; 1107 } 1108 1109 /** 1110 * Gets the passive scan rules of this add-on that were loaded. 1111 * 1112 * @return an unmodifiable {@code List} with the passive scan rules of this add-on that were 1113 * loaded, never {@code null} 1114 * @since 2.4.3 1115 * @see #setLoadedPscanrules(List) 1116 */ getLoadedPscanrules()1117 public List<PluginPassiveScanner> getLoadedPscanrules() { 1118 return loadedPscanrules; 1119 } 1120 1121 /** 1122 * Sets the loaded passive scan rules of the add-on, allowing to set the status of the passive 1123 * scan rules appropriately and keep track of the passive scan rules loaded so that they can be 1124 * removed during uninstallation. 1125 * 1126 * <p><strong>Note:</strong> Helper method to be used (only) by/during (un)installation process 1127 * and loading of the add-on. Should be called when installing/loading the add-on, by setting 1128 * the loaded passive scan rules, and when uninstalling by setting an empty list. The method 1129 * {@code setLoadedPscanrulesSet(boolean)} should also be called. 1130 * 1131 * @param pscanrules the passive scan rules loaded, might be empty if none were actually loaded 1132 * @throws IllegalArgumentException if {@code pscanrules} is {@code null}. 1133 * @since 2.4.3 1134 * @see #setLoadedPscanrulesSet(boolean) 1135 * @see PluginPassiveScanner#setStatus(Status) 1136 */ setLoadedPscanrules(List<PluginPassiveScanner> pscanrules)1137 void setLoadedPscanrules(List<PluginPassiveScanner> pscanrules) { 1138 if (pscanrules == null) { 1139 throw new IllegalArgumentException("Parameter pscanrules must not be null."); 1140 } 1141 1142 if (pscanrules.isEmpty()) { 1143 loadedPscanrules = Collections.emptyList(); 1144 return; 1145 } 1146 1147 for (PluginPassiveScanner pscanrule : pscanrules) { 1148 pscanrule.setStatus(getStatus()); 1149 } 1150 loadedPscanrules = Collections.unmodifiableList(new ArrayList<>(pscanrules)); 1151 } 1152 1153 /** 1154 * Tells whether or not the loaded passive scan rules of the add-on, if any, were already set to 1155 * the add-on. 1156 * 1157 * <p><strong>Note:</strong> Helper method to be used (only) by/during (un)installation process 1158 * and loading of the add-on. 1159 * 1160 * @return {@code true} if the loaded passive scan rules were already set, {@code false} 1161 * otherwise 1162 * @since 2.4.3 1163 * @see #setLoadedPscanrules(List) 1164 * @see #setLoadedPscanrulesSet(boolean) 1165 */ isLoadedPscanrulesSet()1166 boolean isLoadedPscanrulesSet() { 1167 return loadedPscanRulesSet; 1168 } 1169 1170 /** 1171 * Sets whether or not the loaded passive scan rules, if any, where already set to the add-on. 1172 * 1173 * <p><strong>Note:</strong> Helper method to be used (only) by/during (un)installation process 1174 * and loading of the add-on. The method should be called, with {@code true} during 1175 * installation/loading and {@code false} during uninstallation, after calling the method {@code 1176 * setLoadedPscanrules(List)}. 1177 * 1178 * @param pscanrulesSet {@code true} if the loaded passive scan rules were already set, {@code 1179 * false} otherwise 1180 * @since 2.4.3 1181 * @see #setLoadedPscanrules(List) 1182 */ setLoadedPscanrulesSet(boolean pscanrulesSet)1183 void setLoadedPscanrulesSet(boolean pscanrulesSet) { 1184 loadedPscanRulesSet = pscanrulesSet; 1185 } 1186 getFiles()1187 public List<String> getFiles() { 1188 return files; 1189 } 1190 1191 /** 1192 * Gets the bundled libraries of the add-on. 1193 * 1194 * @return the bundled libraries, never {@code null}. 1195 */ getLibs()1196 List<Lib> getLibs() { 1197 return libs; 1198 } 1199 hasMissingLibs()1200 private boolean hasMissingLibs() { 1201 return libs.stream().anyMatch(lib -> lib.getFileSystemUrl() == null); 1202 } 1203 isSameAddOn(AddOn addOn)1204 public boolean isSameAddOn(AddOn addOn) { 1205 return this.getId().equals(addOn.getId()); 1206 } 1207 isUpdateTo(AddOn addOn)1208 public boolean isUpdateTo(AddOn addOn) throws IllegalArgumentException { 1209 if (!this.isSameAddOn(addOn)) { 1210 throw new IllegalArgumentException( 1211 "Different addons: " + this.getId() + " != " + addOn.getId()); 1212 } 1213 int result = this.getVersion().compareTo(addOn.getVersion()); 1214 if (result != 0) { 1215 return result > 0; 1216 } 1217 1218 result = this.getStatus().compareTo(addOn.getStatus()); 1219 if (result != 0) { 1220 return result > 0; 1221 } 1222 1223 if (getFile() == null) { 1224 return false; 1225 } 1226 if (addOn.getFile() == null) { 1227 return true; 1228 } 1229 return getFile().lastModified() > addOn.getFile().lastModified(); 1230 } 1231 1232 /** 1233 * @deprecated (2.4.0) Use {@link #calculateRunRequirements(Collection)} instead. Returns {@code 1234 * false}. 1235 * @return {@code false} always. 1236 */ 1237 @Deprecated canLoad()1238 public boolean canLoad() { 1239 return false; 1240 } 1241 1242 /** 1243 * Tells whether or not this add-on can be loaded in the currently running ZAP version, as given 1244 * by {@code Constant.PROGRAM_VERSION}. 1245 * 1246 * @return {@code true} if the add-on can be loaded in the currently running ZAP version, {@code 1247 * false} otherwise 1248 * @see #canLoadInVersion(String) 1249 * @see Constant#PROGRAM_VERSION 1250 */ canLoadInCurrentVersion()1251 public boolean canLoadInCurrentVersion() { 1252 return canLoadInVersion(Constant.PROGRAM_VERSION); 1253 } 1254 1255 /** 1256 * Tells whether or not this add-on can be run in the currently running Java version. 1257 * 1258 * <p>This is a convenience method that calls {@code canRunInJavaVersion(String)} with the 1259 * running Java version (as given by {@code SystemUtils.JAVA_VERSION}) as parameter. 1260 * 1261 * @return {@code true} if the add-on can be run in the currently running Java version, {@code 1262 * false} otherwise 1263 * @since 2.4.0 1264 * @see #canRunInJavaVersion(String) 1265 * @see SystemUtils#JAVA_VERSION 1266 */ canRunInCurrentJavaVersion()1267 public boolean canRunInCurrentJavaVersion() { 1268 return canRunInJavaVersion(SystemUtils.JAVA_VERSION); 1269 } 1270 1271 /** 1272 * Tells whether or not this add-on can be run in the given {@code javaVersion}. 1273 * 1274 * <p>If the given {@code javaVersion} is {@code null} and this add-on depends on a specific 1275 * java version the method returns {@code false}. 1276 * 1277 * @param javaVersion the java version that will be checked 1278 * @return {@code true} if the add-on can be loaded in the given {@code javaVersion}, {@code 1279 * false} otherwise 1280 * @since 2.4.0 1281 */ canRunInJavaVersion(String javaVersion)1282 public boolean canRunInJavaVersion(String javaVersion) { 1283 if (dependencies == null) { 1284 return true; 1285 } 1286 1287 String requiredVersion = dependencies.getJavaVersion(); 1288 if (requiredVersion == null) { 1289 return true; 1290 } 1291 1292 if (javaVersion == null) { 1293 return false; 1294 } 1295 1296 return getJavaVersion(javaVersion) >= getJavaVersion(requiredVersion); 1297 } 1298 1299 /** 1300 * Calculates the requirements to run this add-on, in the current ZAP and Java versions and with 1301 * the given {@code availableAddOns}. 1302 * 1303 * <p>If the add-on depends on other add-ons, those add-ons are also checked if are also 1304 * runnable. 1305 * 1306 * <p><strong>Note:</strong> All the given {@code availableAddOns} are expected to be loadable 1307 * in the currently running ZAP version, that is, the method {@code 1308 * AddOn.canLoadInCurrentVersion()}, returns {@code true}. 1309 * 1310 * @param availableAddOns the other add-ons available 1311 * @return a requirements to run the add-on, and if not runnable the reason why it's not. 1312 * @since 2.4.0 1313 * @see #canLoadInCurrentVersion() 1314 * @see #canRunInCurrentJavaVersion() 1315 * @see AddOnRunRequirements 1316 */ calculateRunRequirements(Collection<AddOn> availableAddOns)1317 public AddOnRunRequirements calculateRunRequirements(Collection<AddOn> availableAddOns) { 1318 return calculateRunRequirements(availableAddOns, true); 1319 } 1320 1321 /** 1322 * Calculates the requirements to install/run this add-on, same as {@link 1323 * #calculateRunRequirements(Collection)} but does not require the libraries of the add-ons 1324 * (this or dependencies) to exist in the file system. 1325 * 1326 * @param availableAddOns the other add-ons available. 1327 * @return the requirements to install/run the add-on, and if not runnable the reason why it's 1328 * not. 1329 * @since 2.9.0 1330 * @see #calculateRunRequirements(Collection) 1331 * @see AddOnRunRequirements 1332 */ calculateInstallRequirements(Collection<AddOn> availableAddOns)1333 public AddOnRunRequirements calculateInstallRequirements(Collection<AddOn> availableAddOns) { 1334 return calculateRunRequirements(availableAddOns, false); 1335 } 1336 calculateRunRequirements( Collection<AddOn> availableAddOns, boolean checkLibs)1337 private AddOnRunRequirements calculateRunRequirements( 1338 Collection<AddOn> availableAddOns, boolean checkLibs) { 1339 AddOnRunRequirements requirements = new AddOnRunRequirements(this); 1340 calculateRunRequirementsImpl(availableAddOns, requirements, null, this, checkLibs); 1341 if (requirements.isRunnable()) { 1342 checkExtensionsWithDeps(availableAddOns, requirements, this, checkLibs); 1343 } 1344 return requirements; 1345 } 1346 calculateRunRequirementsImpl( Collection<AddOn> availableAddOns, BaseRunRequirements requirements, AddOn parent, AddOn addOn, boolean checkLibs)1347 private static void calculateRunRequirementsImpl( 1348 Collection<AddOn> availableAddOns, 1349 BaseRunRequirements requirements, 1350 AddOn parent, 1351 AddOn addOn, 1352 boolean checkLibs) { 1353 if (checkLibs && addOn.hasMissingLibs()) { 1354 requirements.setMissingLibsIssue(addOn); 1355 return; 1356 } 1357 1358 AddOn installedVersion = getAddOn(availableAddOns, addOn.getId()); 1359 if (installedVersion != null && !addOn.equals(installedVersion)) { 1360 requirements.setIssue( 1361 BaseRunRequirements.DependencyIssue.OLDER_VERSION, installedVersion); 1362 if (logger.isDebugEnabled()) { 1363 logger.debug( 1364 "Add-on " 1365 + addOn 1366 + " not runnable, old version still installed: " 1367 + installedVersion); 1368 } 1369 return; 1370 } 1371 1372 if (!requirements.addDependency(parent, addOn)) { 1373 logger.warn("Cyclic dependency detected with: " + requirements.getDependencies()); 1374 requirements.setIssue( 1375 BaseRunRequirements.DependencyIssue.CYCLIC, requirements.getDependencies()); 1376 return; 1377 } 1378 1379 if (addOn.dependencies == null) { 1380 return; 1381 } 1382 1383 if (!addOn.canRunInCurrentJavaVersion()) { 1384 requirements.setMinimumJavaVersionIssue(addOn, addOn.dependencies.getJavaVersion()); 1385 } 1386 1387 for (AddOnDep dependency : addOn.dependencies.getAddOns()) { 1388 String addOnId = dependency.getId(); 1389 if (addOnId != null) { 1390 AddOn addOnDep = getAddOn(availableAddOns, addOnId); 1391 if (addOnDep == null) { 1392 requirements.setIssue(BaseRunRequirements.DependencyIssue.MISSING, addOnId); 1393 return; 1394 } 1395 1396 if (!dependency.getVersion().isEmpty()) { 1397 if (!versionMatches(addOnDep, dependency)) { 1398 requirements.setIssue( 1399 BaseRunRequirements.DependencyIssue.VERSION, 1400 addOnDep, 1401 dependency.getVersion()); 1402 return; 1403 } 1404 } 1405 1406 calculateRunRequirementsImpl( 1407 availableAddOns, requirements, addOn, addOnDep, checkLibs); 1408 if (requirements.hasDependencyIssue()) { 1409 return; 1410 } 1411 } 1412 } 1413 } 1414 checkExtensionsWithDeps( Collection<AddOn> availableAddOns, AddOnRunRequirements requirements, AddOn addOn, boolean checkLibs)1415 private static void checkExtensionsWithDeps( 1416 Collection<AddOn> availableAddOns, 1417 AddOnRunRequirements requirements, 1418 AddOn addOn, 1419 boolean checkLibs) { 1420 if (addOn.extensionsWithDeps.isEmpty()) { 1421 return; 1422 } 1423 1424 for (ExtensionWithDeps extension : addOn.extensionsWithDeps) { 1425 calculateExtensionRunRequirements( 1426 extension, availableAddOns, requirements, addOn, checkLibs); 1427 } 1428 } 1429 calculateExtensionRunRequirements( ExtensionWithDeps extension, Collection<AddOn> availableAddOns, AddOnRunRequirements requirements, AddOn addOn, boolean checkLibs)1430 private static void calculateExtensionRunRequirements( 1431 ExtensionWithDeps extension, 1432 Collection<AddOn> availableAddOns, 1433 AddOnRunRequirements requirements, 1434 AddOn addOn, 1435 boolean checkLibs) { 1436 ExtensionRunRequirements extensionRequirements = 1437 new ExtensionRunRequirements(addOn, extension.getClassname()); 1438 requirements.addExtensionRequirements(extensionRequirements); 1439 for (AddOnDep dependency : extension.getDependencies()) { 1440 String addOnId = dependency.getId(); 1441 if (addOnId == null) { 1442 continue; 1443 } 1444 1445 AddOn addOnDep = getAddOn(availableAddOns, addOnId); 1446 if (addOnDep == null) { 1447 if (addOn.hasOnlyOneExtensionWithDependencies()) { 1448 requirements.setIssue(BaseRunRequirements.DependencyIssue.MISSING, addOnId); 1449 return; 1450 } 1451 extensionRequirements.setIssue( 1452 BaseRunRequirements.DependencyIssue.MISSING, addOnId); 1453 continue; 1454 } 1455 1456 if (!dependency.getVersion().isEmpty()) { 1457 if (!versionMatches(addOnDep, dependency)) { 1458 if (addOn.hasOnlyOneExtensionWithDependencies()) { 1459 requirements.setIssue( 1460 BaseRunRequirements.DependencyIssue.VERSION, 1461 addOnDep, 1462 dependency.getVersion()); 1463 return; 1464 } 1465 extensionRequirements.setIssue( 1466 BaseRunRequirements.DependencyIssue.VERSION, 1467 addOnDep, 1468 dependency.getVersion()); 1469 continue; 1470 } 1471 } 1472 1473 calculateRunRequirementsImpl( 1474 availableAddOns, extensionRequirements, addOn, addOnDep, checkLibs); 1475 } 1476 } 1477 hasOnlyOneExtensionWithDependencies()1478 private boolean hasOnlyOneExtensionWithDependencies() { 1479 if (extensionsWithDeps.size() != 1) { 1480 return false; 1481 } 1482 if (extensions.isEmpty() 1483 && files.isEmpty() 1484 && pscanrules.isEmpty() 1485 && ascanrules.isEmpty()) { 1486 return true; 1487 } 1488 return false; 1489 } 1490 1491 /** 1492 * Calculates the requirements to run the given {@code extension}, in the current ZAP and Java 1493 * versions and with the given {@code availableAddOns}. 1494 * 1495 * <p>If the extension depends on other add-ons, those add-ons are checked if are also runnable. 1496 * 1497 * <p><strong>Note:</strong> All the given {@code availableAddOns} are expected to be loadable 1498 * in the currently running ZAP version, that is, the method {@code 1499 * AddOn.canLoadInCurrentVersion()}, returns {@code true}. 1500 * 1501 * @param extension the extension that will be checked 1502 * @param availableAddOns the add-ons available 1503 * @return the requirements to run the extension, and if not runnable the reason why it's not. 1504 * @since 2.4.0 1505 * @see AddOnRunRequirements#getExtensionRequirements() 1506 */ calculateExtensionRunRequirements( Extension extension, Collection<AddOn> availableAddOns)1507 public AddOnRunRequirements calculateExtensionRunRequirements( 1508 Extension extension, Collection<AddOn> availableAddOns) { 1509 return calculateExtensionRunRequirementsImpl( 1510 extension.getClass().getCanonicalName(), availableAddOns, true); 1511 } 1512 1513 /** 1514 * Calculates the requirements to install/run the given {@code extension}, same as {@link 1515 * #calculateExtensionRunRequirements(Extension, Collection)} but does not require the libraries 1516 * of the add-ons (this or dependencies) to exist in the file system. 1517 * 1518 * @param extension the extension that will be checked. 1519 * @param availableAddOns the add-ons available. 1520 * @return the requirements to install/run the extension, and if not runnable the reason why 1521 * it's not. 1522 * @since 2.9.0 1523 * @see AddOnRunRequirements#getExtensionRequirements() 1524 */ calculateExtensionInstallRequirements( Extension extension, Collection<AddOn> availableAddOns)1525 public AddOnRunRequirements calculateExtensionInstallRequirements( 1526 Extension extension, Collection<AddOn> availableAddOns) { 1527 return calculateExtensionRunRequirementsImpl( 1528 extension.getClass().getCanonicalName(), availableAddOns, false); 1529 } 1530 1531 /** 1532 * Calculates the requirements to run the extension with the given {@code classname}, in the 1533 * current ZAP and Java versions and with the given {@code availableAddOns}. 1534 * 1535 * <p>If the extension depends on other add-ons, those add-ons are checked if are also runnable. 1536 * 1537 * <p><strong>Note:</strong> All the given {@code availableAddOns} are expected to be loadable 1538 * in the currently running ZAP version, that is, the method {@code 1539 * AddOn.canLoadInCurrentVersion()}, returns {@code true}. 1540 * 1541 * @param classname the classname of extension that will be checked 1542 * @param availableAddOns the add-ons available 1543 * @return the requirements to run the extension, and if not runnable the reason why it's not. 1544 * @since 2.4.0 1545 * @see AddOnRunRequirements#getExtensionRequirements() 1546 */ calculateExtensionRunRequirements( String classname, Collection<AddOn> availableAddOns)1547 public AddOnRunRequirements calculateExtensionRunRequirements( 1548 String classname, Collection<AddOn> availableAddOns) { 1549 return calculateExtensionRunRequirementsImpl(classname, availableAddOns, true); 1550 } 1551 1552 /** 1553 * Calculates the requirements to install/run the given {@code extension}, same as {@link 1554 * #calculateExtensionRunRequirements(String, Collection)} but does not require the libraries of 1555 * the add-ons (this or dependencies) to exist in the file system. 1556 * 1557 * @param classname the classname of extension that will be checked. 1558 * @param availableAddOns the add-ons available. 1559 * @return the requirements to install/run the extension, and if not runnable the reason why 1560 * it's not. 1561 * @since 2.9.0 1562 * @see AddOnRunRequirements#getExtensionRequirements() 1563 */ calculateExtensionInstallRequirements( String classname, Collection<AddOn> availableAddOns)1564 public AddOnRunRequirements calculateExtensionInstallRequirements( 1565 String classname, Collection<AddOn> availableAddOns) { 1566 return calculateExtensionRunRequirementsImpl(classname, availableAddOns, false); 1567 } 1568 calculateExtensionRunRequirementsImpl( String classname, Collection<AddOn> availableAddOns, boolean checkLibs)1569 private AddOnRunRequirements calculateExtensionRunRequirementsImpl( 1570 String classname, Collection<AddOn> availableAddOns, boolean checkLibs) { 1571 AddOnRunRequirements requirements = new AddOnRunRequirements(this); 1572 for (ExtensionWithDeps extensionWithDeps : extensionsWithDeps) { 1573 if (extensionWithDeps.getClassname().equals(classname)) { 1574 calculateExtensionRunRequirements( 1575 extensionWithDeps, availableAddOns, requirements, this, checkLibs); 1576 break; 1577 } 1578 } 1579 return requirements; 1580 } 1581 1582 /** 1583 * Tells whether or not the given {@code extension} has a (direct) dependency on the given 1584 * {@code addOn} (including version). 1585 * 1586 * @param extension the extension that will be checked 1587 * @param addOn the add-on that will be checked in the dependencies on the extension 1588 * @return {@code true} if the extension depends on the given add-on, {@code false} otherwise. 1589 * @since 2.4.0 1590 */ dependsOn(Extension extension, AddOn addOn)1591 public boolean dependsOn(Extension extension, AddOn addOn) { 1592 String classname = extension.getClass().getCanonicalName(); 1593 1594 for (ExtensionWithDeps extensionWithDeps : extensionsWithDeps) { 1595 if (extensionWithDeps.getClassname().equals(classname)) { 1596 return dependsOn(extensionWithDeps.getDependencies(), addOn); 1597 } 1598 } 1599 return false; 1600 } 1601 dependsOn(List<AddOnDep> dependencies, AddOn addOn)1602 private static boolean dependsOn(List<AddOnDep> dependencies, AddOn addOn) { 1603 for (AddOnDep dependency : dependencies) { 1604 if (dependency.getId().equals(addOn.id)) { 1605 if (!dependency.getVersion().isEmpty()) { 1606 return versionMatches(addOn, dependency); 1607 } 1608 return true; 1609 } 1610 } 1611 return false; 1612 } 1613 1614 /** 1615 * Tells whether or not the given add-on version matches the one required by the dependency. 1616 * 1617 * <p>This methods is required to also check the {@code semVer} of the add-on, once removed it 1618 * can match the version directly. 1619 * 1620 * @param addOn the add-on to check 1621 * @param dependency the dependency 1622 * @return {@code true} if the version matches, {@code false} otherwise. 1623 */ versionMatches(AddOn addOn, AddOnDep dependency)1624 private static boolean versionMatches(AddOn addOn, AddOnDep dependency) { 1625 if (addOn.version.matches(dependency.getVersion())) { 1626 return true; 1627 } 1628 1629 if (addOn.semVer != null && addOn.semVer.matches(dependency.getVersion())) { 1630 return true; 1631 } 1632 1633 return false; 1634 } 1635 1636 /** 1637 * Tells whether or not the extension with the given {@code classname} is loaded. 1638 * 1639 * @param classname the classname of the extension 1640 * @return {@code true} if the extension is loaded, {@code false} otherwise 1641 * @since 2.4.0 1642 */ isExtensionLoaded(String classname)1643 public boolean isExtensionLoaded(String classname) { 1644 for (Extension extension : getLoadedExtensions()) { 1645 if (classname.equals(extension.getClass().getCanonicalName())) { 1646 return true; 1647 } 1648 } 1649 return false; 1650 } 1651 1652 /** 1653 * Returns the minimum Java version required to run this add-on or an empty {@code String} if 1654 * there's no minimum version. 1655 * 1656 * @return the minimum Java version required to run this add-on or an empty {@code String} if no 1657 * minimum version 1658 * @since 2.4.0 1659 */ getMinimumJavaVersion()1660 public String getMinimumJavaVersion() { 1661 if (dependencies == null) { 1662 return ""; 1663 } 1664 return dependencies.getJavaVersion(); 1665 } 1666 1667 /** 1668 * Gets the add-on with the given {@code id} from the given collection of {@code addOns}. 1669 * 1670 * @param addOns the collection of add-ons where the search will be made 1671 * @param id the id of the add-on to search for 1672 * @return the {@code AddOn} with the given id, or {@code null} if not found 1673 */ getAddOn(Collection<AddOn> addOns, String id)1674 private static AddOn getAddOn(Collection<AddOn> addOns, String id) { 1675 for (AddOn addOn : addOns) { 1676 if (addOn.getId().equals(id)) { 1677 return addOn; 1678 } 1679 } 1680 return null; 1681 } 1682 1683 /** 1684 * Tells whether or not this add-on can be loaded in the given {@code zapVersion}. 1685 * 1686 * @param zapVersion the ZAP version that will be checked 1687 * @return {@code true} if the add-on can be loaded in the given {@code zapVersion}, {@code 1688 * false} otherwise 1689 */ canLoadInVersion(String zapVersion)1690 public boolean canLoadInVersion(String zapVersion) { 1691 // Require add-ons to declare the version they implement 1692 if (this.notBeforeVersion == null || this.notBeforeVersion.isEmpty()) { 1693 return false; 1694 } 1695 1696 ZapReleaseComparitor zrc = new ZapReleaseComparitor(); 1697 ZapRelease zr = new ZapRelease(zapVersion); 1698 ZapRelease notBeforeRelease = new ZapRelease(this.notBeforeVersion); 1699 if (zrc.compare(zr, notBeforeRelease) < 0) { 1700 return false; 1701 } 1702 1703 if (zrc.compare(notBeforeRelease, v2_4) < 0) { 1704 // Don't load any add-ons that imply they are prior to 2.4.0 - they probably wont work 1705 return false; 1706 } 1707 if (this.notFromVersion != null && this.notFromVersion.length() > 0) { 1708 ZapRelease notFromRelease = new ZapRelease(this.notFromVersion); 1709 return (zrc.compare(zr, notFromRelease) < 0); 1710 } 1711 return true; 1712 } 1713 setNotBeforeVersion(String notBeforeVersion)1714 public void setNotBeforeVersion(String notBeforeVersion) { 1715 this.notBeforeVersion = notBeforeVersion; 1716 } 1717 setNotFromVersion(String notFromVersion)1718 public void setNotFromVersion(String notFromVersion) { 1719 this.notFromVersion = notFromVersion; 1720 } 1721 getNotBeforeVersion()1722 public String getNotBeforeVersion() { 1723 return notBeforeVersion; 1724 } 1725 getNotFromVersion()1726 public String getNotFromVersion() { 1727 return notFromVersion; 1728 } 1729 getInfo()1730 public URL getInfo() { 1731 return info; 1732 } 1733 setInfo(URL info)1734 public void setInfo(URL info) { 1735 this.info = info; 1736 } 1737 1738 /** 1739 * Gets the URL to the browsable repo. 1740 * 1741 * @return the URL to the repo, might be {@code null}. 1742 * @since 2.9.0 1743 */ getRepo()1744 public URL getRepo() { 1745 return repo; 1746 } 1747 getHash()1748 public String getHash() { 1749 return hash; 1750 } 1751 1752 /** 1753 * Gets the date when the add-on was released. 1754 * 1755 * <p>The date has the format {@code YYYY-MM-DD}. 1756 * 1757 * <p><strong>Note:</strong> The date is only available for add-ons created from the 1758 * marketplace. 1759 * 1760 * @return the release date, or {@code null} if not available. 1761 * @since 2.10.0 1762 */ getReleaseDate()1763 public String getReleaseDate() { 1764 return releaseDate; 1765 } 1766 1767 /** 1768 * Gets the data of the bundle declared in the manifest. 1769 * 1770 * @return the bundle data, never {@code null}. 1771 * @since 2.8.0 1772 * @see #getResourceBundle() 1773 */ getBundleData()1774 public BundleData getBundleData() { 1775 return bundleData; 1776 } 1777 1778 /** 1779 * Gets the resource bundle of the add-on. 1780 * 1781 * @return the resource bundle, or {@code null} if none. 1782 * @since 2.8.0 1783 */ getResourceBundle()1784 public ResourceBundle getResourceBundle() { 1785 return resourceBundle; 1786 } 1787 1788 /** 1789 * Sets the resource bundle of the add-on. 1790 * 1791 * <p><strong>Note:</strong> This method should be called only by bootstrap classes. 1792 * 1793 * @param resourceBundle the resource bundle of the add-on, might be {@code null}. 1794 * @since 2.8.0 1795 * @see #getBundleData() 1796 */ setResourceBundle(ResourceBundle resourceBundle)1797 public void setResourceBundle(ResourceBundle resourceBundle) { 1798 this.resourceBundle = resourceBundle; 1799 } 1800 1801 /** 1802 * Gets the data for the HelpSet, declared in the manifest. 1803 * 1804 * @return the HelpSet data, never {@code null}. 1805 * @since 2.8.0 1806 */ getHelpSetData()1807 public HelpSetData getHelpSetData() { 1808 return helpSetData; 1809 } 1810 1811 /** 1812 * Returns the IDs of the add-ons dependencies, an empty collection if none. 1813 * 1814 * @return the IDs of the dependencies. 1815 * @since 2.4.0 1816 */ getIdsAddOnDependencies()1817 public List<String> getIdsAddOnDependencies() { 1818 if (dependencies == null) { 1819 return Collections.emptyList(); 1820 } 1821 1822 List<String> ids = new ArrayList<>(dependencies.getAddOns().size()); 1823 for (AddOnDep dep : dependencies.getAddOns()) { 1824 ids.add(dep.getId()); 1825 } 1826 return ids; 1827 } 1828 1829 /** 1830 * Tells whether or not this add-on has a (direct) dependency on the given {@code addOn} 1831 * (including version). 1832 * 1833 * @param addOn the add-on that will be checked 1834 * @return {@code true} if it depends on the given add-on, {@code false} otherwise. 1835 * @since 2.4.0 1836 */ dependsOn(AddOn addOn)1837 public boolean dependsOn(AddOn addOn) { 1838 if (dependencies == null || dependencies.getAddOns().isEmpty()) { 1839 return false; 1840 } 1841 1842 return dependsOn(dependencies.getAddOns(), addOn); 1843 } 1844 1845 /** 1846 * Tells whether or not this add-on has a (direct) dependency on any of the given {@code addOns} 1847 * (including version). 1848 * 1849 * @param addOns the add-ons that will be checked 1850 * @return {@code true} if it depends on any of the given add-ons, {@code false} otherwise. 1851 * @since 2.4.0 1852 */ dependsOn(Collection<AddOn> addOns)1853 public boolean dependsOn(Collection<AddOn> addOns) { 1854 if (dependencies == null || dependencies.getAddOns().isEmpty()) { 1855 return false; 1856 } 1857 1858 for (AddOn addOn : addOns) { 1859 if (dependsOn(addOn)) { 1860 return true; 1861 } 1862 } 1863 return false; 1864 } 1865 1866 @Override toString()1867 public String toString() { 1868 StringBuilder strBuilder = new StringBuilder(); 1869 strBuilder.append("[id=").append(id); 1870 strBuilder.append(", version=").append(version); 1871 strBuilder.append(']'); 1872 1873 return strBuilder.toString(); 1874 } 1875 1876 @Override hashCode()1877 public int hashCode() { 1878 final int prime = 31; 1879 int result = 1; 1880 result = prime * result + ((id == null) ? 0 : id.hashCode()); 1881 result = prime * result + version.hashCode(); 1882 return result; 1883 } 1884 1885 /** Two add-ons are considered equal if both add-ons have the same ID and version. */ 1886 @Override equals(Object obj)1887 public boolean equals(Object obj) { 1888 if (this == obj) { 1889 return true; 1890 } 1891 if (obj == null) { 1892 return false; 1893 } 1894 if (getClass() != obj.getClass()) { 1895 return false; 1896 } 1897 AddOn other = (AddOn) obj; 1898 if (id == null) { 1899 if (other.id != null) { 1900 return false; 1901 } 1902 } else if (!id.equals(other.id)) { 1903 return false; 1904 } 1905 return version.equals(other.version); 1906 } 1907 1908 /** 1909 * A library (JAR) bundled in the add-on. 1910 * 1911 * <p>Bundled libraries are copied and loaded from the file system, which allows to properly 1912 * maintain/access all JAR's data (e.g. module info, manifest, services). 1913 */ 1914 static class Lib { 1915 private final String jarPath; 1916 private final String name; 1917 private URL fileSystemUrl; 1918 Lib(String path)1919 Lib(String path) { 1920 jarPath = path; 1921 name = extractName(path); 1922 } 1923 getJarPath()1924 String getJarPath() { 1925 return jarPath; 1926 } 1927 getName()1928 String getName() { 1929 return name; 1930 } 1931 getFileSystemUrl()1932 URL getFileSystemUrl() { 1933 return fileSystemUrl; 1934 } 1935 setFileSystemUrl(URL fileSystemUrl)1936 void setFileSystemUrl(URL fileSystemUrl) { 1937 this.fileSystemUrl = fileSystemUrl; 1938 } 1939 extractName(String path)1940 private static String extractName(String path) { 1941 int idx = path.lastIndexOf('/'); 1942 if (idx == -1) { 1943 return path; 1944 } 1945 return path.substring(idx + 1); 1946 } 1947 1948 @Override hashCode()1949 public int hashCode() { 1950 return Objects.hash(jarPath); 1951 } 1952 1953 @Override equals(Object obj)1954 public boolean equals(Object obj) { 1955 if (this == obj) { 1956 return true; 1957 } 1958 if (obj == null) { 1959 return false; 1960 } 1961 if (getClass() != obj.getClass()) { 1962 return false; 1963 } 1964 return Objects.equals(jarPath, ((Lib) obj).jarPath); 1965 } 1966 } 1967 1968 public abstract static class BaseRunRequirements { 1969 1970 /** 1971 * The reason why an add-on can not be run because of a dependency. 1972 * 1973 * <p>More details of the issue can be obtained with the method {@code 1974 * RunRequirements.getDependencyIssueDetails()}. The exact contents are mentioned in each 1975 * {@code DependencyIssue} constant. 1976 * 1977 * @see AddOnRunRequirements#getDependencyIssueDetails() 1978 */ 1979 public enum DependencyIssue { 1980 1981 /** 1982 * A cyclic dependency was detected. 1983 * 1984 * <p>Issue details contain all the add-ons in the cyclic chain. 1985 */ 1986 CYCLIC, 1987 1988 /** 1989 * Older version of the add-on is still installed. 1990 * 1991 * <p>Issue details contain the old version. 1992 */ 1993 OLDER_VERSION, 1994 1995 /** 1996 * A dependency was not found. 1997 * 1998 * <p>Issue details contain the id of the add-on. 1999 */ 2000 MISSING, 2001 2002 /** 2003 * The dependency found has a older version than the version required. 2004 * 2005 * <p>Issue details contain the instance of the {@code AddOn} and the required version. 2006 * 2007 * @deprecated (2.7.0) No longer in use. It should be used just {@link #VERSION}. 2008 */ 2009 @Deprecated 2010 PACKAGE_VERSION_NOT_BEFORE, 2011 2012 /** 2013 * The dependency found has a newer version than the version required. 2014 * 2015 * <p>Issue details contain the instance of the {@code AddOn} and the required version. 2016 * 2017 * @deprecated (2.7.0) No longer in use. It should be used just {@link #VERSION}. 2018 */ 2019 @Deprecated 2020 PACKAGE_VERSION_NOT_FROM, 2021 2022 /** 2023 * The dependency found has a different version. 2024 * 2025 * <p>Issue details contain the instance of the {@code AddOn} and the required version. 2026 */ 2027 VERSION 2028 } 2029 2030 private final AddOn addOn; 2031 private final DirectedGraph<AddOn, DefaultEdge> dependencyTree; 2032 private Set<AddOn> dependencies; 2033 2034 private DependencyIssue depIssue; 2035 private List<Object> issueDetails; 2036 2037 private String minimumJavaVersion; 2038 private AddOn addOnMinimumJavaVersion; 2039 private AddOn addOnMissingLibs; 2040 2041 private boolean runnable; 2042 BaseRunRequirements(AddOn addOn)2043 private BaseRunRequirements(AddOn addOn) { 2044 this.addOn = addOn; 2045 dependencyTree = new DefaultDirectedGraph<>(DefaultEdge.class); 2046 dependencyTree.addVertex(addOn); 2047 runnable = true; 2048 issueDetails = Collections.emptyList(); 2049 } 2050 2051 /** 2052 * Gets the add-on that was tested to check if it can be run. 2053 * 2054 * @return the tested add-on 2055 */ getAddOn()2056 public AddOn getAddOn() { 2057 return addOn; 2058 } 2059 2060 /** 2061 * Tells whether or not this add-on has a dependency issue. 2062 * 2063 * @return {@code true} if the add-on has a dependency issue, {@code false} otherwise 2064 * @see #getDependencyIssue() 2065 * @see #getDependencyIssueDetails() 2066 * @see DependencyIssue 2067 */ hasDependencyIssue()2068 public boolean hasDependencyIssue() { 2069 return (depIssue != null); 2070 } 2071 2072 /** 2073 * Gets the dependency issue, if any. 2074 * 2075 * @return the {@code DependencyIssue} or {@code null}, if none 2076 * @see #hasDependencyIssue() 2077 * @see #getDependencyIssueDetails() 2078 * @see DependencyIssue 2079 */ getDependencyIssue()2080 public DependencyIssue getDependencyIssue() { 2081 return depIssue; 2082 } 2083 2084 /** 2085 * Gets the details of the dependency issue, if any. 2086 * 2087 * @return a list containing the details of the issue or an empty list if none 2088 * @see #hasDependencyIssue() 2089 * @see #getDependencyIssue() 2090 * @see DependencyIssue 2091 */ getDependencyIssueDetails()2092 public List<Object> getDependencyIssueDetails() { 2093 return issueDetails; 2094 } 2095 2096 /** 2097 * Tells whether or not this add-on can be run. 2098 * 2099 * @return {@code true} if the add-on can be run, {@code false} otherwise 2100 */ isRunnable()2101 public boolean isRunnable() { 2102 return runnable; 2103 } 2104 setRunnable(boolean runnable)2105 protected void setRunnable(boolean runnable) { 2106 this.runnable = runnable; 2107 } 2108 2109 /** 2110 * Gets the (found) dependencies of the add-on, including transitive dependencies. 2111 * 2112 * @return a set containing the dependencies of the add-on 2113 * @see AddOn#getIdsAddOnDependencies() 2114 */ getDependencies()2115 public Set<AddOn> getDependencies() { 2116 if (dependencies == null) { 2117 dependencies = new HashSet<>(); 2118 for (TopologicalOrderIterator<AddOn, DefaultEdge> it = 2119 new TopologicalOrderIterator<>(dependencyTree); 2120 it.hasNext(); ) { 2121 dependencies.add(it.next()); 2122 } 2123 dependencies.remove(addOn); 2124 } 2125 return Collections.unmodifiableSet(dependencies); 2126 } 2127 setIssue(DependencyIssue issue, Object... details)2128 protected void setIssue(DependencyIssue issue, Object... details) { 2129 runnable = false; 2130 this.depIssue = issue; 2131 if (details != null) { 2132 issueDetails = Arrays.asList(details); 2133 } else { 2134 issueDetails = Collections.emptyList(); 2135 } 2136 } 2137 addDependency(AddOn parent, AddOn addOn)2138 protected boolean addDependency(AddOn parent, AddOn addOn) { 2139 if (parent == null) { 2140 return true; 2141 } 2142 2143 dependencyTree.addVertex(parent); 2144 dependencyTree.addVertex(addOn); 2145 2146 dependencyTree.addEdge(parent, addOn); 2147 2148 CycleDetector<AddOn, DefaultEdge> cycleDetector = new CycleDetector<>(dependencyTree); 2149 boolean cycle = cycleDetector.detectCycles(); 2150 if (cycle) { 2151 dependencies = cycleDetector.findCycles(); 2152 2153 return false; 2154 } 2155 return true; 2156 } 2157 2158 /** 2159 * Tells whether or not this add-on requires a newer Java version to run. 2160 * 2161 * <p>The requirement might be imposed by a dependency or the add-on itself. To check which 2162 * one use the methods {@code getAddOn()} and {@code getAddOnMinimumJavaVersion()}. 2163 * 2164 * @return {@code true} if the add-on requires a newer Java version, {@code false} 2165 * otherwise. 2166 * @see #getAddOn() 2167 * @see #getAddOnMinimumJavaVersion() 2168 * @see #getMinimumJavaVersion() 2169 */ isNewerJavaVersionRequired()2170 public boolean isNewerJavaVersionRequired() { 2171 return (minimumJavaVersion != null); 2172 } 2173 2174 /** 2175 * Gets the minimum Java version required to run the add-on. 2176 * 2177 * @return the Java version, or {@code null} if no minimum. 2178 * @see #isNewerJavaVersionRequired() 2179 * @see #getAddOn() 2180 * @see #getAddOnMinimumJavaVersion() 2181 */ getMinimumJavaVersion()2182 public String getMinimumJavaVersion() { 2183 return minimumJavaVersion; 2184 } 2185 2186 /** 2187 * Gets the add-on that requires the minimum Java version. 2188 * 2189 * @return the add-on, or {@code null} if no minimum. 2190 * @see #isNewerJavaVersionRequired() 2191 * @see #getMinimumJavaVersion() 2192 * @see #getAddOn() 2193 */ getAddOnMinimumJavaVersion()2194 public AddOn getAddOnMinimumJavaVersion() { 2195 return addOnMinimumJavaVersion; 2196 } 2197 setMinimumJavaVersionIssue(AddOn srcAddOn, String requiredVersion)2198 protected void setMinimumJavaVersionIssue(AddOn srcAddOn, String requiredVersion) { 2199 setRunnable(false); 2200 2201 if (minimumJavaVersion == null) { 2202 setMinimumJavaVersion(srcAddOn, requiredVersion); 2203 } else if (getJavaVersion(requiredVersion) > getJavaVersion(minimumJavaVersion)) { 2204 setMinimumJavaVersion(srcAddOn, requiredVersion); 2205 } 2206 } 2207 setMinimumJavaVersion(AddOn srcAddOn, String requiredVersion)2208 private void setMinimumJavaVersion(AddOn srcAddOn, String requiredVersion) { 2209 addOnMinimumJavaVersion = srcAddOn; 2210 minimumJavaVersion = requiredVersion; 2211 } 2212 2213 /** 2214 * Tells whether or not this add-on has missing libraries (that is, not present in the file 2215 * system). 2216 * 2217 * <p>The issue might be caused by a dependency or the add-on itself. To check which one use 2218 * the methods {@link #getAddOn()} and {@link #getAddOnMissingLibs()}. 2219 * 2220 * @return {@code true} if the add-on has missing libraries, {@code false} otherwise. 2221 * @since 2.9.0 2222 */ hasMissingLibs()2223 public boolean hasMissingLibs() { 2224 return addOnMissingLibs != null; 2225 } 2226 2227 /** 2228 * Gets the add-on that has missing libraries. 2229 * 2230 * @return the add-on, or {@code null} if none. 2231 * @see #hasMissingLibs() 2232 * @see #getAddOn() 2233 * @since 2.9.0 2234 */ getAddOnMissingLibs()2235 public AddOn getAddOnMissingLibs() { 2236 return addOnMissingLibs; 2237 } 2238 setMissingLibsIssue(AddOn srcAddOn)2239 protected void setMissingLibsIssue(AddOn srcAddOn) { 2240 setRunnable(false); 2241 addOnMissingLibs = srcAddOn; 2242 } 2243 } 2244 2245 /** 2246 * The requirements to run an {@code AddOn}. 2247 * 2248 * <p>It can be used to check if an add-on can or not be run, which requirements it has (for 2249 * example, minimum Java version or dependency add-ons) and which issues prevent it from being 2250 * run, if any. 2251 * 2252 * @since 2.4.0 2253 */ 2254 public static class AddOnRunRequirements extends BaseRunRequirements { 2255 2256 private List<ExtensionRunRequirements> addExtensionsRequirements; 2257 AddOnRunRequirements(AddOn addOn)2258 private AddOnRunRequirements(AddOn addOn) { 2259 super(addOn); 2260 } 2261 2262 /** 2263 * Gets the run requirements of the extensions that have dependencies. 2264 * 2265 * @return a {@code List} containing the requirements of each extension that have 2266 * dependencies 2267 * @see #hasExtensionsWithRunningIssues() 2268 */ getExtensionRequirements()2269 public List<ExtensionRunRequirements> getExtensionRequirements() { 2270 if (addExtensionsRequirements == null) { 2271 addExtensionsRequirements = Collections.emptyList(); 2272 } 2273 return addExtensionsRequirements; 2274 } 2275 2276 /** 2277 * Tells whether or not there's at least one extension with running issues. 2278 * 2279 * @return {@code true} if at least one extension has running issues, {@code false} 2280 * otherwise. 2281 * @see #getExtensionRequirements() 2282 */ hasExtensionsWithRunningIssues()2283 public boolean hasExtensionsWithRunningIssues() { 2284 for (ExtensionRunRequirements reqs : getExtensionRequirements()) { 2285 if (!reqs.isRunnable()) { 2286 return true; 2287 } 2288 } 2289 return false; 2290 } 2291 addExtensionRequirements(ExtensionRunRequirements extension)2292 protected void addExtensionRequirements(ExtensionRunRequirements extension) { 2293 if (addExtensionsRequirements == null) { 2294 addExtensionsRequirements = new ArrayList<>(5); 2295 } 2296 addExtensionsRequirements.add(extension); 2297 } 2298 } 2299 2300 /** 2301 * The requirements to run an {@code extension} (with add-on dependencies). 2302 * 2303 * <p>It can be used to check if an extension can or not be run, which requirements it has (for 2304 * example, dependency add-ons) and which issues prevent it from being run, if any. 2305 * 2306 * @since 2.4.0 2307 */ 2308 public static class ExtensionRunRequirements extends BaseRunRequirements { 2309 2310 private final String classname; 2311 ExtensionRunRequirements(AddOn addOn, String classname)2312 private ExtensionRunRequirements(AddOn addOn, String classname) { 2313 super(addOn); 2314 this.classname = classname; 2315 } 2316 2317 /** 2318 * Gets the classname of the extension. 2319 * 2320 * @return the classname of the extension 2321 */ getClassname()2322 public String getClassname() { 2323 return classname; 2324 } 2325 } 2326 2327 /** 2328 * The data of the bundle declared in the manifest of an add-on. 2329 * 2330 * <p>Used to load a {@link ResourceBundle}. 2331 * 2332 * @since 2.8.0 2333 */ 2334 public static final class BundleData { 2335 2336 private static final BundleData EMPTY_BUNDLE = new BundleData(); 2337 2338 private final String baseName; 2339 private final String prefix; 2340 BundleData()2341 private BundleData() { 2342 this("", ""); 2343 } 2344 BundleData(String baseName, String prefix)2345 private BundleData(String baseName, String prefix) { 2346 this.baseName = baseName; 2347 this.prefix = prefix; 2348 } 2349 2350 /** 2351 * Tells whether or not the the bundle data is empty. 2352 * 2353 * <p>An empty {@code BundleData} does not contain any information to load a {@link 2354 * ResourceBundle}. 2355 * 2356 * @return {@code true} if empty, {@code false} otherwise. 2357 */ isEmpty()2358 public boolean isEmpty() { 2359 return this == EMPTY_BUNDLE; 2360 } 2361 2362 /** 2363 * Gets the base name of the bundle. 2364 * 2365 * @return the base name, or empty if not defined. 2366 */ getBaseName()2367 public String getBaseName() { 2368 return baseName; 2369 } 2370 2371 /** 2372 * Gets the prefix of the bundle, to add into a {@link 2373 * org.zaproxy.zap.utils.I18N#addMessageBundle(String, ResourceBundle) I18N}. 2374 * 2375 * @return the prefix, or empty if not defined. 2376 */ getPrefix()2377 public String getPrefix() { 2378 return prefix; 2379 } 2380 } 2381 2382 /** 2383 * The data to load a {@link javax.help.HelpSet HelpSet}, declared in the manifest of an add-on. 2384 * 2385 * <p>Used to dynamically add/remove the help of the add-on. 2386 * 2387 * @since 2.8.0 2388 */ 2389 public static final class HelpSetData { 2390 2391 private static final HelpSetData EMPTY_HELP_SET = new HelpSetData(); 2392 2393 private final String baseName; 2394 private final String localeToken; 2395 HelpSetData()2396 private HelpSetData() { 2397 this("", ""); 2398 } 2399 HelpSetData(String baseName, String localeToken)2400 private HelpSetData(String baseName, String localeToken) { 2401 this.baseName = baseName; 2402 this.localeToken = localeToken; 2403 } 2404 2405 /** 2406 * Tells whether or not the the HelpSet data is empty. 2407 * 2408 * <p>An empty {@code HelpSetData} does not contain any information to load the help. 2409 * 2410 * @return {@code true} if empty, {@code false} otherwise. 2411 */ isEmpty()2412 public boolean isEmpty() { 2413 return this == EMPTY_HELP_SET; 2414 } 2415 2416 /** 2417 * Gets the base name of the HelpSet file. 2418 * 2419 * @return the base name, or empty if not defined. 2420 */ getBaseName()2421 public String getBaseName() { 2422 return baseName; 2423 } 2424 2425 /** 2426 * Gets the locale token. 2427 * 2428 * @return the locale token, or empty if not defined. 2429 */ getLocaleToken()2430 public String getLocaleToken() { 2431 return localeToken; 2432 } 2433 } 2434 2435 /** 2436 * An {@link IOException} that indicates that a file is not a valid add-on. 2437 * 2438 * @since 2.8.0 2439 * @see #getValidationResult() 2440 */ 2441 public static class InvalidAddOnException extends IOException { 2442 2443 private static final long serialVersionUID = 1L; 2444 2445 private final ValidationResult validationResult; 2446 InvalidAddOnException(ValidationResult validationResult)2447 private InvalidAddOnException(ValidationResult validationResult) { 2448 super(getRootCauseMessage(validationResult), validationResult.getException()); 2449 this.validationResult = validationResult; 2450 } 2451 2452 /** 2453 * Gets the result of validating the add-on. 2454 * 2455 * @return the result, never {@code null}. 2456 */ getValidationResult()2457 public ValidationResult getValidationResult() { 2458 return validationResult; 2459 } 2460 } 2461 getRootCauseMessage(ValidationResult validationResult)2462 private static String getRootCauseMessage(ValidationResult validationResult) { 2463 Exception exception = validationResult.getException(); 2464 if (exception == null) { 2465 return validationResult.getValidity().toString(); 2466 } 2467 Throwable root = ExceptionUtils.getRootCause(exception); 2468 return root == null ? exception.getLocalizedMessage() : root.getLocalizedMessage(); 2469 } 2470 getJavaVersion(String javaVersion)2471 private static int getJavaVersion(String javaVersion) { 2472 return toVersionInt(toJavaVersionIntArray(javaVersion, 2)); 2473 } 2474 2475 // NOTE: Following 2 methods copied from org.apache.commons.lang.SystemUtils version 2.6 because 2476 // of constrained visibility toJavaVersionIntArray(String version, int limit)2477 private static int[] toJavaVersionIntArray(String version, int limit) { 2478 if (version == null) { 2479 return ArrayUtils.EMPTY_INT_ARRAY; 2480 } 2481 String[] strings = StringUtils.split(version, "._- "); 2482 int[] ints = new int[Math.min(limit, strings.length)]; 2483 int j = 0; 2484 for (int i = 0; i < strings.length && j < limit; i++) { 2485 String s = strings[i]; 2486 if (s.length() > 0) { 2487 try { 2488 ints[j] = Integer.parseInt(s); 2489 j++; 2490 } catch (Exception e) { 2491 } 2492 } 2493 } 2494 if (ints.length > j) { 2495 int[] newInts = new int[j]; 2496 System.arraycopy(ints, 0, newInts, 0, j); 2497 ints = newInts; 2498 } 2499 return ints; 2500 } 2501 toVersionInt(int[] javaVersions)2502 private static int toVersionInt(int[] javaVersions) { 2503 if (javaVersions == null) { 2504 return 0; 2505 } 2506 int intVersion = 0; 2507 int len = javaVersions.length; 2508 if (len >= 1) { 2509 intVersion = javaVersions[0] * 100; 2510 } 2511 if (len >= 2) { 2512 intVersion += javaVersions[1] * 10; 2513 } 2514 if (len >= 3) { 2515 intVersion += javaVersions[2]; 2516 } 2517 return intVersion; 2518 } 2519 } 2520