All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.composum.sling.cpnl.CpnlElFunctions Maven / Gradle / Ivy

There is a newer version: 4.3.4
Show newest version
package com.composum.sling.cpnl;

import com.composum.sling.core.CoreConfiguration;
import com.composum.sling.core.util.FormatterFormat;
import com.composum.sling.core.util.I18N;
import com.composum.sling.core.util.LinkUtil;
import com.composum.sling.core.util.LoggerFormat;
import com.composum.sling.core.util.XSS;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.text.translate.AggregateTranslator;
import org.apache.commons.lang3.text.translate.CharSequenceTranslator;
import org.apache.commons.lang3.text.translate.EntityArrays;
import org.apache.commons.lang3.text.translate.LookupTranslator;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.resource.Resource;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.osgi.framework.BundleContext;
import org.osgi.framework.FrameworkUtil;
import org.osgi.framework.ServiceReference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.Writer;
import java.text.Format;
import java.text.MessageFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * the set of taglib JSP EL functions
 */
public class CpnlElFunctions {

    private static final Logger LOG = LoggerFactory.getLogger(CpnlElFunctions.class);

    public static final Pattern HREF_PATTERN = Pattern.compile("(]*)?\\s*href\\s*=\\s*['\"])([^'\"]+)([\"'][^>]*>)");

    /**
     * for the 'attr' escaping - the quotation type constants
     */
    public static final int QTYPE_QUOT = 0;
    public static final int QTYPE_APOS = 1;
    public static final String[] QTYPE_CHAR = new String[]{"\"", "'"};
    public static final String[] QTYPE_ESC = new String[]{""", "'"};

    public static final String[] RICH_TEXT_TAGS = new String[]{
            "p", "br", "a", "ul", "li", "ol", "dl", "dt", "dd",
            "strong", "em", "u", "b", "i", "strike", "sub", "sup", "code",
            "table", "thead", "tbody", "tr", "th", "td"
    };

    public static final String[][] RICH_TEXT_BASIC_ESCAPE = {
            // avoid double escape
            {"©", "©"},
            {" ", " "},
            {"&", "&"},
            {"<", "<"},
            {">", ">"},
            // escape if not skipped
            {"&", "&"},
            {"<", "<"},
            {">", ">"},
    };

    protected static final List RICH_TEXT_TAG_START;
    protected static final List RICH_TEXT_TAG_CLOSED;
    protected static final int RICH_TEXT_TAG_MAX_LEN;

    static {
        int maxLen = 0;
        RICH_TEXT_TAG_START = new ArrayList<>();
        for (String tag : RICH_TEXT_TAGS) {
            RICH_TEXT_TAG_START.add("<" + tag + " ");
            int length = tag.length() + 2;
            if (maxLen < length) {
                maxLen = length;
            }
        }
        RICH_TEXT_TAG_CLOSED = new ArrayList<>();
        for (String tag : RICH_TEXT_TAGS) {
            RICH_TEXT_TAG_CLOSED.add("<" + tag + ">");
            RICH_TEXT_TAG_CLOSED.add("<" + tag + "/>");
            RICH_TEXT_TAG_CLOSED.add("");
            int length = tag.length() + 3;
            if (maxLen < length) {
                maxLen = length;
            }
        }
        RICH_TEXT_TAG_MAX_LEN = maxLen;
    }

    public static class RichTextTagsFilter extends LookupTranslator {

        protected boolean isInTagStart = false;

        public RichTextTagsFilter() {
            super(RICH_TEXT_BASIC_ESCAPE);
        }

        @Override
        public int translate(CharSequence input, int index, Writer out) throws IOException {
            if (isInTagStart) {
                char token = input.charAt(index);
                out.write(token);
                if (token == '>') {
                    isInTagStart = false;
                }
                return 1;
            } else {
                String subSeq = input.subSequence(index, index + Math.min(RICH_TEXT_TAG_MAX_LEN, input.length() - index))
                        .toString().toLowerCase();
                for (String pattern : RICH_TEXT_TAG_START) {
                    if (subSeq.startsWith(pattern)) {
                        out.write(pattern);
                        isInTagStart = true;
                        return pattern.length();
                    }
                }
                for (String pattern : RICH_TEXT_TAG_CLOSED) {
                    if (subSeq.startsWith(pattern)) {
                        out.write(pattern);
                        return pattern.length();
                    }
                }
                return super.translate(input, index, out);
            }
        }
    }


    public static final CharSequenceTranslator ESCAPE_RICH_TEXT =
            new AggregateTranslator(
                    new RichTextTagsFilter(),
                    new LookupTranslator(EntityArrays.ISO8859_1_ESCAPE()),
                    new LookupTranslator(EntityArrays.HTML40_EXTENDED_ESCAPE())
            );

    public static String escapeRichText(String input) {
        return ESCAPE_RICH_TEXT.translate(input);
    }

    public static String i18n(SlingHttpServletRequest request, String text) {
        return text(I18N.get(request, text));
    }

    /**
     * Builds the URI for a relative Composum URI (prepends the Composum base).
     *
     * @param path the relative path (resource type)
     * @return the URI with prepended base
     */
    public static String cpm(String uri) {
        if (StringUtils.isNotBlank(uri) && !uri.startsWith("/")) {
            final CoreConfiguration config = getService(CoreConfiguration.class);
            if (config != null) {
                uri = config.getComposumBase() + uri;
            }
        }
        return uri;
    }

    /**
     * Returns the repository path of a child of a resource.
     *
     * @param base the parent resource object
     * @param path the relative path to the child resource
     * @return the absolute path of the child if found, otherwise the original path value
     */
    public static String child(Resource base, String path) {
        Resource child = base.getChild(path);
        return child != null ? child.getPath() : path;
    }

    /**
     * Builds the URL for a repository asset path using the LinkUtil.getURL() method.
     *
     * @param request the current request (domain host hint)
     * @param path    the repository path
     * @return the URL built in the context of the requested domain host
     */
    public static String asset(SlingHttpServletRequest request, String path) {
        return XSS.getValidHref(LinkUtil.getUrl(request, path, null, ""));
    }

    /**
     * Builds the URL for a repository path using the LinkUtil.getURL() method.
     *
     * @param request the current request (domain host hint)
     * @param path    the repository path
     * @return the URL built in the context of the requested domain host
     */
    public static String url(SlingHttpServletRequest request, String path) {
        return XSS.getValidHref(LinkUtil.getUrl(request, path));
    }

    /**
     * Builds the URL for a repository path using the LinkUtil.getMappedURL() method.
     *
     * @param request the current request (domain host hint)
     * @param path    the repository path
     * @return the URL built in the context of the requested domain host
     */
    public static String mappedUrl(SlingHttpServletRequest request, String path) {
        return XSS.getValidHref(LinkUtil.getMappedUrl(request, path));
    }

    /**
     * Builds the URL for a repository path using the LinkUtil.getUnmappedURL() method.
     *
     * @param request the current request (domain host hint)
     * @param path    the repository path
     * @return the URL built in the context of the requested domain host
     */
    public static String unmappedUrl(SlingHttpServletRequest request, String path) {
        return XSS.getValidHref(LinkUtil.getUnmappedUrl(request, path));
    }

    /**
     * Builds an external (full qualified) URL for a repository path using the LinkUtil.getURL() method.
     *
     * @param request the current request (domain host hint)
     * @param path    the repository path
     * @return the URL built in the context of the requested domain host
     */
    public static String externalUrl(SlingHttpServletRequest request, String path) {
        return XSS.getValidHref(LinkUtil.getAbsoluteUrl(request, LinkUtil.getUrl(request, path)));
    }

    /**
     * Builds an external (full qualified) URL for a repository path using the LinkUtil.getMappedURL() method.
     *
     * @param request the current request (domain host hint)
     * @param path    the repository path
     * @return the URL built in the context of the requested domain host
     */
    public static String mappedExternalUrl(SlingHttpServletRequest request, String path) {
        return XSS.getValidHref(LinkUtil.getAbsoluteUrl(request, LinkUtil.getMappedUrl(request, path)));
    }

    /**
     * Builds an external (full qualified) URL for a repository path using the LinkUtil.getUnmappedURL() method.
     *
     * @param request the current request (domain host hint)
     * @param path    the repository path
     * @return the URL built in the context of the requested domain host
     */
    public static String unmappedExternalUrl(SlingHttpServletRequest request, String path) {
        return XSS.getValidHref(LinkUtil.getAbsoluteUrl(request, LinkUtil.getUnmappedUrl(request, path)));
    }

    /**
     * An input field 'value' attribute which should be used as is if possible.
     *
     * @param value the value to render
     * @return the value escaped using encodeForHTMLAttr() if the value is a String
     */
    public static Object value(Object value) {
        return value instanceof String ? XSS.encodeForHTMLAttr((String) value) : value;
    }

    /**
     * Returns the escaped text of a value (HTML escaping to prevent from XSS).
     *
     * @param value the value to escape
     * @return the HTML escaped text of the value
     */
    public static String text(String value) {
        return value != null
                ? /* StringEscapeUtils.escapeHtml4(value) */ XSS.encodeForHTML(value)
                : null;
    }

    /**
     * Returns the escaped text of a rich text value as HTML text for a tag attribute.
     * We assume that the result is used as value for a insertion done by jQuery.html();
     * in this case all '&...' escaped chars are translated back by jQuery and the XSS protection
     * is broken - to avoid this each '&' in the value is 'double escaped'
     *
     * @param value the value to escape
     * @return the HTML escaped rich text of the value
     */
    public static String attr(SlingHttpServletRequest request, String value, int qType) {
        if (value != null) {
            value = rich(request, value);
            value = value
                    .replaceAll("&", "&") // prevent from unescaping in jQuery.html()
                    .replaceAll(QTYPE_CHAR[qType], QTYPE_ESC[qType]);
        }
        return value;
    }

    /**
     * Returns the escaped text of a rich text value (reduced HTML escaping).
     *
     * @param value the rich text value to escape
     * @return the escaped HTML code of the value
     */
    public static String rich(SlingHttpServletRequest request, String value) {
        if (value != null) {
            // ensure that a rich text value is always enclosed with a HTML paragraph tag
            if (StringUtils.isNotBlank(value) && !value.trim().startsWith("

")) { value = "

" + value + "

"; } // transform embedded resource links (paths) to mapped URLs value = map(request, value); value = escapeRichText(value); } return value; } /** * Replaces all 'href' attribute values found in the text value by the resolver mapped value. * * @param request the text (rich text) value * @param value the text (rich text) value * @return the transformed text value */ public static String map(SlingHttpServletRequest request, String value) { StringBuilder result = new StringBuilder(); Matcher matcher = HREF_PATTERN.matcher(value); int len = value.length(); int pos = 0; while (matcher.find(pos)) { String unmapped = matcher.group(3); String mapped = url(request, unmapped); result.append(value, pos, matcher.start()); result.append(matcher.group(1)); result.append(mapped); result.append(matcher.group(4)); pos = matcher.end(); } if (pos >= 0 && pos < len) { result.append(value, pos, len); } return result.toString(); } /** * Prevents the given value string from containing XSS stuff. * * @param value source string * @return string that does not contain XSS stuff */ public static String filter(String value) { return XSS.filter(value); } /** * Prevents the given value string from containing XSS stuff. * * @param context the name of the protection context to use * @param value source string * @return string that does not contain XSS stuff */ public static String context(String context, String value) { return XSS.filter(context, value); } /** * URL encoding for a resource path (without the encoding for the '/' path delimiters). * * @param value the path to encode * @return the encoded path */ public static String path(String value) { return value != null ? LinkUtil.encodePath(value) : null; } /** * Returns the escaped script code of a value (Script escaping to prevent from XSS). * * @param value the value to escape * @return the Script escaped code of the value */ public static String script(String value) { return value != null ? /* StringEscapeUtils.escapeEcmaScript(value) */ XSS.encodeForJSString(value) : null; } /** * Returns the escaped CSS code of a value (style escaping to prevent from XSS). * * @param value the value to escape * @return the CSS escaped code of the value */ public static String style(String value) { return value != null ? XSS.encodeForCSSString(value) : null; } /** * Returns the encapsulated CDATA string of a value (no escaping!). * * @param value the value to encasulate * @return the string with <![CDATA[ ... ]]> around */ public static String cdata(String value) { String result = null; if (value != null) { if (value.contains("]]>")) { result = "", "]]]]>") + "]]>"; } else { result = ""; } } return result; } /** * Checks whether an array of objects or a collection contains another object. * * @param collection an array of objects or collection * @param object the object to check for * @return true if */ public static Boolean contains(java.lang.Object collection, java.lang.Object object) { if (collection instanceof Collection) { return ((Collection) collection).contains(object); } else if (collection instanceof Object[]) { return Arrays.asList( (Object[]) collection).contains(object); } return false; } /** * Creates the formatter for a describing string rule * * @param locale the local to use for formatting * @param format the format string rule * @param type the optional value type * @return the Format instance */ public static Format getFormatter(@NotNull final Locale locale, @NotNull final String format, @Nullable final Class... type) { Format formatter = null; Pattern TEXT_FORMAT_STRING = Pattern.compile("^(\\{([^}]+)}(.+)|(.*\\{}.*))$"); Matcher matcher = TEXT_FORMAT_STRING.matcher(format); if (matcher.matches()) { if (matcher.group(2) != null) { switch (matcher.group(2)) { case "Message": formatter = new MessageFormat(matcher.group(3), locale); break; case "Date": formatter = new SimpleDateFormat(matcher.group(3), locale); break; case "String": formatter = new FormatterFormat(matcher.group(3), locale); break; case "Log": formatter = new LoggerFormat(matcher.group(3)); break; default: if (StringUtils.isBlank(matcher.group(2))) { formatter = new LoggerFormat(matcher.group(4)); } break; } } else { formatter = new LoggerFormat(matcher.group(4)); } } else { if (type != null && type.length == 1 && type[0] != null && (Calendar.class.isAssignableFrom(type[0]) || Date.class.isAssignableFrom(type[0]))) { formatter = new SimpleDateFormat(format, locale); } else { formatter = new LoggerFormat(format); } } return formatter; } protected static T getService(Class serviceClass) { final BundleContext bundleContext = getBundelContext(); final ServiceReference serviceReference = bundleContext.getServiceReference(serviceClass); return serviceReference != null ? bundleContext.getService(serviceReference) : null; } protected static BundleContext getBundelContext() { return FrameworkUtil.getBundle(CpnlElFunctions.class).getBundleContext(); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy