/*
* 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
*/
public class
CanonicalPathUtils {
/**
* 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() {
}
}