diff --git a/src/main/java/org/olat/core/util/vfs/restapi/VFSDepthException.java b/src/main/java/org/olat/core/util/vfs/restapi/VFSDepthException.java new file mode 100644 index 0000000000000000000000000000000000000000..d7271f79e4862c677a41cc569df2a99cf2846f08 --- /dev/null +++ b/src/main/java/org/olat/core/util/vfs/restapi/VFSDepthException.java @@ -0,0 +1,37 @@ +/** + * <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.vfs.restapi; + +import java.io.IOException; + +/** + * + * + * Initial date: 6 juin 2019<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class VFSDepthException extends IOException { + + private static final long serialVersionUID = 7550064282144460698L; + + + +} diff --git a/src/main/java/org/olat/core/util/vfs/restapi/VFSWebservice.java b/src/main/java/org/olat/core/util/vfs/restapi/VFSWebservice.java index 7debe5390c91fd8001a5f87f8c4ff4a62da0eca9..d5fc18116890f5d4f83a011f5eae0d262ee675fb 100644 --- a/src/main/java/org/olat/core/util/vfs/restapi/VFSWebservice.java +++ b/src/main/java/org/olat/core/util/vfs/restapi/VFSWebservice.java @@ -74,8 +74,9 @@ public class VFSWebservice { private static final String VERSION = "1.0"; private static final OLog log = Tracing.createLoggerFor(VFSWebservice.class); + private static final int MAX_FOLDER_DEPTH = 20; - public static CacheControl cc = new CacheControl(); + private static final CacheControl cc = new CacheControl(); static { cc.setMaxAge(-1); } @@ -182,8 +183,13 @@ public class VFSWebservice { public Response postFile64ToRoot(@FormParam("foldername") String foldername, @FormParam("filename") String filename, @FormParam("file") String file, @Context UriInfo uriInfo) { byte[] fileAsBytes = Base64.decodeBase64(file); - InputStream in = new ByteArrayInputStream(fileAsBytes); - return putFile(foldername, filename, in, uriInfo, Collections.<PathSegment>emptyList()); + try(InputStream in = new ByteArrayInputStream(fileAsBytes)) { + return putFile(foldername, filename, in, uriInfo, Collections.<PathSegment>emptyList()); + } catch (VFSDepthException e) { + return Response.serverError().status(Status.NOT_ACCEPTABLE).build(); + } catch (IOException e) { + return Response.serverError().status(Status.INTERNAL_SERVER_ERROR).build(); + } } /** @@ -224,8 +230,13 @@ public class VFSWebservice { public Response postFile64ToFolder(@FormParam("foldername") String foldername, @FormParam("filename") String filename, @FormParam("file") String file, @Context UriInfo uriInfo, @PathParam("path") List<PathSegment> path) { byte[] fileAsBytes = Base64.decodeBase64(file); - InputStream in = new ByteArrayInputStream(fileAsBytes); - return putFile(foldername, filename, in, uriInfo, path); + try(InputStream in = new ByteArrayInputStream(fileAsBytes)) { + return putFile(foldername, filename, in, uriInfo, path); + } catch (VFSDepthException e) { + return Response.serverError().status(Status.NOT_ACCEPTABLE).build(); + } catch (IOException e) { + return Response.serverError().status(Status.INTERNAL_SERVER_ERROR).build(); + } } /** @@ -260,8 +271,13 @@ public class VFSWebservice { @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) public Response putFile64VOToRoot(File64VO file, @Context UriInfo uriInfo) { byte[] fileAsBytes = Base64.decodeBase64(file.getFile()); - InputStream in = new ByteArrayInputStream(fileAsBytes); - return putFile(null, file.getFilename(), in, uriInfo, Collections.<PathSegment>emptyList()); + try(InputStream in = new ByteArrayInputStream(fileAsBytes)) { + return putFile(null, file.getFilename(), in, uriInfo, Collections.<PathSegment>emptyList()); + } catch (VFSDepthException e) { + return Response.serverError().status(Status.NOT_ACCEPTABLE).build(); + } catch (IOException e) { + return Response.serverError().status(Status.INTERNAL_SERVER_ERROR).build(); + } } /** @@ -300,6 +316,9 @@ public class VFSWebservice { } catch (FileNotFoundException e) { log.error("", e); return Response.serverError().status(Status.INTERNAL_SERVER_ERROR).build(); + } catch (VFSDepthException e) { + log.error("", e); + return Response.serverError().status(Status.NOT_ACCEPTABLE).build(); } finally { MultipartReader.closeQuietly(partsReader); IOUtils.closeQuietly(in); @@ -323,8 +342,13 @@ public class VFSWebservice { @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) public Response putFile64ToFolder(File64VO file, @Context UriInfo uriInfo, @PathParam("path") List<PathSegment> path) { byte[] fileAsBytes = Base64.decodeBase64(file.getFile()); - InputStream in = new ByteArrayInputStream(fileAsBytes); - return putFile(null, file.getFilename(), in, uriInfo, path); + try(InputStream in = new ByteArrayInputStream(fileAsBytes)) { + return putFile(null, file.getFilename(), in, uriInfo, path); + } catch (VFSDepthException e) { + return Response.serverError().status(Status.NOT_ACCEPTABLE).build(); + } catch(IOException e) { + return Response.serverError().status(Status.INTERNAL_SERVER_ERROR).build(); + } } /** @@ -368,15 +392,23 @@ public class VFSWebservice { if(container.getLocalSecurityCallback() != null && !container.getLocalSecurityCallback().canWrite()) { return Response.serverError().status(Status.UNAUTHORIZED).build(); } + if(path.size() >= MAX_FOLDER_DEPTH) { + return Response.serverError().status(Status.NOT_ACCEPTABLE).build(); + } - VFSContainer directory = resolveContainer(path, true); - if(directory == null) { - return Response.serverError().status(Status.NOT_FOUND).build(); + try { + VFSContainer directory = resolveContainer(path, true); + if(directory == null) { + return Response.serverError().status(Status.NOT_FOUND).build(); + } + return Response.ok(createFileVO(directory, uriInfo)).build(); + } catch (VFSDepthException e) { + return Response.serverError().status(Status.NOT_ACCEPTABLE).build(); } - return Response.ok(createFileVO(directory, uriInfo)).build(); } - protected Response putFile(String foldername, String filename, InputStream file, UriInfo uriInfo, List<PathSegment> path) { + private Response putFile(String foldername, String filename, InputStream file, UriInfo uriInfo, List<PathSegment> path) + throws VFSDepthException { if(container.getLocalSecurityCallback() != null && !container.getLocalSecurityCallback().canWrite()) { return Response.serverError().status(Status.UNAUTHORIZED).build(); } @@ -476,15 +508,19 @@ public class VFSWebservice { return Response.serverError().status(Status.BAD_REQUEST).build(); } - protected VFSContainer resolveContainer(List<PathSegment> path, boolean create) { + protected VFSContainer resolveContainer(List<PathSegment> path, boolean create) throws VFSDepthException { VFSContainer directory = container; boolean notFound = false; //remove trailing segment if a trailing / is used - if(path.size() > 0 && !StringHelper.containsNonWhitespace(path.get(path.size() - 1).getPath())) { + if(!path.isEmpty() && !StringHelper.containsNonWhitespace(path.get(path.size() - 1).getPath())) { path = path.subList(0, path.size() -1); } + if(create && path.size() >= MAX_FOLDER_DEPTH) { + throw new VFSDepthException(); + } + a_a: for(PathSegment seg:path) { String segPath = seg.getPath(); @@ -517,7 +553,7 @@ public class VFSWebservice { boolean notFound = false; //remove trailing segment if a trailing / is used - if(path.size() > 0 && !StringHelper.containsNonWhitespace(path.get(path.size() - 1).getPath())) { + if(!path.isEmpty() && !StringHelper.containsNonWhitespace(path.get(path.size() - 1).getPath())) { path = path.subList(0, path.size() -1); } diff --git a/src/test/java/org/olat/restapi/CoursesFoldersTest.java b/src/test/java/org/olat/restapi/CoursesFoldersTest.java index 7a17016f1b894dda53312660b7351b34a1f8425e..53b06f5ad4279eab5ad3adb73a0431a6648a37f1 100644 --- a/src/test/java/org/olat/restapi/CoursesFoldersTest.java +++ b/src/test/java/org/olat/restapi/CoursesFoldersTest.java @@ -219,6 +219,20 @@ public class CoursesFoldersTest extends OlatJerseyTestCase { assertTrue(item2 instanceof VFSContainer); } + @Test + public void testCreateFolders_tooMany() throws IOException, URISyntaxException { + assertTrue(conn.login("administrator", "openolat")); + + URI uri = UriBuilder.fromUri(getNodeURI()).path("files").path("RootFolder") + .path("Folder").path("Folder").path("Folder").path("Folder").path("Folder") + .path("Folder").path("Folder").path("Folder").path("Folder").path("Folder") + .path("Folder").path("Folder").path("Folder").path("Folder").path("Folder") + .path("Folder").path("Folder").path("Folder").path("Folder").path("Folder").build(); + HttpPut method = conn.createPut(uri, MediaType.APPLICATION_JSON, true); + HttpResponse response = conn.execute(method); + assertEquals(406, response.getStatusLine().getStatusCode()); + } + @Test public void deleteFolder() throws IOException, URISyntaxException { //add some folders