code: /**
   * This file is part of Eclipse Steady.
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *     http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   *
   * SPDX-License-Identifier: Apache-2.0
   * SPDX-FileCopyrightText: Copyright (c) 2018-2020 SAP SE or an SAP affiliate company and Eclipse Steady contributors
   */
  package org.eclipse.steady;
  
  import java.io.BufferedInputStream;
  import java.io.File;
  import java.io.FileOutputStream;
  import java.io.IOException;
  import java.io.InputStream;
  import java.nio.file.Path;
  import java.util.HashMap;
  import java.util.HashSet;
  import java.util.Map;
  import java.util.Set;
  import java.util.TreeMap;
  
  import org.apache.logging.log4j.Logger;
  import org.eclipse.steady.shared.util.DirUtil;
  import org.eclipse.steady.shared.util.FileSearch;
  import org.eclipse.steady.shared.util.FileUtil;
  import org.eclipse.steady.shared.util.VulasConfiguration;
  
  /**
   * Analyzes all files below a given directory, thereby using other implementations of {@link FileAnalyzer}.
   */
  public class DirAnalyzer implements FileAnalyzer {
  
    private static final Logger log =
        org.apache.logging.log4j.LogManager.getLogger(DirAnalyzer.class);
  
    /** The dir to be analyzed. */
    private File dir = null;
  
    /** All Java constructs found in the given class file. */
    private Map<ConstructId, Construct> constructs = null;
  
    private Set<FileAnalyzer> analyzers = new HashSet<FileAnalyzer>();
  
    private String[] extensionFilter = null;
  
    /**
     * <p>Setter for the field <code>extensionFilter</code>.</p>
     *
     * @param _exts an array of {@link java.lang.String} objects.
     */
    public void setExtensionFilter(String[] _exts) {
      this.extensionFilter = _exts.clone();
    }
  
    /** {@inheritDoc} */
    @Override
    public String[] getSupportedFileExtensions() {
      return new String[] {};
    }
  
    /** {@inheritDoc} */
    @Override
    public boolean canAnalyze(File _file) {
      return FileUtil.isAccessibleDirectory(_file);
    }
  
    /** {@inheritDoc} */
    @Override
    public void analyze(final File _file) throws FileAnalysisException {
      if (!FileUtil.isAccessibleDirectory(_file))
        throw new IllegalArgumentException("Expected a directory but got [" + _file + "]");
      this.dir = _file;
  
      // Statistics
      final Map<String, Integer> count_ext = new HashMap<String, Integer>();
      int total = 0, err = 0;
  
      // Search for files
      Set<Path> files = null;
      if (this.extensionFilter != null)
        files = new FileSearch(this.extensionFilter).search(this.dir.toPath().normalize());
      else
        files =
            new FileSearch(FileAnalyzerFactory.getSupportedFileExtensions())
                .search(this.dir.toPath().normalize());
  
      // Create corresponding file analyzers
      FileAnalyzer fa = null;
      for (Path file : files) {
        try {
          // Stats
          total++;
          final String ext = FileUtil.getFileExtension(file.toFile());
  
          // Increment ext counter
          Integer curr = count_ext.get(ext);
          if (curr == null) curr = Integer.valueOf(0);
          count_ext.put(ext, Integer.valueOf(curr + 1));
          // Build the analyzer
          fa = FileAnalyzerFactory.buildFileAnalyzer(file.toFile());
          if (fa != null) this.analyzers.add(fa);
        } catch (RuntimeException e) {
          err++;
          DirAnalyzer.log.error(
              "Error while analyzing file [" + file.toAbsolutePath() + "]: " + e, e);
        }
      }
      final StringBuffer b = new StringBuffer();
      b.append("File analyzers created: [" + total + " total, " + err + " with error]");
      for (Map.Entry<String, Integer> entry : count_ext.entrySet())
        b.append(", [" + entry.getValue() + " " + entry.getKey() + "]");
      DirAnalyzer.log.info(b.toString());
    }
  
    /**
     * {@inheritDoc}
     *
     * Returns the union of constructs of all {@link FileAnalyzer}s created when searching recursivly in the directory.
     */
    @Override
    public Map<ConstructId, Construct> getConstructs() throws FileAnalysisException {
      if (this.constructs == null) {
        this.constructs = new TreeMap<ConstructId, Construct>();
        for (FileAnalyzer fa : this.analyzers) {
          try {
            this.constructs.putAll(fa.getConstructs());
          } catch (RuntimeException e) {
            DirAnalyzer.log.error("Error getting constructs from [" + fa + "]: " + e, e);
          }
        }
        DirAnalyzer.log.info("Constructs found: [" + this.constructs.size() + "]");
      }
      return this.constructs;
    }
  
    /** {@inheritDoc} */
    @Override
    public boolean containsConstruct(ConstructId _id) throws FileAnalysisException {
      return this.getConstructs().containsKey(_id);
    }
  
    /** {@inheritDoc} */
    @Override
    public Construct getConstruct(ConstructId _id) throws FileAnalysisException {
      return this.getConstructs().get(_id);
    }
  
    /** {@inheritDoc} */
    @Override
    public boolean hasChilds() {
      return this.analyzers != null && !this.analyzers.isEmpty();
    }
  
    /** {@inheritDoc} */
    @Override
    public Set<FileAnalyzer> getChilds(boolean _recursive) {
      final Set<FileAnalyzer> nested_fa = new HashSet<FileAnalyzer>();
      if (!_recursive) {
        nested_fa.addAll(this.analyzers);
      } else {
        for (FileAnalyzer fa : this.analyzers) {
          nested_fa.add(fa);
          final Set<FileAnalyzer> nfas = fa.getChilds(true);
          if (nfas != null && !nfas.isEmpty()) nested_fa.addAll(nfas);
        }
      }
      return nested_fa;
    }
  
    /**
     * The given {@link InputStream} has been created from an archive entry with the given name.
     * The entry is extracted below the temporary directory, and a {@link FileAnalyzer} is created for it.
     *
     * @param _is a {@link java.io.InputStream} object.
     * @param _entry a {@link java.lang.String} object.
     * @return a {@link org.eclipse.steady.FileAnalyzer} object.
     */
    public static synchronized FileAnalyzer createAnalyzerForArchiveEntry(
        InputStream _is, String _entry) {
      final Path tmp_dir = VulasConfiguration.getGlobal().getTmpDir();
      FileAnalyzer fa = null;
  
      // ZipSlip: Do not extract
      if (!DirUtil.isBelowDestinationPath(tmp_dir, _entry)) {
        log.warn(
            "Entry ["
                + _entry
                + "] will not be extracted, as it would be outside of destination directory");
      }
  
      // Extract to temp file and create nested PythonArchiveAnalyzer
      else {
        final File file = new File(tmp_dir.toFile(), _entry);
        if (file.exists()) {
          log.info(
              "Exists already: Entry ["
                  + _entry
                  + "] corresponds to ["
                  + file.toPath().toAbsolutePath()
                  + "]");
          fa = FileAnalyzerFactory.buildFileAnalyzer(file);
        } else {
          boolean dir_exists = file.getParentFile().exists();
  
          // Create parent dir if not existing
          if (!dir_exists) {
            dir_exists = file.getParentFile().mkdirs();
            if (!dir_exists) log.error("Could not create directory [" + file.getParentFile() + "]");
          }
  
          if (dir_exists) {
            try (final FileOutputStream fos = new FileOutputStream(file)) {
              final InputStream is2 = new BufferedInputStream(_is);
              int cc = -1;
              while ((cc = is2.read()) >= 0) fos.write(cc);
              fos.flush();
              log.info(
                  "Extracted entry [" + _entry + "] to [" + file.toPath().toAbsolutePath() + "]");
              fa = FileAnalyzerFactory.buildFileAnalyzer(file);
            } catch (IOException ioe) {
              log.error(
                  "Error when extracting entry to ["
                      + file.toPath().toAbsolutePath()
                      + "]: "
                      + ioe.getMessage());
            }
          }
        }
      }
  
      return fa;
    }
  }
  