You can use Path.normalize() to strip out ".." elements (and their preceding elements) from a path — e.g. it'll turn "a/b/../c" into "a/c". Note that it won't strip out a ".." at the beginning of a path, since there's no preceding directory component for it to remove as well. So if you're going to prepend another path, do that first, then normalize the result.
You can also use Path.startsWith(Path) to check whether one path is a descendant of another. And Path.isAbsolute() tells you, unsurprisingly, whether a path is absolute or relative.
Here's how I'd process the untrusted paths coming into the API:
/**
* Resolves an untrusted user-specified path against the API's base directory.
* Paths that try to escape the base directory are rejected.
*
* @param baseDirPath the absolute path of the base directory that all
user-specified paths should be within
* @param userPath the untrusted path provided by the API user, expected to be
relative to {@code baseDirPath}
*/
public Path resolvePath(final Path baseDirPath, final Path userPath) {
if (!baseDirPath.isAbsolute()) {
throw new IllegalArgumentException("Base path must be absolute");
}
if (userPath.isAbsolute()) {
throw new IllegalArgumentException("User path must be relative");
}
// Join the two paths together, then normalize so that any ".." elements
// in the userPath can remove parts of baseDirPath.
// (e.g. "/foo/bar/baz" + "../attack" -> "/foo/bar/attack")
final Path resolvedPath = baseDirPath.resolve(userPath).normalize();
// Make sure the resulting path is still within the required directory.
// (In the example above, "/foo/bar/attack" is not.)
if (!resolvedPath.startsWith(baseDirPath)) {
throw new IllegalArgumentException("User path escapes the base path");
}
return resolvedPath;
}