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