8066943: (fs) Path.relativize() gives incorrect result for ".."

Reviewed-by: prappo, bpb
This commit is contained in:
Alan Bateman 2016-08-25 10:01:20 +01:00
parent 7b8ef7554e
commit 97fca016ab
3 changed files with 1167 additions and 90 deletions
jdk
src/java.base
unix/classes/sun/nio/fs
windows/classes/sun/nio/fs
test/java/nio/file/Path

@ -252,6 +252,21 @@ class UnixPath implements Path {
return new UnixPath(getFileSystem(), new byte[0]);
}
// return true if this path has "." or ".."
private boolean hasDotOrDotDot() {
int n = getNameCount();
for (int i=0; i<n; i++) {
byte[] bytes = getName(i).path;
if ((bytes.length == 1 && bytes[0] == '.'))
return true;
if ((bytes.length == 2 && bytes[0] == '.') && bytes[1] == '.') {
return true;
}
}
return false;
}
@Override
public UnixFileSystem getFileSystem() {
return fs;
@ -405,80 +420,94 @@ class UnixPath implements Path {
@Override
public UnixPath relativize(Path obj) {
UnixPath other = toUnixPath(obj);
if (other.equals(this))
UnixPath child = toUnixPath(obj);
if (child.equals(this))
return emptyPath();
// can only relativize paths of the same type
if (this.isAbsolute() != other.isAbsolute())
if (this.isAbsolute() != child.isAbsolute())
throw new IllegalArgumentException("'other' is different type of Path");
// this path is the empty path
if (this.isEmpty())
return other;
return child;
int bn = this.getNameCount();
int cn = other.getNameCount();
UnixPath base = this;
if (base.hasDotOrDotDot() || child.hasDotOrDotDot()) {
base = base.normalize();
child = child.normalize();
}
int baseCount = base.getNameCount();
int childCount = child.getNameCount();
// skip matching names
int n = (bn > cn) ? cn : bn;
int n = Math.min(baseCount, childCount);
int i = 0;
while (i < n) {
if (!this.getName(i).equals(other.getName(i)))
if (!base.getName(i).equals(child.getName(i)))
break;
i++;
}
int dotdots = bn - i;
if (i < cn) {
// remaining name components in other
UnixPath remainder = other.subpath(i, cn);
if (dotdots == 0)
return remainder;
// other is the empty path
boolean isOtherEmpty = other.isEmpty();
// result is a "../" for each remaining name in base
// followed by the remaining names in other. If the remainder is
// the empty path then we don't add the final trailing slash.
int len = dotdots*3 + remainder.path.length;
if (isOtherEmpty) {
assert remainder.isEmpty();
len--;
}
byte[] result = new byte[len];
int pos = 0;
while (dotdots > 0) {
result[pos++] = (byte)'.';
result[pos++] = (byte)'.';
if (isOtherEmpty) {
if (dotdots > 1) result[pos++] = (byte)'/';
} else {
result[pos++] = (byte)'/';
}
dotdots--;
}
System.arraycopy(remainder.path, 0, result, pos, remainder.path.length);
return new UnixPath(getFileSystem(), result);
// remaining elements in child
UnixPath childRemaining;
boolean isChildEmpty;
if (i == childCount) {
childRemaining = emptyPath();
isChildEmpty = true;
} else {
// no remaining names in other so result is simply a sequence of ".."
byte[] result = new byte[dotdots*3 - 1];
int pos = 0;
while (dotdots > 0) {
result[pos++] = (byte)'.';
result[pos++] = (byte)'.';
// no tailing slash at the end
if (dotdots > 1)
result[pos++] = (byte)'/';
dotdots--;
}
return new UnixPath(getFileSystem(), result);
childRemaining = child.subpath(i, childCount);
isChildEmpty = childRemaining.isEmpty();
}
// matched all of base
if (i == baseCount) {
return childRemaining;
}
// the remainder of base cannot contain ".."
UnixPath baseRemaining = base.subpath(i, baseCount);
if (baseRemaining.hasDotOrDotDot()) {
throw new IllegalArgumentException("Unable to compute relative "
+ " path from " + this + " to " + obj);
}
if (baseRemaining.isEmpty())
return childRemaining;
// number of ".." needed
int dotdots = baseRemaining.getNameCount();
if (dotdots == 0) {
return childRemaining;
}
// result is a "../" for each remaining name in base followed by the
// remaining names in child. If the remainder is the empty path
// then we don't add the final trailing slash.
int len = dotdots*3 + childRemaining.path.length;
if (isChildEmpty) {
assert childRemaining.isEmpty();
len--;
}
byte[] result = new byte[len];
int pos = 0;
while (dotdots > 0) {
result[pos++] = (byte)'.';
result[pos++] = (byte)'.';
if (isChildEmpty) {
if (dotdots > 1) result[pos++] = (byte)'/';
} else {
result[pos++] = (byte)'/';
}
dotdots--;
}
System.arraycopy(childRemaining.path,0, result, pos,
childRemaining.path.length);
return new UnixPath(getFileSystem(), result);
}
@Override
public Path normalize() {
public UnixPath normalize() {
final int count = getNameCount();
if (count == 0 || isEmpty())
return this;

@ -375,57 +375,108 @@ class WindowsPath implements Path {
return (WindowsPath)path;
}
// return true if this path has "." or ".."
private boolean hasDotOrDotDot() {
int n = getNameCount();
for (int i=0; i<n; i++) {
String name = elementAsString(i);
if (name.length() == 1 && name.charAt(0) == '.')
return true;
if (name.length() == 2
&& name.charAt(0) == '.' && name.charAt(1) == '.')
return true;
}
return false;
}
@Override
public WindowsPath relativize(Path obj) {
WindowsPath other = toWindowsPath(obj);
if (this.equals(other))
WindowsPath child = toWindowsPath(obj);
if (this.equals(child))
return emptyPath();
// can only relativize paths of the same type
if (this.type != other.type)
if (this.type != child.type)
throw new IllegalArgumentException("'other' is different type of Path");
// can only relativize paths if root component matches
if (!this.root.equalsIgnoreCase(other.root))
if (!this.root.equalsIgnoreCase(child.root))
throw new IllegalArgumentException("'other' has different root");
// this path is the empty path
if (this.isEmpty())
return other;
return child;
int bn = this.getNameCount();
int cn = other.getNameCount();
WindowsPath base = this;
if (base.hasDotOrDotDot() || child.hasDotOrDotDot()) {
base = base.normalize();
child = child.normalize();
}
int baseCount = base.getNameCount();
int childCount = child.getNameCount();
// skip matching names
int n = (bn > cn) ? cn : bn;
int n = Math.min(baseCount, childCount);
int i = 0;
while (i < n) {
if (!this.getName(i).equals(other.getName(i)))
if (!base.getName(i).equals(child.getName(i)))
break;
i++;
}
// append ..\ for remaining names in the base
// remaining elements in child
WindowsPath childRemaining;
boolean isChildEmpty;
if (i == childCount) {
childRemaining = emptyPath();
isChildEmpty = true;
} else {
childRemaining = child.subpath(i, childCount);
isChildEmpty = childRemaining.isEmpty();
}
// matched all of base
if (i == baseCount) {
return childRemaining;
}
// the remainder of base cannot contain ".."
WindowsPath baseRemaining = base.subpath(i, baseCount);
if (baseRemaining.hasDotOrDotDot()) {
throw new IllegalArgumentException("Unable to compute relative "
+ " path from " + this + " to " + obj);
}
if (baseRemaining.isEmpty())
return childRemaining;
// number of ".." needed
int dotdots = baseRemaining.getNameCount();
if (dotdots == 0) {
return childRemaining;
}
StringBuilder result = new StringBuilder();
for (int j=i; j<bn; j++) {
for (int j=0; j<dotdots; j++) {
result.append("..\\");
}
// append remaining names in child
if (!other.isEmpty()) {
for (int j=i; j<cn; j++) {
result.append(other.getName(j).toString());
if (!isChildEmpty) {
for (int j=0; j<childRemaining.getNameCount(); j++) {
result.append(childRemaining.getName(j).toString());
result.append("\\");
}
}
// drop trailing slash in result
// drop trailing slash
result.setLength(result.length()-1);
return createFromNormalizedPath(getFileSystem(), result.toString());
}
@Override
public Path normalize() {
public WindowsPath normalize() {
final int count = getNameCount();
if (count == 0 || isEmpty())
return this;

File diff suppressed because it is too large Load Diff