/* * Copyright 2011 Google Inc. * * 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 com.google.template.soy.data.internalutils; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableBiMap; import com.google.common.collect.ImmutableMap; import com.google.common.html.types.SafeHtmlProto; import com.google.common.html.types.SafeScriptProto; import com.google.common.html.types.SafeStyleProto; import com.google.common.html.types.SafeStyleSheetProto; import com.google.common.html.types.SafeUrlProto; import com.google.common.html.types.TrustedResourceUrlProto; import com.google.protobuf.Descriptors.Descriptor; import com.google.template.soy.data.SanitizedContent.ContentKind; import java.util.EnumSet; import java.util.Set; /** * Utility methods for values of the {@code kind} attribute on {@code {param}} nodes. * * <p>This attribute specifies the {@link ContentKind} that the content of the node will evaluate * to, and in turn determines the HTML context to use when contextually autoescaping the node's * content (see {@link * com.google.template.soy.parsepasses.contextautoesc.Context#forContentKind(ContentKind)}). * */ public class NodeContentKinds { private static final ImmutableBiMap<String, ContentKind> KIND_ATTRIBUTE_TO_SANITIZED_CONTENT_KIND_BI_MAP = ImmutableBiMap.<String, ContentKind>builder() .put("attributes", ContentKind.ATTRIBUTES) .put("css", ContentKind.CSS) .put("html", ContentKind.HTML) .put("js", ContentKind.JS) .put("text", ContentKind.TEXT) .put("uri", ContentKind.URI) .put("trusted_resource_uri", ContentKind.TRUSTED_RESOURCE_URI) .build(); private static final ImmutableMap<ContentKind, String> KIND_TO_JS_CTOR_NAME = ImmutableMap.<ContentKind, String>builder() .put(ContentKind.HTML, "goog.soy.data.SanitizedHtml") .put(ContentKind.ATTRIBUTES, "goog.soy.data.SanitizedHtmlAttribute") .put(ContentKind.JS, "goog.soy.data.SanitizedJs") .put(ContentKind.URI, "goog.soy.data.SanitizedUri") .put(ContentKind.CSS, "goog.soy.data.SanitizedCss") .put(ContentKind.TRUSTED_RESOURCE_URI, "goog.soy.data.SanitizedTrustedResourceUri") // NOTE: Text intentionally doesn't follow the convention. Note that we don't just // convert them to a string, because the UnsanitizedText wrapper helps prevent the // content from getting used elsewhere in a noAutoescape. .put(ContentKind.TEXT, "goog.soy.data.UnsanitizedText") .build(); private static final ImmutableMap<ContentKind, String> IDOM_KIND_TO_JS_CTOR_NAME = ImmutableMap.<ContentKind, String>builder() .put(ContentKind.HTML, "Function") .put(ContentKind.ATTRIBUTES, "Function") .put(ContentKind.JS, "goog.soy.data.SanitizedJs") .put(ContentKind.URI, "goog.soy.data.SanitizedUri") .put(ContentKind.CSS, "goog.soy.data.SanitizedCss") .put(ContentKind.TRUSTED_RESOURCE_URI, "goog.soy.data.SanitizedTrustedResourceUri") // NOTE: Text intentionally doesn't follow the convention. Note that we don't just // convert them to a string, because the UnsanitizedText wrapper helps prevent the // content from getting used elsewhere in a noAutoescape. .put(ContentKind.TEXT, "goog.soy.data.UnsanitizedText") .build(); /** The Javascript sanitized ordainer functions. */ private static final ImmutableMap<ContentKind, String> KIND_TO_JS_ORDAINER_NAME = ImmutableMap.<ContentKind, String>builder() .put(ContentKind.HTML, "soydata.VERY_UNSAFE.ordainSanitizedHtml") .put(ContentKind.ATTRIBUTES, "soydata.VERY_UNSAFE.ordainSanitizedHtmlAttribute") .put(ContentKind.JS, "soydata.VERY_UNSAFE.ordainSanitizedJs") .put(ContentKind.URI, "soydata.VERY_UNSAFE.ordainSanitizedUri") .put(ContentKind.CSS, "soydata.VERY_UNSAFE.ordainSanitizedCss") .put( ContentKind.TRUSTED_RESOURCE_URI, "soydata.VERY_UNSAFE.ordainSanitizedTrustedResourceUri") .put(ContentKind.TEXT, "soydata.markUnsanitizedText") .build(); /** * The specialized ordainers used for param and let blocks. These ones do not wrap if the input is * empty string, in order that empty strings can still be truth-tested. This is an incomplete * solution to the truth testing problem, but dramatically simplifies migration. */ private static final ImmutableMap<ContentKind, String> KIND_TO_JS_ORDAINER_NAME_FOR_INTERNAL_BLOCKS = ImmutableMap.<ContentKind, String>builder() .put(ContentKind.HTML, "soydata.VERY_UNSAFE.$$ordainSanitizedHtmlForInternalBlocks") .put( ContentKind.ATTRIBUTES, "soydata.VERY_UNSAFE.$$ordainSanitizedAttributesForInternalBlocks") .put(ContentKind.JS, "soydata.VERY_UNSAFE.$$ordainSanitizedJsForInternalBlocks") .put(ContentKind.URI, "soydata.VERY_UNSAFE.$$ordainSanitizedUriForInternalBlocks") .put(ContentKind.CSS, "soydata.VERY_UNSAFE.$$ordainSanitizedCssForInternalBlocks") .put( ContentKind.TRUSTED_RESOURCE_URI, "soydata.VERY_UNSAFE.$$ordainSanitizedTrustedResourceUriForInternalBlocks") .put(ContentKind.TEXT, "soydata.$$markUnsanitizedTextForInternalBlocks") .build(); /** The JavaScript method to unpack a safe proto to sanitized object. */ private static final ImmutableMap<String, String> PROTO_TO_JS_UNPACK_FN = ImmutableMap.<String, String>builder() .put(SafeHtmlProto.getDescriptor().getFullName(), "soydata.unpackProtoToSanitizedHtml") .put(SafeScriptProto.getDescriptor().getFullName(), "soydata.unpackProtoToSanitizedJs") .put(SafeUrlProto.getDescriptor().getFullName(), "soydata.unpackProtoToSanitizedUri") .put(SafeStyleProto.getDescriptor().getFullName(), "soydata.unpackProtoToSanitizedCss") .put( SafeStyleSheetProto.getDescriptor().getFullName(), "soydata.unpackProtoToSanitizedCss") .put( TrustedResourceUrlProto.getDescriptor().getFullName(), "soydata.unpackProtoToSanitizedTrustedResourceUri") .build(); /** The JavaScript method to pack a sanitized object into a safe proto. */ private static final ImmutableMap<String, String> JS_TO_PROTO_PACK_FN = ImmutableMap.<String, String>builder() .put( SafeHtmlProto.getDescriptor().getFullName(), "soydata.packSanitizedHtmlToProtoSoyRuntimeOnly") .put( SafeScriptProto.getDescriptor().getFullName(), "soydata.packSanitizedJsToProtoSoyRuntimeOnly") .put( SafeUrlProto.getDescriptor().getFullName(), "soydata.packSanitizedUriToProtoSoyRuntimeOnly") .put( SafeStyleProto.getDescriptor().getFullName(), "soydata.packSanitizedCssToSafeStyleProtoSoyRuntimeOnly") .put( SafeStyleSheetProto.getDescriptor().getFullName(), "soydata.packSanitizedCssToSafeStyleSheetProtoSoyRuntimeOnly") .put( TrustedResourceUrlProto.getDescriptor().getFullName(), "soydata.packSanitizedTrustedResourceUriToProtoSoyRuntimeOnly") .build(); /** The Python sanitized classes. */ private static final ImmutableMap<ContentKind, String> KIND_TO_PY_SANITIZED_NAME = ImmutableMap.<ContentKind, String>builder() .put(ContentKind.HTML, "sanitize.SanitizedHtml") .put(ContentKind.ATTRIBUTES, "sanitize.SanitizedHtmlAttribute") .put(ContentKind.JS, "sanitize.SanitizedJs") .put(ContentKind.URI, "sanitize.SanitizedUri") .put(ContentKind.CSS, "sanitize.SanitizedCss") .put(ContentKind.TRUSTED_RESOURCE_URI, "sanitize.SanitizedTrustedResourceUri") .put(ContentKind.TEXT, "sanitize.UnsanitizedText") .build(); static { if (!KIND_TO_JS_CTOR_NAME.keySet().containsAll(EnumSet.allOf(ContentKind.class))) { throw new AssertionError("Not all ContentKind enums have a JS constructor"); } if (!IDOM_KIND_TO_JS_CTOR_NAME.keySet().containsAll(EnumSet.allOf(ContentKind.class))) { throw new AssertionError("Not all ContentKind enums have a Incremental DOM JS constructor"); } // These are the content kinds that actually have a native Soy language representation. Set<ContentKind> soyContentKinds = KIND_ATTRIBUTE_TO_SANITIZED_CONTENT_KIND_BI_MAP.values(); if (!KIND_TO_JS_ORDAINER_NAME.keySet().containsAll(soyContentKinds)) { throw new AssertionError("Not all Soy-accessible ContentKind enums have a JS ordainer"); } if (!KIND_TO_JS_ORDAINER_NAME_FOR_INTERNAL_BLOCKS.keySet().containsAll(soyContentKinds)) { throw new AssertionError("Not all Soy-accessible ContentKind enums have a JS ordainer"); } if (!KIND_TO_PY_SANITIZED_NAME.keySet().containsAll(soyContentKinds)) { throw new AssertionError("Not all Soy-accessible ContentKind enums have a Python sanitizer"); } } /** Given an allowed value of the attribute, returns the corresponding {@link ContentKind}). */ public static ContentKind forAttributeValue(String attributeValue) { return KIND_ATTRIBUTE_TO_SANITIZED_CONTENT_KIND_BI_MAP.get(attributeValue); } /** Given a ContentKind, returns the attribute string. */ public static String toAttributeValue(ContentKind kind) { return KIND_ATTRIBUTE_TO_SANITIZED_CONTENT_KIND_BI_MAP.inverse().get(kind); } /** The set of permitted values of the {@code kind} attribute. */ public static Set<String> getAttributeValues() { return KIND_ATTRIBUTE_TO_SANITIZED_CONTENT_KIND_BI_MAP.keySet(); } /** * Given a {@link ContentKind}, returns the corresponding JS SanitizedContent constructor. * * <p>These constructors may not be directly instantiated -- instead, an "ordainer" function is * used. This is so that Soy developers have to jump through extra hoops and carefully think about * the implications of directly calling the SanitizedContent constructors. */ public static String toJsSanitizedContentCtorName(ContentKind contentKind) { // goog.soy.data.SanitizedHtml types etc are defined in Closure. return Preconditions.checkNotNull(KIND_TO_JS_CTOR_NAME.get(contentKind)); } /** * Given a {@link ContentKind}, returns the corresponding JS SanitizedContent constructor. * * <p>This functions similarly to {@link #toJsSanitizedContentCtorName}, but replaces HTML and * Attribute types with Function instead of their sanitized types since in Incremental DOM, the * HTML types are actually functions that are invoked. */ public static String toIDOMSanitizedContentCtorName(ContentKind contentKind) { return Preconditions.checkNotNull(IDOM_KIND_TO_JS_CTOR_NAME.get(contentKind)); } /** * Given a {@link ContentKind}, returns the corresponding JS SanitizedContent factory function. */ public static String toJsSanitizedContentOrdainer(ContentKind contentKind) { // soydata.VERY_UNSAFE.ordainSanitizedHtml etc are defined in soyutils{,_usegoog}.js. return Preconditions.checkNotNull(KIND_TO_JS_ORDAINER_NAME.get(contentKind)); } /** * Returns the ordainer function for param and let blocks, which behaves subtly differently than * the normal ordainers to ease migration. */ public static String toJsSanitizedContentOrdainerForInternalBlocks(ContentKind contentKind) { // Functions are defined in soyutils{,_usegoog}.js. return Preconditions.checkNotNull( KIND_TO_JS_ORDAINER_NAME_FOR_INTERNAL_BLOCKS.get(contentKind)); } /** * Returns the namespace to {@code goog.require} to access the ordainer functions provided by * {@link #toJsSanitizedContentOrdainer(ContentKind)} and {@link * #toJsSanitizedContentOrdainerForInternalBlocks(ContentKind)}. * * @param contentKind */ public static String getJsImportForOrdainersFunctions(ContentKind contentKind) { if (contentKind == ContentKind.TEXT) { return "soydata"; } return "soydata.VERY_UNSAFE"; } /** Returns the pack function for converting SanitizedContent objects to safe protos. */ public static String toJsPackFunction(Descriptor protoDescriptor) { return Preconditions.checkNotNull(JS_TO_PROTO_PACK_FN.get(protoDescriptor.getFullName())); } /** Returns the unpack function for converting safe protos to JS SanitizedContent. */ public static String toJsUnpackFunction(Descriptor protoDescriptor) { return Preconditions.checkNotNull(PROTO_TO_JS_UNPACK_FN.get(protoDescriptor.getFullName())); } /** Given a {@link ContentKind}, returns the corresponding Python sanitize class. */ public static String toPySanitizedContentOrdainer(ContentKind contentKind) { // Sanitization classes are defined in sanitize.py. return Preconditions.checkNotNull(KIND_TO_PY_SANITIZED_NAME.get(contentKind)); } // Prevent instantiation. private NodeContentKinds() {} }