Skip to content
Permalink
Browse files Browse the repository at this point in the history
OO-5549: check parent by unzip
  • Loading branch information
lainsr committed Jun 21, 2021
1 parent 3f219ac commit 5668a41
Show file tree
Hide file tree
Showing 17 changed files with 180 additions and 15 deletions.
25 changes: 19 additions & 6 deletions src/main/java/org/olat/core/util/ZipUtil.java
Expand Up @@ -37,6 +37,7 @@
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
Expand Down Expand Up @@ -188,20 +189,24 @@ public static boolean unzip(VFSLeaf zipLeaf, VFSContainer targetDir, Identity id
private static boolean unzip(InputStream in, VFSContainer targetDir, Identity identity, boolean versioning) {

VFSRepositoryService vfsRepositoryService = CoreSpringFactory.getImpl(VFSRepositoryService.class);

try(ZipInputStream oZip = new ZipInputStream(in)) {
// unzip files
ZipEntry oEntr = oZip.getNextEntry();
while (oEntr != null) {
if (oEntr.getName() != null && !oEntr.getName().startsWith(DIR_NAME__MACOSX)) {
String name = oEntr.getName();
if(!targetDir.isInPath(name)) {
throw new IOException("Invalip ZIP");
}

if (name != null && !name.startsWith(DIR_NAME__MACOSX)) {
if (oEntr.isDirectory()) {
// skip MacOSX specific metadata directory
// create directories
getAllSubdirs(targetDir, oEntr.getName(), identity, true);
getAllSubdirs(targetDir, name, identity, true);
} else {
// create file
VFSContainer createIn = targetDir;
String name = oEntr.getName();
// check if entry has directories which did not show up as
// directories above
int dirSepIndex = name.lastIndexOf('/');
Expand Down Expand Up @@ -317,7 +322,7 @@ private static boolean unzipNonStrict(InputStream in, VFSContainer targetDir, Id
VFSRepositoryService vfsRepositoryService = CoreSpringFactory.getImpl(VFSRepositoryService.class);

// unzip files
net.sf.jazzlib.ZipEntry oEntr = oZip.getNextEntry();
net.sf.jazzlib.ZipEntry oEntr = oZip.getNextEntry();//TODO zip

VFSLeaf lastLeaf = null;
while (oEntr != null) {
Expand Down Expand Up @@ -455,7 +460,7 @@ public static List<String> checkLockedFileBeforeUnzipNonStrict(VFSLeaf zipLeaf,
// skip MacOSX specific metadata directory
// directories aren't locked
oZip.closeEntry();
oEntr = oZip.getNextEntry();
oEntr = oZip.getNextEntry();//TODO zip
continue;
} else {
// search file
Expand Down Expand Up @@ -847,9 +852,17 @@ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IO
* @param outdir, path to output directory, relative to cwd or absolute
*/
private static void xxunzip(InputStream is, String outdir) throws IOException {
final Path outPath = Paths.get(outdir);

try(ZipInputStream zis = new ZipInputStream (new BufferedInputStream(is))) {
ZipEntry entry;
while ((entry = zis.getNextEntry()) != null) {
Path filePath = Paths.get(outdir, entry.getName());
Path normalizedPath = filePath.normalize();
if(!normalizedPath.startsWith(outPath)) {
throw new IOException("Invalid ZIP");
}

File of = new File(outdir, entry.getName());
if (entry.isDirectory()) {
of.mkdirs();
Expand Down
8 changes: 8 additions & 0 deletions src/main/java/org/olat/core/util/vfs/LocalFolderImpl.java
Expand Up @@ -335,6 +335,14 @@ public VFSItem resolve(String path) {
return resolved;
}

@Override
public boolean isInPath(String path) {
Path bFile = getBasefile().toPath();
Path filePath = bFile.resolve(path);
Path normalizedPath = filePath.normalize();
return normalizedPath.startsWith(bFile);
}

@Override
public String getRelPath() {
Path bFile = getBasefile().toPath();
Expand Down
5 changes: 5 additions & 0 deletions src/main/java/org/olat/core/util/vfs/MergeSource.java
Expand Up @@ -202,6 +202,11 @@ public VFSLeaf createChildLeaf(String name) {
return rootWriteContainer.createChildLeaf(name);
}

@Override
public boolean isInPath(String path) {
return rootWriteContainer.isInPath(path);
}

@Override
public VFSStatus copyFrom(VFSItem source) {
if (canWrite() != VFSConstants.YES) {
Expand Down
5 changes: 5 additions & 0 deletions src/main/java/org/olat/core/util/vfs/NamedContainerImpl.java
Expand Up @@ -121,6 +121,11 @@ public String getRelPath() {
return getDelegate().getRelPath();
}

@Override
public boolean isInPath(String path) {
return getDelegate().isInPath(path);
}

@Override
public VFSStatus delete() {
return getDelegate().delete();
Expand Down
7 changes: 7 additions & 0 deletions src/main/java/org/olat/core/util/vfs/VFSContainer.java
Expand Up @@ -86,6 +86,13 @@ public interface VFSContainer extends VFSItem {
*/
public VFSLeaf createChildLeaf(String name);

/**
*
* @param path
* @return
*/
public boolean isInPath(String path);

/**
* Set a default filter that will be applied to this container getItems method
*
Expand Down
5 changes: 5 additions & 0 deletions src/main/java/org/olat/core/util/vfs/VirtualContainer.java
Expand Up @@ -105,6 +105,11 @@ public String getRelPath() {
return null;
}

@Override
public boolean isInPath(String path) {
return false;
}

@Override
public VFSItem resolve(String path) {
if(path != null && path.length() > 1 && path.startsWith("/")) {
Expand Down
Expand Up @@ -366,7 +366,7 @@ private void processReturnFiles(VFSLeaf target, List<BulkAssessmentRow> rows) {
try(InputStream is = target.getInputStream();
ZipInputStream zis = new ZipInputStream(is)) {
byte[] b = new byte[FileUtils.BSIZE];
while ((entry = zis.getNextEntry()) != null) {
while ((entry = zis.getNextEntry()) != null) {//TODO zip
if(!entry.isDirectory()) {
while (zis.read(b) > 0) {
//continue
Expand Down
5 changes: 5 additions & 0 deletions src/main/java/org/olat/ims/cp/ui/VFSCPContainer.java
Expand Up @@ -96,6 +96,11 @@ public VFSItem resolve(String path) {
public String getRelPath() {
return null;
}

@Override
public boolean isInPath(String path) {
return false;
}

@Override
public List<VFSItem> getItems() {
Expand Down
5 changes: 5 additions & 0 deletions src/main/java/org/olat/ims/cp/ui/VFSCPNamedContainerItem.java
Expand Up @@ -71,6 +71,11 @@ public List<VFSItem> getItems(VFSItemFilter filter) {
return getItems();
}

@Override
public boolean isInPath(String path) {
return false;
}

@Override
public VFSStatus copyFrom(VFSItem source) {
return VFSConstants.NO;
Expand Down
5 changes: 5 additions & 0 deletions src/main/java/org/olat/ims/cp/ui/VFSMediaFilesContainer.java
Expand Up @@ -85,6 +85,11 @@ public String getRelPath() {
return rootContainer.getRelPath();
}

@Override
public boolean isInPath(String path) {
return rootContainer.isInPath(path);
}

@Override
public VFSContainer getParentContainer() {
return null;
Expand Down
5 changes: 5 additions & 0 deletions src/main/java/org/olat/ims/cp/ui/VFSRootCPContainer.java
Expand Up @@ -112,6 +112,11 @@ public String getRelPath() {
return rootContainer.getRelPath();
}

@Override
public boolean isInPath(String path) {
return rootContainer.isInPath(path);
}

@Override
public VFSItem resolve(String path) {
// 1) try to resolve directly from root (HTML editor instance)
Expand Down
Expand Up @@ -161,6 +161,11 @@ private QuestionItem processResource(ResourceType resource, Path imsmanifestPath
if(Files.notExists(assessmentItemPath)) {
return null;
}

Path normalizedPath = assessmentItemPath.normalize();
if(!normalizedPath.startsWith(parentPath)) {
throw new IOException("Invalid Item");
}

String dir = qpoolFileStorage.generateDir();
//storage
Expand Down Expand Up @@ -213,6 +218,10 @@ private QuestionItem processResource(ResourceType resource, Path imsmanifestPath
for(String material:materials) {
if(material.indexOf("://") < 0) {// material can be an external URL
Path materialFile = assessmentItemPath.getParent().resolve(material);
Path normalizedMaterialPath = materialFile.normalize();
if(!normalizedMaterialPath.startsWith(parentPath)) {
throw new IOException("Invalid Item");
}
PathUtils.copyFileToDir(materialFile, outputFile.getParentFile(), material);
}
}
Expand Down
Expand Up @@ -115,8 +115,10 @@ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
if(filename.startsWith(".")) {
//ignore
} else if(filename.endsWith("xml") && !filename.equals("imsmanifest.xml")) {
checkPath(destFile);
convertXmlFile(file, destFile);
} else {
checkPath(destFile);
Files.copy(file, destFile, StandardCopyOption.REPLACE_EXISTING);
}
}
Expand All @@ -127,13 +129,22 @@ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs)
throws IOException {
Path relativeDir = source.relativize(dir);
final Path dirToCreate = Paths.get(destDir.toString(), relativeDir.toString());
final Path dirToCreate = Paths.get(destDir.toString(), relativeDir.toString());
checkPath(dirToCreate);

if(!dirToCreate.toFile().exists()) {
Files.createDirectory(dirToCreate);
}
return FileVisitResult.CONTINUE;
}

private void checkPath(Path file) throws IOException {
Path normalizedPath = file.normalize();
if(!normalizedPath.startsWith(destDir)) {
throw new IOException("Invalid ZIP");
}
}

/**
* Convert the XML files, assessmentItem or assessmentTest
*
Expand Down
19 changes: 12 additions & 7 deletions src/main/java/org/olat/modules/wiki/WikiManager.java
Expand Up @@ -298,15 +298,20 @@ public ImportVisitor(Path destDir) throws IOException {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
throws IOException {
String filename = file.getFileName().toString();
String filename = file.getFileName().toString();
Path normalizedPath = file.normalize();
if(!normalizedPath.startsWith(destDir)) {
throw new IOException("Invalid ZIP");
}

if(filename.endsWith(WikiManager.WIKI_PROPERTIES_SUFFIX)) {
String f = convertAlternativeFilename(file.toString());
final Path destFile = Paths.get(wikiDir.toString(), f);
resetAndCopyProperties(file, destFile);
String f = convertAlternativeFilename(file.toString());
final Path destFile = Paths.get(wikiDir.toString(), f);
resetAndCopyProperties(file, destFile);
} else if (filename.endsWith(WIKI_FILE_SUFFIX)) {
String f = convertAlternativeFilename(file.toString());
final Path destFile = Paths.get(wikiDir.toString(), f);
Files.copy(file, destFile, StandardCopyOption.REPLACE_EXISTING);
String f = convertAlternativeFilename(file.toString());
final Path destFile = Paths.get(wikiDir.toString(), f);
Files.copy(file, destFile, StandardCopyOption.REPLACE_EXISTING);
} else if (!filename.contains(WIKI_FILE_SUFFIX + "-")
&& !filename.contains(WIKI_PROPERTIES_SUFFIX + "-")) {
final Path destFile = Paths.get(mediaDir.toString(), file.toString());
Expand Down
Binary file added src/test/java/org/olat/core/util/Slide.zip
Binary file not shown.
76 changes: 76 additions & 0 deletions src/test/java/org/olat/core/util/ZipUtilTest.java
@@ -0,0 +1,76 @@
/**
* <a href="http://www.openolat.org">
* OpenOLAT - Online Learning and Training</a><br>
* <p>
* Licensed under the Apache License, Version 2.0 (the "License"); <br>
* you may not use this file except in compliance with the License.<br>
* You may obtain a copy of the License at the
* <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
* <p>
* Unless required by applicable law or agreed to in writing,<br>
* software distributed under the License is distributed on an "AS IS" BASIS, <br>
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
* See the License for the specific language governing permissions and <br>
* limitations under the License.
* <p>
* Initial code contributed and copyrighted by<br>
* frentix GmbH, http://www.frentix.com
* <p>
*/
package org.olat.core.util;

import java.io.File;
import java.net.URL;

import org.junit.Assert;
import org.junit.Test;
import org.olat.core.commons.modules.bc.FolderConfig;
import org.olat.core.id.Identity;
import org.olat.core.util.vfs.VFSContainer;
import org.olat.core.util.vfs.VFSItem;
import org.olat.core.util.vfs.VFSLeaf;
import org.olat.core.util.vfs.VFSManager;
import org.olat.core.util.vfs.callbacks.FullAccessCallback;
import org.olat.test.OlatTestCase;
import org.olat.test.VFSJavaIOFile;

/**
*
* Initial date: 21 juin 2021<br>
* @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
*
*/
public class ZipUtilTest extends OlatTestCase {

@Test
public void unzipFileToDir() throws Exception {
File tmpFolder = new File(WebappHelper.getTmpDir(), "zip" + CodeHelper.getRAMUniqueID());
URL url = ZipUtilTest.class.getResource("Slide.zip");
File zipFile = new File(url.toURI());

File unzipDir = new File(tmpFolder, "unzip1");
unzipDir.mkdirs();

boolean success = ZipUtil.unzip(zipFile, unzipDir);
Assert.assertFalse(success);
File img = new File(tmpFolder, "IMG_1489.png");
Assert.assertFalse(img.exists());
FileUtils.deleteDirsAndFiles(tmpFolder, true, true);
}

@Test
public void unzipLeafToContainer() throws Exception {
URL url = ZipUtilTest.class.getResource("Slide.zip");
VFSLeaf zipLeaf = new VFSJavaIOFile(url.toURI());
VFSContainer tmpDir = VFSManager.olatRootContainer(FolderConfig.getRelativeTmpDir());
tmpDir.setLocalSecurityCallback(new FullAccessCallback());
VFSContainer targetDir = tmpDir.createChildContainer("zip" + CodeHelper.getForeverUniqueID());
boolean success = ZipUtil.unzip(zipLeaf, targetDir, (Identity)null, false);

VFSItem img = tmpDir.resolve("IMG_1489.png");
Assert.assertNull(img);
Assert.assertFalse(success);

targetDir.deleteSilently();
}
}
1 change: 1 addition & 0 deletions src/test/java/org/olat/test/AllTestsJunit4.java
Expand Up @@ -73,6 +73,7 @@
org.olat.core.util.SimpleHtmlParserTest.class,
org.olat.core.util.IPUtilsTest.class,
org.olat.core.util.IPUtilsValidRangeTest.class,
org.olat.core.util.ZipUtilTest.class,
org.olat.core.util.ZipUtilConcatTest.class,
org.olat.core.util.mail.EmailAddressValidatorTest.class,
org.olat.core.util.mail.manager.MailManagerTest.class,
Expand Down

0 comments on commit 5668a41

Please sign in to comment.