/*
* JBoss, Home of Professional Open Source.
* Copyright 2014 Red Hat, Inc., and individual contributors
* as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.undertow.util;
import java.util.ArrayList;
import java.util.List;
Author: Stuart Douglas
/**
* @author Stuart Douglas
*/
public class CanonicalPathUtils {
System property the revert to legacy behaviour of ignoring backslash
/**
* System property the revert to legacy behaviour of ignoring backslash
*/
private static final boolean DONT_CANONICALIZE_BACKSLASH = Boolean.parseBoolean("io.undertow.DONT_CANONICALIZE_BACKSLASH");
public static String canonicalize(final String path) {
int state = START;
for (int i = path.length() - 1; i >= 0; --i) {
final char c = path.charAt(i);
switch (c) {
case '/':
if (state == FIRST_SLASH) {
return realCanonicalize(path, i + 1, FIRST_SLASH);
} else if (state == ONE_DOT) {
return realCanonicalize(path, i + 2, FIRST_SLASH);
} else if (state == TWO_DOT) {
return realCanonicalize(path, i + 3, FIRST_SLASH);
}
state = FIRST_SLASH;
break;
case '.':
if (state == FIRST_SLASH || state == START || state == FIRST_BACKSLASH) {
state = ONE_DOT;
} else if(state == ONE_DOT) {
state = TWO_DOT;
} else {
state = NORMAL;
}
break;
case '\\':
if(!DONT_CANONICALIZE_BACKSLASH) {
if (state == FIRST_BACKSLASH) {
return realCanonicalize(path, i + 1, FIRST_BACKSLASH);
} else if (state == ONE_DOT) {
return realCanonicalize(path, i + 2, FIRST_BACKSLASH);
} else if (state == TWO_DOT) {
return realCanonicalize(path, i + 3, FIRST_BACKSLASH);
}
state = FIRST_BACKSLASH;
break;
}
//fall through
default:
state = NORMAL;
break;
}
}
return path;
}
static final int START = -1;
static final int NORMAL = 0;
static final int FIRST_SLASH = 1;
static final int ONE_DOT = 2;
static final int TWO_DOT = 3;
static final int FIRST_BACKSLASH = 4;
private static String realCanonicalize(final String path, final int lastDot, final int initialState) {
int state = initialState;
int eatCount = 0;
int tokenEnd = path.length();
final List<String> parts = new ArrayList<>();
for (int i = lastDot - 1; i >= 0; --i) {
final char c = path.charAt(i);
switch (state) {
case NORMAL: {
if (c == '/') {
state = FIRST_SLASH;
if (eatCount > 0) {
--eatCount;
tokenEnd = i;
}
} else if (c == '\\' && !DONT_CANONICALIZE_BACKSLASH) {
state = FIRST_BACKSLASH;
if (eatCount > 0) {
--eatCount;
tokenEnd = i;
}
}
break;
}
case FIRST_SLASH: {
if (c == '.') {
state = ONE_DOT;
} else if (c == '/') {
if (eatCount > 0) {
--eatCount;
tokenEnd = i;
} else {
parts.add(path.substring(i + 1, tokenEnd));
tokenEnd = i;
}
} else {
state = NORMAL;
}
break;
}
case FIRST_BACKSLASH: {
if (c == '.') {
state = ONE_DOT;
} else if (c == '\\') {
if (eatCount > 0) {
--eatCount;
tokenEnd = i;
} else {
parts.add(path.substring(i + 1, tokenEnd));
tokenEnd = i;
}
} else {
state = NORMAL;
}
break;
}
case ONE_DOT: {
if (c == '.') {
state = TWO_DOT;
} else if (c == '/' || (c == '\\' && !DONT_CANONICALIZE_BACKSLASH)) {
if (i + 2 != tokenEnd) {
parts.add(path.substring(i + 2, tokenEnd));
}
tokenEnd = i;
state = c == '/' ? FIRST_SLASH : FIRST_BACKSLASH;
} else {
state = NORMAL;
}
break;
}
case TWO_DOT: {
if (c == '/' || (c == '\\' && !DONT_CANONICALIZE_BACKSLASH)) {
if (i + 3 != tokenEnd) {
parts.add(path.substring(i + 3, tokenEnd));
}
tokenEnd = i;
eatCount++;
state = c == '/' ? FIRST_SLASH : FIRST_BACKSLASH;
} else {
state = NORMAL;
}
}
}
}
final StringBuilder result = new StringBuilder();
if (tokenEnd != 0) {
result.append(path.substring(0, tokenEnd));
}
for (int i = parts.size() - 1; i >= 0; --i) {
result.append(parts.get(i));
}
if(result.length() == 0) {
return "/";
}
return result.toString();
}
private CanonicalPathUtils() {
}
}