/* * See the NOTICE file distributed with this work for additional * information regarding copyright ownership. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.xwiki.model.internal.reference; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import javax.inject.Named; import javax.inject.Singleton; import org.xwiki.component.annotation.Component; import org.xwiki.model.reference.EntityReference; /** * Generate a path representation of an entity reference (eg "Wiki/Space/Page" for a Document Reference in the * "wiki" Wiki, the "space" Space and the "page" Page). * * @version $Id: 9cf7d5c9eec70ceb54490b667c1f3660d050170d $ * @since 3.0M2 */ @Component @Named("path") @Singleton public class PathStringEntityReferenceSerializer extends AbstractStringEntityReferenceSerializer { /** * {@inheritDoc} * <p> * Add a segment to the path. All non-URL compatible characters are escaped in the URL-escape format * (%NN). Dot (".") and Star ("*") characters are also encoded. If this is not the last segment in the reference, * append a "/" separator between reference element. * <p> */ @Override protected void serializeEntityReference(EntityReference currentReference, StringBuilder representation, boolean isLastReference, Object... parameters) { if (currentReference.getParent() != null) { // Note: Java will convert the file separator to the proper separator for the underlying FileSystem. // Note: Using "/" allows us to reuse the serialized result into URLs. Caveat: The % character might need // to be escaped as %25 in this case as otherwise the browser will automatically decode % encoding. representation.append('/'); } representation.append(encodeReferenceName(currentReference.getName(), isLastReference)); } /** * @param name the reference name for this portion of the reference * @param isLastReference true if this portion of the reference is the last one (ie the deepest one) * @return the encoded reference name so that it can be used in a filesystem path. */ protected String encodeReferenceName(String name, boolean isLastReference) { try { // Note: We assume the FileSystem is case-sensitive. This is not the case for 16 bit Windows but we // consider that we don't support these. If we wanted to support case-insensitive File systems we would // need to escape all capital or lower-case letters. // Encode special non ASCII characters and handle "." and "*" in a special way since they're ASCII char // (and thus not encoded by URLEncoder.encode()) but have some special meanings in some File systems: // - On Unix a file starting with dot is a hidden file. On Windows the part after the last dot represents // the file extension and a file cannot end with a dot // - On Windows, the star is a wildcard. // See https://en.wikipedia.org/wiki/Filename#Reserved_characters_and_words and // http://stackoverflow.com/questions/2304221/what-character-sequence-should-i-not-allow-in-a-filename String encodedName = URLEncoder.encode(name, "UTF-8"); encodedName = replaceDot(encodedName, isLastReference); encodedName = replaceStar(encodedName, isLastReference); return encodedName; } catch (UnsupportedEncodingException e) { // This will never happen, UTF-8 is always available throw new RuntimeException("UTF-8 encoding is not present on the system!", e); } } /** * Replace "." with the encoded equivalent. * * @param name the reference name for this portion of the reference * @param isLastReference true if this portion of the reference is the last one (ie the deepest one) * @return the reference name with "." encoded */ protected String replaceDot(String name, boolean isLastReference) { return name.replace(".", "%2E"); } /** * Replace "*" with the encoded equivalent. * * @param name the reference name for this portion of the reference * @param isLastReference true if this portion of the reference is the last one (ie the deepest one) * @return the reference name with "*" encoded */ protected String replaceStar(String name, boolean isLastReference) { return name.replace("*", "%2A"); } }