VYPR
Moderate severityNVD Advisory· Published Mar 20, 2024· Updated Aug 1, 2024

GeoServer arbitrary file renaming vulnerability in REST Coverage/Data Store API

CVE-2024-23634

Description

GeoServer is an open source software server written in Java that allows users to share and edit geospatial data. An arbitrary file renaming vulnerability exists in versions prior to 2.23.5 and 2.24.2 that enables an authenticated administrator with permissions to modify stores through the REST Coverage Store or Data Store API to rename arbitrary files and directories with a name that does not end in .zip. Store file uploads rename zip files to have a .zip extension if it doesn't already have one before unzipping the file. This is fine for file and url upload methods where the files will be in a specific subdirectory of the data directory but, when using the external upload method, this allows arbitrary files and directories to be renamed. Renaming GeoServer files will most likely result in a denial of service, either completely preventing GeoServer from running or effectively deleting specific resources (such as a workspace, layer or style). In some cases, renaming GeoServer files could revert to the default settings for that file which could be relatively harmless like removing contact information or have more serious consequences like allowing users to make OGC requests that the customized settings would have prevented them from making. The impact of renaming non-GeoServer files depends on the specific environment although some sort of denial of service is a likely outcome. Versions 2.23.5 and 2.24.2 contain a fix for this issue.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
org.geoserver:gs-restconfigMaven
< 2.23.52.23.5
org.geoserver:gs-restconfigMaven
>= 2.24.0, < 2.24.22.24.2

Affected products

1

Patches

2
c37f58fbacdf

[GEOS-11213] Improve REST external upload method unzipping (#7359)

https://github.com/geoserver/geoserverSteve IkeokaJan 22, 2024via ghsa
5 files changed · +245 28
  • src/restconfig/src/main/java/org/geoserver/rest/catalog/AbstractStoreUploadController.java+29 9 modified
    @@ -95,23 +95,36 @@ protected List<Resource> handleFileUpload(
             // handle the case that the uploaded file was a zip file, if so unzip it
             if (RESTUtils.isZipMediaType(request)) {
                 // rename to .zip if need be
    -            if (!uploadedFile.name().endsWith(".zip")) {
    +            if (external || !uploadedFile.name().endsWith(".zip")) {
    +                // for file and url upload methods, rename files in their current directory
    +                // for external upload method, copy the file into a directory where it can
    +                // be more safely unzipped
                     Resource newUploadedFile =
    -                        uploadedFile
    -                                .parent()
    +                        (external ? directory : uploadedFile.parent())
                                     .get(FilenameUtils.getBaseName(uploadedFile.path()) + ".zip");
                     String oldFileName = uploadedFile.name();
    -                if (!uploadedFile.renameTo(newUploadedFile)) {
    -                    String errorMessage =
    -                            "Error renaming zip file from "
    -                                    + oldFileName
    -                                    + " -> "
    -                                    + newUploadedFile.name();
    +                String errorMessage =
    +                        "Error renaming zip file from "
    +                                + oldFileName
    +                                + " -> "
    +                                + newUploadedFile.name();
    +                // do not rename or copy directories (only possible with external upload)
    +                // do not allow renaming/copying to overwrite an existing directory
    +                if (uploadedFile.getType() != Resource.Type.RESOURCE
    +                        || newUploadedFile.getType() == Resource.Type.DIRECTORY
    +                        || (!external && !uploadedFile.renameTo(newUploadedFile))) {
                         throw new RestException(errorMessage, HttpStatus.INTERNAL_SERVER_ERROR);
    +                } else if (external) {
    +                    try {
    +                        Resources.copy(uploadedFile, newUploadedFile);
    +                    } catch (Exception e) {
    +                        throw new RestException(errorMessage, HttpStatus.INTERNAL_SERVER_ERROR, e);
    +                    }
                     }
                     uploadedFile = newUploadedFile;
                 }
                 // unzip the file
    +            boolean success = false;
                 try {
                     // Unzipping of the file and, if it is a POST request, filling of the File List
                     RESTUtils.unzipFile(uploadedFile, directory, workspace, store, files, external);
    @@ -121,6 +134,7 @@ protected List<Resource> handleFileUpload(
                     Resource primaryFile = findPrimaryFile(directory, format);
                     if (primaryFile != null) {
                         uploadedFile = primaryFile;
    +                    success = true;
                     } else {
                         throw new RestException(
                                 "Could not find appropriate " + format + " file in archive",
    @@ -131,6 +145,12 @@ protected List<Resource> handleFileUpload(
                 } catch (Exception e) {
                     throw new RestException(
                             "Error occured unzipping file", HttpStatus.INTERNAL_SERVER_ERROR, e);
    +            } finally {
    +                if (!success) {
    +                    // clean up files if not successful
    +                    files.forEach(Resource::delete);
    +                    uploadedFile.delete();
    +                }
                 }
             }
             // If the File List is empty then the uploaded file must be added
    
  • src/restconfig/src/main/java/org/geoserver/rest/catalog/CoverageStoreFileController.java+8 10 modified
    @@ -443,16 +443,14 @@ protected List<Resource> doFileUpload(
             boolean postRequest =
                     request != null && HttpMethod.POST.name().equalsIgnoreCase(request.getMethod());
     
    -        // Prepare the directory only in case this is not an external upload
    -        if (method.isInline()) {
    -            // Mapping of the input directory
    -            if (method == UploadMethod.url) {
    -                // For URL upload method, workspace and StoreName are not considered
    -                directory = RESTUtils.createUploadRoot(catalog, null, null, postRequest);
    -            } else {
    -                directory =
    -                        RESTUtils.createUploadRoot(catalog, workspaceName, storeName, postRequest);
    -            }
    +        // Mapping of the input directory
    +        if (method == UploadMethod.url) {
    +            // For URL upload method, workspace and StoreName are not considered
    +            directory = RESTUtils.createUploadRoot(catalog, null, null, postRequest);
    +        } else if (method == UploadMethod.file
    +                || (method == UploadMethod.external && RESTUtils.isZipMediaType(request))) {
    +            // Prepare the directory for file upload or external upload of a zip file
    +            directory = RESTUtils.createUploadRoot(catalog, workspaceName, storeName, postRequest);
             }
             return handleFileUpload(
                     storeName, workspaceName, filename, method, format, directory, request);
    
  • src/restconfig/src/main/java/org/geoserver/rest/catalog/DataStoreFileController.java+9 9 modified
    @@ -40,6 +40,7 @@
     import org.geoserver.rest.RestException;
     import org.geoserver.rest.util.IOUtils;
     import org.geoserver.rest.util.RESTUploadPathMapper;
    +import org.geoserver.rest.util.RESTUtils;
     import org.geotools.data.DataAccess;
     import org.geotools.data.DataAccessFactory;
     import org.geotools.data.DataStore;
    @@ -567,15 +568,14 @@ protected List<Resource> doFileUpload(
             boolean postRequest =
                     request != null && HttpMethod.POST.name().equalsIgnoreCase(request.getMethod());
     
    -        // Prepare the directory only in case this is not an external upload
    -        if (method.isInline()) {
    -            // Mapping of the input directory
    -            if (method == UploadMethod.url) {
    -                // For URL upload method, workspace and StoreName are not considered
    -                directory = createFinalRoot(null, null, postRequest);
    -            } else {
    -                directory = createFinalRoot(workspaceName, storeName, postRequest);
    -            }
    +        // Mapping of the input directory
    +        if (method == UploadMethod.url) {
    +            // For URL upload method, workspace and StoreName are not considered
    +            directory = createFinalRoot(null, null, postRequest);
    +        } else if (method == UploadMethod.file
    +                || (method == UploadMethod.external && RESTUtils.isZipMediaType(request))) {
    +            // Prepare the directory for file upload or external upload of a zip file
    +            directory = createFinalRoot(workspaceName, storeName, postRequest);
             }
             return handleFileUpload(
                     storeName, workspaceName, filename, method, format, directory, request);
    
  • src/restconfig/src/test/java/org/geoserver/rest/catalog/CoverageStoreFileUploadTest.java+102 0 modified
    @@ -5,9 +5,14 @@
      */
     package org.geoserver.rest.catalog;
     
    +import static org.geoserver.rest.RestBaseController.ROOT_PATH;
    +import static org.hamcrest.MatcherAssert.assertThat;
    +import static org.hamcrest.Matchers.startsWith;
     import static org.junit.Assert.assertEquals;
     import static org.junit.Assert.assertNotEquals;
     import static org.junit.Assert.assertNotNull;
    +import static org.junit.Assert.assertNull;
    +import static org.junit.Assert.assertTrue;
     import static org.junit.Assert.fail;
     
     import java.io.ByteArrayInputStream;
    @@ -48,8 +53,10 @@
     import org.geotools.util.URLs;
     import org.geotools.util.factory.GeoTools;
     import org.junit.Before;
    +import org.junit.ClassRule;
     import org.junit.Ignore;
     import org.junit.Test;
    +import org.junit.rules.TemporaryFolder;
     import org.opengis.coverage.grid.GridCoverageReader;
     import org.opengis.referencing.FactoryException;
     import org.springframework.http.MediaType;
    @@ -59,6 +66,8 @@
     
     public class CoverageStoreFileUploadTest extends CatalogRESTTestSupport {
     
    +    @ClassRule public static TemporaryFolder temp = new TemporaryFolder();
    +
         @Override
         protected void onSetUp(SystemTestData testData) throws Exception {
             super.onSetUp(testData);
    @@ -83,6 +92,7 @@ public void cleanup() throws IOException {
                 removeStore(
                         coverage.getStore().getWorkspace().getName(), coverage.getStore().getName());
             }
    +        removeStore("sf", "usa");
         }
     
         @Test
    @@ -754,4 +764,96 @@ static void testBBoxLayerConfiguration(
                 assertConsumer.accept(current, old);
             }
         }
    +
    +    @Test
    +    public void testWorldImageUploadExternalZipDirectory() throws Exception {
    +        // get the path to a directory
    +        File file = temp.getRoot();
    +        String body = file.getAbsolutePath();
    +        // the request will fail since it won't attempt to copy a directory
    +        MockHttpServletResponse response =
    +                putAsServletResponse(
    +                        ROOT_PATH + "/workspaces/foo/coveragestores/bar/external.worldimage",
    +                        body,
    +                        "application/zip");
    +        assertEquals(500, response.getStatus());
    +        assertThat(response.getContentAsString(), startsWith("Error renaming zip file from "));
    +        // verify that the external file was not deleted
    +        assertTrue("The external file was unexpectedly deleted", file.exists());
    +    }
    +
    +    @Test
    +    public void testWorldImageUploadExternalZipExistingDirectory() throws Exception {
    +        // create a file to copy and get its path
    +        File file1 = temp.newFile("test1.zip");
    +        String body = file1.getAbsolutePath();
    +        // create the file in the data directory
    +        File file2 = getResourceLoader().createDirectory("data/foo/bar1/test1.zip");
    +        // the request will fail since it won't overwrite an existing zip file
    +        MockHttpServletResponse response =
    +                putAsServletResponse(
    +                        ROOT_PATH + "/workspaces/foo/coveragestores/bar1/external.worldimage",
    +                        body,
    +                        "application/zip");
    +        assertEquals(500, response.getStatus());
    +        assertThat(response.getContentAsString(), startsWith("Error renaming zip file from "));
    +        // verify that the external file was not deleted
    +        assertTrue("The external file was unexpectedly deleted", file1.exists());
    +        // verify that the file in the data directory was not deleted
    +        assertTrue("The file in the data directory was unexpectedly deleted", file2.isDirectory());
    +    }
    +
    +    @Test
    +    public void testWorldImageUploadExternalZipBadFile() throws Exception {
    +        // create a file that is not a valid zip file and get its path
    +        File file = temp.newFile("test2.zip");
    +        String body = file.getAbsolutePath();
    +        // the request will fail unzipping since it is not a valid zip fail
    +        MockHttpServletResponse response =
    +                putAsServletResponse(
    +                        ROOT_PATH + "/workspaces/foo/coveragestores/bar2/external.worldimage",
    +                        body,
    +                        "application/zip");
    +        assertEquals(500, response.getStatus());
    +        assertEquals("Error occured unzipping file", response.getContentAsString());
    +        // verify that the external file was not deleted
    +        assertTrue("The external file was unexpectedly deleted", file.exists());
    +        // verify that the zip file was deleted from the data directory
    +        assertEquals(
    +                "The data directory file was not deleted",
    +                Resource.Type.UNDEFINED,
    +                getResourceLoader().get("data/foo/bar2/test2.zip").getType());
    +    }
    +
    +    @Test
    +    public void testWorldImageUploadExternalZipValid() throws Exception {
    +        // create a valid zip file and get its path
    +        File file = temp.newFile("test3.zip");
    +        FileUtils.copyURLToFile(getClass().getResource("test-data/usa.zip"), file);
    +        String body = file.getAbsolutePath();
    +        // verify that the coverage does not already exist
    +        assertNull(getCatalog().getCoverageStoreByName("sf", "usa"));
    +        assertNull(getCatalog().getCoverageByName("sf", "usa"));
    +        // the request should succeed
    +        MockHttpServletResponse response =
    +                putAsServletResponse(
    +                        ROOT_PATH + "/workspaces/sf/coveragestores/usa/external.worldimage",
    +                        body,
    +                        "application/zip");
    +        assertEquals(201, response.getStatus());
    +        assertEquals(MediaType.APPLICATION_XML_VALUE, response.getContentType());
    +        String content = response.getContentAsString();
    +        Document d = dom(new ByteArrayInputStream(content.getBytes()));
    +        assertEquals("coverageStore", d.getDocumentElement().getNodeName());
    +        // verify that the coverage was created successfully
    +        assertNotNull(getCatalog().getCoverageStoreByName("sf", "usa"));
    +        assertNotNull(getCatalog().getCoverageByName("sf", "usa"));
    +        // verify that the external file was not deleted
    +        assertTrue("The external file was unexpectedly deleted", file.exists());
    +        // verify that the zip file was deleted from the data directory
    +        assertEquals(
    +                "The data directory file was not deleted",
    +                Resource.Type.UNDEFINED,
    +                getResourceLoader().get("data/sf/usa/test3.zip").getType());
    +    }
     }
    
  • src/restconfig/src/test/java/org/geoserver/rest/catalog/DataStoreFileUploadTest.java+97 0 modified
    @@ -5,6 +5,8 @@
     package org.geoserver.rest.catalog;
     
     import static org.geoserver.rest.RestBaseController.ROOT_PATH;
    +import static org.hamcrest.MatcherAssert.assertThat;
    +import static org.hamcrest.Matchers.startsWith;
     import static org.junit.Assert.assertEquals;
     import static org.junit.Assert.assertNotNull;
     import static org.junit.Assert.assertNull;
    @@ -19,6 +21,7 @@
     import java.io.OutputStreamWriter;
     import java.net.URL;
     import java.nio.charset.StandardCharsets;
    +import java.nio.file.Files;
     import java.util.ArrayList;
     import java.util.Collections;
     import java.util.List;
    @@ -34,18 +37,24 @@
     import org.geoserver.data.test.SystemTestData;
     import org.geoserver.filters.LoggingFilter;
     import org.geoserver.platform.GeoServerResourceLoader;
    +import org.geoserver.platform.resource.Resource;
     import org.geotools.util.URLs;
     import org.h2.tools.DeleteDbFiles;
     import org.junit.After;
     import org.junit.Before;
    +import org.junit.ClassRule;
     import org.junit.Ignore;
     import org.junit.Test;
    +import org.junit.rules.TemporaryFolder;
     import org.springframework.mock.web.MockHttpServletResponse;
     import org.w3c.dom.Document;
     import org.w3c.dom.Node;
     import org.w3c.dom.NodeList;
     
     public class DataStoreFileUploadTest extends CatalogRESTTestSupport {
    +
    +    @ClassRule public static TemporaryFolder temp = new TemporaryFolder();
    +
         @Override
         protected void onSetUp(SystemTestData testData) throws Exception {
             super.onSetUp(testData);
    @@ -73,6 +82,7 @@ protected List<Filter> getFilters() {
         public void removePdsDataStore() {
             removeStore("gs", "pds");
             removeStore("gs", "store with spaces");
    +        removeStore("gs", "san_andres_y_providencia");
         }
     
         @After
    @@ -406,4 +416,91 @@ private byte[] appSchemaAlternativeMappingAsBytes() throws Exception {
                 return null;
             }
         }
    +
    +    @Test
    +    public void testShapefileUploadExternalZipDirectory() throws Exception {
    +        // get the path to a directory
    +        File file = temp.getRoot();
    +        String body = file.getAbsolutePath();
    +        // the request will fail since it won't attempt to copy a directory
    +        MockHttpServletResponse response =
    +                putAsServletResponse(
    +                        ROOT_PATH + "/workspaces/foo/datastores/bar/external.shp",
    +                        body,
    +                        "application/zip");
    +        assertEquals(500, response.getStatus());
    +        assertThat(response.getContentAsString(), startsWith("Error renaming zip file from "));
    +        // verify that the external file was not deleted
    +        assertTrue("The external file was unexpectedly deleted", file.exists());
    +    }
    +
    +    @Test
    +    public void testShapefileUploadExternalZipExistingDirectory() throws Exception {
    +        // create a file to copy and get its path
    +        File file1 = temp.newFile("test1.zip");
    +        String body = file1.getAbsolutePath();
    +        // create the file in the data directory
    +        File file2 = getResourceLoader().createDirectory("data/foo/bar1/test1.zip");
    +        // the request will fail since it won't overwrite an existing zip file
    +        MockHttpServletResponse response =
    +                putAsServletResponse(
    +                        ROOT_PATH + "/workspaces/foo/datastores/bar1/external.shp",
    +                        body,
    +                        "application/zip");
    +        assertEquals(500, response.getStatus());
    +        assertThat(response.getContentAsString(), startsWith("Error renaming zip file from "));
    +        // verify that the external file was not deleted
    +        assertTrue("The external file was unexpectedly deleted", file1.exists());
    +        // verify that the file in the data directory was not deleted
    +        assertTrue("The file in the data directory was unexpectedly deleted", file2.isDirectory());
    +    }
    +
    +    @Test
    +    public void testShapefileUploadExternalZipBadFile() throws Exception {
    +        // create a file that is not a valid zip file and get its path
    +        File file = temp.newFile("test2.zip");
    +        String body = file.getAbsolutePath();
    +        // the request will fail unzipping since it is not a valid zip fail
    +        MockHttpServletResponse response =
    +                putAsServletResponse(
    +                        ROOT_PATH + "/workspaces/foo/datastores/bar2/external.shp",
    +                        body,
    +                        "application/zip");
    +        assertEquals(500, response.getStatus());
    +        assertEquals("Error occured unzipping file", response.getContentAsString());
    +        // verify that the external file was not deleted
    +        assertTrue("The external file was unexpectedly deleted", file.exists());
    +        // verify that the zip file was deleted from the data directory
    +        assertEquals(
    +                "The data directory file was not deleted",
    +                Resource.Type.UNDEFINED,
    +                getResourceLoader().get("data/foo/bar2/test2.zip").getType());
    +    }
    +
    +    @Test
    +    public void testShapefileUploadExternalZipValid() throws Exception {
    +        // create a valid zip file and get its path
    +        File file = temp.newFile("test3.zip");
    +        Files.write(file.toPath(), shpSanAndresShapefilesZipAsBytes());
    +        String body = file.getAbsolutePath();
    +        // verify that the datastore does not already exist
    +        Catalog cat = getCatalog();
    +        assertNull(cat.getDataStoreByName("gs", "san_andres_y_providencia"));
    +        // the request should succeed
    +        put(
    +                ROOT_PATH + "/workspaces/gs/datastores/san_andres_y_providencia/external.shp",
    +                body,
    +                "application/zip");
    +        // verify that the datastore was created successfully
    +        DataStoreInfo ds = cat.getDataStoreByName("gs", "san_andres_y_providencia");
    +        assertNotNull(ds);
    +        assertEquals(1, cat.getFeatureTypesByDataStore(ds).size());
    +        // verify that the external file was not deleted
    +        assertTrue("The external file was unexpectedly deleted", file.exists());
    +        // verify that the zip file was deleted from the data directory
    +        assertEquals(
    +                "The data directory file was not deleted",
    +                Resource.Type.UNDEFINED,
    +                getResourceLoader().get("data/gs/san_andres_y_providencia/test3.zip").getType());
    +    }
     }
    
5d6af2f8ba9a

[GEOS-11213] Improve REST external upload method unzipping

https://github.com/geoserver/geoserverSteve IkeokaNov 22, 2023via ghsa
5 files changed · +245 28
  • src/restconfig/src/main/java/org/geoserver/rest/catalog/AbstractStoreUploadController.java+29 9 modified
    @@ -95,23 +95,36 @@ protected List<Resource> handleFileUpload(
             // handle the case that the uploaded file was a zip file, if so unzip it
             if (RESTUtils.isZipMediaType(request)) {
                 // rename to .zip if need be
    -            if (!uploadedFile.name().endsWith(".zip")) {
    +            if (external || !uploadedFile.name().endsWith(".zip")) {
    +                // for file and url upload methods, rename files in their current directory
    +                // for external upload method, copy the file into a directory where it can
    +                // be more safely unzipped
                     Resource newUploadedFile =
    -                        uploadedFile
    -                                .parent()
    +                        (external ? directory : uploadedFile.parent())
                                     .get(FilenameUtils.getBaseName(uploadedFile.path()) + ".zip");
                     String oldFileName = uploadedFile.name();
    -                if (!uploadedFile.renameTo(newUploadedFile)) {
    -                    String errorMessage =
    -                            "Error renaming zip file from "
    -                                    + oldFileName
    -                                    + " -> "
    -                                    + newUploadedFile.name();
    +                String errorMessage =
    +                        "Error renaming zip file from "
    +                                + oldFileName
    +                                + " -> "
    +                                + newUploadedFile.name();
    +                // do not rename or copy directories (only possible with external upload)
    +                // do not allow renaming/copying to overwrite an existing directory
    +                if (uploadedFile.getType() != Resource.Type.RESOURCE
    +                        || newUploadedFile.getType() == Resource.Type.DIRECTORY
    +                        || (!external && !uploadedFile.renameTo(newUploadedFile))) {
                         throw new RestException(errorMessage, HttpStatus.INTERNAL_SERVER_ERROR);
    +                } else if (external) {
    +                    try {
    +                        Resources.copy(uploadedFile, newUploadedFile);
    +                    } catch (Exception e) {
    +                        throw new RestException(errorMessage, HttpStatus.INTERNAL_SERVER_ERROR, e);
    +                    }
                     }
                     uploadedFile = newUploadedFile;
                 }
                 // unzip the file
    +            boolean success = false;
                 try {
                     // Unzipping of the file and, if it is a POST request, filling of the File List
                     RESTUtils.unzipFile(uploadedFile, directory, workspace, store, files, external);
    @@ -121,6 +134,7 @@ protected List<Resource> handleFileUpload(
                     Resource primaryFile = findPrimaryFile(directory, format);
                     if (primaryFile != null) {
                         uploadedFile = primaryFile;
    +                    success = true;
                     } else {
                         throw new RestException(
                                 "Could not find appropriate " + format + " file in archive",
    @@ -131,6 +145,12 @@ protected List<Resource> handleFileUpload(
                 } catch (Exception e) {
                     throw new RestException(
                             "Error occured unzipping file", HttpStatus.INTERNAL_SERVER_ERROR, e);
    +            } finally {
    +                if (!success) {
    +                    // clean up files if not successful
    +                    files.forEach(Resource::delete);
    +                    uploadedFile.delete();
    +                }
                 }
             }
             // If the File List is empty then the uploaded file must be added
    
  • src/restconfig/src/main/java/org/geoserver/rest/catalog/CoverageStoreFileController.java+8 10 modified
    @@ -443,16 +443,14 @@ protected List<Resource> doFileUpload(
             boolean postRequest =
                     request != null && HttpMethod.POST.name().equalsIgnoreCase(request.getMethod());
     
    -        // Prepare the directory only in case this is not an external upload
    -        if (method.isInline()) {
    -            // Mapping of the input directory
    -            if (method == UploadMethod.url) {
    -                // For URL upload method, workspace and StoreName are not considered
    -                directory = RESTUtils.createUploadRoot(catalog, null, null, postRequest);
    -            } else {
    -                directory =
    -                        RESTUtils.createUploadRoot(catalog, workspaceName, storeName, postRequest);
    -            }
    +        // Mapping of the input directory
    +        if (method == UploadMethod.url) {
    +            // For URL upload method, workspace and StoreName are not considered
    +            directory = RESTUtils.createUploadRoot(catalog, null, null, postRequest);
    +        } else if (method == UploadMethod.file
    +                || (method == UploadMethod.external && RESTUtils.isZipMediaType(request))) {
    +            // Prepare the directory for file upload or external upload of a zip file
    +            directory = RESTUtils.createUploadRoot(catalog, workspaceName, storeName, postRequest);
             }
             return handleFileUpload(
                     storeName, workspaceName, filename, method, format, directory, request);
    
  • src/restconfig/src/main/java/org/geoserver/rest/catalog/DataStoreFileController.java+9 9 modified
    @@ -40,6 +40,7 @@
     import org.geoserver.rest.RestException;
     import org.geoserver.rest.util.IOUtils;
     import org.geoserver.rest.util.RESTUploadPathMapper;
    +import org.geoserver.rest.util.RESTUtils;
     import org.geotools.api.data.DataAccess;
     import org.geotools.api.data.DataAccessFactory;
     import org.geotools.api.data.DataStore;
    @@ -567,15 +568,14 @@ protected List<Resource> doFileUpload(
             boolean postRequest =
                     request != null && HttpMethod.POST.name().equalsIgnoreCase(request.getMethod());
     
    -        // Prepare the directory only in case this is not an external upload
    -        if (method.isInline()) {
    -            // Mapping of the input directory
    -            if (method == UploadMethod.url) {
    -                // For URL upload method, workspace and StoreName are not considered
    -                directory = createFinalRoot(null, null, postRequest);
    -            } else {
    -                directory = createFinalRoot(workspaceName, storeName, postRequest);
    -            }
    +        // Mapping of the input directory
    +        if (method == UploadMethod.url) {
    +            // For URL upload method, workspace and StoreName are not considered
    +            directory = createFinalRoot(null, null, postRequest);
    +        } else if (method == UploadMethod.file
    +                || (method == UploadMethod.external && RESTUtils.isZipMediaType(request))) {
    +            // Prepare the directory for file upload or external upload of a zip file
    +            directory = createFinalRoot(workspaceName, storeName, postRequest);
             }
             return handleFileUpload(
                     storeName, workspaceName, filename, method, format, directory, request);
    
  • src/restconfig/src/test/java/org/geoserver/rest/catalog/CoverageStoreFileUploadTest.java+102 0 modified
    @@ -5,9 +5,14 @@
      */
     package org.geoserver.rest.catalog;
     
    +import static org.geoserver.rest.RestBaseController.ROOT_PATH;
    +import static org.hamcrest.MatcherAssert.assertThat;
    +import static org.hamcrest.Matchers.startsWith;
     import static org.junit.Assert.assertEquals;
     import static org.junit.Assert.assertNotEquals;
     import static org.junit.Assert.assertNotNull;
    +import static org.junit.Assert.assertNull;
    +import static org.junit.Assert.assertTrue;
     import static org.junit.Assert.fail;
     
     import java.io.ByteArrayInputStream;
    @@ -50,15 +55,19 @@
     import org.geotools.util.URLs;
     import org.geotools.util.factory.GeoTools;
     import org.junit.Before;
    +import org.junit.ClassRule;
     import org.junit.Ignore;
     import org.junit.Test;
    +import org.junit.rules.TemporaryFolder;
     import org.springframework.http.MediaType;
     import org.springframework.mock.web.MockHttpServletRequest;
     import org.springframework.mock.web.MockHttpServletResponse;
     import org.w3c.dom.Document;
     
     public class CoverageStoreFileUploadTest extends CatalogRESTTestSupport {
     
    +    @ClassRule public static TemporaryFolder temp = new TemporaryFolder();
    +
         @Override
         protected void onSetUp(SystemTestData testData) throws Exception {
             super.onSetUp(testData);
    @@ -83,6 +92,7 @@ public void cleanup() throws IOException {
                 removeStore(
                         coverage.getStore().getWorkspace().getName(), coverage.getStore().getName());
             }
    +        removeStore("sf", "usa");
         }
     
         @Test
    @@ -754,4 +764,96 @@ static void testBBoxLayerConfiguration(
                 assertConsumer.accept(current, old);
             }
         }
    +
    +    @Test
    +    public void testWorldImageUploadExternalZipDirectory() throws Exception {
    +        // get the path to a directory
    +        File file = temp.getRoot();
    +        String body = file.getAbsolutePath();
    +        // the request will fail since it won't attempt to copy a directory
    +        MockHttpServletResponse response =
    +                putAsServletResponse(
    +                        ROOT_PATH + "/workspaces/foo/coveragestores/bar/external.worldimage",
    +                        body,
    +                        "application/zip");
    +        assertEquals(500, response.getStatus());
    +        assertThat(response.getContentAsString(), startsWith("Error renaming zip file from "));
    +        // verify that the external file was not deleted
    +        assertTrue("The external file was unexpectedly deleted", file.exists());
    +    }
    +
    +    @Test
    +    public void testWorldImageUploadExternalZipExistingDirectory() throws Exception {
    +        // create a file to copy and get its path
    +        File file1 = temp.newFile("test1.zip");
    +        String body = file1.getAbsolutePath();
    +        // create the file in the data directory
    +        File file2 = getResourceLoader().createDirectory("data/foo/bar1/test1.zip");
    +        // the request will fail since it won't overwrite an existing zip file
    +        MockHttpServletResponse response =
    +                putAsServletResponse(
    +                        ROOT_PATH + "/workspaces/foo/coveragestores/bar1/external.worldimage",
    +                        body,
    +                        "application/zip");
    +        assertEquals(500, response.getStatus());
    +        assertThat(response.getContentAsString(), startsWith("Error renaming zip file from "));
    +        // verify that the external file was not deleted
    +        assertTrue("The external file was unexpectedly deleted", file1.exists());
    +        // verify that the file in the data directory was not deleted
    +        assertTrue("The file in the data directory was unexpectedly deleted", file2.isDirectory());
    +    }
    +
    +    @Test
    +    public void testWorldImageUploadExternalZipBadFile() throws Exception {
    +        // create a file that is not a valid zip file and get its path
    +        File file = temp.newFile("test2.zip");
    +        String body = file.getAbsolutePath();
    +        // the request will fail unzipping since it is not a valid zip fail
    +        MockHttpServletResponse response =
    +                putAsServletResponse(
    +                        ROOT_PATH + "/workspaces/foo/coveragestores/bar2/external.worldimage",
    +                        body,
    +                        "application/zip");
    +        assertEquals(500, response.getStatus());
    +        assertEquals("Error occured unzipping file", response.getContentAsString());
    +        // verify that the external file was not deleted
    +        assertTrue("The external file was unexpectedly deleted", file.exists());
    +        // verify that the zip file was deleted from the data directory
    +        assertEquals(
    +                "The data directory file was not deleted",
    +                Resource.Type.UNDEFINED,
    +                getResourceLoader().get("data/foo/bar2/test2.zip").getType());
    +    }
    +
    +    @Test
    +    public void testWorldImageUploadExternalZipValid() throws Exception {
    +        // create a valid zip file and get its path
    +        File file = temp.newFile("test3.zip");
    +        FileUtils.copyURLToFile(getClass().getResource("test-data/usa.zip"), file);
    +        String body = file.getAbsolutePath();
    +        // verify that the coverage does not already exist
    +        assertNull(getCatalog().getCoverageStoreByName("sf", "usa"));
    +        assertNull(getCatalog().getCoverageByName("sf", "usa"));
    +        // the request should succeed
    +        MockHttpServletResponse response =
    +                putAsServletResponse(
    +                        ROOT_PATH + "/workspaces/sf/coveragestores/usa/external.worldimage",
    +                        body,
    +                        "application/zip");
    +        assertEquals(201, response.getStatus());
    +        assertEquals(MediaType.APPLICATION_XML_VALUE, response.getContentType());
    +        String content = response.getContentAsString();
    +        Document d = dom(new ByteArrayInputStream(content.getBytes()));
    +        assertEquals("coverageStore", d.getDocumentElement().getNodeName());
    +        // verify that the coverage was created successfully
    +        assertNotNull(getCatalog().getCoverageStoreByName("sf", "usa"));
    +        assertNotNull(getCatalog().getCoverageByName("sf", "usa"));
    +        // verify that the external file was not deleted
    +        assertTrue("The external file was unexpectedly deleted", file.exists());
    +        // verify that the zip file was deleted from the data directory
    +        assertEquals(
    +                "The data directory file was not deleted",
    +                Resource.Type.UNDEFINED,
    +                getResourceLoader().get("data/sf/usa/test3.zip").getType());
    +    }
     }
    
  • src/restconfig/src/test/java/org/geoserver/rest/catalog/DataStoreFileUploadTest.java+97 0 modified
    @@ -5,6 +5,8 @@
     package org.geoserver.rest.catalog;
     
     import static org.geoserver.rest.RestBaseController.ROOT_PATH;
    +import static org.hamcrest.MatcherAssert.assertThat;
    +import static org.hamcrest.Matchers.startsWith;
     import static org.junit.Assert.assertEquals;
     import static org.junit.Assert.assertNotNull;
     import static org.junit.Assert.assertNull;
    @@ -19,6 +21,7 @@
     import java.io.OutputStreamWriter;
     import java.net.URL;
     import java.nio.charset.StandardCharsets;
    +import java.nio.file.Files;
     import java.util.ArrayList;
     import java.util.Collections;
     import java.util.List;
    @@ -34,18 +37,24 @@
     import org.geoserver.data.test.SystemTestData;
     import org.geoserver.filters.LoggingFilter;
     import org.geoserver.platform.GeoServerResourceLoader;
    +import org.geoserver.platform.resource.Resource;
     import org.geotools.util.URLs;
     import org.h2.tools.DeleteDbFiles;
     import org.junit.After;
     import org.junit.Before;
    +import org.junit.ClassRule;
     import org.junit.Ignore;
     import org.junit.Test;
    +import org.junit.rules.TemporaryFolder;
     import org.springframework.mock.web.MockHttpServletResponse;
     import org.w3c.dom.Document;
     import org.w3c.dom.Node;
     import org.w3c.dom.NodeList;
     
     public class DataStoreFileUploadTest extends CatalogRESTTestSupport {
    +
    +    @ClassRule public static TemporaryFolder temp = new TemporaryFolder();
    +
         @Override
         protected void onSetUp(SystemTestData testData) throws Exception {
             super.onSetUp(testData);
    @@ -73,6 +82,7 @@ protected List<Filter> getFilters() {
         public void removePdsDataStore() {
             removeStore("gs", "pds");
             removeStore("gs", "store with spaces");
    +        removeStore("gs", "san_andres_y_providencia");
         }
     
         @After
    @@ -406,4 +416,91 @@ private byte[] appSchemaAlternativeMappingAsBytes() throws Exception {
                 return null;
             }
         }
    +
    +    @Test
    +    public void testShapefileUploadExternalZipDirectory() throws Exception {
    +        // get the path to a directory
    +        File file = temp.getRoot();
    +        String body = file.getAbsolutePath();
    +        // the request will fail since it won't attempt to copy a directory
    +        MockHttpServletResponse response =
    +                putAsServletResponse(
    +                        ROOT_PATH + "/workspaces/foo/datastores/bar/external.shp",
    +                        body,
    +                        "application/zip");
    +        assertEquals(500, response.getStatus());
    +        assertThat(response.getContentAsString(), startsWith("Error renaming zip file from "));
    +        // verify that the external file was not deleted
    +        assertTrue("The external file was unexpectedly deleted", file.exists());
    +    }
    +
    +    @Test
    +    public void testShapefileUploadExternalZipExistingDirectory() throws Exception {
    +        // create a file to copy and get its path
    +        File file1 = temp.newFile("test1.zip");
    +        String body = file1.getAbsolutePath();
    +        // create the file in the data directory
    +        File file2 = getResourceLoader().createDirectory("data/foo/bar1/test1.zip");
    +        // the request will fail since it won't overwrite an existing zip file
    +        MockHttpServletResponse response =
    +                putAsServletResponse(
    +                        ROOT_PATH + "/workspaces/foo/datastores/bar1/external.shp",
    +                        body,
    +                        "application/zip");
    +        assertEquals(500, response.getStatus());
    +        assertThat(response.getContentAsString(), startsWith("Error renaming zip file from "));
    +        // verify that the external file was not deleted
    +        assertTrue("The external file was unexpectedly deleted", file1.exists());
    +        // verify that the file in the data directory was not deleted
    +        assertTrue("The file in the data directory was unexpectedly deleted", file2.isDirectory());
    +    }
    +
    +    @Test
    +    public void testShapefileUploadExternalZipBadFile() throws Exception {
    +        // create a file that is not a valid zip file and get its path
    +        File file = temp.newFile("test2.zip");
    +        String body = file.getAbsolutePath();
    +        // the request will fail unzipping since it is not a valid zip fail
    +        MockHttpServletResponse response =
    +                putAsServletResponse(
    +                        ROOT_PATH + "/workspaces/foo/datastores/bar2/external.shp",
    +                        body,
    +                        "application/zip");
    +        assertEquals(500, response.getStatus());
    +        assertEquals("Error occured unzipping file", response.getContentAsString());
    +        // verify that the external file was not deleted
    +        assertTrue("The external file was unexpectedly deleted", file.exists());
    +        // verify that the zip file was deleted from the data directory
    +        assertEquals(
    +                "The data directory file was not deleted",
    +                Resource.Type.UNDEFINED,
    +                getResourceLoader().get("data/foo/bar2/test2.zip").getType());
    +    }
    +
    +    @Test
    +    public void testShapefileUploadExternalZipValid() throws Exception {
    +        // create a valid zip file and get its path
    +        File file = temp.newFile("test3.zip");
    +        Files.write(file.toPath(), shpSanAndresShapefilesZipAsBytes());
    +        String body = file.getAbsolutePath();
    +        // verify that the datastore does not already exist
    +        Catalog cat = getCatalog();
    +        assertNull(cat.getDataStoreByName("gs", "san_andres_y_providencia"));
    +        // the request should succeed
    +        put(
    +                ROOT_PATH + "/workspaces/gs/datastores/san_andres_y_providencia/external.shp",
    +                body,
    +                "application/zip");
    +        // verify that the datastore was created successfully
    +        DataStoreInfo ds = cat.getDataStoreByName("gs", "san_andres_y_providencia");
    +        assertNotNull(ds);
    +        assertEquals(1, cat.getFeatureTypesByDataStore(ds).size());
    +        // verify that the external file was not deleted
    +        assertTrue("The external file was unexpectedly deleted", file.exists());
    +        // verify that the zip file was deleted from the data directory
    +        assertEquals(
    +                "The data directory file was not deleted",
    +                Resource.Type.UNDEFINED,
    +                getResourceLoader().get("data/gs/san_andres_y_providencia/test3.zip").getType());
    +    }
     }
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

7

News mentions

0

No linked articles in our index yet.