18403881c365ab36b721ccc4500af1b3a5bd25870mikesamuel// Copyright (c) 2011, Mike Samuel
28403881c365ab36b721ccc4500af1b3a5bd25870mikesamuel// All rights reserved.
38403881c365ab36b721ccc4500af1b3a5bd25870mikesamuel//
48403881c365ab36b721ccc4500af1b3a5bd25870mikesamuel// Redistribution and use in source and binary forms, with or without
58403881c365ab36b721ccc4500af1b3a5bd25870mikesamuel// modification, are permitted provided that the following conditions
68403881c365ab36b721ccc4500af1b3a5bd25870mikesamuel// are met:
78403881c365ab36b721ccc4500af1b3a5bd25870mikesamuel//
88403881c365ab36b721ccc4500af1b3a5bd25870mikesamuel// Redistributions of source code must retain the above copyright
98403881c365ab36b721ccc4500af1b3a5bd25870mikesamuel// notice, this list of conditions and the following disclaimer.
108403881c365ab36b721ccc4500af1b3a5bd25870mikesamuel// Redistributions in binary form must reproduce the above copyright
118403881c365ab36b721ccc4500af1b3a5bd25870mikesamuel// notice, this list of conditions and the following disclaimer in the
128403881c365ab36b721ccc4500af1b3a5bd25870mikesamuel// documentation and/or other materials provided with the distribution.
138403881c365ab36b721ccc4500af1b3a5bd25870mikesamuel// Neither the name of the OWASP nor the names of its contributors may
148403881c365ab36b721ccc4500af1b3a5bd25870mikesamuel// be used to endorse or promote products derived from this software
158403881c365ab36b721ccc4500af1b3a5bd25870mikesamuel// without specific prior written permission.
168403881c365ab36b721ccc4500af1b3a5bd25870mikesamuel// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
178403881c365ab36b721ccc4500af1b3a5bd25870mikesamuel// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
188403881c365ab36b721ccc4500af1b3a5bd25870mikesamuel// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
198403881c365ab36b721ccc4500af1b3a5bd25870mikesamuel// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
208403881c365ab36b721ccc4500af1b3a5bd25870mikesamuel// COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
218403881c365ab36b721ccc4500af1b3a5bd25870mikesamuel// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
228403881c365ab36b721ccc4500af1b3a5bd25870mikesamuel// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
238403881c365ab36b721ccc4500af1b3a5bd25870mikesamuel// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
248403881c365ab36b721ccc4500af1b3a5bd25870mikesamuel// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
258403881c365ab36b721ccc4500af1b3a5bd25870mikesamuel// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
268403881c365ab36b721ccc4500af1b3a5bd25870mikesamuel// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
278403881c365ab36b721ccc4500af1b3a5bd25870mikesamuel// POSSIBILITY OF SUCH DAMAGE.
288403881c365ab36b721ccc4500af1b3a5bd25870mikesamuel
294e867904c8295537803c1c8a076e130df5674b58mikesamuelpackage org.owasp.html;
304e867904c8295537803c1c8a076e130df5674b58mikesamuel
31b600c3cd7edfb02d79c264fd83b1306e94053b7emikesamuelfinal class CssGrammar {
32b600c3cd7edfb02d79c264fd83b1306e94053b7emikesamuel
33b600c3cd7edfb02d79c264fd83b1306e94053b7emikesamuel  private static void errorRecoveryUntilSemiOrCloseBracket(
34b600c3cd7edfb02d79c264fd83b1306e94053b7emikesamuel      CssTokens.TokenIterator it) {
35b600c3cd7edfb02d79c264fd83b1306e94053b7emikesamuel    int bracketDepth = 0;
36b600c3cd7edfb02d79c264fd83b1306e94053b7emikesamuel    for (; it.hasNext(); it.advance()) {
37b600c3cd7edfb02d79c264fd83b1306e94053b7emikesamuel      switch (it.type()) {
38b600c3cd7edfb02d79c264fd83b1306e94053b7emikesamuel        case SEMICOLON:
39b600c3cd7edfb02d79c264fd83b1306e94053b7emikesamuel          it.advance();
40b600c3cd7edfb02d79c264fd83b1306e94053b7emikesamuel          return;
41b600c3cd7edfb02d79c264fd83b1306e94053b7emikesamuel        case LEFT_CURLY:
42b600c3cd7edfb02d79c264fd83b1306e94053b7emikesamuel        case LEFT_PAREN:
43b600c3cd7edfb02d79c264fd83b1306e94053b7emikesamuel        case LEFT_SQUARE:
44b600c3cd7edfb02d79c264fd83b1306e94053b7emikesamuel          ++bracketDepth;
45b600c3cd7edfb02d79c264fd83b1306e94053b7emikesamuel          break;
46b600c3cd7edfb02d79c264fd83b1306e94053b7emikesamuel        case RIGHT_CURLY:
47b600c3cd7edfb02d79c264fd83b1306e94053b7emikesamuel        case RIGHT_PAREN:
48b600c3cd7edfb02d79c264fd83b1306e94053b7emikesamuel        case RIGHT_SQUARE:
49b600c3cd7edfb02d79c264fd83b1306e94053b7emikesamuel          --bracketDepth;
50b600c3cd7edfb02d79c264fd83b1306e94053b7emikesamuel          if (bracketDepth <= 0) {
51b600c3cd7edfb02d79c264fd83b1306e94053b7emikesamuel            if (bracketDepth != 0) { it.advance(); }
52b600c3cd7edfb02d79c264fd83b1306e94053b7emikesamuel            return;
53b600c3cd7edfb02d79c264fd83b1306e94053b7emikesamuel          }
54b600c3cd7edfb02d79c264fd83b1306e94053b7emikesamuel          break;
55b600c3cd7edfb02d79c264fd83b1306e94053b7emikesamuel        default:
56b600c3cd7edfb02d79c264fd83b1306e94053b7emikesamuel          break;
57b600c3cd7edfb02d79c264fd83b1306e94053b7emikesamuel      }
58b600c3cd7edfb02d79c264fd83b1306e94053b7emikesamuel    }
594e867904c8295537803c1c8a076e130df5674b58mikesamuel  }
604e867904c8295537803c1c8a076e130df5674b58mikesamuel
61b600c3cd7edfb02d79c264fd83b1306e94053b7emikesamuel  static void parsePropertyGroup(String css, PropertyHandler handler) {
624e867904c8295537803c1c8a076e130df5674b58mikesamuel    // Split tokens by semicolons/curly-braces, then by first colon,
634e867904c8295537803c1c8a076e130df5674b58mikesamuel    // dropping spaces and comments to identify property names and token runs
644e867904c8295537803c1c8a076e130df5674b58mikesamuel    // that form the value.
654e867904c8295537803c1c8a076e130df5674b58mikesamuel
66b600c3cd7edfb02d79c264fd83b1306e94053b7emikesamuel    CssTokens tokens = CssTokens.lex(css);
67b600c3cd7edfb02d79c264fd83b1306e94053b7emikesamuel    CssTokens.TokenIterator it = tokens.iterator();
684e867904c8295537803c1c8a076e130df5674b58mikesamuel    propertyNameLoop:
69b600c3cd7edfb02d79c264fd83b1306e94053b7emikesamuel    while (it.hasTokenAfterSpace()) {
704e867904c8295537803c1c8a076e130df5674b58mikesamuel      // Check that we have an identifier that might be a property name.
71b600c3cd7edfb02d79c264fd83b1306e94053b7emikesamuel      if (it.type() != CssTokens.TokenType.IDENT) {
72b600c3cd7edfb02d79c264fd83b1306e94053b7emikesamuel        errorRecoveryUntilSemiOrCloseBracket(it);
73b600c3cd7edfb02d79c264fd83b1306e94053b7emikesamuel        continue;
74b600c3cd7edfb02d79c264fd83b1306e94053b7emikesamuel      }
754e867904c8295537803c1c8a076e130df5674b58mikesamuel
76b600c3cd7edfb02d79c264fd83b1306e94053b7emikesamuel      String name = it.next();
774e867904c8295537803c1c8a076e130df5674b58mikesamuel
784e867904c8295537803c1c8a076e130df5674b58mikesamuel      // Look for a colon.
79b600c3cd7edfb02d79c264fd83b1306e94053b7emikesamuel      if (!(it.hasTokenAfterSpace() && ":".equals(it.token()))) {
80b600c3cd7edfb02d79c264fd83b1306e94053b7emikesamuel        errorRecoveryUntilSemiOrCloseBracket(it);
81b600c3cd7edfb02d79c264fd83b1306e94053b7emikesamuel        continue propertyNameLoop;
824e867904c8295537803c1c8a076e130df5674b58mikesamuel      }
83b600c3cd7edfb02d79c264fd83b1306e94053b7emikesamuel      it.advance();
844e867904c8295537803c1c8a076e130df5674b58mikesamuel
85b600c3cd7edfb02d79c264fd83b1306e94053b7emikesamuel      handler.startProperty(Strings.toLowerCase(name));
86b600c3cd7edfb02d79c264fd83b1306e94053b7emikesamuel      parsePropertyValue(it, handler);
87b600c3cd7edfb02d79c264fd83b1306e94053b7emikesamuel      handler.endProperty();
88b600c3cd7edfb02d79c264fd83b1306e94053b7emikesamuel    }
89b600c3cd7edfb02d79c264fd83b1306e94053b7emikesamuel  }
904e867904c8295537803c1c8a076e130df5674b58mikesamuel
91b600c3cd7edfb02d79c264fd83b1306e94053b7emikesamuel  private static void parsePropertyValue(
92b600c3cd7edfb02d79c264fd83b1306e94053b7emikesamuel      CssTokens.TokenIterator it, PropertyHandler handler) {
93b600c3cd7edfb02d79c264fd83b1306e94053b7emikesamuel    propertyValueLoop:
94b600c3cd7edfb02d79c264fd83b1306e94053b7emikesamuel    while (it.hasNext()) {
95b600c3cd7edfb02d79c264fd83b1306e94053b7emikesamuel      CssTokens.TokenType type = it.type();
96b600c3cd7edfb02d79c264fd83b1306e94053b7emikesamuel      String token = it.token();
97b600c3cd7edfb02d79c264fd83b1306e94053b7emikesamuel      switch (type) {
98b600c3cd7edfb02d79c264fd83b1306e94053b7emikesamuel        case SEMICOLON:
99b600c3cd7edfb02d79c264fd83b1306e94053b7emikesamuel          it.advance();
100b600c3cd7edfb02d79c264fd83b1306e94053b7emikesamuel          break propertyValueLoop;
101b600c3cd7edfb02d79c264fd83b1306e94053b7emikesamuel        case FUNCTION:
102b600c3cd7edfb02d79c264fd83b1306e94053b7emikesamuel          CssTokens.TokenIterator actuals = it.spliceToEnd();
103b600c3cd7edfb02d79c264fd83b1306e94053b7emikesamuel          handler.startFunction(token);
104b600c3cd7edfb02d79c264fd83b1306e94053b7emikesamuel          parsePropertyValue(actuals, handler);
105b600c3cd7edfb02d79c264fd83b1306e94053b7emikesamuel          handler.endFunction(token);
106b600c3cd7edfb02d79c264fd83b1306e94053b7emikesamuel          continue;  // Skip the advance over token.
107b600c3cd7edfb02d79c264fd83b1306e94053b7emikesamuel        case IDENT:
108b600c3cd7edfb02d79c264fd83b1306e94053b7emikesamuel          handler.identifier(token);
109b600c3cd7edfb02d79c264fd83b1306e94053b7emikesamuel          break;
110b600c3cd7edfb02d79c264fd83b1306e94053b7emikesamuel        case HASH_UNRESTRICTED:
1115e810f7ffa3dc2f6baefc762abd7e4ad31a640cbmikesamuel          if (token.length() == 4 || token.length() == 7) {
1125e810f7ffa3dc2f6baefc762abd7e4ad31a640cbmikesamuel            handler.hash(token);
1135e810f7ffa3dc2f6baefc762abd7e4ad31a640cbmikesamuel          }
114b600c3cd7edfb02d79c264fd83b1306e94053b7emikesamuel          break;
115b600c3cd7edfb02d79c264fd83b1306e94053b7emikesamuel        case STRING:
116b600c3cd7edfb02d79c264fd83b1306e94053b7emikesamuel          handler.quotedString(token);
117b600c3cd7edfb02d79c264fd83b1306e94053b7emikesamuel          break;
118b600c3cd7edfb02d79c264fd83b1306e94053b7emikesamuel        case URL:
119b600c3cd7edfb02d79c264fd83b1306e94053b7emikesamuel          handler.url(token);
120b600c3cd7edfb02d79c264fd83b1306e94053b7emikesamuel          break;
121b600c3cd7edfb02d79c264fd83b1306e94053b7emikesamuel        case DIMENSION:
122b600c3cd7edfb02d79c264fd83b1306e94053b7emikesamuel        case NUMBER:
123b600c3cd7edfb02d79c264fd83b1306e94053b7emikesamuel        case PERCENTAGE:
124b600c3cd7edfb02d79c264fd83b1306e94053b7emikesamuel          handler.quantity(token);
125b600c3cd7edfb02d79c264fd83b1306e94053b7emikesamuel          break;
126b600c3cd7edfb02d79c264fd83b1306e94053b7emikesamuel        case AT:
1276afee9b02bc894e2f91eec3ac2e7e9c0c30c2878mikesamuel        case BAD_DIMENSION:
128b600c3cd7edfb02d79c264fd83b1306e94053b7emikesamuel        case COLUMN:
129b600c3cd7edfb02d79c264fd83b1306e94053b7emikesamuel        case DOT_IDENT:
130b600c3cd7edfb02d79c264fd83b1306e94053b7emikesamuel        case HASH_ID:
131b600c3cd7edfb02d79c264fd83b1306e94053b7emikesamuel        case MATCH:
132b600c3cd7edfb02d79c264fd83b1306e94053b7emikesamuel        case UNICODE_RANGE:
133b600c3cd7edfb02d79c264fd83b1306e94053b7emikesamuel        case WHITESPACE:
134b600c3cd7edfb02d79c264fd83b1306e94053b7emikesamuel          break;
135b600c3cd7edfb02d79c264fd83b1306e94053b7emikesamuel        case LEFT_CURLY:
136b600c3cd7edfb02d79c264fd83b1306e94053b7emikesamuel        case LEFT_PAREN:
137b600c3cd7edfb02d79c264fd83b1306e94053b7emikesamuel        case LEFT_SQUARE:
138b600c3cd7edfb02d79c264fd83b1306e94053b7emikesamuel        case RIGHT_CURLY:
139b600c3cd7edfb02d79c264fd83b1306e94053b7emikesamuel        case RIGHT_PAREN:
140b600c3cd7edfb02d79c264fd83b1306e94053b7emikesamuel        case RIGHT_SQUARE:
141b600c3cd7edfb02d79c264fd83b1306e94053b7emikesamuel        case COMMA:
142b600c3cd7edfb02d79c264fd83b1306e94053b7emikesamuel        case COLON:
143b600c3cd7edfb02d79c264fd83b1306e94053b7emikesamuel        case DELIM:
144b600c3cd7edfb02d79c264fd83b1306e94053b7emikesamuel          handler.punctuation(token);
145b600c3cd7edfb02d79c264fd83b1306e94053b7emikesamuel          break;
1464e867904c8295537803c1c8a076e130df5674b58mikesamuel      }
147b600c3cd7edfb02d79c264fd83b1306e94053b7emikesamuel      it.advance();
1484e867904c8295537803c1c8a076e130df5674b58mikesamuel    }
1494e867904c8295537803c1c8a076e130df5674b58mikesamuel  }
1504e867904c8295537803c1c8a076e130df5674b58mikesamuel
1514e867904c8295537803c1c8a076e130df5674b58mikesamuel  /**
1524e867904c8295537803c1c8a076e130df5674b58mikesamuel   * Decodes any escape sequences and strips any quotes from the input.
1534e867904c8295537803c1c8a076e130df5674b58mikesamuel   */
1544e867904c8295537803c1c8a076e130df5674b58mikesamuel  static String cssContent(String token) {
1554e867904c8295537803c1c8a076e130df5674b58mikesamuel    int n = token.length();
1564e867904c8295537803c1c8a076e130df5674b58mikesamuel    int pos = 0;
1574e867904c8295537803c1c8a076e130df5674b58mikesamuel    StringBuilder sb = null;
1584e867904c8295537803c1c8a076e130df5674b58mikesamuel    if (n >= 2) {
1594e867904c8295537803c1c8a076e130df5674b58mikesamuel      char ch0 = token.charAt(0);
1604e867904c8295537803c1c8a076e130df5674b58mikesamuel      if (ch0 == '"' || ch0 == '\'') {
1614e867904c8295537803c1c8a076e130df5674b58mikesamuel        if (ch0 == token.charAt(n - 1)) {
1624e867904c8295537803c1c8a076e130df5674b58mikesamuel          pos = 1;
1634e867904c8295537803c1c8a076e130df5674b58mikesamuel          --n;
1644e867904c8295537803c1c8a076e130df5674b58mikesamuel          sb = new StringBuilder(n);
1654e867904c8295537803c1c8a076e130df5674b58mikesamuel        }
1664e867904c8295537803c1c8a076e130df5674b58mikesamuel      }
1674e867904c8295537803c1c8a076e130df5674b58mikesamuel    }
1684e867904c8295537803c1c8a076e130df5674b58mikesamuel    for (int esc; (esc = token.indexOf('\\', pos)) >= 0;) {
1694e867904c8295537803c1c8a076e130df5674b58mikesamuel      int end = esc + 2;
1704e867904c8295537803c1c8a076e130df5674b58mikesamuel      if (esc > n) { break; }
1714e867904c8295537803c1c8a076e130df5674b58mikesamuel      if (sb == null) { sb = new StringBuilder(n); }
1724e867904c8295537803c1c8a076e130df5674b58mikesamuel      sb.append(token, pos, esc);
1734e867904c8295537803c1c8a076e130df5674b58mikesamuel      int codepoint = token.charAt(end - 1);
1744e867904c8295537803c1c8a076e130df5674b58mikesamuel      if (isHex(codepoint)) {
1754e867904c8295537803c1c8a076e130df5674b58mikesamuel        // Parse \hhhhh<opt-break> where hhhhh is one or more hex digits
1764e867904c8295537803c1c8a076e130df5674b58mikesamuel        // and <opt-break> is an optional space or tab character that can be
1774e867904c8295537803c1c8a076e130df5674b58mikesamuel        // used to separate an escape sequence from a following literal hex
1784e867904c8295537803c1c8a076e130df5674b58mikesamuel        // digit.
1794e867904c8295537803c1c8a076e130df5674b58mikesamuel        while (end < n && isHex(token.charAt(end))) { ++end; }
1804e867904c8295537803c1c8a076e130df5674b58mikesamuel        try {
1814e867904c8295537803c1c8a076e130df5674b58mikesamuel          codepoint = Integer.parseInt(token.substring(esc + 1, end), 16);
1824e867904c8295537803c1c8a076e130df5674b58mikesamuel        } catch (RuntimeException ex) {
1834e867904c8295537803c1c8a076e130df5674b58mikesamuel          codepoint = 0xfffd;  // Unknown codepoint.
1844e867904c8295537803c1c8a076e130df5674b58mikesamuel        }
1854e867904c8295537803c1c8a076e130df5674b58mikesamuel        if (end < n) {
1864e867904c8295537803c1c8a076e130df5674b58mikesamuel          char ch = token.charAt(end);
1874e867904c8295537803c1c8a076e130df5674b58mikesamuel          if (ch == ' ' || ch == '\t') {  // Ignorable hex follower.
1884e867904c8295537803c1c8a076e130df5674b58mikesamuel            ++end;
1894e867904c8295537803c1c8a076e130df5674b58mikesamuel          }
1904e867904c8295537803c1c8a076e130df5674b58mikesamuel        }
1914e867904c8295537803c1c8a076e130df5674b58mikesamuel      }
1924e867904c8295537803c1c8a076e130df5674b58mikesamuel      sb.appendCodePoint(codepoint);
1934e867904c8295537803c1c8a076e130df5674b58mikesamuel      pos = end;
1944e867904c8295537803c1c8a076e130df5674b58mikesamuel    }
1954e867904c8295537803c1c8a076e130df5674b58mikesamuel    if (sb == null) { return token; }
1964e867904c8295537803c1c8a076e130df5674b58mikesamuel    return sb.append(token, pos, n).toString();
1974e867904c8295537803c1c8a076e130df5674b58mikesamuel  }
1984e867904c8295537803c1c8a076e130df5674b58mikesamuel
1994e867904c8295537803c1c8a076e130df5674b58mikesamuel  private static boolean isHex(int codepoint) {
2004e867904c8295537803c1c8a076e130df5674b58mikesamuel    return ('0' <= codepoint && codepoint <= '9')
2014e867904c8295537803c1c8a076e130df5674b58mikesamuel        || ('A' <= codepoint && codepoint <= 'F')
2024e867904c8295537803c1c8a076e130df5674b58mikesamuel        || ('a' <= codepoint && codepoint <= 'f');
2034e867904c8295537803c1c8a076e130df5674b58mikesamuel  }
2044e867904c8295537803c1c8a076e130df5674b58mikesamuel
2054e867904c8295537803c1c8a076e130df5674b58mikesamuel  interface PropertyHandler {
2064e867904c8295537803c1c8a076e130df5674b58mikesamuel    void startProperty(String propertyName);
2074e867904c8295537803c1c8a076e130df5674b58mikesamuel    void quantity(String token);
208b600c3cd7edfb02d79c264fd83b1306e94053b7emikesamuel    void identifier(String token);
209b600c3cd7edfb02d79c264fd83b1306e94053b7emikesamuel    void hash(String token);
2104e867904c8295537803c1c8a076e130df5674b58mikesamuel    void quotedString(String token);
2114e867904c8295537803c1c8a076e130df5674b58mikesamuel    void url(String token);
2124e867904c8295537803c1c8a076e130df5674b58mikesamuel    void punctuation(String token);
213b600c3cd7edfb02d79c264fd83b1306e94053b7emikesamuel    void startFunction(String token);
214b600c3cd7edfb02d79c264fd83b1306e94053b7emikesamuel    void endFunction(String token);
2154e867904c8295537803c1c8a076e130df5674b58mikesamuel    void endProperty();
2164e867904c8295537803c1c8a076e130df5674b58mikesamuel  }
2174e867904c8295537803c1c8a076e130df5674b58mikesamuel
2184e867904c8295537803c1c8a076e130df5674b58mikesamuel}
219