2 * ============LICENSE_START=======================================================
3 * Copyright (C) 2021 Nordix Foundation
4 * ================================================================================
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
9 * http://www.apache.org/licenses/LICENSE-2.0
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
17 * SPDX-License-Identifier: Apache-2.0
18 * ============LICENSE_END=========================================================
21 package org.openecomp.sdc.be.csar.storage;
23 import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
25 import java.io.BufferedOutputStream;
26 import java.io.IOException;
27 import java.nio.file.Files;
28 import java.nio.file.Path;
29 import java.util.List;
31 import java.util.UUID;
32 import java.util.concurrent.atomic.AtomicBoolean;
33 import java.util.concurrent.atomic.AtomicInteger;
34 import java.util.function.Consumer;
35 import java.util.stream.Collectors;
36 import java.util.zip.ZipEntry;
37 import java.util.zip.ZipFile;
38 import java.util.zip.ZipOutputStream;
40 import org.apache.commons.collections4.CollectionUtils;
41 import org.apache.commons.io.FilenameUtils;
42 import org.openecomp.sdc.be.csar.storage.exception.CsarSizeReducerException;
43 import org.slf4j.Logger;
44 import org.slf4j.LoggerFactory;
46 public class MinIoStorageCsarSizeReducer implements PackageSizeReducer {
48 private static final Logger LOGGER = LoggerFactory.getLogger(MinIoStorageCsarSizeReducer.class);
49 private static final Set<String> ALLOWED_SIGNATURE_EXTENSIONS = Set.of("cms");
50 private static final Set<String> ALLOWED_CERTIFICATE_EXTENSIONS = Set.of("cert", "crt");
51 private static final String CSAR_EXTENSION = "csar";
52 private static final String UNEXPECTED_PROBLEM_HAPPENED_WHILE_READING_THE_CSAR = "An unexpected problem happened while reading the CSAR '%s'";
54 private final AtomicBoolean reduced = new AtomicBoolean(false);
56 private final CsarPackageReducerConfiguration configuration;
58 public MinIoStorageCsarSizeReducer(final CsarPackageReducerConfiguration configuration) {
59 this.configuration = configuration;
63 public byte[] reduce(final Path csarPackagePath) {
64 if (hasSignedPackageStructure(csarPackagePath)) {
65 return reduce(csarPackagePath, this::signedZipProcessingConsumer);
67 return reduce(csarPackagePath, this::unsignedZipProcessingConsumer);
71 private byte[] reduce(final Path csarPackagePath, final ZipProcessFunction zipProcessingFunction) {
72 final var reducedCsarPath = Path.of(csarPackagePath + "." + UUID.randomUUID());
74 try (final var zf = new ZipFile(csarPackagePath.toString());
75 final var zos = new ZipOutputStream(new BufferedOutputStream(Files.newOutputStream(reducedCsarPath)))) {
76 zf.entries().asIterator().forEachRemaining(zipProcessingFunction.getProcessZipConsumer(csarPackagePath, zf, zos));
77 } catch (final IOException ex1) {
78 rollback(reducedCsarPath);
79 final var errorMsg = String.format(UNEXPECTED_PROBLEM_HAPPENED_WHILE_READING_THE_CSAR, csarPackagePath);
80 throw new CsarSizeReducerException(errorMsg, ex1);
82 final byte[] reducedCsarBytes;
85 reducedCsarBytes = Files.readAllBytes(reducedCsarPath);
87 reducedCsarBytes = Files.readAllBytes(csarPackagePath);
89 } catch (final IOException e) {
90 final var errorMsg = String.format("Could not read bytes of file '%s'", csarPackagePath);
91 throw new CsarSizeReducerException(errorMsg, e);
94 Files.delete(reducedCsarPath);
95 } catch (final IOException e) {
96 final var errorMsg = String.format("Could not delete temporary file '%s'", reducedCsarPath);
97 throw new CsarSizeReducerException(errorMsg, e);
100 return reducedCsarBytes;
103 private Consumer<ZipEntry> signedZipProcessingConsumer(final Path csarPackagePath, final ZipFile zf, final ZipOutputStream zos) {
104 final var thresholdEntries = configuration.getThresholdEntries();
105 final var totalEntryArchive = new AtomicInteger(0);
107 final var entryName = zipEntry.getName();
109 if (totalEntryArchive.getAndIncrement() > thresholdEntries) {
110 // too much entries in this archive, can lead to inodes exhaustion of the system
111 final var errorMsg = String.format("Failed to extract '%s' from zip '%s'", entryName, csarPackagePath);
112 throw new CsarSizeReducerException(errorMsg);
114 zos.putNextEntry(new ZipEntry(entryName));
115 if (!zipEntry.isDirectory()) {
116 if (entryName.toLowerCase().endsWith(CSAR_EXTENSION)) {
117 final var internalCsarExtractPath = Path.of(csarPackagePath + "." + UUID.randomUUID());
118 Files.copy(zf.getInputStream(zipEntry), internalCsarExtractPath, REPLACE_EXISTING);
119 zos.write(reduce(internalCsarExtractPath, this::unsignedZipProcessingConsumer));
120 Files.delete(internalCsarExtractPath);
122 zos.write(zf.getInputStream(zipEntry).readAllBytes());
126 } catch (final IOException ei) {
127 final var errorMsg = String.format("Failed to extract '%s' from zip '%s'", entryName, csarPackagePath);
128 throw new CsarSizeReducerException(errorMsg, ei);
133 private Consumer<ZipEntry> unsignedZipProcessingConsumer(final Path csarPackagePath, final ZipFile zf, final ZipOutputStream zos) {
134 final var thresholdEntries = configuration.getThresholdEntries();
135 final var totalEntryArchive = new AtomicInteger(0);
137 final var entryName = zipEntry.getName();
138 if (totalEntryArchive.getAndIncrement() > thresholdEntries) {
139 // too much entries in this archive, can lead to inodes exhaustion of the system
140 final var errorMsg = String.format("Failed to extract '%s' from zip '%s'", entryName, csarPackagePath);
141 throw new CsarSizeReducerException(errorMsg);
144 zos.putNextEntry(new ZipEntry(entryName));
145 if (!zipEntry.isDirectory()) {
146 if (isCandidateToRemove(zipEntry)) {
147 // replace with EMPTY string to avoid package description inconsistency/validation errors
148 zos.write("".getBytes());
151 zos.write(zf.getInputStream(zipEntry).readAllBytes());
155 } catch (final IOException ei) {
156 final var errorMsg = String.format("Failed to extract '%s' from zip '%s'", entryName, csarPackagePath);
157 throw new CsarSizeReducerException(errorMsg, ei);
162 private void rollback(final Path reducedCsarPath) {
163 if (Files.exists(reducedCsarPath)) {
165 Files.delete(reducedCsarPath);
166 } catch (final Exception ex2) {
167 LOGGER.warn("Could not delete temporary file '{}'", reducedCsarPath, ex2);
172 private boolean isCandidateToRemove(final ZipEntry zipEntry) {
173 final String zipEntryName = zipEntry.getName();
174 return configuration.getFoldersToStrip().stream().anyMatch(Path.of(zipEntryName)::startsWith)
175 || zipEntry.getSize() > configuration.getSizeLimit();
178 private boolean hasSignedPackageStructure(final Path csarPackagePath) {
179 final List<Path> packagePathList;
180 try (final var zf = new ZipFile(csarPackagePath.toString())) {
181 packagePathList = zf.stream()
182 .filter(zipEntry -> !zipEntry.isDirectory())
183 .map(ZipEntry::getName).map(Path::of)
184 .collect(Collectors.toList());
185 } catch (final IOException e) {
186 final var errorMsg = String.format(UNEXPECTED_PROBLEM_HAPPENED_WHILE_READING_THE_CSAR, csarPackagePath);
187 throw new CsarSizeReducerException(errorMsg, e);
190 if (CollectionUtils.isEmpty(packagePathList)) {
193 final int numberOfFiles = packagePathList.size();
194 if (numberOfFiles == 2) {
195 return hasOneInternalPackageFile(packagePathList) && hasOneSignatureFile(packagePathList);
197 if (numberOfFiles == 3) {
198 return hasOneInternalPackageFile(packagePathList) && hasOneSignatureFile(packagePathList) && hasOneCertificateFile(packagePathList);
203 private boolean hasOneInternalPackageFile(final List<Path> packagePathList) {
204 return packagePathList.parallelStream()
206 .map(FilenameUtils::getExtension)
207 .map(String::toLowerCase)
208 .filter(extension -> extension.endsWith(CSAR_EXTENSION)).count() == 1;
211 private boolean hasOneSignatureFile(final List<Path> packagePathList) {
212 return packagePathList.parallelStream()
214 .map(FilenameUtils::getExtension)
215 .map(String::toLowerCase)
216 .filter(ALLOWED_SIGNATURE_EXTENSIONS::contains).count() == 1;
219 private boolean hasOneCertificateFile(final List<Path> packagePathList) {
220 return packagePathList.parallelStream()
222 .map(FilenameUtils::getExtension)
223 .map(String::toLowerCase)
224 .filter(ALLOWED_CERTIFICATE_EXTENSIONS::contains).count() == 1;
228 private interface ZipProcessFunction {
230 Consumer<ZipEntry> getProcessZipConsumer(Path csarPackagePath, ZipFile zf, ZipOutputStream zos);