/* * This library is part of OpenCms - * the Open Source Content Management System * * Copyright (c) Alkacon Software GmbH (http://www.alkacon.com) * * This library 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 library 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. * * For further information about Alkacon Software GmbH, please see the * company website: http://www.alkacon.com * * For further information about OpenCms, please see the * project website: http://www.opencms.org * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package org.opencms.file.wrapper; import org.opencms.file.CmsFile; import org.opencms.file.CmsObject; import org.opencms.file.CmsProperty; import org.opencms.file.CmsPropertyDefinition; import org.opencms.file.CmsResource; import org.opencms.file.types.CmsResourceTypePlain; import org.opencms.i18n.CmsEncoder; import org.opencms.main.CmsException; import org.opencms.main.OpenCms; import org.opencms.util.CmsStringUtil; import org.opencms.workplace.commons.CmsPropertyAdvanced; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Helper class with several methods used by different implementations of the * interface {@link I_CmsResourceWrapper}.<p> * * It provides methods to add or remove file extensions to resources, to handle * creating and writing property files and to add the byte order mask to UTF-8 * byte contents.<p> * * @since 6.2.4 */ public final class CmsResourceWrapperUtils { /** The extension to use for the property file. */ public static final String EXTENSION_PROPERTIES = "properties"; /** The prefix used for a shared property entry. */ public static final String SUFFIX_PROP_INDIVIDUAL = ".i"; /** The prefix used for a shared property entry. */ public static final String SUFFIX_PROP_SHARED = ".s"; /** The UTF-8 bytes to add to the beginning of text contents. */ public static final byte[] UTF8_MARKER = new byte[] {(byte)0xEF, (byte)0xBB, (byte)0xBF}; /** Pattern to use for incoming strings before storing in OpenCms. */ private static final Pattern PATTERN_UNESCAPE = Pattern.compile("\\\\([^ntru\n\r])"); /** * Hide utility class constructor.<p> */ private CmsResourceWrapperUtils() { // noop } /** * Adds a file extension to the resource name.<p> * * If the file with the new extension already exists, an index count will be * added before the final extension.<p> * * For example: <code>index.html.1.jsp</code>.<p> * * @see #removeFileExtension(CmsObject, String, String) * * @param cms the actual CmsObject * @param resourcename the name of the resource where to add the file extension * @param extension the extension to add * * @return the resource name with the added file extension */ public static String addFileExtension(CmsObject cms, String resourcename, String extension) { if (!extension.startsWith(".")) { extension = "." + extension; } if (!resourcename.endsWith(extension)) { String name = resourcename + extension; int count = 0; while (cms.existsResource(name)) { count++; name = resourcename + "." + count + extension; } return name; } return resourcename; } /** * Adds the UTF-8 marker add the beginning of the byte array.<p> * * @param content the byte array where to add the UTF-8 marker * * @return the byte with the added UTF-8 marker at the beginning */ public static byte[] addUtf8Marker(byte[] content) { if ((content != null) && (content.length >= 3) && (content[0] == UTF8_MARKER[0]) && (content[1] == UTF8_MARKER[1]) && (content[2] == UTF8_MARKER[2])) { return content; } if (content == null) { content = new byte[0]; } byte[] ret = new byte[UTF8_MARKER.length + content.length]; System.arraycopy(UTF8_MARKER, 0, ret, 0, UTF8_MARKER.length); System.arraycopy(content, 0, ret, UTF8_MARKER.length, content.length); return ret; } /** * Creates a virtual CmsFile with the individual and shared properties as content.<p> * * For example looks like this:<br/> * Title.i=The title of the resource set as individual property<br/> * Title.s=The title of the resource set as shared property<br/> * * @see #writePropertyFile(CmsObject, String, byte[]) * * @param cms the initialized CmsObject * @param res the resource where to read the properties from * @param path the full path to set for the created property file * * @return the created CmsFile with the individual and shared properties as the content * * @throws CmsException if something goes wrong */ public static CmsFile createPropertyFile(CmsObject cms, CmsResource res, String path) throws CmsException { StringBuffer content = new StringBuffer(); // header content.append("# Properties for resource "); content.append(res.getRootPath()); content.append("\n"); content.append("#\n"); content.append("# ${property_name}.i : individual property\n"); content.append("# ${property_name}.s : shared property\n\n"); List<CmsPropertyDefinition> propertyDef = cms.readAllPropertyDefinitions(); Map<String, CmsProperty> activeProperties = CmsPropertyAdvanced.getPropertyMap(cms.readPropertyObjects( res, false)); // iterate over all possible properties for the resource Iterator<CmsPropertyDefinition> i = propertyDef.iterator(); while (i.hasNext()) { CmsPropertyDefinition currentPropertyDef = i.next(); String propName = currentPropertyDef.getName(); CmsProperty currentProperty = activeProperties.get(propName); if (currentProperty == null) { currentProperty = new CmsProperty(); } String individualValue = currentProperty.getStructureValue(); String sharedValue = currentProperty.getResourceValue(); if (individualValue == null) { individualValue = ""; } if (sharedValue == null) { sharedValue = ""; } individualValue = escapeString(individualValue); sharedValue = escapeString(sharedValue); content.append(propName); content.append(SUFFIX_PROP_INDIVIDUAL); content.append("="); content.append(individualValue); content.append("\n"); content.append(propName); content.append(SUFFIX_PROP_SHARED); content.append("="); content.append(sharedValue); content.append("\n\n"); } CmsWrappedResource wrap = new CmsWrappedResource(res); wrap.setRootPath(addFileExtension(cms, path, EXTENSION_PROPERTIES)); int plainId = OpenCms.getResourceManager().getResourceType(CmsResourceTypePlain.getStaticTypeName()).getTypeId(); wrap.setTypeId(plainId); wrap.setFolder(false); CmsFile ret = wrap.getFile(); try { ret.setContents(content.toString().getBytes(CmsEncoder.ENCODING_UTF_8)); } catch (UnsupportedEncodingException e) { // this will never happen since UTF-8 is always supported ret.setContents(content.toString().getBytes()); } return ret; } /** * Removes an added file extension from the resource name.<p> * * <ul> * <li>If there is only one extension, nothing will be removed.</li> * <li>If there are two extensions, the last one will be removed.</li> * <li>If there are more than two extensions the last one will be removed and * if then the last extension is a number, the extension with the number * will be removed too.</li> * </ul> * * @see #addFileExtension(CmsObject, String, String) * * @param cms the initialized CmsObject * @param resourcename the resource name to remove the file extension from * @param extension the extension to remove * * @return the resource name without the removed file extension */ public static String removeFileExtension(CmsObject cms, String resourcename, String extension) { if (resourcename.equals("")) { resourcename = "/"; } // get the filename without the path String name = CmsResource.getName(resourcename); String[] tokens = name.split("\\."); String suffix = null; // check if there is more than one extension if (tokens.length > 2) { // check if last extension is "jsp" if (extension.equalsIgnoreCase(tokens[tokens.length - 1])) { suffix = "." + extension; // check if there is another extension with a numeric index if (tokens.length > 3) { try { int index = Integer.valueOf(tokens[tokens.length - 2]).intValue(); suffix = "." + index + suffix; } catch (NumberFormatException ex) { // noop } } } } else if (tokens.length == 2) { // there is only one extension!! // only remove the last extension, if the resource without the extension exists // and the extension fits if ((cms.existsResource(CmsResource.getFolderPath(resourcename) + tokens[0])) && (extension.equals(tokens[1]))) { suffix = "." + tokens[1]; } } if (suffix != null) { String path = resourcename.substring(0, resourcename.length() - suffix.length()); return path; } return resourcename; } /** * Removes the UTF-8 marker from the beginning of the byte array.<p> * * @param content the byte array where to remove the UTF-8 marker * * @return the byte with the removed UTF-8 marker at the beginning */ public static byte[] removeUtf8Marker(byte[] content) { if ((content != null) && (content.length >= 3) && (content[0] == UTF8_MARKER[0]) && (content[1] == UTF8_MARKER[1]) && (content[2] == UTF8_MARKER[2])) { byte[] ret = new byte[content.length - UTF8_MARKER.length]; System.arraycopy(content, 3, ret, 0, content.length - UTF8_MARKER.length); return ret; } return content; } /** * Takes the content which should be formatted as a property file and set them * as properties to the resource.<p> * * @see #createPropertyFile(CmsObject, CmsResource, String) * * @param cms the initialized CmsObject * @param resourcename the name of the resource where to set the properties * @param content the properties to set (formatted as a property file) * * @throws CmsException if something goes wrong */ public static void writePropertyFile(CmsObject cms, String resourcename, byte[] content) throws CmsException { Properties properties = new Properties(); try { String props = CmsEncoder.createString(content, CmsEncoder.ENCODING_UTF_8); props = unescapeString(props); props = CmsEncoder.encodeJavaEntities(props, CmsEncoder.ENCODING_ISO_8859_1); byte[] modContent = props.getBytes(CmsEncoder.ENCODING_ISO_8859_1); properties.load(new ByteArrayInputStream(modContent)); List<CmsProperty> propList = new ArrayList<CmsProperty>(); Iterator<Map.Entry<Object, Object>> it = properties.entrySet().iterator(); while (it.hasNext()) { Map.Entry<Object, Object> e = it.next(); String key = (String)e.getKey(); String value = (String)e.getValue(); if (key.endsWith(SUFFIX_PROP_SHARED)) { propList.add(new CmsProperty( key.substring(0, key.length() - SUFFIX_PROP_SHARED.length()), null, value)); } else if (key.endsWith(SUFFIX_PROP_INDIVIDUAL)) { propList.add(new CmsProperty( key.substring(0, key.length() - SUFFIX_PROP_INDIVIDUAL.length()), value, null)); } } cms.writePropertyObjects(resourcename, propList); } catch (IOException e) { // noop } } /** * Escapes the value of a property in OpenCms to be displayed * correctly in a property file.<p> * * Mainly handles all escaping sequences that start with a backslash.<p> * * @see #unescapeString(String) * * @param value the value with the string to be escaped * * @return the escaped string */ private static String escapeString(String value) { Map<String, String> substitutions = new HashMap<String, String>(); substitutions.put("\n", "\\n"); substitutions.put("\t", "\\t"); substitutions.put("\r", "\\r"); return CmsStringUtil.substitute(value, substitutions); } /** * Unescapes the value of a property in a property file to * be saved correctly in OpenCms.<p> * * Mainly handles all escaping sequences that start with a backslash.<p> * * @see #escapeString(String) * * @param value the value taken form the property file * * @return the unescaped string value */ private static String unescapeString(String value) { Matcher matcher = PATTERN_UNESCAPE.matcher(value); return matcher.replaceAll("\\\\\\\\$1"); } }