| /** |
| ******************************************************************************** |
| * Copyright (c) 2021-2022 Robert Bosch GmbH and others. |
| * |
| * This program and the accompanying materials are made |
| * available under the terms of the Eclipse Public License 2.0 |
| * which is available at https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * Robert Bosch GmbH - initial API and implementation |
| ******************************************************************************** |
| */ |
| |
| package org.eclipse.app4mc.amalthea.model.editor.handler; |
| |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.nio.file.Files; |
| import java.util.zip.ZipEntry; |
| import java.util.zip.ZipInputStream; |
| |
| import javax.inject.Named; |
| |
| import org.eclipse.app4mc.amalthea.model.editor.container.AmaltheaModelContainer; |
| import org.eclipse.app4mc.amalthea.model.editor.util.AmaltheaEditorUtil; |
| import org.eclipse.app4mc.amalthea.model.emf.AmaltheaResource; |
| import org.eclipse.app4mc.amalthea.model.io.AmaltheaFileHelper; |
| import org.eclipse.core.resources.IFile; |
| import org.eclipse.core.resources.IResource; |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.Platform; |
| import org.eclipse.e4.core.di.annotations.Execute; |
| import org.eclipse.e4.ui.services.IServiceConstants; |
| import org.eclipse.emf.ecore.resource.Resource; |
| import org.eclipse.emf.ecore.resource.ResourceSet; |
| import org.eclipse.jface.dialogs.MessageDialog; |
| import org.eclipse.jface.viewers.IStructuredSelection; |
| import org.eclipse.swt.widgets.Display; |
| import org.eclipse.swt.widgets.Shell; |
| import org.eclipse.ui.IDecoratorManager; |
| import org.eclipse.ui.PlatformUI; |
| |
| public class UncompressHandler { |
| |
| private static final int BUFFER_SIZE = 2048; |
| private static final long MAX_FILE_SIZE = 2_000_000_000L; // 2 GB |
| private static final String COMPRESS_ERROR = "Compress error"; |
| |
| @Execute |
| public void execute(@Named(IServiceConstants.ACTIVE_SELECTION) IStructuredSelection selection, Shell shell, IDecoratorManager decoratorManager) { |
| Object firstElement = selection.getFirstElement(); |
| if (firstElement instanceof IFile) { |
| IFile iFile = (IFile) firstElement; |
| |
| if (AmaltheaFileHelper.isModelFileExtension(iFile.getFileExtension())) { |
| AmaltheaModelContainer modelContainer = AmaltheaEditorUtil.getModelContainer(iFile); |
| |
| if (modelContainer != null && modelContainer.getInitialModelFiles().contains(iFile)) { |
| // Resource is available (model is in memory) |
| markResourceUnzipped(iFile, modelContainer); |
| // Give feedback to user (because file will not be changed immediately) |
| showInfoDialog(); |
| } else { |
| // Unzip model file |
| unzipModelFile(iFile, shell, decoratorManager); |
| } |
| } |
| } |
| } |
| |
| private void showInfoDialog() { |
| Display.getDefault().asyncExec( () -> MessageDialog.openInformation( |
| PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(), |
| "Uncompress", |
| "Resource successfully marked as \"expanded\".\n\nSave the model to persist changes.") ); |
| } |
| |
| private void markResourceUnzipped(IFile iFile, AmaltheaModelContainer modelContainer) { |
| ResourceSet resourceSet = modelContainer.getResourceSet(); |
| if (resourceSet != null) { |
| for (Resource resource : resourceSet.getResources()) { |
| if (resource instanceof AmaltheaResource && resource.getURI().lastSegment().equals(iFile.getName())) { |
| // Modify zip flag of loaded resource |
| ((AmaltheaResource) resource).setUseZip(false); |
| // Mark resource as modified |
| resource.setModified(true); |
| // Mark editors dirty |
| modelContainer.updateDirtyStateOfEditors(); |
| break; |
| } |
| } |
| } |
| } |
| |
| /** |
| * Extracts first file of Zip archive (generic implementation based on java.util.zip package) |
| * |
| * Following vulnerabilities are handled: |
| * - Zip Slip (https://snyk.io/research/zip-slip-vulnerability) |
| * - Zip Bomb (https://wiki.sei.cmu.edu/confluence/display/java/IDS04-J.+Safely+extract+files+from+ZipInputStream) |
| * |
| * @param iFile |
| * @param shell |
| * @param decoratorManager |
| */ |
| private void unzipModelFile(IFile iFile, Shell shell, IDecoratorManager decoratorManager) { |
| if (!AmaltheaEditorUtil.isZipFile(iFile)) { |
| MessageDialog.openError(shell, COMPRESS_ERROR, "Invalid file!"); |
| return; // stop further actions |
| } |
| |
| File modelFile = AmaltheaEditorUtil.getLocalFile(iFile); |
| File modelFileTmp = new File(modelFile.getParentFile(), modelFile.getName() + ".tmp"); |
| |
| // Unzip model file (write first entry to temporary file) |
| try ( ZipInputStream inputStream = new ZipInputStream(new FileInputStream(modelFile)); |
| FileOutputStream outputStream = new FileOutputStream(modelFileTmp);) |
| { |
| ZipEntry zipEntry = inputStream.getNextEntry(); |
| if (zipEntry != null && !zipEntry.isDirectory()) { |
| long total = 0; |
| byte[] bytes = new byte[BUFFER_SIZE]; |
| int length; |
| while((length = inputStream.read(bytes)) > 0) { |
| // check size of extracted data (to avoid Zip bombs) |
| if (total + length > MAX_FILE_SIZE) { |
| MessageDialog.openError(shell, COMPRESS_ERROR, "File being unzipped is too big."); |
| return; // stop further actions |
| } |
| |
| outputStream.write(bytes, 0, length); |
| total += length; |
| } |
| } |
| } catch (IOException e) { |
| MessageDialog.openError(shell, COMPRESS_ERROR, "Failed to uncompress model file! " + e.getMessage()); |
| return; // stop further actions |
| } |
| |
| // Delete old model file and rename temporary file |
| try { |
| Files.delete(modelFile.toPath()); |
| Files.move(modelFileTmp.toPath(), modelFile.toPath()); |
| } catch (IOException e) { |
| MessageDialog.openError(shell, COMPRESS_ERROR, "Failed to replace model file! " + e.getMessage()); |
| } |
| |
| // Refresh file and decorators |
| try { |
| iFile.refreshLocal(IResource.DEPTH_INFINITE, null); |
| decoratorManager.update("org.eclipse.app4mc.amalthea.file.compressed.decorator"); |
| } catch (CoreException e) { |
| Platform.getLog(getClass()).error(e.getLocalizedMessage(), e); |
| } |
| } |
| |
| } |