Package com.google.caja.plugin

Source Code of com.google.caja.plugin.CssRewriterTest

// Copyright (C) 2006 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;

import com.google.caja.lang.css.CssSchema;
import com.google.caja.lang.html.HtmlSchema;
import com.google.caja.lexer.ExternalReference;
import com.google.caja.lexer.FilePosition;
import com.google.caja.lexer.InputSource;
import com.google.caja.lexer.ParseException;
import com.google.caja.parser.AncestorChain;
import com.google.caja.parser.MutableParseTreeNode;
import com.google.caja.parser.ParseTreeNode;
import com.google.caja.parser.Visitor;
import com.google.caja.parser.css.CssTree;
import com.google.caja.parser.js.ArrayConstructor;
import com.google.caja.parser.js.Expression;
import com.google.caja.parser.js.ObjProperty;
import com.google.caja.parser.js.ObjectConstructor;
import com.google.caja.parser.js.Operation;
import com.google.caja.parser.js.Operator;
import com.google.caja.parser.js.StringLiteral;
import com.google.caja.parser.js.ValueProperty;
import com.google.caja.reporting.Message;
import com.google.caja.reporting.MessageLevel;
import com.google.caja.reporting.MessagePart;
import com.google.caja.util.CajaTestCase;
import com.google.caja.util.Function;
import com.google.caja.util.Lists;
import com.google.caja.util.MoreAsserts;
import com.google.caja.util.Name;
import com.google.caja.util.Sets;

import java.net.URI;
import java.net.URISyntaxException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;

import junit.framework.AssertionFailedError;

/**
*
* @author mikesamuel@gmail.com
*/
public class CssRewriterTest extends CajaTestCase {

  public final void testCssRewriterEquivalence() throws Exception {
    Expression tests = jsExpr(fromResource("css-stylesheet-tests.js"));
    // tests is a JSONP style JavaScript expression.
    // Normalize "foo" + "bar" -> "foo bar"
    tests.acceptPostOrder(new Visitor() {
      @Override
      public boolean visit(AncestorChain<?> chain) {
        if (Operation.is(chain.node, Operator.ADDITION)) {
          Operation op = chain.cast(Operation.class).node;
          Expression left = op.children().get(0);
          Expression right = op.children().get(1);
          if (left instanceof StringLiteral && right instanceof StringLiteral) {
            StringLiteral concatenation = StringLiteral.valueOf(
                FilePosition.span(
                    left.getFilePosition(), right.getFilePosition()),
                ((StringLiteral) left).getUnquotedValue()
                + ((StringLiteral) right).getUnquotedValue());
            chain.parent.cast(MutableParseTreeNode.class).node.replaceChild(
                concatenation, op);
          }
        }
        return true;
      }
    }, null);

    AssertionFailedError failure = null;

    // InputSource for file positions in error message goldens.
    is = new InputSource(new URI("http://example.org/test"));

    // Extract the JSON style-object from the call.
    assertTrue(render(tests), Operation.is(tests, Operator.FUNCTION_CALL));
    Operation call = (Operation) tests;
    assertEquals(2, call.children().size());
    Expression testArray = call.children().get(1);
    // testArray is an array like
    // [{ test_name: ..., tests: [] }]
    for (Expression test : ((ArrayConstructor) testArray).children()) {
      ObjectConstructor obj = (ObjectConstructor) test;
      String name = (String)
           ((ValueProperty) obj.propertyWithName("test_name"))
           .getValueExpr().getValue();
      ValueProperty testcases = (ValueProperty) obj.propertyWithName("tests");
      // testcases is an object like
      // [{ cssText: ..., golden: ..., messages: ... }]
      for (Expression testCase
           : ((ArrayConstructor) testcases.getValueExpr()).children()) {
        ObjectConstructor testCaseObj = (ObjectConstructor) testCase;
        String cssText = null;
        String golden = null;
        ArrayConstructor messages = null;
        for (ObjProperty oprop : testCaseObj.children()) {
          ValueProperty prop = (ValueProperty) oprop;
          String pname = prop.getPropertyName();
          try {
            if ("cssText".equals(pname)) {
              cssText = ((StringLiteral) prop.getValueExpr())
                  .getUnquotedValue();
            } else if ("golden".equals(pname)) {
              golden = ((StringLiteral) prop.getValueExpr())
                  .getUnquotedValue();
            } else if ("messages".equals(pname)) {
              messages = (ArrayConstructor) prop.getValueExpr();
            } else if ("altGolden".equals(pname)) {
              // OK.
            } else {
              fail(
                  "Unrecognized testcase property " + pname + " in "
                  + render(testCase) + " at " + testCase.getFilePosition());
            }
          } catch (RuntimeException ex) {
            System.err.println(
                "Type mismatch in " + name
                + " at " + testCase.getFilePosition());
            throw ex;
          }
        }

        String normalizedGolden = "".equals(golden)
            ? "" : render(css(fromString(golden)));

        mq.getMessages().clear();
        try {
          runTest(cssText, normalizedGolden);
          if (messages != null) {
            for (Expression message : messages.children()) {
              ObjectConstructor messageObj = (ObjectConstructor) message;
              String type = ((StringLiteral)
                  ((ValueProperty) messageObj.propertyWithName("type"))
                  .getValueExpr())
                  .getUnquotedValue();
              String level = ((StringLiteral)
                  ((ValueProperty) messageObj.propertyWithName("level"))
                  .getValueExpr())
                  .getUnquotedValue();
              List<String> args = Lists.newArrayList();
              ArrayConstructor argsArray = (ArrayConstructor)
                  ((ValueProperty) messageObj.propertyWithName("args"))
                  .getValueExpr();
              for (Expression argExpr : argsArray.children()) {
                args.add(((StringLiteral) argExpr).getUnquotedValue());
              }
              consumeMessage(message.getFilePosition(), type, level, args);
            }
            assertNoErrors();
          }
        } catch (Exception ex) {
          System.err.println("Test " + name + "\n" + render(testCase));
          throw ex;
        } catch (AssertionFailedError ex) {
          System.err.println("Test " + name + "\n" + render(testCase));
          ex.printStackTrace();
          if (failure == null) {
            failure = ex;
          }
        }
      }
    }
    if (failure != null) { throw failure; }
  }

  public final void testSubstitutions() throws Exception {
    try {
      runTest("#foo { left: ${x * 4}px; top: ${y * 4}px; }",
              "", false);
      fail("allowed substitutions when parsing of substitutions disabled");
    } catch (ParseException ex) {
      // pass
    }
    runTest(
        "#foo { left: ${x * 4}px; top: ${y * 4}px; }",
        ".namespace__ #foo-namespace__"
        + " {\n  left: ${x * 4}px;\n  top: ${y * 4}px\n}",
        true);
  }

  public final void testZIndexRange() throws Exception {
    runTest("div { z-index: 0 }", ".namespace__ div {\n  z-index: 0\n}", false);
    assertNoErrors();
    runTest(
        "div { z-index: -9999999 }",
        ".namespace__ div {\n  z-index: -9999999\n}",
        false);
    assertNoErrors();
    runTest(
        "div { z-index: 9999999 }",
        ".namespace__ div {\n  z-index: 9999999\n}",
        false);
    assertNoErrors();
    runTest(
        "div { z-index: -10000000 }",
        ".namespace__ div {\n  z-index: -10000000\n}",
        false);
    assertMessage(PluginMessageType.CSS_VALUE_OUT_OF_RANGE,
        MessageLevel.WARNING,
        Name.css("z-index"));
    runTest(
        "div { z-index: 10000000 }",
        ".namespace__ div {\n  z-index: 10000000\n}",
        false);
    assertMessage(PluginMessageType.CSS_VALUE_OUT_OF_RANGE,
        MessageLevel.WARNING,
        Name.css("z-index"));
  }

  public final void testNonStandardColors() throws Exception {
    FilePosition u = FilePosition.UNKNOWN;
    assertNull(CssRewriter.colorHash(u, Name.css("invisible")));
    // Can get color hashes even for standard colors.
    assertEquals("#00f", CssRewriter.colorHash(u, Name.css("blue")).getValue());
    // Is case insensitive.
    assertEquals("#00f", CssRewriter.colorHash(u, Name.css("Blue")).getValue());
    assertEquals("#00f", CssRewriter.colorHash(u, Name.css("BLUE")).getValue());

    assertEquals("#000", CssRewriter.colorHash(u, 0).getValue());
    assertEquals("#fff", CssRewriter.colorHash(u, 0xffffff).getValue());
    assertEquals("#123", CssRewriter.colorHash(u, 0x112233).getValue());
    // A change in any quartet causes the long form to be used.
    assertEquals(
        "#022233", CssRewriter.colorHash(u, 0x112233 ^ 0x130000).getValue());
    assertEquals(
        "#111333", CssRewriter.colorHash(u, 0x112233 ^ 0x003100).getValue());
    assertEquals(
        "#112220", CssRewriter.colorHash(u, 0x112233 ^ 0x000013).getValue());
  }

  public final void testUrisCalledWithProperPropertyPart() throws Exception {
    // The CssRewriter needs to rewrite URIs.
    // When it does so it passes
    assertCallsUriRewriterWithPropertyPart(
        "background: 'foo.png'",
        "background::bg-image::image");
    assertCallsUriRewriterWithPropertyPart(
        ""
        + "img.trans {"
        + "  filter: progid:DXImageTransform.Microsoft.AlphaImageLoader("
        + "      src='bar.png', sizingMethod='image');"
        + "}",
        "filter::prog-id::prog-id-alpha-image-loader::page-url");
  }

  private void assertUriPolicy(
      UriPolicy uriPolicy,
      String css,
      List<String> urisExpectedSafe,
      List<String> urisExpectedUnsafe)
      throws Exception {
    final List<String> urisFoundSafe = Lists.newArrayList();
    final List<String> urisFoundUnsafe = Lists.newArrayList();
    CssTree t = css(fromString(css), false);
    new CssValidator(CssSchema.getDefaultCss21Schema(mq),
        HtmlSchema.getDefault(mq), mq)
        .validateCss(AncestorChain.instance(t));
    new CssRewriter(uriPolicy, CssSchema.getDefaultCss21Schema(mq), mq)
        .rewrite(AncestorChain.instance(t));
    t.acceptPreOrder(new Visitor() {
      public boolean visit(AncestorChain<?> ancestors) {
        ParseTreeNode node = ancestors.node;
        if (node instanceof CssTree.UriLiteral) {
          String value = ((CssTree.CssLiteral) node).getValue();
          if (node instanceof SafeUriLiteral) {
            urisFoundSafe.add(value);
          } else if (node instanceof UnsafeUriLiteral) {
            urisFoundUnsafe.add(value);
          } else {
            fail("Tree should not contain any plain CssTree.UriLiteral");
          }
        }
        return true;
      }
    }, null);
    MoreAsserts.assertListsEqual(
        urisExpectedSafe,
        Lists.newArrayList(urisFoundSafe));
    MoreAsserts.assertListsEqual(
        urisExpectedUnsafe,
        Lists.newArrayList(urisFoundUnsafe));
  }

  public final void testUriPolicyPresent() throws Exception {
    assertUriPolicy(
        UriPolicy.IDENTITY,
        ""
            + "div { background: url(bar.png); }",
        Arrays.asList("http://example.org/bar.png"),
        Arrays.<String>asList());
    assertUriPolicy(
        UriPolicy.IDENTITY,
        ""
        + "div { background: 'bar.png' }",
        Arrays.asList("http://example.org/bar.png"),
        Arrays.<String>asList());
  }

  public final void testUriPolicyAbsent() throws Exception {
    assertUriPolicy(
        null,
        ""
        + "div { background-image: url(bar.png); }",
        Arrays.<String>asList(),
        Arrays.asList("http://example.org/bar.png"));
    assertUriPolicy(
        null,
        ""
        + "div { background-image: url(bar.png); }",
        Arrays.<String>asList(),
        Arrays.asList("http://example.org/bar.png"));
  }

  private void runTest(String css, String golden) throws Exception {
    runTest(css, golden, false);
  }

  private void runTest(String css, String golden, boolean allowSubstitutions)
      throws Exception {
    mq.getMessages().clear();
    mc.relevantKeys = Collections.singleton(CssValidator.INVALID);

    CssTree t = css(fromString(css), allowSubstitutions);

    String msg;
    {
      StringBuilder msgBuf = new StringBuilder();
      t.formatTree(mc, 0, msgBuf);
      msg = msgBuf.toString();
    }

    CssSchema cssSchema = CssSchema.getDefaultCss21Schema(mq);
    new CssValidator(cssSchema, HtmlSchema.getDefault(mq), mq)
        .validateCss(AncestorChain.instance(t));
    new CssRewriter(
        new UriPolicy() {
          public String rewriteUri(
              ExternalReference ref, UriEffect effect, LoaderType loader,
              Map<String, ?> hints) {
            URI uri = ref.getUri();

            if ("http".equals(uri.getScheme())  // Used by CajaTestCase
                && "example.org".equals(uri.getHost())
                && uri.getPath() != null
                && uri.getPath().startsWith("/")) {
              try {
                return new URI(null, null, "/foo" + uri.getPath(),
                               uri.getQuery(), uri.getFragment())
                    .toString();
              } catch (URISyntaxException ex) {
                ex.printStackTrace();
                return null;
              }
            } else if ("whitelisted-host.com".equals(uri.getHost())) {
              return uri.toString();
            } else {
              return null;
            }
          }
        },
        cssSchema, mq)
        .rewrite(AncestorChain.instance(t));

    {
      StringBuilder msgBuf = new StringBuilder();
      t.formatTree(mc, 0, msgBuf);
      msg += "\n  ->\n" + msgBuf.toString();
    }

    assertEquals(msg, golden, render(t));
  }

  private void assertCallsUriRewriterWithPropertyPart(
      String cssCode, String... expectedParts)
      throws ParseException {
    final Set<String> propertyParts = Sets.newLinkedHashSet();

    CssTree t = cssCode.trim().endsWith("}")
        ? css(fromString(cssCode)) : cssDecls(fromString(cssCode));

    CssSchema cssSchema = CssSchema.getDefaultCss21Schema(mq);
    new CssValidator(cssSchema, HtmlSchema.getDefault(mq), mq)
        .validateCss(AncestorChain.instance(t));
    new CssRewriter(
        new UriPolicy() {
          public String rewriteUri(
              ExternalReference ref, UriEffect effect, LoaderType loader,
              Map<String, ?> hints) {
            propertyParts.add(
                UriPolicyHintKey.CSS_PROP.valueFrom(hints)
                    .getCanonicalForm());
            return ref.getUri().toString();
          }
        },
        cssSchema, mq)
        .rewrite(AncestorChain.instance(t));

    MoreAsserts.assertListsEqual(
        Arrays.asList(expectedParts),
        Lists.newArrayList(propertyParts));
  }

  private void consumeMessage(
      FilePosition pos, final String type, final String level,
      final List<String> parts) {
    try {
      assertMessage(
          true,
          new Function<Message, Integer>() {
            @Override
            public Integer apply(Message msg) {
              int score = 0;
              if (msg.getMessageType().name().equals(type)) { ++score; }
              if (msg.getMessageLevel().name().equals(level)) { ++score; }
              score -= partsMissing(msg, parts);
              return (score == 2) ? Integer.MAX_VALUE : score;
            }
          }, "type=" + type + ", level=" + level);
    } catch (AssertionFailedError err) {
      System.err.println("Message specified at " + pos + " was not found");
      throw err;
    }
  }

  private static int partsMissing(Message msg, List<? extends String> parts) {
    int missing = 0;
    outerLoop:
    for (String expectedPart : parts) {
      for (MessagePart candidate : msg.getMessageParts()) {
        String candidatePart = candidate.toString();
        if (candidatePart.equals(expectedPart)) { continue outerLoop; }
      }
      ++missing;
    }
    return missing;
  }
}
TOP

Related Classes of com.google.caja.plugin.CssRewriterTest

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.