Skip to content

Commit

Permalink
Add support for nested jars
Browse files Browse the repository at this point in the history
Daffodil supports resolving schemaLocations relative to a jar URI, which
only looks for the schemaLocation in that same jar. Such jar URI's
usually look like this:

  jar:file:/path/to/some.jar!/path/to/schema.dfdl.xsd

To resolve a path relative to this, we split this into its components: a
jar part and the resource path part inside that jar and resolve the
schemaLocation relative to the path part.

Although Java does not support loading resources within nested jars,
tools like Spring Boot have added such support. This can result in jar
URIs that look like this:

  jar:file:/path/to/outer.jar!/path/to/inner.jar!/path/to/schema.dfdl.xsd

This breaks our current code because we expect there to only be two
components, but we end up with three because of the multiple exclamation
points.

To fix this and to support nested jars, this changes the logic to only
split on the last exclamation point. Everything before that is
considered the jar component and everything after is considered the path
component in that jar, which could be a nested jar. We then resolve
relative paths to this path component as usual and look for the result
to be in that same jar component.

DAFFODIL-2892
  • Loading branch information
stevedlawrence committed Apr 23, 2024
1 parent d0bb41c commit a853a2f
Showing 1 changed file with 29 additions and 10 deletions.
39 changes: 29 additions & 10 deletions daffodil-lib/src/main/scala/org/apache/daffodil/lib/util/Misc.scala
Expand Up @@ -159,6 +159,26 @@ object Misc {
}
}

/**
* Split a jar URI into its two component parts: 1) the path to a jar file 2) the resource
* path within that jar file. By definition, these two parts are delimited by the last
* occurrence of an exclamation point. If the resource path part contains an exclamation point
* it should have already been escaped using "%21", which is the URI escape code for an
* exclamation point.
*
* Note that it is important that we look for the last index of an exclamation point, since it
* is possible some tools have added support for nested jars, and could have multiple
* exclamation points for each nesting.
*/
def splitJarUri(uri: URI): (String, String) = {
Assert.invariant(uri.getScheme == "jar")
var uriStr = uri.toString
val exclamIdx = uriStr.lastIndexOf("!")
val jarPart = uriStr.substring(0, exclamIdx)
val pathPart = uriStr.substring(exclamIdx + 1)
(jarPart, pathPart)
}

/**
* Java 20 deprecated the 2-arg URL constructor which worked to create relative URIs
* within the same Jar file.
Expand All @@ -172,23 +192,19 @@ object Misc {
*
* `jar:file:/..absolute path to jar file.jar!/absolute path from root inside jar to file``
*
* We split at the !/, make a relative path on just the inside-jar-file part, then glue
* back together.
*
* We split this URI into its component parts, make a relative path on just the resource path
* inside the jar, then glue back together.
*
* @param contextURI
* @param relPath
* @return Some(uri) for an existing relative path within the same jar file, or None if it does not exist.
*/
def optRelativeJarFileURI(contextURI: URI, relPath: String): Option[URI] = {
val parts = contextURI.toString.split("\\!\\/")
Assert.invariant(parts.length == 2)
val jarPart = parts(0)
val pathPart = parts(1)
Assert.invariant(pathPart ne null)
val (jarPart, pathPart) = Misc.splitJarUri(contextURI)
Assert.invariant(pathPart.startsWith("/"))
val contextURIPathOnly = URI.create(pathPart)
val resolvedURIPathOnly = contextURIPathOnly.resolve(relPath)
val newJarPathURI = URI.create(jarPart + "!/" + resolvedURIPathOnly.toString)
val newJarPathURI = URI.create(jarPart + "!" + resolvedURIPathOnly.toString)
try {
newJarPathURI.toURL.openStream().close()
// that worked, so we can open it so it exists.
Expand Down Expand Up @@ -711,7 +727,10 @@ object Misc {
*/
def uriToDiagnosticFile(uri: URI): File = {
uri.getScheme match {
case "jar" => Paths.get(uri.toString.split("\\.jar!").last).toFile
case "jar" => {
val (_, pathPart) = Misc.splitJarUri(uri)
Paths.get(pathPart).toFile
}
case "file" => Paths.get(uri).toFile
case _ => Paths.get(uri.getPath).toFile
}
Expand Down

0 comments on commit a853a2f

Please sign in to comment.