diff --git a/source/java/org/alfresco/web/app/servlet/FacesHelper.java b/source/java/org/alfresco/web/app/servlet/FacesHelper.java index 9c43e05f74..698504d868 100644 --- a/source/java/org/alfresco/web/app/servlet/FacesHelper.java +++ b/source/java/org/alfresco/web/app/servlet/FacesHelper.java @@ -53,15 +53,25 @@ import org.apache.commons.logging.LogFactory; public final class FacesHelper { private static Log logger = LogFactory.getLog(FacesHelper.class); - private static Pattern FACES_ID_PATTERN = Pattern.compile("[^a-z^A-Z^_]?[^a-z^A-Z^0-9^_^-]"); - + + /** + * Mask for hex encoding + */ + private static final int MASK = (1 << 4) - 1; + + /** + * Digits used for hex string encoding + */ + private static final char[] DIGITS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; + + /** * Private constructor */ private FacesHelper() { } - + /** * Return a valid FacesContext for the specific context, request and response. * The FacesContext can be constructor for Servlet use. @@ -76,7 +86,7 @@ public final class FacesHelper { return getFacesContextImpl(request, response, context, null); } - + /** * Return a valid FacesContext for the specific context, request and response. * The FacesContext can be constructor for Servlet use. @@ -106,7 +116,7 @@ public final class FacesHelper { return getFacesContextImpl(request, response, context, null); } - + /** * Return a valid FacesContext for the specific context, request and response. * The FacesContext can be constructor for Servlet and Portlet use. @@ -122,13 +132,13 @@ public final class FacesHelper FacesContextFactory contextFactory = (FacesContextFactory)FactoryFinder.getFactory(FactoryFinder.FACES_CONTEXT_FACTORY); LifecycleFactory lifecycleFactory = (LifecycleFactory)FactoryFinder.getFactory(FactoryFinder.LIFECYCLE_FACTORY); Lifecycle lifecycle = lifecycleFactory.getLifecycle(LifecycleFactory.DEFAULT_LIFECYCLE); - + // Doesn't set this instance as the current instance of FacesContext.getCurrentInstance FacesContext facesContext = contextFactory.getFacesContext(context, request, response, lifecycle); - + // Set using our inner class InnerFacesContext.setFacesContextAsCurrent(facesContext); - + // set a new viewRoot, otherwise context.getViewRoot returns null if (viewRoot == null) { @@ -136,10 +146,10 @@ public final class FacesHelper } UIViewRoot view = facesContext.getApplication().getViewHandler().createView(facesContext, viewRoot); facesContext.setViewRoot(view); - + return facesContext; } - + /** * Return a JSF managed bean reference. * @@ -153,7 +163,7 @@ public final class FacesHelper ValueBinding vb = fc.getApplication().createValueBinding("#{" + name + "}"); return vb.getValue(fc); } - + /** * Sets up the id for the given component, if the id is null a unique one * is generated using the standard Faces algorithm. If an id is present it @@ -174,27 +184,23 @@ public final class FacesHelper // make sure we do not have illegal characters in the id id = makeLegalId(id); } - + component.setId(id); } /** - * Makes the given id a legal JSF component id by replacing illegal - * characters with underscores. + * Makes the given id a legal JSF component id by replacing illegal characters + * with ISO9075 encoding - which itself a subset of valid HTML ID characters. * - * @param id The id to make legal - * @return The legalised id + * @param id The id to make legal + * + * @return the legalised id */ public static String makeLegalId(String id) { - if (id != null) - { - id = FACES_ID_PATTERN.matcher(id).replaceAll("_"); - } - - return id; + return (id != null ? validFacesId(id) : null); } - + /** * Retrieves the named component generator implementation. * If the named generator is not found the TextFieldGenerator is looked up @@ -207,37 +213,37 @@ public final class FacesHelper public static IComponentGenerator getComponentGenerator(FacesContext context, String generatorName) { IComponentGenerator generator = lookupComponentGenerator(context, generatorName); - + if (generator == null) { // create a text field if we can't find a component generator (a warning should have already been // displayed on the appserver console) - + logger.warn("Attempting to find default component generator '" + RepoConstants.GENERATOR_TEXT_FIELD + "'"); generator = lookupComponentGenerator(context, RepoConstants.GENERATOR_TEXT_FIELD); } - + // if we still don't have a component generator we should abort as vital configuration is missing if (generator == null) { throw new AlfrescoRuntimeException("Failed to find a component generator, please ensure the '" + RepoConstants.GENERATOR_TEXT_FIELD + "' bean is present in your configuration"); } - + return generator; } - + private static IComponentGenerator lookupComponentGenerator(FacesContext context, String generatorName) { IComponentGenerator generator = null; - + Object obj = FacesHelper.getManagedBean(context, generatorName); if (obj != null) { if (obj instanceof IComponentGenerator) { generator = (IComponentGenerator)obj; - + if (logger.isDebugEnabled()) logger.debug("Found component generator for '" + generatorName + "': " + generator); } @@ -250,10 +256,10 @@ public final class FacesHelper { logger.warn("Failed to find component generator with name of '" + generatorName + "'"); } - + return generator; } - + /** * We need an inner class to be able to call FacesContext.setCurrentInstance * since it's a protected method @@ -265,4 +271,59 @@ public final class FacesHelper FacesContext.setCurrentInstance(facesContext); } } + + /** + * Helper to ensure only valid and acceptable characters are output as Faces component IDs. + * Based on ISO9075 encoding - which itself a subset of valid HTML ID characters. + */ + private static String validFacesId(String id) + { + int len = id.length(); + StringBuilder buf = new StringBuilder(len + (len>>1)); + for (int i = 0; i= 65 && ci <= 90) || // A-Z + (ci >= 97 && ci <= 122)) // a-z + { + buf.append(c); + } + else + { + encode(c, buf); + } + } + else + { + if ((ci >= 65 && ci <= 90) || // A-Z + (ci >= 97 && ci <= 122) || // a-z + (ci >= 48 && ci <= 57) || // 0-9 + ci == 45 || ci == 95) // - and _ + { + buf.append(c); + } + else + { + encode(c, buf); + } + } + } + return buf.toString(); + } + + private static void encode(char c, StringBuilder builder) + { + char[] buf = new char[] { 'x', '0', '0', '0', '0', '_' }; + int charPos = 5; + do + { + buf[--charPos] = DIGITS[c & MASK]; + c >>>= 4; + } + while (c != 0); + builder.append(buf); + } } diff --git a/source/java/org/alfresco/web/app/servlet/UploadContentServlet.java b/source/java/org/alfresco/web/app/servlet/UploadContentServlet.java index 55ea1739d3..0f4bebe00d 100644 --- a/source/java/org/alfresco/web/app/servlet/UploadContentServlet.java +++ b/source/java/org/alfresco/web/app/servlet/UploadContentServlet.java @@ -242,7 +242,7 @@ public class UploadContentServlet extends BaseServlet writer.setEncoding(encoding); // Stream the content into the repository - writer.putContent(req.getInputStream()); + writer.putContent(inputStream); if (logger.isDebugEnabled() == true) {