/* * ============================================================================= * * Copyright (c) 2011-2016, The THYMELEAF team (http://www.thymeleaf.org) * * 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 org.thymeleaf.templateresource; import org.thymeleaf.util.StringUtils; /** * <p> * Utility methods used by several implementations of {@link ITemplateResource} * </p> * * @author Daniel Fernández * @since 3.0.0 * */ final class TemplateResourceUtils { static String cleanPath(final String path) { if (path == null) { return null; } // First replace Windows folder separators with UNIX's String unixPath = StringUtils.replace(path, "\\", "/"); // Some shortcuts, just in case this is empty or simply has no '.' or '..' (and no double-/ we should simplify) if (unixPath.length() == 0 || (unixPath.indexOf("/.") < 0 && unixPath.indexOf("//") < 0)) { return unixPath; } // We make sure path starts with '/' in order to simplify the algorithm boolean rootBased = (unixPath.charAt(0) == '/'); unixPath = (rootBased? unixPath : ('/' + unixPath)); // We will traverse path in reverse order, looking for '.' and '..' tokens and processing them final StringBuilder strBuilder = new StringBuilder(unixPath.length()); int index = unixPath.lastIndexOf('/'); int pos = unixPath.length() - 1; int topCount = 0; while (index >= 0) { // will always be 0 for the last iteration, as we prefixed the path with '/' final int tokenLen = pos - index; if (tokenLen > 0) { if (tokenLen == 1 && unixPath.charAt(index + 1) == '.') { // Token is '.' -> just ignore it } else if (tokenLen == 2 && unixPath.charAt(index + 1) == '.' && unixPath.charAt(index + 2) == '.') { // Token is '..' -> count as a 'top' operation topCount++; } else if (topCount > 0){ // Whatever comes here has been removed by a 'top' operation, so ignore topCount--; } else { // Token is OK, just add (with its corresponding '/') strBuilder.insert(0, unixPath, index, (index + tokenLen + 1)); } } pos = index - 1; index = (pos >= 0? unixPath.lastIndexOf('/', pos) : -1); } // Add all 'top' tokens appeared at the very beginning of the path for (int i = 0; i < topCount; i++) { strBuilder.insert(0, "/.."); } // Perform last cleanup if (!rootBased) { strBuilder.deleteCharAt(0); } return strBuilder.toString(); } static String computeRelativeLocation(final String location, final String relativeLocation) { final int separatorPos = location.lastIndexOf('/'); if (separatorPos != -1) { final StringBuilder relativeBuilder = new StringBuilder(location.length() + relativeLocation.length()); relativeBuilder.append(location, 0, separatorPos); if (relativeLocation.charAt(0) != '/') { relativeBuilder.append('/'); } relativeBuilder.append(relativeLocation); return relativeBuilder.toString(); } return relativeLocation; } static String computeBaseName(final String path) { if (path == null || path.length() == 0) { return null; } // First remove a trailing '/' if it exists final String basePath = (path.charAt(path.length() - 1) == '/'? path.substring(0,path.length() - 1) : path); final int slashPos = basePath.lastIndexOf('/'); if (slashPos != -1) { final int dotPos = basePath.lastIndexOf('.'); if (dotPos != -1 && dotPos > slashPos + 1) { return basePath.substring(slashPos + 1, dotPos); } return basePath.substring(slashPos + 1); } else { final int dotPos = basePath.lastIndexOf('.'); if (dotPos != -1) { return basePath.substring(0, dotPos); } } return (basePath.length() > 0? basePath : null); } private TemplateResourceUtils() { super(); } }