Package com.google.caja.lang.html

Source Code of com.google.caja.lang.html.HtmlSchema

// Copyright (C) 2008 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.caja.lang.html;

import com.google.caja.SomethingWidgyHappenedError;
import com.google.caja.config.ConfigUtil;
import com.google.caja.config.WhiteList;
import com.google.caja.lexer.ParseException;
import com.google.caja.parser.html.AttribKey;
import com.google.caja.parser.html.ElKey;
import com.google.caja.parser.html.Namespaces;
import com.google.caja.plugin.LoaderType;
import com.google.caja.plugin.UriEffect;
import com.google.caja.reporting.Message;
import com.google.caja.reporting.MessageQueue;
import com.google.caja.reporting.SimpleMessageQueue;
import com.google.caja.util.Lists;
import com.google.caja.util.Maps;
import com.google.caja.util.Multimap;
import com.google.caja.util.Multimaps;
import com.google.caja.util.Pair;
import com.google.caja.util.Sets;
import com.google.caja.util.Strings;

import java.io.IOException;
import java.net.URI;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
* An HTML schema which defines attributes of elements and which elements are
* allowed.
*
* @author mikesamuel@gmail.com
*/
public final class HtmlSchema {
  private final Set<ElKey> allowedElements;
  private final Map<ElKey, HTML.Element> elementDetails;
  private final Set<AttribKey> allowedAttributes;
  private final Map<AttribKey, HTML.Attribute> attributeDetails;

  private static Pair<HtmlSchema, List<Message>> defaultSchema;
  /**
   * The default HTML4 whitelist.  See the JSON files in this directory for
   * the actual definitions.
   */
  public static HtmlSchema getDefault(MessageQueue mq) {
    if (defaultSchema == null) {
      SimpleMessageQueue cacheMq = new SimpleMessageQueue();
      URI elSrc = URI.create(
              "resource:///com/google/caja/lang/html/"
              + "html4-elements-extensions.json");
      URI attrSrc = URI.create(
              "resource:///com/google/caja/lang/html/"
              + "html4-attributes-extensions.json");
      try {
        defaultSchema = Pair.pair(
            new HtmlSchema(
                ConfigUtil.loadWhiteListFromJson(
                    elSrc, ConfigUtil.RESOURCE_RESOLVER, cacheMq),
                ConfigUtil.loadWhiteListFromJson(
                    attrSrc, ConfigUtil.RESOURCE_RESOLVER, cacheMq)),
            cacheMq.getMessages());
      // If the default schema is borked, there's not much we can do.
      } catch (IOException ex) {
        mq.getMessages().addAll(cacheMq.getMessages());
        throw new SomethingWidgyHappenedError("Default schema is borked", ex);
      } catch (ParseException ex) {
        cacheMq.getMessages().add(ex.getCajaMessage());
        mq.getMessages().addAll(cacheMq.getMessages());
        throw new SomethingWidgyHappenedError("Default schema is borked", ex);
      }
    }
    mq.getMessages().addAll(defaultSchema.b);
    return defaultSchema.a;
  }

  /**
   * Elements that can be removed from the DOM without changing behavior as long
   * as their children are folded into the element's parent.
   */
  public static boolean isElementFoldable(ElKey el) {
    if (!el.isHtml()) { return false; }
    String cname = el.localName;
    return "head".equals(cname) || "body".equals(cname) || "html".equals(cname);
  }

  public HtmlSchema(WhiteList tagList, WhiteList attribList) {
    this.allowedAttributes = Sets.newHashSet();
    for (String key : attribList.allowedItems()) {
      allowedAttributes.add(attribKey(key));
    }
    Map<AttribKey, RegularCriterion> criteria = Maps.newHashMap();
    for (WhiteList.TypeDefinition def : attribList.typeDefinitions().values()) {
      final String values = (String) def.get("values", null);
      RegularCriterion criterion = null;
      if (values != null) {
        criterion = RegularCriterion.Factory.fromValueSet(
            Arrays.asList(values.split(",")));
      } else {
        String pattern = (String) def.get("pattern", null);
        if (pattern != null) {
          criterion = RegularCriterion.Factory.fromPattern(
              "(?i:" + pattern + ")");
        }
      }
      if (criterion != null) {
        String key = Strings.lower((String) def.get("key", null));
        criteria.put(attribKey(key), criterion);
      }
    }

    this.attributeDetails = Maps.newHashMap();
    Multimap<ElKey, HTML.Attribute> attributeDetailsByElement
        = Multimaps.newListHashMultimap();
    for (WhiteList.TypeDefinition def : attribList.typeDefinitions().values()) {
      String key = Strings.lower((String) def.get("key", null));
      AttribKey elAndAttrib = attribKey(key);
      if (elAndAttrib == null) { throw new NullPointerException(key); }
      ElKey element = elAndAttrib.el;
      HTML.Attribute.Type type = HTML.Attribute.Type.NONE;
      String typeName = (String) def.get("type", null);
      if (typeName != null) {
        // TODO(mikesamuel): divert IllegalArgumentExceptions to MessageQueue
        type = HTML.Attribute.Type.valueOf(typeName);
      }
      String loaderTypeStr = (String) def.get("loaderType", null);
      // TODO(mikesamuel): divert IllegalArgumentExceptions to MessageQueue
      LoaderType loaderType = loaderTypeStr != null
          ? LoaderType.valueOf(loaderTypeStr) : null;
      String uriEffectStr = (String) def.get("uriEffect", null);
      // TODO(mikesamuel): divert IllegalArgumentExceptions to MessageQueue
      UriEffect uriEffect = uriEffectStr != null
          ? UriEffect.valueOf(uriEffectStr) : null;
      RegularCriterion elCriterion = criteria.get(elAndAttrib);
      RegularCriterion wcCriterion = criteria.get(elAndAttrib.onAnyElement());
      RegularCriterion criterion = conjunction(elCriterion, wcCriterion);
      String defaultValue = (String) def.get("default", null);
      boolean optional = Boolean.TRUE.equals(def.get("optional", true));
      String safeValue = (String) def.get("safeValue", null);
      if (safeValue == null) {
        String candidate = defaultValue != null ? defaultValue : "";
        if (criterion.accept(candidate)) {
          safeValue = candidate;
        } else {
          String values = (String) def.get("values", null);
          if (values != null) {
            safeValue = values.split(",")[0];
          }
        }
      }
      boolean valueless = Boolean.TRUE.equals(def.get("valueless", false));
      // For valueless attributes, like checked, we allow the blank value.
      if (valueless && !criterion.accept("")) {
        criterion = RegularCriterion.Factory.or(
            criterion,
            RegularCriterion.Factory.fromValueSet(Collections.singleton("")));
      }
      HTML.Attribute a = new HTML.Attribute(
          elAndAttrib, type, defaultValue, safeValue, valueless, optional,
          loaderType, uriEffect, criterion);
      attributeDetails.put(elAndAttrib, a);

      attributeDetailsByElement.put(element, a);
    }

    this.allowedElements = Sets.newHashSet();
    for (String qualifiedName : tagList.allowedItems()) {
      allowedElements.add(
          ElKey.forElement(Namespaces.HTML_DEFAULT, qualifiedName));
    }
    this.elementDetails = Maps.newHashMap();
    for (WhiteList.TypeDefinition def : tagList.typeDefinitions().values()) {
      String qualifiedTagName = (String) def.get("key", null);
      ElKey key = ElKey.forElement(Namespaces.HTML_DEFAULT, qualifiedTagName);
      Collection<HTML.Attribute> specific = attributeDetailsByElement.get(key);
      ElKey wc = ElKey.wildcard(key.ns);
      Collection<HTML.Attribute> general = attributeDetailsByElement.get(wc);
      List<HTML.Attribute> attrs = Lists.newArrayList(specific);
      if (!general.isEmpty()) {
        Set<AttribKey> present = Sets.newHashSet();
        for (HTML.Attribute a : attrs) {
          present.add(a.getKey().onElement(wc));
        }
        for (HTML.Attribute a : general) {
          if (!present.contains(a.getKey())) { attrs.add(a); }
        }
      }
      boolean empty = (Boolean) def.get("empty", Boolean.FALSE);
      boolean optionalEnd = (Boolean) def.get("optionalEnd", Boolean.FALSE);
      boolean containsText = (Boolean) def.get("textContent", Boolean.TRUE)
          && !empty;
      elementDetails.put(
          key, new HTML.Element(key, attrs, empty, optionalEnd, containsText));
    }
  }

  public Set<AttribKey> getAttributeNames() {
    return attributeDetails.keySet();
  }

  public Set<ElKey> getElementNames() {
    return elementDetails.keySet();
  }

  public boolean isElementAllowed(ElKey elementName) {
    return allowedElements.contains(elementName);
  }

  public HTML.Element lookupElement(ElKey elementName) {
    return elementDetails.get(elementName);
  }

  public boolean isAttributeAllowed(AttribKey k) {
    HTML.Attribute a = lookupAttribute(k);
    if (a != null && allowedAttributes.contains(a.getKey())) { return true; }
    String keyName = k.localName;
    return keyName.startsWith("data-caja-") && !keyName.endsWith("___");
  }

  public HTML.Attribute lookupAttribute(AttribKey k) {
    HTML.Attribute attr = attributeDetails.get(k);
    if (attr == null) {
      attr = attributeDetails.get(k.onAnyElement());
    }
    return attr;
  }

  private static AttribKey attribKey(String key) {
    int separator = key.indexOf("::");
    String elQName = key.substring(0, separator);
    String attrQName = key.substring(separator + 2);
    ElKey el = ElKey.forElement(Namespaces.HTML_DEFAULT, elQName);
    if (el == null) { throw new NullPointerException(elQName); }
    AttribKey a = AttribKey.forAttribute(
        Namespaces.HTML_DEFAULT, el, attrQName);
    if (a == null) { throw new NullPointerException(attrQName); }
    return a;
  }

  private static RegularCriterion conjunction(
      RegularCriterion a, RegularCriterion b) {
    RegularCriterion c = a;
    if (b != null) { c = c == null ? b : RegularCriterion.Factory.and(c, b); }
    return c != null ? c : RegularCriterion.Factory.optimist();
  }
}
TOP

Related Classes of com.google.caja.lang.html.HtmlSchema

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.