This is an automated email from the ASF dual-hosted git repository. reschke pushed a commit to branch OAK-11657c in repository https://gitbox.apache.org/repos/asf/jackrabbit-oak.git
commit 0ec55dda83e9fa2ffa2064d4dd3b33d6df843fef Author: Julian Reschke <[email protected]> AuthorDate: Thu May 22 20:57:19 2025 +0100 OAK-11657: JackrabbitSession.getExpandedName/Path need to return stable expanded names, getExpandedName needs to fail for broken namespace names --- .../apache/jackrabbit/oak/namepath/NameMapper.java | 4 +-- .../oak/namepath/impl/GlobalNameMapper.java | 28 ++++++++++------ .../apache/jackrabbit/api/JackrabbitSession.java | 9 +++++- .../jackrabbit/oak/jcr/session/SessionImpl.java | 7 +++- .../oak/jcr/session/JackrabbitSessionTest.java | 37 +++++++++++++++++++--- 5 files changed, 67 insertions(+), 18 deletions(-) diff --git a/oak-core-spi/src/main/java/org/apache/jackrabbit/oak/namepath/NameMapper.java b/oak-core-spi/src/main/java/org/apache/jackrabbit/oak/namepath/NameMapper.java index 94c8d9bf84..dc94f4d706 100644 --- a/oak-core-spi/src/main/java/org/apache/jackrabbit/oak/namepath/NameMapper.java +++ b/oak-core-spi/src/main/java/org/apache/jackrabbit/oak/namepath/NameMapper.java @@ -87,11 +87,11 @@ public interface NameMapper { * @param oakName Oak name * @return JCR name in expanded form * @since Oak 1.78.0 - * @throws IllegalStateException in case the namespace URI for the given Oak name cannot be resolved + * @throws IllegalStateException in case the namespace URI for the given + * Oak name cannot be resolved or is invalid * * @see <a href="https://s.apache.org/jcr-2.0-spec/3_Repository_Model.html#3.2.5.1%20Expanded%20Form">JCR 2.0, 3.2.5.1 Expanded Form</a> */ @NotNull String getExpandedJcrName(@NotNull String oakName); - } diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/namepath/impl/GlobalNameMapper.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/namepath/impl/GlobalNameMapper.java index 546d9aab13..ec26596ee5 100644 --- a/oak-core/src/main/java/org/apache/jackrabbit/oak/namepath/impl/GlobalNameMapper.java +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/namepath/impl/GlobalNameMapper.java @@ -35,6 +35,7 @@ import static org.apache.jackrabbit.oak.spi.namespace.NamespaceConstants.REP_URI import java.util.Map; import java.util.Map.Entry; +import javax.jcr.NamespaceException; import javax.jcr.RepositoryException; import org.apache.jackrabbit.oak.api.Root; @@ -65,16 +66,17 @@ public class GlobalNameMapper implements NameMapper { return name.startsWith(":"); } + private static boolean isValidNamespaceName(String namespace) { + // the empty namespace and "internal" are valid as well, otherwise it always contains a colon (as it is a URI) + // compare with RFC 3986, Section 3 (https://datatracker.ietf.org/doc/html/rfc3986#section-3) + return namespace.isEmpty() || namespace.equals(NamespaceConstants.NAMESPACE_REP) || namespace.contains(":"); + } + protected static boolean isExpandedName(String name) { if (name.startsWith("{")) { int brace = name.indexOf('}', 1); if (brace != -1) { - String namespace = name.substring(1, brace); - // the empty namespace and "internal" are valid as well, otherwise it always contains a colon (as it is a URI) - // compare with RFC 3986, Section 3 (https://datatracker.ietf.org/doc/html/rfc3986#section-3) - if (namespace.isEmpty() || namespace.equals(NamespaceConstants.NAMESPACE_REP)|| namespace.indexOf(':') != -1) { - return true; - } + return isValidNamespaceName(name.substring(1, brace)); } } return false; @@ -143,17 +145,23 @@ public class GlobalNameMapper implements NameMapper { int colon = oakName.indexOf(':'); if (colon > 0) { String oakPrefix = oakName.substring(0, colon); - // local mapping must take precedence... - uri = getSessionLocalMappings().get(oakPrefix); + uri = getNamespacesProperty(oakPrefix); + // global mapping must take precedence... if (uri == null) { - // ...over global mappings - uri = getNamespacesProperty(oakPrefix); + // ...over local mappings + uri = getSessionLocalMappings().get(oakPrefix); } if (uri == null) { throw new IllegalStateException( "No namespace mapping found for " + oakName); } localName = oakName.substring(colon + 1); + // check namespace name for validity in Oak + if (!isValidNamespaceName(uri)) { + throw new IllegalStateException( + new NamespaceException("Cannot determine expanded name for '" + oakName + + "' as registered namespace name '" + uri + "' is invalid")); + } } else { uri = ""; localName = oakName; diff --git a/oak-jackrabbit-api/src/main/java/org/apache/jackrabbit/api/JackrabbitSession.java b/oak-jackrabbit-api/src/main/java/org/apache/jackrabbit/api/JackrabbitSession.java index db1bf7cb0d..f7e1f71187 100644 --- a/oak-jackrabbit-api/src/main/java/org/apache/jackrabbit/api/JackrabbitSession.java +++ b/oak-jackrabbit-api/src/main/java/org/apache/jackrabbit/api/JackrabbitSession.java @@ -25,6 +25,7 @@ import javax.jcr.Node; import javax.jcr.Property; import javax.jcr.Session; import javax.jcr.AccessDeniedException; +import javax.jcr.NamespaceException; import javax.jcr.RepositoryException; import javax.jcr.UnsupportedRepositoryOperationException; @@ -282,7 +283,10 @@ public interface JackrabbitSession extends Session { * Returns the expanded name of the given {@code Item}. * @param item the item for which to retrieve the name * @return the name of the item in expanded form. - * @throws RepositoryException if another error occurs. + * @throws NamespaceException when no expanded name can + * be determined (for instance, if the registered namespace + * (name) is invalid) + * @throws RepositoryException if another error occurs * @since 1.78.0 * @see <a href="https://s.apache.org/jcr-2.0-spec/3_Repository_Model.html#3.2.5.1%20Expanded%20Form">JCR 2.0, 3.2.5.1 Expanded Form</a> */ @@ -292,6 +296,9 @@ public interface JackrabbitSession extends Session { * Returns the expanded path of the given {@code Item}. * @param item the item for which to retrieve the name * @return the path of the item in expanded form. + * @throws NamespaceException when no expanded name can + * be determined (for instance, if the registered namespace + * (name) is invalid) * @throws RepositoryException if another error occurs. * @since 1.78.0 * @see <a href="https://s.apache.org/jcr-2.0-spec/3_Repository_Model.html#3.2.5.1%20Expanded%20Form">JCR 2.0, 3.2.5.1 Expanded Form</a> diff --git a/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/SessionImpl.java b/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/SessionImpl.java index 5eb76868cb..6f37d57b0b 100644 --- a/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/SessionImpl.java +++ b/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/SessionImpl.java @@ -860,7 +860,12 @@ public class SessionImpl implements JackrabbitSession { ItemImpl<?> itemImpl = checkItemImpl(item); return itemImpl.sessionContext.getExpandedJcrName(itemImpl.getOakName()); } catch (IllegalStateException e) { - throw new RepositoryException("Namespace exception " + e.getMessage()); + // unwrap RepositoryException when available + if (e.getCause() instanceof RepositoryException) { + throw (RepositoryException) e.getCause(); + } else { + throw new RepositoryException("Namespace exception " + e.getMessage()); + } } } diff --git a/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/session/JackrabbitSessionTest.java b/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/session/JackrabbitSessionTest.java index f7c86562ad..926502a382 100644 --- a/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/session/JackrabbitSessionTest.java +++ b/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/session/JackrabbitSessionTest.java @@ -24,10 +24,13 @@ import org.jetbrains.annotations.Nullable; import javax.jcr.GuestCredentials; import javax.jcr.Item; +import javax.jcr.NamespaceException; import javax.jcr.Node; import javax.jcr.Property; import javax.jcr.RepositoryException; +import java.util.UUID; + import static org.mockito.Mockito.mock; public class JackrabbitSessionTest extends AbstractJCRTest { @@ -86,20 +89,46 @@ public class JackrabbitSessionTest extends AbstractJCRTest { assertEquals("{}testroot", s.getExpandedName(testRootNode)); Node n = testRootNode.addNode("test:bar"); assertEquals("{http://www.apache.org/jackrabbit/test}bar", s.getExpandedName(n)); - // now remap namespace uri + // now remap namespace uri - should not affect expanded name + assertEquals("prefix 'test' has unexpected mapping", + "http://www.apache.org/jackrabbit/test", s.getNamespaceURI("test")); s.setNamespacePrefix("test", "urn:foo"); - assertEquals("{urn:foo}bar", s.getExpandedName(n)); + assertEquals("{http://www.apache.org/jackrabbit/test}bar", s.getExpandedName(n)); // use special namespace uri n = testRootNode.addNode("rep:bar"); assertEquals("{internal}bar", s.getExpandedName(n)); } + public void testGetExpandedNameBrokenNamespace() throws RepositoryException { + // empty namespace uri + assertEquals("{}testroot", s.getExpandedName(testRootNode)); + + String randomNamespacePrefix = "prefix-" + UUID.randomUUID(); + // below is not a valid namespace a.k.a. namespace URI + String randomNamespaceName = "name-" + UUID.randomUUID(); + + // register broken namespace prefix/name mapping + s.getWorkspace().getNamespaceRegistry().registerNamespace(randomNamespacePrefix, randomNamespaceName); + + try { + Node n = testRootNode.addNode(randomNamespacePrefix + ":qux"); + + // there is no expanded name, thus we expect an exception here + String result = s.getExpandedName(n); + fail("there is no expanded name in this case, so we expect the call to fail, however we get: " + result); + } catch (NamespaceException ex) { + // expected + } finally { + s.getWorkspace().getNamespaceRegistry().unregisterNamespace(randomNamespacePrefix); + } + } + public void testGetExpandedPath() throws RepositoryException { assertEquals("/{}testroot", s.getExpandedPath(testRootNode)); Node n = testRootNode.addNode("test:bar").addNode("rep:bar"); assertEquals("/{}testroot/{http://www.apache.org/jackrabbit/test}bar/{internal}bar", s.getExpandedPath(n)); - // now remap namespace uri + // now remap namespace uri - should not affect expanded name s.setNamespacePrefix("test", "urn:foo"); - assertEquals("/{}testroot/{urn:foo}bar/{internal}bar", s.getExpandedPath(n)); + assertEquals("/{}testroot/{http://www.apache.org/jackrabbit/test}bar/{internal}bar", s.getExpandedPath(n)); } } \ No newline at end of file
