EMMA Coverage Report (generated Mon Nov 01 16:48:29 PDT 2010)
[all classes][com.google.caja.plugin]

COVERAGE SUMMARY FOR SOURCE FILE [CssRewriter.java]

nameclass, %method, %block, %line, %
CssRewriter.java100% (12/12)100% (66/66)96%  (2485/2590)93%  (340.2/364)

COVERAGE BREAKDOWN BY CLASS AND METHOD

nameclass, %method, %block, %line, %
     
class CssRewriter100% (1/1)100% (44/44)97%  (1608/1658)94%  (177.2/188)
<static initializer> 100% (1/1)100% (661/663)100% (10/10)
CssRewriter (UriPolicy, CssSchema, MessageQueue): void 100% (1/1)76%  (25/33)91%  (7.3/8)
access$000 (CssRewriter, CssTree$RuleSet): Pair 100% (1/1)100% (4/4)100% (1/1)
access$100 (): Set 100% (1/1)100% (2/2)100% (1/1)
access$1000 (AncestorChain): CssTree$Selector 100% (1/1)100% (3/3)100% (1/1)
access$1100 (AncestorChain): CssTree$Declaration 100% (1/1)100% (3/3)100% (1/1)
access$1200 (AncestorChain): CssTree$SimpleSelector 100% (1/1)100% (3/3)100% (1/1)
access$1300 (CssRewriter): UriPolicy 100% (1/1)100% (3/3)100% (1/1)
access$200 (ParseTreeNode): CssPropertyPartType 100% (1/1)100% (3/3)100% (1/1)
access$300 (String): boolean 100% (1/1)100% (3/3)100% (1/1)
access$400 (CssRewriter): MessageQueue 100% (1/1)100% (3/3)100% (1/1)
access$500 (ParseTreeNode): Name 100% (1/1)100% (3/3)100% (1/1)
access$600 (String): boolean 100% (1/1)100% (3/3)100% (1/1)
access$700 (): Set 100% (1/1)100% (2/2)100% (1/1)
access$800 (CssRewriter, AncestorChain): boolean 100% (1/1)100% (4/4)100% (1/1)
access$900 (CssRewriter): MessageLevel 100% (1/1)100% (3/3)100% (1/1)
colorHash (FilePosition, Name): CssTree$HashLiteral 100% (1/1)100% (15/15)100% (2/2)
colorHash (FilePosition, int): CssTree$HashLiteral 100% (1/1)100% (33/33)100% (3/3)
combineLooseWords (CssTree$Expr): void 100% (1/1)99%  (153/154)97%  (30/31)
containsLinkPseudoClass (CssTree$SimpleSelector): boolean 100% (1/1)100% (16/16)100% (3/3)
declarationFor (AncestorChain): CssTree$Declaration 100% (1/1)89%  (17/19)75%  (3/4)
fixTerms (AncestorChain): void 100% (1/1)94%  (31/33)83%  (5/6)
isLooseWord (CssTree$Term): boolean 100% (1/1)100% (15/15)100% (1/1)
isSafeSelectorPart (String): boolean 100% (1/1)100% (5/5)100% (1/1)
isZeroOrHasUnits (String): boolean 100% (1/1)100% (37/37)100% (7/7)
mightContainUrl (CssTree$Expr): boolean 100% (1/1)71%  (20/28)71%  (3.6/5)
propertyPart (ParseTreeNode): Name 100% (1/1)100% (6/6)100% (1/1)
propertyPartType (ParseTreeNode): CssPropertyPartType 100% (1/1)100% (6/6)100% (1/1)
quoteLooseWords (AncestorChain): void 100% (1/1)100% (31/31)100% (5/5)
removeEmptyDeclarationsAndSelectors (AncestorChain): void 100% (1/1)100% (12/12)100% (2/2)
removeEmptyRuleSets (AncestorChain): void 100% (1/1)100% (12/12)100% (2/2)
removeForbiddenIdents (AncestorChain): void 100% (1/1)100% (12/12)100% (2/2)
removeInvalidNodes (AncestorChain): void 100% (1/1)86%  (50/58)82%  (9/11)
removeUnsafeConstructs (AncestorChain): void 100% (1/1)100% (48/48)100% (6/6)
rewrite (AncestorChain): void 100% (1/1)100% (28/28)100% (10/10)
rewriteHistorySensitiveRuleset (CssTree$RuleSet): Pair 100% (1/1)100% (68/68)100% (14/14)
rewriteHistorySensitiveRulesets (AncestorChain): void 100% (1/1)100% (13/13)100% (2/2)
selectorFor (AncestorChain): CssTree$Selector 100% (1/1)89%  (17/19)75%  (3/4)
simpleSelectorFor (AncestorChain): CssTree$SimpleSelector 100% (1/1)89%  (17/19)75%  (3/4)
strippedPropertiesBannedInLinkClasses (AncestorChain): boolean 100% (1/1)98%  (117/119)99%  (20.7/21)
translateUrls (AncestorChain): void 100% (1/1)100% (12/12)100% (2/2)
vetLinkToHistorySensitiveSelector (CssTree$Selector): boolean 100% (1/1)100% (26/26)100% (5/5)
vetLinkToHistorySensitiveSimpleSelector (CssTree$SimpleSelector): boolean 100% (1/1)82%  (58/71)90%  (11.7/13)
withInvalidNodeMessageLevel (MessageLevel): CssRewriter 100% (1/1)100% (5/5)100% (2/2)
     
class CssRewriter$1100% (1/1)100% (2/2)100% (53/53)100% (8/8)
CssRewriter$1 (CssRewriter, AncestorChain): void 100% (1/1)100% (9/9)100% (1/1)
visit (AncestorChain): boolean 100% (1/1)100% (44/44)100% (7/7)
     
class CssRewriter$10100% (1/1)100% (2/2)100% (39/39)100% (6/6)
CssRewriter$10 (CssRewriter): void 100% (1/1)100% (6/6)100% (1/1)
visit (AncestorChain): boolean 100% (1/1)100% (33/33)100% (5/5)
     
class CssRewriter$11100% (1/1)100% (2/2)91%  (85/93)85%  (17/20)
CssRewriter$11 (CssRewriter): void 100% (1/1)100% (6/6)100% (1/1)
visit (AncestorChain): boolean 100% (1/1)91%  (79/87)84%  (16/19)
     
class CssRewriter$2100% (1/1)100% (2/2)100% (41/41)100% (9/9)
CssRewriter$2 (CssRewriter, boolean []): void 100% (1/1)100% (9/9)100% (1/1)
visit (AncestorChain): boolean 100% (1/1)100% (32/32)100% (8/8)
     
class CssRewriter$3100% (1/1)100% (2/2)96%  (156/163)93%  (25/27)
CssRewriter$3 (CssRewriter, Pattern): void 100% (1/1)100% (9/9)100% (1/1)
visit (AncestorChain): boolean 100% (1/1)95%  (147/154)92%  (24/26)
     
class CssRewriter$4100% (1/1)100% (2/2)81%  (44/54)80%  (12/15)
CssRewriter$4 (CssRewriter): void 100% (1/1)100% (6/6)100% (1/1)
visit (AncestorChain): boolean 100% (1/1)79%  (38/48)79%  (11/14)
     
class CssRewriter$5100% (1/1)100% (2/2)100% (48/48)100% (10/10)
CssRewriter$5 (CssRewriter): void 100% (1/1)100% (6/6)100% (1/1)
visit (AncestorChain): boolean 100% (1/1)100% (42/42)100% (9/9)
     
class CssRewriter$6100% (1/1)100% (2/2)100% (74/74)100% (13/13)
CssRewriter$6 (CssRewriter): void 100% (1/1)100% (6/6)100% (1/1)
visit (AncestorChain): boolean 100% (1/1)100% (68/68)100% (12/12)
     
class CssRewriter$7100% (1/1)100% (2/2)100% (71/71)100% (13/13)
CssRewriter$7 (CssRewriter): void 100% (1/1)100% (6/6)100% (1/1)
visit (AncestorChain): boolean 100% (1/1)100% (65/65)100% (12/12)
     
class CssRewriter$8100% (1/1)100% (2/2)100% (122/122)100% (21/21)
CssRewriter$8 (CssRewriter): void 100% (1/1)100% (6/6)100% (1/1)
visit (AncestorChain): boolean 100% (1/1)100% (116/116)100% (20/20)
     
class CssRewriter$9100% (1/1)100% (2/2)83%  (144/174)85%  (29/34)
CssRewriter$9 (CssRewriter): void 100% (1/1)100% (6/6)100% (1/1)
visit (AncestorChain): boolean 100% (1/1)82%  (138/168)85%  (28/33)

1// Copyright (C) 2006 Google Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//      http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14 
15package com.google.caja.plugin;
16 
17import com.google.caja.SomethingWidgyHappenedError;
18import com.google.caja.lang.css.CssPropertyPatterns;
19import com.google.caja.lang.css.CssSchema;
20import com.google.caja.lang.css.CssSchema.SymbolInfo;
21import com.google.caja.lexer.ExternalReference;
22import com.google.caja.lexer.FilePosition;
23import com.google.caja.lexer.TokenConsumer;
24import com.google.caja.parser.AncestorChain;
25import com.google.caja.parser.MutableParseTreeNode;
26import com.google.caja.parser.ParseTreeNode;
27import com.google.caja.parser.Visitor;
28import com.google.caja.parser.css.CssTree;
29import com.google.caja.parser.html.ElKey;
30import com.google.caja.parser.html.Namespaces;
31import com.google.caja.render.Concatenator;
32import com.google.caja.render.CssPrettyPrinter;
33import com.google.caja.reporting.Message;
34import com.google.caja.reporting.MessageLevel;
35import com.google.caja.reporting.MessagePart;
36import com.google.caja.reporting.MessageQueue;
37import com.google.caja.reporting.RenderContext;
38import com.google.caja.util.Lists;
39import com.google.caja.util.Maps;
40import com.google.caja.util.Name;
41import com.google.caja.util.Pair;
42import com.google.caja.util.Sets;
43 
44import java.net.URI;
45import java.net.URISyntaxException;
46import java.util.Collections;
47import java.util.List;
48import java.util.Map;
49import java.util.Set;
50import java.util.regex.Pattern;
51 
52/**
53 * Rewrites CSS to be safer and shorter.
54 * Excises disallowed constructs, removes extraneous nodes, and collapses
55 * duplicate rule-set selectors.
56 * <p>
57 * Does not separate rules into separate name-spaces.
58 *
59 * @author mikesamuel@gmail.com
60 */
61public final class CssRewriter {
62  private final UriPolicy uriPolicy;
63  private final CssSchema schema;
64  private final MessageQueue mq;
65  private MessageLevel invalidNodeMessageLevel = MessageLevel.ERROR;
66 
67  public CssRewriter(UriPolicy uriPolicy, CssSchema schema, MessageQueue mq) {
68    assert null != mq;
69    assert null != uriPolicy;
70    this.uriPolicy = uriPolicy;
71    this.schema = schema;
72    this.mq = mq;
73  }
74 
75  /**
76   * Specifies the level of messages issued when nodes are marked
77   * {@link CssValidator#INVALID}.
78   * If you are dealing with noisy CSS and later remove invalid nodes, then
79   * this can be set to {@link MessageLevel#WARNING}.
80   * @return this
81   */
82  public CssRewriter withInvalidNodeMessageLevel(MessageLevel messageLevel) {
83    this.invalidNodeMessageLevel = messageLevel;
84    return this;
85  }
86 
87  /**
88   * Rewrite the given CSS tree to be safer and shorter.
89   *
90   * If the tree could not be made safe, then there will be
91   * {@link MessageLevel#ERROR error}s on the {@link MessageQueue} passed
92   * to the constructor.
93   *
94   * @param t non null.  modified in place.
95   */
96  public void rewrite(AncestorChain<? extends CssTree> t) {
97    rewriteHistorySensitiveRulesets(t);
98    quoteLooseWords(t);
99    fixTerms(t);
100    // Once at the beginning, and again at the end.
101    removeUnsafeConstructs(t);
102    removeEmptyDeclarationsAndSelectors(t);
103    // After we remove declarations, we may have some rulesets without any
104    // declarations which is technically illegal, so we remove rulesets without
105    // declarations.
106    removeEmptyRuleSets(t);
107    // Disallow classes and IDs that end in double underscore.
108    removeForbiddenIdents(t);
109    // Do this again to make sure no earlier changes introduce unsafe constructs
110    removeUnsafeConstructs(t);
111 
112    translateUrls(t);
113  }
114 
115  /**
116   * A set of pseudo classes that are allowed in restricted context because they
117   * can leak user history information.
118   * <p>
119   * From http://www.w3.org/TR/css3-selectors/#dynamic-pseudos : <blockquote>
120   *   <h3>6.6.1. Dynamic pseudo-classes</h3>
121   *   The link pseudo-classes: :link and :visited<br>
122   *   <br>
123   *   User agents commonly display unvisited links differently from previously
124   *   visited ones. Selectors provides the pseudo-classes :link and :visited to
125   *   distinguish them:<ul>
126   *     <li>The :link pseudo-class applies to links that have not yet been
127   *         visited.
128   *     <li>The :visited pseudo-class applies once the link has been visited by
129   *         the user.
130   *   </ul>
131   * </blockquote>
132   */
133  private static final Set<Name> LINK_PSEUDO_CLASSES = Sets.immutableSet(
134      Name.css("link"), Name.css("visited"));
135 
136  /**
137   * Split any ruleset containing :link or :visited pseudoclasses into two
138   * rulesets: one with these pseudoclasses in the selector, and one without.
139   * (One of these resulting rulesets may be empty and thus not emitted.) So
140   * for example, the stylesheet:
141   *
142   * <pre>
143   *   :visited, a:link, p, div { color: blue }
144   * </pre>
145   *
146   * <p>becomes:
147   *
148   * <pre>
149   *   :visited, a:link { color: blue }
150   *   p, div { color: blue }
151   * </pre>
152   *
153   * <p>We do this because, downstream, we are going to cull away declarations
154   * for properties which are not permitted to depend on the :link or :visisted
155   * pseudoclasses. We do this, in turn, to prevent history mining attacks.
156   *
157   * <p>Furthermore, scope any selectors containing linkey pseudo classes to
158   * operate only on anchor (<A>) elements. Modify it if necessary, or record
159   * an error if the selector is already scoped to some element that is not an
160   * anchor. For example:
161   *
162   * <pre>
163   *   div#foo     -->  div#foo     (unmodified)
164   *   :visited    -->  a:visited
165   *   :link       -->  a:link
166   *   *:visited   -->  a:visited
167   *   p:visited   -->  ERROR
168   * </pre>
169   *
170   * <p>We do this to ensure the most predictable possible browser behavior
171   * around this sensitive and exploitable issue.
172   */
173  private void rewriteHistorySensitiveRulesets(
174      final AncestorChain<? extends CssTree> t) {
175    t.node.acceptPreOrder(new Visitor() {
176        public boolean visit(AncestorChain<?> ancestors) {
177          if (!(ancestors.node instanceof CssTree.RuleSet)) { return true; }
178          Pair<CssTree.RuleSet, CssTree.RuleSet> rewritten =
179              rewriteHistorySensitiveRuleset((CssTree.RuleSet) ancestors.node);
180          if (rewritten != null) {
181            t.node.insertBefore(rewritten.a, ancestors.node);
182            t.node.insertBefore(rewritten.b, ancestors.node);
183            t.node.removeChild(ancestors.node);
184          }
185          return false;
186        }
187      }, t.parent);
188  }
189 
190  private Pair<CssTree.RuleSet, CssTree.RuleSet> rewriteHistorySensitiveRuleset(
191      CssTree.RuleSet ruleSet) {
192    List<CssTree> linkeyChildren = Lists.newArrayList();
193    List<CssTree> nonLinkeyChildren = Lists.newArrayList();
194 
195    for (CssTree child : ruleSet.children()) {
196      if (child instanceof CssTree.Selector) {
197        CssTree.Selector selector = (CssTree.Selector) child;
198        if (vetLinkToHistorySensitiveSelector(selector)) {
199          linkeyChildren.add(selector);
200        } else {
201          nonLinkeyChildren.add(selector);
202        }
203      } else {
204        // All the selectors come first, so now we know whether we need to split
205        // the child lists in two.
206        if (linkeyChildren.isEmpty() || nonLinkeyChildren.isEmpty()) {
207          return null;
208        } else {
209          linkeyChildren.add(child);
210          nonLinkeyChildren.add((CssTree) child.clone());
211        }
212      }
213    }
214 
215    return Pair.pair(
216        new CssTree.RuleSet(ruleSet.getFilePosition(), linkeyChildren),
217        new CssTree.RuleSet(ruleSet.getFilePosition(), nonLinkeyChildren));
218  }
219 
220  /**
221   * Rewrites any visited or link pseudo class elements to have element name A.
222   * @return true if argument is a compound selector like
223   *     {@code div#foo > p > *:visited}.
224   */
225  private boolean vetLinkToHistorySensitiveSelector(CssTree.Selector selector) {
226    boolean modified = false;
227    for (CssTree child : selector.children()) {
228      if (child instanceof CssTree.SimpleSelector) {
229        modified |= vetLinkToHistorySensitiveSimpleSelector(
230            (CssTree.SimpleSelector) child);
231      }
232    }
233    return modified;
234  }
235 
236  /** The name of an anchor {@code <A>} HTML tag. */
237  private static final ElKey HTML_ANCHOR = ElKey.forHtmlElement("a");
238 
239  /**
240   * Rewrites any visited or link pseudo class elements to have element name A.
241   * @return true iff argument is a simple selector like {@code *:visited}.
242   */
243  private boolean vetLinkToHistorySensitiveSimpleSelector(
244      CssTree.SimpleSelector selector) {
245    if (selector.children().isEmpty()) { return false; }
246    if (!containsLinkPseudoClass(selector)) { return false; }
247    CssTree firstChild = selector.children().get(0);
248    if (firstChild instanceof CssTree.WildcardElement) {
249      // "*#foo:visited" --> "a#foo:visited"
250      selector.replaceChild(
251          new CssTree.IdentLiteral(
252              firstChild.getFilePosition(), HTML_ANCHOR.toString()),
253          firstChild);
254      return true;
255    } else if (firstChild instanceof CssTree.IdentLiteral) {
256      // "a#foo:visited" is legal; "p#foo:visited" is not
257      String value = ((CssTree.IdentLiteral) firstChild).getValue();
258      if (!HTML_ANCHOR.equals(
259              ElKey.forElement(Namespaces.HTML_DEFAULT, value))) {
260        mq.addMessage(
261            PluginMessageType.CSS_LINK_PSEUDO_SELECTOR_NOT_ALLOWED_ON_NONANCHOR,
262            firstChild.getFilePosition());
263      }
264      return false;
265    } else {
266      // "#foo:visited" --> "a#foo:visited"
267      selector.insertBefore(
268          new CssTree.IdentLiteral(
269              firstChild.getFilePosition(), HTML_ANCHOR.toString()),
270          firstChild);
271      return true;
272    }
273  }
274 
275  private boolean containsLinkPseudoClass(CssTree.SimpleSelector selector) {
276    final boolean[] result = new boolean[1];
277    selector.acceptPreOrder(new Visitor() {
278      public boolean visit(AncestorChain<?> chain) {
279        if (chain.node instanceof CssTree.Pseudo) {
280          CssTree firstChild = (CssTree) chain.node.children().get(0);
281          if (firstChild instanceof CssTree.IdentLiteral) {
282            CssTree.IdentLiteral ident = (CssTree.IdentLiteral) firstChild;
283            if (LINK_PSEUDO_CLASSES.contains(Name.css(ident.getValue()))) {
284              result[0] = true;
285              return false;
286            }
287          }
288        }
289        return true;
290      }
291    }, null);
292    return result[0];
293  }
294 
295  /**
296   * Turn a run of unquoted identifiers into a single string, where the property
297   * description says "Names containing space *should* be quoted", but does not
298   * require it.
299   * <p>
300   * This is important for font {@code family-name}s where
301   * {@code font: Times New Roman} should be written as
302   * {@code font: "Times New Roman"} to avoid any possible ambiguity between
303   * the individual terms and special values such as {@code serif}.
304   *
305   * @see CssPropertyPartType#LOOSE_WORD
306   */
307  private void quoteLooseWords(AncestorChain<? extends CssTree> t) {
308    if (t.node instanceof CssTree.Expr) {
309      combineLooseWords(t.cast(CssTree.Expr.class).node);
310    }
311    for (CssTree child : t.node.children()) {
312      quoteLooseWords(AncestorChain.instance(t, child));
313    }
314  }
315 
316  private void combineLooseWords(CssTree.Expr e) {
317    for (int i = 0, n = e.getNTerms(); i < n; ++i) {
318      CssTree.Term t = e.getNthTerm(i);
319      if (!isLooseWord(t)) { continue; }
320 
321      Name propertyPart = propertyPart(t);
322      StringBuilder sb = new StringBuilder();
323      sb.append(t.getExprAtom().getValue());
324 
325      // Compile a mutation that removes all the extraneous terms and that
326      // replaces t with a string literal.
327      MutableParseTreeNode.Mutation mut = e.createMutation();
328 
329      // Compute end, the term index after the last of the run of loose terms
330      // for t's property part.
331      int start = i;
332      int end = i + 1;
333      while (end < n) {
334        CssTree.Operation op = e.getNthOperation(end - 1);
335        CssTree.Term t2 = e.getNthTerm(end);
336        if (!(CssTree.Operator.NONE == op.getOperator() && isLooseWord(t2)
337              && propertyPart.equals(propertyPart(t2)))) {
338          break;
339        }
340        mut.removeChild(op);
341        mut.removeChild(t2);
342        sb.append(' ').append(e.getNthTerm(end).getExprAtom().getValue());
343        ++end;
344      }
345 
346      // Create a string literal to replace all the terms [start:end-1].
347      // Make sure it has the same synthetic attributes and file position.
348      String text = sb.toString();
349      FilePosition pos = FilePosition.span(
350          t.getFilePosition(), e.getNthTerm(end - 1).getFilePosition());
351      CssTree.StringLiteral quotedWords = new CssTree.StringLiteral(pos, text);
352      CssTree.Term quotedTerm = new CssTree.Term(pos, null, quotedWords);
353      quotedTerm.getAttributes().putAll(t.getAttributes());
354      quotedTerm.getAttributes().set(CssValidator.CSS_PROPERTY_PART_TYPE,
355                                     CssPropertyPartType.STRING);
356 
357      mut.replaceChild(quotedTerm, t);
358      mut.execute();
359 
360      // If we made a substantive change, combining multiple terms into one,
361      // then issue a line message.  We don't need to issue a warning on all
362      // changes, since we only reach this code if we passed validation.
363      if (end - start > 1) {
364        mq.addMessage(PluginMessageType.QUOTED_CSS_VALUE,
365                      pos, MessagePart.Factory.valueOf(text));
366      }
367 
368      n = e.getNTerms();
369    }
370  }
371 
372  /** @see CssPropertyPartType#LOOSE_WORD */
373  private static boolean isLooseWord(CssTree.Term t) {
374    return t.getOperator() == null
375        && t.getExprAtom() instanceof CssTree.IdentLiteral
376        && propertyPartType(t) == CssPropertyPartType.LOOSE_WORD;
377  }
378 
379  /**
380   * Make sure that unitless lengths have units, and convert non-standard
381   * colors to hex constants.
382   * <a href="http://www.w3.org/TR/CSS21/syndata.html#length-units">Lengths</a>
383   * require units unless the value is zero.  All browsers assume px if the
384   * suffix is missing.
385   */
386  private void fixTerms(AncestorChain<? extends CssTree> t) {
387    SymbolInfo stdColors = schema.getSymbol(Name.css("color-standard"));
388    final Pattern stdColorMatcher;
389    if (stdColors != null) {
390      stdColorMatcher = new CssPropertyPatterns(schema)
391          .cssPropertyToJavaRegex(stdColors.sig);
392    } else {
393      stdColorMatcher = null;
394    }
395    t.node.acceptPreOrder(new Visitor() {
396        public boolean visit(AncestorChain<?> ancestors) {
397          if (!(ancestors.node instanceof CssTree.Term)) {
398            return true;
399          }
400          CssTree.Term term = (CssTree.Term) ancestors.node;
401          CssPropertyPartType partType = propertyPartType(term);
402          if (CssPropertyPartType.LENGTH == partType
403              && term.getExprAtom() instanceof CssTree.QuantityLiteral) {
404            CssTree.QuantityLiteral quantity = (CssTree.QuantityLiteral)
405                term.getExprAtom();
406            String value = quantity.getValue();
407            if (!isZeroOrHasUnits(value)) {
408              // Missing units.
409              CssTree.QuantityLiteral withUnits = new CssTree.QuantityLiteral(
410                  quantity.getFilePosition(), value + "px");
411              withUnits.getAttributes().putAll(quantity.getAttributes());
412              term.replaceChild(withUnits, quantity);
413              mq.addMessage(PluginMessageType.ASSUMING_PIXELS_FOR_LENGTH,
414                            quantity.getFilePosition(),
415                            MessagePart.Factory.valueOf(value));
416            }
417            return false;
418          } else if (stdColorMatcher != null
419                     && CssPropertyPartType.IDENT == partType
420                     && (propertyPart(term).getCanonicalForm()
421                         .endsWith("::color"))) {
422            Name colorName = Name.css(
423                ((CssTree.IdentLiteral) term.getExprAtom()).getValue());
424            if (!stdColorMatcher.matcher(colorName.getCanonicalForm() + " ")
425                .matches()) {
426              FilePosition pos = term.getExprAtom().getFilePosition();
427              CssTree.HashLiteral replacement = colorHash(pos, colorName);
428              MessageLevel lvl = MessageLevel.LINT;
429              if (replacement == null) {
430                lvl = MessageLevel.ERROR;
431                replacement = CssTree.HashLiteral.hex(pos, 0, 3);
432              }
433              term.replaceChild(replacement, term.getExprAtom());
434              mq.addMessage(
435                  PluginMessageType.NON_STANDARD_COLOR, lvl, pos, colorName,
436                  MessagePart.Factory.valueOf(replacement.getValue()));
437            }
438            return false;
439          }
440          return true;
441        }
442      }, t.parent);
443  }
444  private static boolean isZeroOrHasUnits(String value) {
445    int len = value.length();
446    char ch = value.charAt(len - 1);
447    if (ch == '.' || ('0' <= ch && ch <= '9')) {  // Missing units
448      for (int i = len; --i >= 0;) {
449        ch = value.charAt(i);
450        if ('1' <= ch && ch <= '9') { return false; }
451      }
452    }
453    return true;
454  }
455 
456  /** Get rid of rules like <code>p { }</code>. */
457  private void removeEmptyDeclarationsAndSelectors(
458      AncestorChain<? extends CssTree> t) {
459    t.node.acceptPreOrder(new Visitor() {
460        public boolean visit(AncestorChain<?> ancestors) {
461          ParseTreeNode node = ancestors.node;
462          if (node instanceof CssTree.EmptyDeclaration) {
463            ParseTreeNode parent = ancestors.getParentNode();
464            if (parent instanceof MutableParseTreeNode) {
465              ((MutableParseTreeNode) parent).removeChild(node);
466            }
467            return false;
468          } else if (node instanceof CssTree.Selector) {
469            CssTree.Selector sel = (CssTree.Selector) node;
470            if (sel.children().isEmpty()
471                || !(sel.children().get(0) instanceof CssTree.SimpleSelector)) {
472              // Remove from parent
473              ParseTreeNode parent = ancestors.getParentNode();
474              if (parent instanceof MutableParseTreeNode) {
475                ((MutableParseTreeNode) parent).removeChild(sel);
476              }
477            }
478            return false;
479          }
480          return true;
481        }
482      }, t.parent);
483  }
484  private void removeEmptyRuleSets(AncestorChain<? extends CssTree> t) {
485    t.node.acceptPreOrder(new Visitor() {
486        public boolean visit(AncestorChain<?> ancestors) {
487          ParseTreeNode node = ancestors.node;
488          if (!(node instanceof CssTree.RuleSet)) { return true; }
489          CssTree.RuleSet rset = (CssTree.RuleSet) node;
490          List<? extends CssTree> children = rset.children();
491          if (children.isEmpty()
492              || (children.get(children.size() - 1)
493                  instanceof CssTree.Selector)
494              || !(children.get(0) instanceof CssTree.Selector)) {
495            // No declarations or no selectors, so either the properties apply
496            // to nothing or there are no properties to apply.
497            ParseTreeNode parent = ancestors.getParentNode();
498            if (parent instanceof MutableParseTreeNode) {
499              ((MutableParseTreeNode) parent).removeChild(rset);
500            }
501          }
502          return false;
503        }
504      }, t.parent);
505  }
506  private void removeForbiddenIdents(AncestorChain<? extends CssTree> t) {
507    t.node.acceptPreOrder(new Visitor() {
508        public boolean visit(AncestorChain<?> ac) {
509          if (!(ac.node instanceof CssTree.SimpleSelector)) { return true; }
510          CssTree.SimpleSelector ss = (CssTree.SimpleSelector) ac.node;
511          boolean ok = false;
512          for (CssTree child : ss.children()) {
513            if (child instanceof CssTree.ClassLiteral
514                || child instanceof CssTree.IdLiteral) {
515              String literal = (String) child.getValue();
516              if (literal.endsWith("__")) {
517                mq.addMessage(PluginMessageType.UNSAFE_CSS_IDENTIFIER,
518                    child.getFilePosition(),
519                    MessagePart.Factory.valueOf(literal));
520                ac.parent.node.getAttributes().set(CssValidator.INVALID, true);
521                ok = false;
522              }
523            }
524          }
525          return ok;
526        }
527      }, t.parent);
528  }
529 
530  private static final Set<Name> ALLOWED_PSEUDO_CLASSES = Sets.immutableSet(
531      Name.css("active"), Name.css("after"), Name.css("before"),
532      Name.css("first-child"), Name.css("first-letter"), Name.css("focus"),
533      Name.css("link"), Name.css("hover"));
534  void removeUnsafeConstructs(AncestorChain<? extends CssTree> t) {
535 
536    // 1) Check that all classes, ids, property names, etc. are valid
537    //    css identifiers.
538    t.node.acceptPreOrder(new Visitor() {
539        public boolean visit(AncestorChain<?> ancestors) {
540          ParseTreeNode node = ancestors.node;
541          if (node instanceof CssTree.SimpleSelector) {
542            for (CssTree child : ((CssTree.SimpleSelector) node).children()) {
543              if (child instanceof CssTree.Pseudo) {
544                child = child.children().get(0);
545                // TODO(mikesamuel): check argument if child now a FunctionCall
546              }
547              Object value = child.getValue();
548              if (value != null && !isSafeSelectorPart(value.toString())) {
549                mq.addMessage(PluginMessageType.UNSAFE_CSS_IDENTIFIER,
550                              child.getFilePosition(),
551                              MessagePart.Factory.valueOf(value.toString()));
552                // Will be deleted by a later pass after all messages have been
553                // generated
554                node.getAttributes().set(CssValidator.INVALID, Boolean.TRUE);
555                return false;
556              }
557            }
558          }
559          // The CssValidator checks the safety of CSS property names.
560          return true;
561        }
562      }, t.parent);
563 
564    // 2) Ban content properties, and attr pseudo classes, and any other
565    //    pseudo selectors that don't match the whitelist
566    t.node.acceptPreOrder(new Visitor() {
567        public boolean visit(AncestorChain<?> ancestors) {
568          ParseTreeNode node = ancestors.node;
569          if (node instanceof CssTree.Pseudo) {
570            boolean remove = false;
571            CssTree child = ((CssTree.Pseudo) node).children().get(0);
572            if (child instanceof CssTree.IdentLiteral) {
573              Name pseudoName = Name.css(
574                  ((CssTree.IdentLiteral) child).getValue());
575              if (!ALLOWED_PSEUDO_CLASSES.contains(pseudoName)) {
576                // Allow the visited pseudo selector but not with any styles
577                // that can be fetched via getComputedStyle in DOMita's
578                // COMPUTED_STYLE_WHITELIST.
579                if (!(LINK_PSEUDO_CLASSES.contains(pseudoName)
580                      && strippedPropertiesBannedInLinkClasses(
581                          ancestors.parent.parent.cast(CssTree.Selector.class)
582                          ))) {
583                  mq.addMessage(PluginMessageType.UNSAFE_CSS_PSEUDO_SELECTOR,
584                                invalidNodeMessageLevel, node.getFilePosition(),
585                                node);
586                  remove = true;
587                }
588              }
589            } else {
590              StringBuilder rendered = new StringBuilder();
591              TokenConsumer tc = new CssPrettyPrinter(
592                  new Concatenator(rendered));
593              node.render(new RenderContext(tc));
594              tc.noMoreTokens();
595              mq.addMessage(PluginMessageType.UNSAFE_CSS_PSEUDO_SELECTOR,
596                            invalidNodeMessageLevel, node.getFilePosition(),
597                            MessagePart.Factory.valueOf(rendered.toString()));
598              remove = true;
599            }
600            if (remove) {
601              // Delete the containing selector, since otherwise we'd broaden
602              // the rule.
603              selectorFor(ancestors).getAttributes().set(
604                  CssValidator.INVALID, Boolean.TRUE);
605            }
606          }
607          return true;
608        }
609      }, t.parent);
610    // 3) Remove any properties and attributes that didn't validate
611    t.node.acceptPreOrder(new Visitor() {
612        public boolean visit(AncestorChain<?> ancestors) {
613          ParseTreeNode node = ancestors.node;
614          if (node instanceof CssTree.Property) {
615            if (node.getAttributes().is(CssValidator.INVALID)) {
616              declarationFor(ancestors).getAttributes().set(
617                  CssValidator.INVALID, Boolean.TRUE);
618            }
619          } else if (node instanceof CssTree.Attrib) {
620            if (node.getAttributes().is(CssValidator.INVALID)) {
621              simpleSelectorFor(ancestors).getAttributes().set(
622                  CssValidator.INVALID, Boolean.TRUE);
623            }
624          } else if (node instanceof CssTree.Term
625                     && (CssPropertyPartType.URI == propertyPartType(node))) {
626 
627            boolean remove = false;
628            Message removeMsg = null;
629 
630            CssTree term = (CssTree.Term) node;
631            CssTree.CssLiteral content =
632                (CssTree.CssLiteral) term.children().get(0);
633 
634            if (content instanceof CssTree.Substitution) {
635              return true;  // Handled by later pass.
636            }
637 
638            String uriStr = content.getValue();
639            try {
640              URI baseUri = content.getFilePosition().source().getUri();
641              URI uri = baseUri.resolve(new URI(uriStr));
642              ExternalReference ref = new ExternalReference(
643                  uri, content.getFilePosition());
644              Name propertyPart = propertyPart(node);  // TODO
645              if (uriPolicy.rewriteUri(
646                      ref, UriEffect.SAME_DOCUMENT, LoaderType.SANDBOXED,
647                      Collections.singletonMap(
648                          UriPolicyHintKey.CSS_PROP.key, propertyPart))
649                      == null) {
650                removeMsg = new Message(
651                    PluginMessageType.DISALLOWED_URI,
652                    node.getFilePosition(),
653                    MessagePart.Factory.valueOf(uriStr));
654                remove = true;
655              }
656            } catch (URISyntaxException ex) {
657              removeMsg = new Message(
658                  PluginMessageType.DISALLOWED_URI,
659                  node.getFilePosition(), MessagePart.Factory.valueOf(uriStr));
660              remove = true;
661            }
662 
663            if (remove) {
664              // condemn the containing declaration
665              CssTree.Declaration decl = declarationFor(ancestors);
666              if (null != decl) {
667                if (!decl.getAttributes().is(CssValidator.INVALID)) {
668                  if (null != removeMsg) { mq.getMessages().add(removeMsg); }
669                  decl.getAttributes().set(CssValidator.INVALID, Boolean.TRUE);
670                }
671              }
672            }
673          }
674          return true;
675        }
676      }, t.parent);
677 
678    // 4) Remove invalid nodes
679    removeInvalidNodes(t);
680 
681    // 5) Cleanup.  Remove any rulesets with empty selectors
682    // Since this is a post order traversal, we will first remove empty
683    // selectors, and then consider any rulesets that have become empty due to
684    // a lack of selectors.
685    t.node.acceptPreOrder(new Visitor() {
686        public boolean visit(AncestorChain<?> ancestors) {
687          ParseTreeNode node = ancestors.node;
688          if ((node instanceof CssTree.Selector && node.children().isEmpty())
689              || (node instanceof CssTree.RuleSet
690                  && (node.children().isEmpty()
691                      || node.children().get(0) instanceof CssTree.Declaration))
692              ) {
693            ((MutableParseTreeNode) ancestors.parent.node).removeChild(node);
694            return false;
695          }
696          return true;
697        }
698      }, t.parent);
699  }
700 
701  private void removeInvalidNodes(AncestorChain<? extends CssTree> t) {
702    if (t.node.getAttributes().is(CssValidator.INVALID)) {
703      ((MutableParseTreeNode) t.parent.node).removeChild(t.node);
704      return;
705    }
706 
707    // Use a mutation to remove invalid nodes so that the sanity checks in
708    // childrenChanged sees all removals at once.
709    MutableParseTreeNode.Mutation mut = null;
710    for (CssTree child : t.node.children()) {
711      if (child.getAttributes().is(CssValidator.INVALID)) {
712        if (mut == null) { mut = t.node.createMutation(); }
713        mut.removeChild(child);
714      } else {
715        removeInvalidNodes(AncestorChain.instance(t, child));
716      }
717    }
718    if (mut != null) { mut.execute(); }
719  }
720 
721  private void translateUrls(AncestorChain<? extends CssTree> t) {
722      t.node.acceptPreOrder(new Visitor() {
723          public boolean visit(AncestorChain<?> ancestors) {
724            ParseTreeNode node = ancestors.node;
725            if (node instanceof CssTree.Term
726                && CssPropertyPartType.URI == propertyPartType(node)) {
727              CssTree term = (CssTree.Term) node;
728 
729              CssTree.CssLiteral content =
730                  (CssTree.CssLiteral) term.children().get(0);
731              if (content instanceof CssTree.Substitution) {
732                return true;  // Handled by later pass.
733              }
734 
735              Name propertyPart = propertyPart(node);
736              String uriStr = content.getValue();
737              try {
738                URI baseUri = content.getFilePosition().source().getUri();
739                URI uri = baseUri.resolve(new URI(uriStr));
740                // Rewrite the URI.
741                // TODO(mikesamuel): for content: and other URI types, use
742                // mime-type of text/*.
743                ExternalReference ref = new ExternalReference(
744                    uri, content.getFilePosition());
745                String rewrittenUri = uriPolicy.rewriteUri(
746                    ref, UriEffect.SAME_DOCUMENT, LoaderType.SANDBOXED,
747                    Collections.singletonMap(
748                        UriPolicyHintKey.CSS_PROP.key, propertyPart));
749                CssTree.UriLiteral replacement = new CssTree.UriLiteral(
750                        content.getFilePosition(), URI.create(rewrittenUri));
751                replacement.getAttributes().putAll(content.getAttributes());
752                term.replaceChild(replacement, content);
753              } catch (URISyntaxException ex) {
754                // Should've been checked in removeUnsafeConstructs.
755                throw new SomethingWidgyHappenedError(ex);
756              }
757            }
758            return true;
759          }
760        }, t.parent);
761  }
762 
763  private static CssTree.Declaration declarationFor(AncestorChain<?> chain) {
764    for (AncestorChain<?> c = chain; null != c; c = c.parent) {
765      if (c.node instanceof CssTree.Declaration) {
766        return (CssTree.Declaration) c.node;
767      }
768    }
769    return null;
770  }
771 
772  private static CssTree.SimpleSelector simpleSelectorFor(
773      AncestorChain<?> chain) {
774    for (AncestorChain<?> c = chain; null != c; c = c.parent) {
775      if (c.node instanceof CssTree.SimpleSelector) {
776        return (CssTree.SimpleSelector) c.node;
777      }
778    }
779    return null;
780  }
781 
782  private static CssTree.Selector selectorFor(AncestorChain<?> chain) {
783    for (AncestorChain<?> c = chain; null != c; c = c.parent) {
784      if (c.node instanceof CssTree.Selector) {
785        return (CssTree.Selector) c.node;
786      }
787    }
788    return null;
789  }
790 
791  private static final Set<Name> PROPERTIES_ALLOWED_IN_LINK_CLASSES;
792  static {
793    Set<Name> propNames = Sets.newHashSet(
794        Name.css("background-color"), Name.css("color"), Name.css("cursor"));
795    // Rules limited to link and visited styles cannot allow properties that
796    // can be tested by DOMita's getComputedStyle since it would allow history
797    // mining.
798    // Do not inline the below.  The removeAll relies on the input being a set
799    // of names, but since removeAll takes a Collection<?> it would fail
800    // silently if the whitelist were changed to a Collection<String>.
801    // Assigning to a local does type-check though.
802    Set<Name> computedStyleNames
803        = CssPropertyPatterns.HISTORY_INSENSITIVE_STYLE_WHITELIST;
804    propNames.removeAll(computedStyleNames);
805    PROPERTIES_ALLOWED_IN_LINK_CLASSES = Sets.immutableSet(propNames);
806  }
807  private boolean strippedPropertiesBannedInLinkClasses(
808      AncestorChain<CssTree.Selector> sel) {
809    if (!(sel.parent.node instanceof CssTree.RuleSet)) { return false; }
810    Set<Name> propertyNames = PROPERTIES_ALLOWED_IN_LINK_CLASSES;
811    CssTree.RuleSet rs = sel.parent.cast(CssTree.RuleSet.class).node;
812    MutableParseTreeNode.Mutation mut = rs.createMutation();
813    for (CssTree child : rs.children()) {
814      if (child instanceof CssTree.Selector
815          || child instanceof CssTree.EmptyDeclaration) {
816        continue;
817      }
818      CssTree.PropertyDeclaration pd;
819      if (child instanceof CssTree.PropertyDeclaration) {
820        pd = (CssTree.PropertyDeclaration) child;
821      } else {
822        pd = ((CssTree.UserAgentHack) child).getDeclaration();
823      }
824      CssTree.Property p = pd.getProperty();
825      Name propName = p.getPropertyName();
826      boolean allowedInLinkClass = propertyNames.contains(propName);
827      if (!allowedInLinkClass && propName.getCanonicalForm().startsWith("_")) {
828        allowedInLinkClass = propertyNames.contains(Name.css(
829            propName.getCanonicalForm().substring(1)));
830      }
831      if (!allowedInLinkClass || mightContainUrl(pd.getExpr())) {
832        mq.getMessages().add(new Message(
833            PluginMessageType.DISALLOWED_CSS_PROPERTY_IN_SELECTOR,
834            this.invalidNodeMessageLevel,
835            p.getFilePosition(), p.getPropertyName(),
836            sel.node.getFilePosition()));
837        mut.removeChild(child);
838      }
839    }
840    mut.execute();
841    return true;
842  }
843 
844  private boolean mightContainUrl(CssTree.Expr expr) {
845    for (int n = expr.getNTerms(), i = 0; i < n; ++i) {
846      CssTree.CssExprAtom atom = expr.getNthTerm(i).getExprAtom();
847      if (!(atom instanceof CssTree.IdentLiteral
848            || atom instanceof CssTree.QuantityLiteral
849            || atom instanceof CssTree.HashLiteral)) {
850        return true;
851      }
852    }
853    return false;
854  }
855 
856  private static final Pattern SAFE_SELECTOR_PART
857      = Pattern.compile("^[#!\\.]?[a-zA-Z][_a-zA-Z0-9\\-]*$");
858  /**
859   * Restrict selectors to ascii characters until we can test browser handling
860   * of escape sequences.
861   */
862  private static boolean isSafeSelectorPart(String s) {
863    return SAFE_SELECTOR_PART.matcher(s).matches();
864  }
865 
866  private static Name propertyPart(ParseTreeNode node) {
867    return node.getAttributes().get(CssValidator.CSS_PROPERTY_PART);
868  }
869 
870  private static CssPropertyPartType propertyPartType(ParseTreeNode node) {
871    return node.getAttributes().get(CssValidator.CSS_PROPERTY_PART_TYPE);
872  }
873 
874  public static CssTree.HashLiteral colorHash(FilePosition pos, Name color) {
875    Integer hexI = CSS3_COLORS.get(color.getCanonicalForm());
876    return hexI != null ? colorHash(pos, hexI) : null;
877  }
878 
879  public static CssTree.HashLiteral colorHash(FilePosition pos, int hex) {
880    if ((hex & 0x0f0f0f) == ((hex >>> 4) & 0x0f0f0f)) {  // #rgb
881      return CssTree.HashLiteral.hex(
882          pos, ((hex >>> 8) & 0xf00) | ((hex >>> 4) & 0xf0) | (hex & 0xf), 3);
883    } else { // #rrggbb
884      return CssTree.HashLiteral.hex(pos, hex, 6);
885    }
886  }
887 
888  // http://www.w3.org/TR/css3-iccprof#x11-color
889  private static final Map<String, Integer> CSS3_COLORS
890      = Maps.<String, Integer>immutableMap()
891        .put("aliceblue", 0xF0F8FF)
892        .put("antiquewhite", 0xFAEBD7)
893        .put("aqua", 0x00FFFF)
894        .put("aquamarine", 0x7FFFD4)
895        .put("azure", 0xF0FFFF)
896        .put("beige", 0xF5F5DC)
897        .put("bisque", 0xFFE4C4)
898        .put("black", 0x000000)
899        .put("blanchedalmond", 0xFFEBCD)
900        .put("blue", 0x0000FF)
901        .put("blueviolet", 0x8A2BE2)
902        .put("brown", 0xA52A2A)
903        .put("burlywood", 0xDEB887)
904        .put("cadetblue", 0x5F9EA0)
905        .put("chartreuse", 0x7FFF00)
906        .put("chocolate", 0xD2691E)
907        .put("coral", 0xFF7F50)
908        .put("cornflowerblue", 0x6495ED)
909        .put("cornsilk", 0xFFF8DC)
910        .put("crimson", 0xDC143C)
911        .put("cyan", 0x00FFFF)
912        .put("darkblue", 0x00008B)
913        .put("darkcyan", 0x008B8B)
914        .put("darkgoldenrod", 0xB8860B)
915        .put("darkgray", 0xA9A9A9)
916        .put("darkgreen", 0x006400)
917        .put("darkkhaki", 0xBDB76B)
918        .put("darkmagenta", 0x8B008B)
919        .put("darkolivegreen", 0x556B2F)
920        .put("darkorange", 0xFF8C00)
921        .put("darkorchid", 0x9932CC)
922        .put("darkred", 0x8B0000)
923        .put("darksalmon", 0xE9967A)
924        .put("darkseagreen", 0x8FBC8F)
925        .put("darkslateblue", 0x483D8B)
926        .put("darkslategray", 0x2F4F4F)
927        .put("darkturquoise", 0x00CED1)
928        .put("darkviolet", 0x9400D3)
929        .put("deeppink", 0xFF1493)
930        .put("deepskyblue", 0x00BFFF)
931        .put("dimgray", 0x696969)
932        .put("dodgerblue", 0x1E90FF)
933        .put("firebrick", 0xB22222)
934        .put("floralwhite", 0xFFFAF0)
935        .put("forestgreen", 0x228B22)
936        .put("fuchsia", 0xFF00FF)
937        .put("gainsboro", 0xDCDCDC)
938        .put("ghostwhite", 0xF8F8FF)
939        .put("gold", 0xFFD700)
940        .put("goldenrod", 0xDAA520)
941        .put("gray", 0x808080)
942        .put("green", 0x008000)
943        .put("greenyellow", 0xADFF2F)
944        .put("honeydew", 0xF0FFF0)
945        .put("hotpink", 0xFF69B4)
946        .put("indianred", 0xCD5C5C)
947        .put("indigo", 0x4B0082)
948        .put("ivory", 0xFFFFF0)
949        .put("khaki", 0xF0E68C)
950        .put("lavender", 0xE6E6FA)
951        .put("lavenderblush", 0xFFF0F5)
952        .put("lawngreen", 0x7CFC00)
953        .put("lemonchiffon", 0xFFFACD)
954        .put("lightblue", 0xADD8E6)
955        .put("lightcoral", 0xF08080)
956        .put("lightcyan", 0xE0FFFF)
957        .put("lightgoldenrodyellow", 0xFAFAD2)
958        .put("lightgreen", 0x90EE90)
959        .put("lightgrey", 0xD3D3D3)
960        .put("lightpink", 0xFFB6C1)
961        .put("lightsalmon", 0xFFA07A)
962        .put("lightseagreen", 0x20B2AA)
963        .put("lightskyblue", 0x87CEFA)
964        .put("lightslategray", 0x778899)
965        .put("lightsteelblue", 0xB0C4DE)
966        .put("lightyellow", 0xFFFFE0)
967        .put("lime", 0x00FF00)
968        .put("limegreen", 0x32CD32)
969        .put("linen", 0xFAF0E6)
970        .put("magenta", 0xFF00FF)
971        .put("maroon", 0x800000)
972        .put("mediumaquamarine", 0x66CDAA)
973        .put("mediumblue", 0x0000CD)
974        .put("mediumorchid", 0xBA55D3)
975        .put("mediumpurple", 0x9370DB)
976        .put("mediumseagreen", 0x3CB371)
977        .put("mediumslateblue", 0x7B68EE)
978        .put("mediumspringgreen", 0x00FA9A)
979        .put("mediumturquoise", 0x48D1CC)
980        .put("mediumvioletred", 0xC71585)
981        .put("midnightblue", 0x191970)
982        .put("mintcream", 0xF5FFFA)
983        .put("mistyrose", 0xFFE4E1)
984        .put("moccasin", 0xFFE4B5)
985        .put("navajowhite", 0xFFDEAD)
986        .put("navy", 0x000080)
987        .put("oldlace", 0xFDF5E6)
988        .put("olive", 0x808000)
989        .put("olivedrab", 0x6B8E23)
990        .put("orange", 0xFFA500)
991        .put("orangered", 0xFF4500)
992        .put("orchid", 0xDA70D6)
993        .put("palegoldenrod", 0xEEE8AA)
994        .put("palegreen", 0x98FB98)
995        .put("paleturquoise", 0xAFEEEE)
996        .put("palevioletred", 0xDB7093)
997        .put("papayawhip", 0xFFEFD5)
998        .put("peachpuff", 0xFFDAB9)
999        .put("peru", 0xCD853F)
1000        .put("pink", 0xFFC0CB)
1001        .put("plum", 0xDDA0DD)
1002        .put("powderblue", 0xB0E0E6)
1003        .put("purple", 0x800080)
1004        .put("red", 0xFF0000)
1005        .put("rosybrown", 0xBC8F8F)
1006        .put("royalblue", 0x4169E1)
1007        .put("saddlebrown", 0x8B4513)
1008        .put("salmon", 0xFA8072)
1009        .put("sandybrown", 0xF4A460)
1010        .put("seagreen", 0x2E8B57)
1011        .put("seashell", 0xFFF5EE)
1012        .put("sienna", 0xA0522D)
1013        .put("silver", 0xC0C0C0)
1014        .put("skyblue", 0x87CEEB)
1015        .put("slateblue", 0x6A5ACD)
1016        .put("slategray", 0x708090)
1017        .put("snow", 0xFFFAFA)
1018        .put("springgreen", 0x00FF7F)
1019        .put("steelblue", 0x4682B4)
1020        .put("tan", 0xD2B48C)
1021        .put("teal", 0x008080)
1022        .put("thistle", 0xD8BFD8)
1023        .put("tomato", 0xFF6347)
1024        .put("turquoise", 0x40E0D0)
1025        .put("violet", 0xEE82EE)
1026        .put("wheat", 0xF5DEB3)
1027        .put("white", 0xFFFFFF)
1028        .put("whitesmoke", 0xF5F5F5)
1029        .put("yellow", 0xFFFF00)
1030        .put("yellowgreen", 0x9ACD32)
1031        .create();
1032}

[all classes][com.google.caja.plugin]
EMMA 2.0.5312 (C) Vladimir Roubtsov