Package com.google.caja.service

Source Code of com.google.caja.service.AbstractCajolingHandler$IOCallback

// Copyright 2010 Google Inc. All Rights Reserved.
// 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.service;

import java.io.IOException;
import java.io.OutputStream;
import java.net.URI;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

import com.google.caja.lexer.CharProducer;
import com.google.caja.lexer.InputSource;
import com.google.caja.lexer.JsLexer;
import com.google.caja.lexer.JsTokenQueue;
import com.google.caja.lexer.ParseException;
import com.google.caja.lexer.TokenConsumer;
import com.google.caja.parser.ParseTreeNode;
import com.google.caja.parser.js.Identifier;
import com.google.caja.parser.js.Parser;
import com.google.caja.parser.js.Reference;
import com.google.caja.parser.quasiliteral.QuasiBuilder;
import com.google.caja.render.JsMinimalPrinter;
import com.google.caja.render.JsPrettyPrinter;
import com.google.caja.reporting.SimpleMessageQueue;
import com.google.caja.util.ContentType;
import com.google.caja.util.Maps;
import org.w3c.dom.Node;

import com.google.caja.lexer.FetchedData;
import com.google.caja.lexer.FilePosition;
import com.google.caja.parser.html.Nodes;
import com.google.caja.parser.js.ArrayConstructor;
import com.google.caja.parser.js.Expression;
import com.google.caja.parser.js.IntegerLiteral;
import com.google.caja.parser.js.ObjectConstructor;
import com.google.caja.parser.js.StringLiteral;
import com.google.caja.parser.js.ValueProperty;
import com.google.caja.plugin.UriFetcher;
import com.google.caja.render.Concatenator;
import com.google.caja.reporting.BuildInfo;
import com.google.caja.reporting.Message;
import com.google.caja.reporting.MessageQueue;
import com.google.caja.reporting.RenderContext;
import com.google.caja.util.Callback;
import com.google.caja.util.Lists;
import com.google.caja.util.Pair;

/**
* Common parent class for handlers that invoke the cajoler
* and render the result
*
* @author jasvir@google.com (Jasvir Nagra)
*/
public abstract class AbstractCajolingHandler implements ContentHandler {
  protected final BuildInfo buildInfo;
  protected final UriFetcher uriFetcher;
  protected final String hostedService;

  public AbstractCajolingHandler(
      BuildInfo buildInfo, String hostedService, UriFetcher uriFetcher) {
    this.buildInfo = buildInfo;
    this.hostedService = hostedService;
    this.uriFetcher = uriFetcher != null ? uriFetcher : UriFetcher.NULL_NETWORK;
  }

  public abstract boolean canHandle(URI uri,
      CajolingService.Transform transform,
      List<CajolingService.Directive> directives,
      String inputContentType,
      ContentTypeCheck checker);

  public abstract Pair<String,String> apply(URI uri,
      CajolingService.Transform transform,
      List<CajolingService.Directive> directives,
      ContentHandlerArgs args,
      String inputContentType,
      ContentTypeCheck checker,
      FetchedData input,
      OutputStream response,
      MessageQueue mq)
      throws UnsupportedContentTypeException;

  private static StringLiteral lit(String s) {
    return StringLiteral.valueOf(FilePosition.UNKNOWN, s);
  }

  private static IntegerLiteral lit(int i) {
    return new IntegerLiteral(FilePosition.UNKNOWN, i);
  }

  private static ArrayConstructor arr(List<? extends Expression> items) {
    return new ArrayConstructor(FilePosition.UNKNOWN, items);
  }

  private static ObjectConstructor obj(List<? extends ValueProperty> props) {
    return new ObjectConstructor(FilePosition.UNKNOWN, props);
  }

  private static ValueProperty prop(String key, Expression e) {
    return new ValueProperty(FilePosition.UNKNOWN, lit(key), e);
  }

  /**
   * Checks whether a string is a JavaScript Identifier.
   */
  /* visible for testing */ static boolean checkIdentifier(String candidate) {
    // Using a simple regex is possible if we reject anything but 7-bit ASCII.
    // However, this implementation ensures Caja has a single point of truth
    // regarding what constitutes a JS identifier.
    MessageQueue mq = new SimpleMessageQueue();
    Parser parser = new Parser(
        new JsTokenQueue(
            new JsLexer(
                CharProducer.Factory.fromString(
                    "var " + candidate + ";",
                    InputSource.UNKNOWN)),
            InputSource.UNKNOWN),
        mq);
    ParseTreeNode node;
    try { node = parser.parse(); } catch (ParseException e) { return false; }
    if (node == null || !mq.getMessages().isEmpty()) { return false; }
    Map<String, ParseTreeNode> bindings = Maps.newHashMap();
    if (!QuasiBuilder.match("{ var @p; }", node, bindings)) { return false; }
    if (bindings.size() != 1) { return false; }
    if (bindings.get("p") == null) { return false; }
    if (!(bindings.get("p") instanceof Identifier)) { return false; }
    Identifier p = (Identifier) bindings.get("p");
    if (!candidate.equals(p.getName())) { return false; }
    return true;
  }

  private static class IOCallback implements Callback<IOException> {
    public IOException ex = null;
    public void handle(IOException e) {
      if (this.ex != null) { this.ex = e; }
    }
  }

  protected static void renderAsJSON(
      Node staticHtml,
      ParseTreeNode javascript,
      String jsonpCallback,
      MessageQueue mq,
      Appendable output,
      boolean pretty)
      throws IOException {
    String html = staticHtml == null ? null : Nodes.render(staticHtml);
    String js =
      javascript == null ? null : renderJavascript(javascript, pretty);
    renderAsJSON(html, js, jsonpCallback, mq, output, pretty);
  }

  protected static void renderAsJSON(
    String staticHtml,
    String javascript,
    String jsonpCallback,
    MessageQueue mq,
    Appendable output,
    boolean pretty) throws IOException {

    List<ValueProperty> props = Lists.newArrayList();

    if (staticHtml != null) {
      props.add(prop("html", lit(staticHtml)));
    }
    if (javascript != null) {
      props.add(prop("js", lit(javascript)));
    }
    List<Expression> messages = Lists.newArrayList();
    for (Message m : mq.getMessages()) {
      messages.add(obj(Arrays.asList(
          prop("level", lit(m.getMessageLevel().ordinal())),
          prop("name", lit(m.getMessageLevel().name())),
          prop("type", lit(m.getMessageType().name())),
          prop("message", lit(m.toString())))));
    }
    props.add(prop("messages", arr(messages)));

    if (jsonpCallback != null && !checkIdentifier(jsonpCallback)) {
      throw new RuntimeException("Detected XSS attempt; aborting request");
    }

    ParseTreeNode result = (jsonpCallback == null)
        ? obj(props)
        : QuasiBuilder.substV("@c(@o);",
            "c", new Reference(
                     new Identifier(
                         FilePosition.UNKNOWN,
                         jsonpCallback)),
            "o", obj(props));

    IOCallback callback = new IOCallback();
    RenderContext rc = makeRenderContext(output, callback, pretty, true);
    result.render(rc);
    rc.getOut().noMoreTokens();
    if (callback.ex != null) { throw callback.ex; }
  }

  private static String renderJavascript(
      ParseTreeNode javascript, boolean pretty)
      throws IOException {
    StringBuilder jsOut = new StringBuilder();
    IOCallback callback = new IOCallback();
    RenderContext rc = makeRenderContext(jsOut, callback, pretty, false);
    javascript.render(rc);
    rc.getOut().noMoreTokens();
    if (callback.ex != null) { throw callback.ex; }
    return jsOut.toString();
  }

  private static RenderContext makeRenderContext(
      Appendable a, IOCallback cb,
      boolean pretty,
      boolean json) {
    TokenConsumer tc = pretty
        ? new JsPrettyPrinter(new Concatenator(a, cb))
        : new JsMinimalPrinter(new Concatenator(a, cb));
    return new RenderContext(tc).withJson(json);
  }

  protected static Pair<ContentType, String> getReturnedContentParams(
      ContentHandlerArgs args) {
    String alt = CajaArguments.ALT.get(args);
    if ("json".equals(alt) || alt == null) {
      return Pair.pair(ContentType.JSON, null);
    } else if ("json-in-script".equals(alt)) {
      String callback = CajaArguments.CALLBACK.get(args);
      if (callback == null) {
        throw new RuntimeException(
            "Missing value for parameter " + CajaArguments.CALLBACK);
      } else {
        return Pair.pair(ContentType.JS, callback);
      }
    } else {
      throw new RuntimeException(
          "Invalid value " + alt + " for parameter " + CajaArguments.ALT);
    }
  }
}
TOP

Related Classes of com.google.caja.service.AbstractCajolingHandler$IOCallback

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.