Index: extensions/modeshape-connector-filesystem/src/main/java/org/modeshape/connector/filesystem/FileSystemI18n.java =================================================================== --- extensions/modeshape-connector-filesystem/src/main/java/org/modeshape/connector/filesystem/FileSystemI18n.java (revision 2505) +++ extensions/modeshape-connector-filesystem/src/main/java/org/modeshape/connector/filesystem/FileSystemI18n.java (working copy) @@ -84,6 +84,8 @@ public final class FileSystemI18n { public static I18n parentIsReadOnly; public static I18n fileAlreadyExists; public static I18n couldNotCreateFile; + public static I18n cannotRenameFileToExcludedPattern; + public static I18n cannotCreateFileAsExcludedPattern; public static I18n unsupportedPrimaryType; public static I18n invalidNameForResource; public static I18n invalidPathForResource; Index: extensions/modeshape-connector-filesystem/src/main/java/org/modeshape/connector/filesystem/FileSystemRepository.java =================================================================== --- extensions/modeshape-connector-filesystem/src/main/java/org/modeshape/connector/filesystem/FileSystemRepository.java (revision 2505) +++ extensions/modeshape-connector-filesystem/src/main/java/org/modeshape/connector/filesystem/FileSystemRepository.java (working copy) @@ -29,6 +29,7 @@ import java.util.UUID; import org.modeshape.common.i18n.I18n; import org.modeshape.graph.ExecutionContext; import org.modeshape.graph.JcrNtLexicon; +import org.modeshape.graph.Location; import org.modeshape.graph.connector.RepositoryContext; import org.modeshape.graph.connector.base.PathNode; import org.modeshape.graph.connector.base.PathTransaction; @@ -36,6 +37,8 @@ import org.modeshape.graph.connector.base.Processor; import org.modeshape.graph.connector.base.Repository; import org.modeshape.graph.connector.base.Transaction; import org.modeshape.graph.observe.Observer; +import org.modeshape.graph.property.InvalidPathException; +import org.modeshape.graph.property.Name; import org.modeshape.graph.property.Path; import org.modeshape.graph.property.Property; import org.modeshape.graph.property.Path.Segment; @@ -189,6 +192,43 @@ public class FileSystemRepository extends Repository properties ) { + String newFileName = name.getLocalName(); + if (!source.filenameFilter(true).accept(null, newFileName)) { + throw new InvalidPathException(FileSystemI18n.cannotCreateFileAsExcludedPattern.text(newFileName, + workspace.getName())); + } + + return super.addChild(workspace, parent, name, index, uuid, properties); + } + + @Override + public Location addChild( FileSystemWorkspace workspace, + PathNode parent, + PathNode originalChild, + PathNode beforeOtherChild, + Name desiredName ) { + + if (desiredName != null) { + String newFileName = desiredName.getLocalName(); + if (!source.filenameFilter(true).accept(null, newFileName)) { + String oldFileName = originalChild.getName().getString(this.context.getNamespaceRegistry()); + throw new InvalidPathException(FileSystemI18n.cannotRenameFileToExcludedPattern.text(oldFileName, + newFileName, + workspace.getName())); + } + } + + return super.addChild(workspace, parent, originalChild, beforeOtherChild, desiredName); + } } /** Index: extensions/modeshape-connector-filesystem/src/main/resources/org/modeshape/connector/filesystem/FileSystemI18n.properties =================================================================== --- extensions/modeshape-connector-filesystem/src/main/resources/org/modeshape/connector/filesystem/FileSystemI18n.properties (revision 2505) +++ extensions/modeshape-connector-filesystem/src/main/resources/org/modeshape/connector/filesystem/FileSystemI18n.properties (working copy) @@ -73,6 +73,8 @@ parentIsReadOnly = The parent node at path "{0}" in workspace "{1}" in {2} canno unableToCreateWorkspaces = {0} does not allow creating new workspaces (request was to create "{1}") fileAlreadyExists = The path "{0}" in workspace "{1}" in {2} already exists. couldNotCreateFile = Error creating the path "{0}" in workspace "{1}" in {2}: {3} +cannotCreateFileAsExcludedPattern = Cannot create the file named "{0}" in workspace "{1}" because it matches the exclusion pattern +cannotRenameFileToExcludedPattern = Cannot rename the file named "{0}" to "{1}" in workspace "{2}" because it matches the exclusion pattern unsupportedPrimaryType = Primary type "{3}" for path "{0}" in workspace "{1}" in {2} is not valid for the file system connector. Valid primary types are nt\:file, nt\:folder, nt\:resource, and dna\:resouce. invalidNameForResource = Invalid node name "{3}" for node at path "{0}" in workspace "{1}" in {2}. The name of nodes with primary type nt:resource or dna:resource must be "jcr:content". invalidPathForResource = Invalid parent type for node at path "{0}" in workspace "{1}" in {2}. The parent node for nodes with primary type nt:resource or dna:resource must be of type nt:file. Index: extensions/modeshape-connector-filesystem/src/test/java/org/modeshape/connector/filesystem/FileSystemConnectorWritableTest.java =================================================================== --- extensions/modeshape-connector-filesystem/src/test/java/org/modeshape/connector/filesystem/FileSystemConnectorWritableTest.java (revision 2505) +++ extensions/modeshape-connector-filesystem/src/test/java/org/modeshape/connector/filesystem/FileSystemConnectorWritableTest.java (working copy) @@ -42,6 +42,7 @@ import org.modeshape.graph.Graph.Batch; import org.modeshape.graph.connector.RepositorySource; import org.modeshape.graph.connector.RepositorySourceException; import org.modeshape.graph.connector.test.AbstractConnectorTest; +import org.modeshape.graph.property.InvalidPathException; import org.modeshape.graph.request.InvalidRequestException; public class FileSystemConnectorWritableTest extends AbstractConnectorTest { @@ -620,6 +621,25 @@ public class FileSystemConnectorWritableTest extends AbstractConnectorTest { assertFalse(new File(testWorkspaceRoot, "a").exists()); } + @Test( expected = InvalidPathException.class ) + public void shouldNotAllowWriteToExcludedFilename() { + graph.create("/.svn").and(); + } + + @Test( expected = InvalidPathException.class ) + public void shouldNotAllowMoveToExcludedFilename() { + graph.create("/test").and(); + + graph.move("/test").as(".svn").into("/"); + } + + @Test( expected = InvalidPathException.class ) + public void shouldNotAllowCopyToExcludedFilename() { + graph.create("/test").and(); + + graph.copy("/test").to("/.svn"); + } + protected void assertContents( File file, String contents ) { assertTrue(file.exists()); Index: modeshape-integration-tests/src/test/java/org/modeshape/test/integration/FileSystemFilterTest.java new file mode 100644 =================================================================== --- /dev/null (revision 2505) +++ modeshape-integration-tests/src/test/java/org/modeshape/test/integration/FileSystemFilterTest.java (working copy) @@ -0,0 +1,109 @@ +package org.modeshape.test.integration; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.util.concurrent.TimeUnit; +import javax.jcr.Node; +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import javax.jcr.ValueFactory; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.modeshape.common.FixFor; +import org.modeshape.common.collection.Problem; +import org.modeshape.common.util.FileUtil; +import org.modeshape.connector.filesystem.FileSystemSource; +import org.modeshape.jcr.JcrConfiguration; +import org.modeshape.jcr.JcrEngine; +import org.modeshape.jcr.JcrRepository; + +public class FileSystemFilterTest { + + private static final String SOURCE_NAME = "Source"; + private static final String REPO_NAME = "Repository"; + private static final String STORAGE_PATH = "./target/scratch"; + private static final String EXCLUSION_PATTERN = ".*mode"; + + private JcrEngine engine; + private Session session; + private ValueFactory vf; + private File scratchSpace; + + @Before + public void beforeEach() throws Exception { + scratchSpace = new File(STORAGE_PATH); + + if (!scratchSpace.exists()) { + scratchSpace.mkdir(); + } + + JcrConfiguration config = new JcrConfiguration(); + + config.repositorySource(SOURCE_NAME).usingClass(FileSystemSource.class).setProperty("exclusionPattern", EXCLUSION_PATTERN).setProperty("workspaceRootPath", + STORAGE_PATH).setProperty("defaultWorkspaceName", + "default").setProperty("updatesAllowed", + true); + config.repository(REPO_NAME).setSource(SOURCE_NAME).setOption(JcrRepository.Option.ANONYMOUS_USER_ROLES, "readwrite"); + + engine = config.build(); + engine.start(); + + if (engine.getProblems().hasProblems()) { + for (Problem problem : engine.getProblems()) { + System.err.println(problem.getMessageString()); + } + } + + JcrRepository repo = engine.getRepository(REPO_NAME); + session = repo.login("default"); + vf = session.getValueFactory(); + } + + @After + public void afterEach() throws Exception { + if (session != null) session.logout(); + engine.shutdownAndAwaitTermination(3, TimeUnit.SECONDS); + + FileUtil.delete(scratchSpace); + } + + @FixFor( "MODE-986" ) + @Test( expected = RepositoryException.class ) + public void shouldNotBeAbleToCreateFileWithFilteredName() throws Exception { + Node root = session.getRootNode(); + Node file = root.addNode("createfile.mode", "nt:file"); + Node content = file.addNode("jcr:content", "nt:resource"); + content.setProperty("jcr:data", vf.createBinary(new ByteArrayInputStream("Write 1".getBytes()))); + + session.save(); + + } + + @FixFor( "MODE-986" ) + @Test( expected = RepositoryException.class ) + public void shouldNotBeAbleToRenameToFileWithFilteredName() throws Exception { + Node root = session.getRootNode(); + Node file = root.addNode("moveSource.txt", "nt:file"); + Node content = file.addNode("jcr:content", "nt:resource"); + content.setProperty("jcr:data", vf.createBinary(new ByteArrayInputStream("Write 1".getBytes()))); + session.save(); + + session.move("/moveSource.txt", "/createfile.mode"); + session.save(); + } + + @FixFor( "MODE-986" ) + @Test( expected = RepositoryException.class ) + public void shouldNotBeAbleToCopyToFileWithFilteredName() throws Exception { + Node root = session.getRootNode(); + Node file = root.addNode("copySource.txt", "nt:file"); + Node content = file.addNode("jcr:content", "nt:resource"); + content.setProperty("jcr:data", vf.createBinary(new ByteArrayInputStream("Write 1".getBytes()))); + session.save(); + + session.getWorkspace().copy("/copySource.txt", "/createfile.mode"); + session.save(); + + } +} Index: modeshape-jcr/src/main/java/org/modeshape/jcr/JcrWorkspace.java =================================================================== --- modeshape-jcr/src/main/java/org/modeshape/jcr/JcrWorkspace.java (revision 2505) +++ modeshape-jcr/src/main/java/org/modeshape/jcr/JcrWorkspace.java (working copy) @@ -65,6 +65,7 @@ import org.modeshape.graph.connector.RepositoryConnectionFactory; import org.modeshape.graph.connector.RepositorySource; import org.modeshape.graph.connector.RepositorySourceException; import org.modeshape.graph.connector.UuidAlreadyExistsException; +import org.modeshape.graph.property.InvalidPathException; import org.modeshape.graph.property.Name; import org.modeshape.graph.property.NameFactory; import org.modeshape.graph.property.NamespaceRegistry; @@ -672,6 +673,8 @@ class JcrWorkspace implements Workspace { throw new RepositoryException(e.getLocalizedMessage(), e); } catch (AccessControlException ace) { throw new AccessDeniedException(ace); + } catch (InvalidPathException e) { + throw new RepositoryException(e.getLocalizedMessage(), e); } }