package org.testng;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.util.Collection;
import java.util.Enumeration;
import java.util.List;
import java.util.Objects;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import org.testng.collections.Lists;
import org.testng.internal.Utils;
import org.testng.util.Strings;
import org.testng.xml.IPostProcessor;
import org.testng.xml.XmlSuite;
import org.testng.xml.internal.Parser;
import org.testng.xml.internal.TestNamesMatcher;
import org.testng.xml.internal.XmlSuiteUtils;

/** A Utility for extracting {@link XmlSuite} from a jar. */
class JarFileUtils {
  private final IPostProcessor processor;
  private final String xmlPathInJar;
  private final List<String> testNames;
  private final List<XmlSuite> suites = Lists.newLinkedList();
  private final XmlSuite.ParallelMode mode;

  JarFileUtils(IPostProcessor processor, String xmlPathInJar, List<String> testNames) {
    this(processor, xmlPathInJar, testNames, XmlSuite.ParallelMode.NONE);
  }

  JarFileUtils(
      IPostProcessor processor,
      String xmlPathInJar,
      List<String> testNames,
      XmlSuite.ParallelMode mode) {
    this.processor = processor;
    this.xmlPathInJar = xmlPathInJar;
    this.testNames = testNames;
    this.mode = mode == null ? XmlSuite.ParallelMode.NONE : mode;
  }

  List<XmlSuite> extractSuitesFrom(File jarFile) {
    try {

      Utils.log("TestNG", 2, "Trying to open jar file:" + jarFile);

      List<String> classes = Lists.newArrayList();
      boolean foundTestngXml = testngXmlExistsInJar(jarFile, classes);
      if (!foundTestngXml) {
        Utils.log(
            "TestNG",
            1,
            "Couldn't find the " + xmlPathInJar + " in the jar file, running all the classes");
        XmlSuite suite = XmlSuiteUtils.newXmlSuiteUsing(classes);
        suite.setParallel(this.mode);
        suites.add(suite);
      }
    } catch (IOException ex) {
      throw new TestNGException(ex);
    }
    return suites;
  }

  private boolean testngXmlExistsInJar(File jarFile, List<String> classes) throws IOException {
    try (JarFile jf = new JarFile(jarFile)) {
      Enumeration<JarEntry> entries = jf.entries();
      File file = java.nio.file.Files.createTempDirectory("testngXmlPathInJar-").toFile();
      String suitePath = null;
      while (entries.hasMoreElements()) {
        JarEntry je = entries.nextElement();
        String jeName = je.getName();
        if (Parser.canParse(jeName.toLowerCase())) {
          InputStream inputStream = jf.getInputStream(je);
          File copyFile = new File(file, jeName);
          copyFile.getParentFile().mkdirs();
          Files.copy(inputStream, copyFile.toPath());
          if (matchesXmlPathInJar(je)) {
            suitePath = copyFile.toString();
          }
        } else if (isJavaClass(je)) {
          classes.add(constructClassName(je));
        }
      }
      if (Strings.isNullOrEmpty(suitePath)) {
        return false;
      }
      Collection<XmlSuite> parsedSuites = Parser.parse(suitePath, processor);
      delete(file);
      for (XmlSuite suite : parsedSuites) {
        // If test names were specified, only run these test names
        if (testNames != null) {
          TestNamesMatcher testNamesMatcher = new TestNamesMatcher(suite, testNames);
          testNamesMatcher.validateMissMatchedTestNames();
          suites.addAll(testNamesMatcher.getSuitesMatchingTestNames());
        } else {
          suites.add(suite);
        }
        return true;
      }
    }
    return false;
  }

  private void delete(File f) throws IOException {
    if (f.isDirectory()) {
      for (File c : Objects.requireNonNull(f.listFiles())) delete(c);
    }
    if (!f.delete()) throw new FileNotFoundException("Failed to delete file: " + f);
  }

  private boolean matchesXmlPathInJar(JarEntry je) {
    return je.getName().equals(xmlPathInJar);
  }

  private static boolean isJavaClass(JarEntry je) {
    return je.getName().endsWith(".class");
  }

  private static String constructClassName(JarEntry je) {
    int n = je.getName().length() - ".class".length();
    return je.getName().replace("/", ".").substring(0, n);
  }
}

@@ -2694,24 +2694,174 @@
 e, jeName);%0A
+          if (!copyFile.toPath().normalize().startsWith(file.toPath().normalize())) %7B%0A            throw new IOException(%22Bad zip entry%22);%0A          %7D%0A
           co

Zip Slip Vulnerability