1/* 2 * Copyright (C) 2003 Lars Knoll (knoll@kde.org) 3 * Copyright (C) 2005 Allan Sandfeld Jensen (kde@carewolf.com) 4 * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012 Apple Inc. All rights reserved. 5 * Copyright (C) 2007 Nicholas Shanks <webkit@nickshanks.com> 6 * Copyright (C) 2008 Eric Seidel <eric@webkit.org> 7 * Copyright (C) 2009 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.com/) 8 * Copyright (C) 2012 Adobe Systems Incorporated. All rights reserved. 9 * Copyright (C) 2012 Intel Corporation. All rights reserved. 10 * 11 * This library is free software; you can redistribute it and/or 12 * modify it under the terms of the GNU Library General Public 13 * License as published by the Free Software Foundation; either 14 * version 2 of the License, or (at your option) any later version. 15 * 16 * This library is distributed in the hope that it will be useful, 17 * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 19 * Library General Public License for more details. 20 * 21 * You should have received a copy of the GNU Library General Public License 22 * along with this library; see the file COPYING.LIB. If not, write to 23 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 24 * Boston, MA 02110-1301, USA. 25 */ 26 27#include "config.h" 28#include "core/css/CSSMarkup.h" 29 30#include "wtf/HexNumber.h" 31#include "wtf/text/StringBuffer.h" 32 33namespace blink { 34 35template <typename CharacterType> 36static inline bool isCSSTokenizerIdentifier(const CharacterType* characters, unsigned length) 37{ 38 const CharacterType* end = characters + length; 39 40 // -? 41 if (characters != end && characters[0] == '-') 42 ++characters; 43 44 // {nmstart} 45 if (characters == end || !(characters[0] == '_' || characters[0] >= 128 || isASCIIAlpha(characters[0]))) 46 return false; 47 ++characters; 48 49 // {nmchar}* 50 for (; characters != end; ++characters) { 51 if (!(characters[0] == '_' || characters[0] == '-' || characters[0] >= 128 || isASCIIAlphanumeric(characters[0]))) 52 return false; 53 } 54 55 return true; 56} 57 58// "ident" from the CSS tokenizer, minus backslash-escape sequences 59static bool isCSSTokenizerIdentifier(const String& string) 60{ 61 unsigned length = string.length(); 62 63 if (!length) 64 return false; 65 66 if (string.is8Bit()) 67 return isCSSTokenizerIdentifier(string.characters8(), length); 68 return isCSSTokenizerIdentifier(string.characters16(), length); 69} 70 71template <typename CharacterType> 72static inline bool isCSSTokenizerURL(const CharacterType* characters, unsigned length) 73{ 74 const CharacterType* end = characters + length; 75 76 for (; characters != end; ++characters) { 77 CharacterType c = characters[0]; 78 switch (c) { 79 case '!': 80 case '#': 81 case '$': 82 case '%': 83 case '&': 84 break; 85 default: 86 if (c < '*') 87 return false; 88 if (c <= '~') 89 break; 90 if (c < 128) 91 return false; 92 } 93 } 94 95 return true; 96} 97 98// "url" from the CSS tokenizer, minus backslash-escape sequences 99static bool isCSSTokenizerURL(const String& string) 100{ 101 unsigned length = string.length(); 102 103 if (!length) 104 return true; 105 106 if (string.is8Bit()) 107 return isCSSTokenizerURL(string.characters8(), length); 108 return isCSSTokenizerURL(string.characters16(), length); 109} 110 111template <typename CharacterType> 112static inline String quoteCSSStringInternal(const CharacterType* characters, unsigned length) 113{ 114 // For efficiency, we first pre-calculate the length of the quoted string, then we build the actual one. 115 // Please see below for the actual logic. 116 unsigned quotedStringSize = 2; // Two quotes surrounding the entire string. 117 bool afterEscape = false; 118 for (unsigned i = 0; i < length; ++i) { 119 CharacterType ch = characters[i]; 120 if (ch == '\\' || ch == '\'') { 121 quotedStringSize += 2; 122 afterEscape = false; 123 } else if (ch < 0x20 || ch == 0x7F) { 124 quotedStringSize += 2 + (ch >= 0x10); 125 afterEscape = true; 126 } else { 127 quotedStringSize += 1 + (afterEscape && (isASCIIHexDigit(ch) || ch == ' ')); 128 afterEscape = false; 129 } 130 } 131 132 StringBuffer<CharacterType> buffer(quotedStringSize); 133 unsigned index = 0; 134 buffer[index++] = '\''; 135 afterEscape = false; 136 for (unsigned i = 0; i < length; ++i) { 137 CharacterType ch = characters[i]; 138 if (ch == '\\' || ch == '\'') { 139 buffer[index++] = '\\'; 140 buffer[index++] = ch; 141 afterEscape = false; 142 } else if (ch < 0x20 || ch == 0x7F) { // Control characters. 143 buffer[index++] = '\\'; 144 placeByteAsHexCompressIfPossible(ch, buffer, index, Lowercase); 145 afterEscape = true; 146 } else { 147 // Space character may be required to separate backslash-escape sequence and normal characters. 148 if (afterEscape && (isASCIIHexDigit(ch) || ch == ' ')) 149 buffer[index++] = ' '; 150 buffer[index++] = ch; 151 afterEscape = false; 152 } 153 } 154 buffer[index++] = '\''; 155 156 ASSERT(quotedStringSize == index); 157 return String::adopt(buffer); 158} 159 160// We use single quotes for now because markup.cpp uses double quotes. 161String quoteCSSString(const String& string) 162{ 163 // This function expands each character to at most 3 characters ('\u0010' -> '\' '1' '0') as well as adds 164 // 2 quote characters (before and after). Make sure the resulting size (3 * length + 2) will not overflow unsigned. 165 166 unsigned length = string.length(); 167 168 if (!length) 169 return String("\'\'"); 170 171 if (length > std::numeric_limits<unsigned>::max() / 3 - 2) 172 return emptyString(); 173 174 if (string.is8Bit()) 175 return quoteCSSStringInternal(string.characters8(), length); 176 return quoteCSSStringInternal(string.characters16(), length); 177} 178 179String quoteCSSStringIfNeeded(const String& string) 180{ 181 return isCSSTokenizerIdentifier(string) ? string : quoteCSSString(string); 182} 183 184String quoteCSSURLIfNeeded(const String& string) 185{ 186 return isCSSTokenizerURL(string) ? string : quoteCSSString(string); 187} 188 189} // namespace blink 190