Package com.google.caja.plugin.stages

Source Code of com.google.caja.plugin.stages.RewriteFlashStage

// Copyright (C) 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.caja.plugin.stages;

import java.io.IOException;
import java.net.URI;
import java.util.Map;

import org.w3c.dom.Element;
import org.w3c.dom.Node;

import com.google.caja.lexer.FilePosition;
import com.google.caja.parser.html.Dom;
import com.google.caja.parser.html.Namespaces;
import com.google.caja.parser.html.Nodes;
import com.google.caja.parser.js.Block;
import com.google.caja.parser.js.Expression;
import com.google.caja.parser.js.ExpressionStmt;
import com.google.caja.parser.js.Literal;
import com.google.caja.parser.js.NullLiteral;
import com.google.caja.parser.js.StringLiteral;
import com.google.caja.parser.quasiliteral.QuasiBuilder;
import com.google.caja.plugin.Job;
import com.google.caja.plugin.JobEnvelope;
import com.google.caja.plugin.Jobs;
import com.google.caja.reporting.MessageContext;
import com.google.caja.reporting.MessageLevel;
import com.google.caja.reporting.MessagePart;
import com.google.caja.reporting.MessageQueue;
import com.google.caja.reporting.MessageType;
import com.google.caja.reporting.MessageTypeInt;
import com.google.caja.util.ContentType;
import com.google.caja.util.Maps;
import com.google.caja.util.Pipeline;
import com.google.caja.util.Strings;

/**
* This searches for Flash embeds in unsanitized html and rewrites them.
*
* @author felix8a@gmail.com
*/

public final class RewriteFlashStage implements Pipeline.Stage<Jobs> {
  public boolean apply(Jobs jobs) {
    FlashRewriter rewriter = new FlashRewriter(jobs);
    for (JobEnvelope env: jobs.getJobsByType(ContentType.HTML)) {
      Node root = ((Dom) env.job.getRoot()).getValue();
      rewriter.rewriteChildren(root);
    }
    rewriter.finish();
    return true;
  }
}

final class FlashRewriter {
  final Block embedder = new Block();
  final Jobs jobs;
  final MessageQueue mq;

  FlashRewriter(Jobs jobs) {
    this.jobs = jobs;
    this.mq = jobs.getMessageQueue();
  }

  void finish() {
    if (embedder.children().size() != 0) {
      URI baseUri = null; // TODO(felix8a)
      jobs.getJobs().add(JobEnvelope.of(Job.jsJob(embedder, baseUri)));
    }
    return;
  }

  void rewriteChildren(Node node) {
    Node c = node.getFirstChild();
    for (; c != null; c = c.getNextSibling()) {
      if (c instanceof Element) {
        c = rewriteElement((Element) c);
      }
      rewriteChildren(c);
    }
  }

  private Element rewriteElement(Element el) {
    String tagName = Strings.lower(el.getLocalName());
    if ("embed".equals(tagName)) {
      if (APPLICATION_FLASH.equals(getAttr(el, "type"))) {
        return rewriteEmbed(el);
      }
    } else if ("object".equals(tagName)) {
      if (APPLICATION_FLASH.equals(getAttr(el, "type"))) {
        return rewriteObject(el);
      } else if (Strings.eqIgnoreCase(
          CLASSID_FLASH, getAttr(el, "classid"))) {
        return rewriteObject(el);
      }
    }
    // If an <embed> or <object> tag doesn't match a usage we recognize,
    // leave it alone, it will get stripped by HtmlSanitizerStage.
    return el;
  }

  private Element rewriteEmbed(Element el) {
    String src = getAttr(el, "src");
    if (empty(src)) {
      mq.addMessage(Messages.MISSING_SRC, Nodes.getFilePositionFor(el));
      return el;
    }
    String height = getAttr(el, "height");
    String width = getAttr(el, "width");
    Element r = emplacehold(el);
    if (r == null) {
      mq.addMessage(Messages.NO_PARENT, Nodes.getFilePositionFor(el));
      return el;
    }
    String id = getPlaceholderId(r);
    FilePosition pos = Nodes.getFilePositionFor(el);
    Expression e = (Expression) QuasiBuilder.substV(
        ""
        + "IMPORTS___.htmlEmitter___."
        + "/*@synthetic*/ handleEmbed({"
        + " id: @id,"
        + " src: @src,"
        + " height: @height,"
        + " width: @width })",
        "id", literal(id, pos),
        "src", literal(src, pos),
        "height", literal(height, pos),
        "width", literal(width, pos));
    embedder.appendChild(new ExpressionStmt(e));
    return r;
  }

  private Element rewriteObject(Element el) {
    Element r = emplacehold(el);
    if (r == null) {
      mq.addMessage(Messages.NO_PARENT, Nodes.getFilePositionFor(el));
      return el;
    }

    Map<String, String> params = Maps.newHashMap();
    Node c = el.getFirstChild();
    Node next = null;
    for (; c != null; c = next) {
      next = c.getNextSibling();
      String tagName = getTagName(c);
      if ("param".equals(tagName)) {
        String name = getAttr(c, "name");
        String value = getAttr(c, "value");
        if (!empty(name) && !empty(value)) {
          params.put(name, value);
        }
        c.getParentNode().removeChild(c);
      } else {
        r.appendChild(c);
      }
    }

    String src = getAttr(el, "data");
    if (empty(src)) {
      src = params.get("movie");
    }
    String width = getAttr(el, "width");
    if (empty(width)) {
      width = params.get("width");
    }
    String height = getAttr(el, "height");
    if (empty(height)) {
      height = params.get("height");
    }

    String id = getPlaceholderId(r);
    FilePosition pos = Nodes.getFilePositionFor(el);
    Expression e = (Expression) QuasiBuilder.substV(
        ""
        + "IMPORTS___.htmlEmitter___."
        + "/*@synthetic*/ handleEmbed({"
        + " id: @id,"
        + " src: @src,"
        + " height: @height,"
        + " width: @width })",
        // TODO(felix8a): need to add params and attrs
        "id", literal(id, pos),
        "src", literal(src, pos),
        "height", literal(height, pos),
        "width", literal(width, pos));
    embedder.appendChild(new ExpressionStmt(e));
    return r;
  }

  private Literal literal(String value, FilePosition pos) {
    if (empty(value)) {
      return new NullLiteral(pos);
    } else {
      return StringLiteral.valueOf(pos, value);
    }
  }

  private Element emplacehold(Element el) {
    Node parent = el.getParentNode();
    if (parent == null) { return null; }
    Element span = el.getOwnerDocument().createElementNS(HTML_NS, "span");
    // We're labeling the placeholder span with class rather than id,
    // because there are several complications getting ids through
    // all the HTML rewriter stages.
    span.setAttributeNS(HTML_NS, "class", generateId());
    parent.insertBefore(span, el);
    parent.removeChild(el);
    return span;
  }

  private String getTagName(Node n) {
    if (n instanceof Element) {
      return Strings.lower(n.getLocalName());
    } else {
      return null;
    }
  }

  private String getAttr(Node n, String attrib) {
    if (n instanceof Element) {
      return ((Element) n).getAttributeNS(HTML_NS, attrib);
    } else {
      return null;
    }
  }

  private boolean empty(String s) {
    return s == null || s.length() == 0;
  }

  private String generateId() {
    int guid = jobs.getPluginMeta().generateGuid();
    return "cajaEmbed" + guid;
  }

  private String getPlaceholderId(Element el) {
    return el.getAttributeNS(HTML_NS, "class");
  }

  private static final String HTML_NS = Namespaces.HTML_NAMESPACE_URI;

  private static final String APPLICATION_FLASH =
    "application/x-shockwave-flash";

  private static final String CLASSID_FLASH =
    "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000";

  private enum Messages implements MessageTypeInt {

    MISSING_SRC(MessageLevel.WARNING,
        "%s: Embed is missing src attribute"),

    NO_PARENT(MessageLevel.WARNING,
        "%s: Element doesn't have a parent??");

    private final String formatString;
    private final MessageLevel level;
    private int paramCount = -1;

    Messages(MessageLevel level, String formatString) {
      this.level = level;
      this.formatString = formatString;
    }

    public int getParamCount() {
      if (paramCount < 0) {
        paramCount = MessageType.formatStringArity(formatString);
      }
      return paramCount;
    }

    public void format(MessagePart[] parts, MessageContext context,
                       Appendable out) throws IOException {
      MessageType.formatMessage(formatString, parts, context, out);
    }

    public MessageLevel getLevel() { return level; }
  }
}
TOP

Related Classes of com.google.caja.plugin.stages.RewriteFlashStage

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.