Remove URL decoding in srcset handling
[WebKit-https.git] / Source / WebCore / html / parser / HTMLParserIdioms.cpp
1 /*
2  * Copyright (C) 2010 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1.  Redistributions of source code must retain the above copyright
8  *     notice, this list of conditions and the following disclaimer.
9  * 2.  Redistributions in binary form must reproduce the above copyright
10  *     notice, this list of conditions and the following disclaimer in the
11  *     documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
15  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
16  * DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY
17  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
18  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
19  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
20  * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
22  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23  */
24
25 #include "config.h"
26 #include "HTMLParserIdioms.h"
27
28 #include "Decimal.h"
29 #include "HTMLIdentifier.h"
30 #include "KURL.h"
31 #include "QualifiedName.h"
32 #include <limits>
33 #include <wtf/MathExtras.h>
34 #include <wtf/text/AtomicString.h>
35 #include <wtf/text/StringBuilder.h>
36
37 namespace WebCore {
38
39 template <typename CharType>
40 static String stripLeadingAndTrailingHTMLSpaces(String string, CharType characters, unsigned length)
41 {
42     unsigned numLeadingSpaces = 0;
43     unsigned numTrailingSpaces = 0;
44
45     for (; numLeadingSpaces < length; ++numLeadingSpaces) {
46         if (isNotHTMLSpace(characters[numLeadingSpaces]))
47             break;
48     }
49
50     if (numLeadingSpaces == length)
51         return string.isNull() ? string : emptyAtom.string();
52
53     for (; numTrailingSpaces < length; ++numTrailingSpaces) {
54         if (isNotHTMLSpace(characters[length - numTrailingSpaces - 1]))
55             break;
56     }
57
58     ASSERT(numLeadingSpaces + numTrailingSpaces < length);
59
60     if (!(numLeadingSpaces | numTrailingSpaces))
61         return string;
62
63     return string.substring(numLeadingSpaces, length - (numLeadingSpaces + numTrailingSpaces));
64 }
65
66 String stripLeadingAndTrailingHTMLSpaces(const String& string)
67 {
68     unsigned length = string.length();
69
70     if (!length)
71         return string.isNull() ? string : emptyAtom.string();
72
73     if (string.is8Bit())
74         return stripLeadingAndTrailingHTMLSpaces(string, string.characters8(), length);
75
76     return stripLeadingAndTrailingHTMLSpaces(string, string.characters(), length);
77 }
78
79 String serializeForNumberType(const Decimal& number)
80 {
81     if (number.isZero()) {
82         // Decimal::toString appends exponent, e.g. "0e-18"
83         return number.isNegative() ? "-0" : "0";
84     }
85     return number.toString();
86 }
87
88 String serializeForNumberType(double number)
89 {
90     // According to HTML5, "the best representation of the number n as a floating
91     // point number" is a string produced by applying ToString() to n.
92     return String::numberToStringECMAScript(number);
93 }
94
95 Decimal parseToDecimalForNumberType(const String& string, const Decimal& fallbackValue)
96 {
97     // See HTML5 2.5.4.3 `Real numbers.' and parseToDoubleForNumberType
98
99     // String::toDouble() accepts leading + and whitespace characters, which are not valid here.
100     const UChar firstCharacter = string[0];
101     if (firstCharacter != '-' && firstCharacter != '.' && !isASCIIDigit(firstCharacter))
102         return fallbackValue;
103
104     const Decimal value = Decimal::fromString(string);
105     if (!value.isFinite())
106         return fallbackValue;
107
108     // Numbers are considered finite IEEE 754 single-precision floating point values.
109     // See HTML5 2.5.4.3 `Real numbers.'
110     // FIXME: We should use numeric_limits<double>::max for number input type.
111     const Decimal floatMax = Decimal::fromDouble(std::numeric_limits<float>::max());
112     if (value < -floatMax || value > floatMax)
113         return fallbackValue;
114
115     // We return +0 for -0 case.
116     return value.isZero() ? Decimal(0) : value;
117 }
118
119 Decimal parseToDecimalForNumberType(const String& string)
120 {
121     return parseToDecimalForNumberType(string, Decimal::nan());
122 }
123
124 double parseToDoubleForNumberType(const String& string, double fallbackValue)
125 {
126     // See HTML5 2.5.4.3 `Real numbers.'
127
128     // String::toDouble() accepts leading + and whitespace characters, which are not valid here.
129     UChar firstCharacter = string[0];
130     if (firstCharacter != '-' && firstCharacter != '.' && !isASCIIDigit(firstCharacter))
131         return fallbackValue;
132
133     bool valid = false;
134     double value = string.toDouble(&valid);
135     if (!valid)
136         return fallbackValue;
137
138     // NaN and infinity are considered valid by String::toDouble, but not valid here.
139     if (!std::isfinite(value))
140         return fallbackValue;
141
142     // Numbers are considered finite IEEE 754 single-precision floating point values.
143     // See HTML5 2.5.4.3 `Real numbers.'
144     if (-std::numeric_limits<float>::max() > value || value > std::numeric_limits<float>::max())
145         return fallbackValue;
146
147     // The following expression converts -0 to +0.
148     return value ? value : 0;
149 }
150
151 double parseToDoubleForNumberType(const String& string)
152 {
153     return parseToDoubleForNumberType(string, std::numeric_limits<double>::quiet_NaN());
154 }
155
156 template <typename CharacterType>
157 static bool parseHTMLIntegerInternal(const CharacterType* position, const CharacterType* end, int& value)
158 {
159     // Step 3
160     int sign = 1;
161
162     // Step 4
163     while (position < end) {
164         if (!isHTMLSpace(*position))
165             break;
166         ++position;
167     }
168
169     // Step 5
170     if (position == end)
171         return false;
172     ASSERT(position < end);
173
174     // Step 6
175     if (*position == '-') {
176         sign = -1;
177         ++position;
178     } else if (*position == '+')
179         ++position;
180     if (position == end)
181         return false;
182     ASSERT(position < end);
183
184     // Step 7
185     if (!isASCIIDigit(*position))
186         return false;
187
188     // Step 8
189     StringBuilder digits;
190     while (position < end) {
191         if (!isASCIIDigit(*position))
192             break;
193         digits.append(*position++);
194     }
195
196     // Step 9
197     bool ok;
198     if (digits.is8Bit())
199         value = sign * charactersToIntStrict(digits.characters8(), digits.length(), &ok);
200     else
201         value = sign * charactersToIntStrict(digits.characters16(), digits.length(), &ok);
202     return ok;
203 }
204
205 // http://www.whatwg.org/specs/web-apps/current-work/#rules-for-parsing-integers
206 bool parseHTMLInteger(const String& input, int& value)
207 {
208     // Step 1
209     // Step 2
210     unsigned length = input.length();
211     if (!length || input.is8Bit()) {
212         const LChar* start = input.characters8();
213         return parseHTMLIntegerInternal(start, start + length, value);
214     }
215
216     const UChar* start = input.characters16();
217     return parseHTMLIntegerInternal(start, start + length, value);
218 }
219
220 template <typename CharacterType>
221 static bool parseHTMLNonNegativeIntegerInternal(const CharacterType* position, const CharacterType* end, unsigned& value)
222 {
223     // Step 3
224     while (position < end) {
225         if (!isHTMLSpace(*position))
226             break;
227         ++position;
228     }
229
230     // Step 4
231     if (position == end)
232         return false;
233     ASSERT(position < end);
234
235     // Step 5
236     if (*position == '+')
237         ++position;
238
239     // Step 6
240     if (position == end)
241         return false;
242     ASSERT(position < end);
243
244     // Step 7
245     if (!isASCIIDigit(*position))
246         return false;
247
248     // Step 8
249     StringBuilder digits;
250     while (position < end) {
251         if (!isASCIIDigit(*position))
252             break;
253         digits.append(*position++);
254     }
255
256     // Step 9
257     bool ok;
258     if (digits.is8Bit())
259         value = charactersToUIntStrict(digits.characters8(), digits.length(), &ok);
260     else
261         value = charactersToUIntStrict(digits.characters16(), digits.length(), &ok);
262     return ok;
263 }
264
265
266 // http://www.whatwg.org/specs/web-apps/current-work/#rules-for-parsing-non-negative-integers
267 bool parseHTMLNonNegativeInteger(const String& input, unsigned& value)
268 {
269     // Step 1
270     // Step 2
271     unsigned length = input.length();
272     if (length && input.is8Bit()) {
273         const LChar* start = input.characters8();
274         return parseHTMLNonNegativeIntegerInternal(start, start + length, value);
275     }
276     
277     const UChar* start = input.characters();
278     return parseHTMLNonNegativeIntegerInternal(start, start + length, value);
279 }
280
281 static bool threadSafeEqual(const StringImpl* a, const StringImpl* b)
282 {
283     if (a == b)
284         return true;
285     if (a->hash() != b->hash())
286         return false;
287     return equalNonNull(a, b);
288 }
289
290 bool threadSafeMatch(const QualifiedName& a, const QualifiedName& b)
291 {
292     return threadSafeEqual(a.localName().impl(), b.localName().impl());
293 }
294
295 #if ENABLE(THREADED_HTML_PARSER)
296 bool threadSafeMatch(const HTMLIdentifier& localName, const QualifiedName& qName)
297 {
298     return threadSafeEqual(localName.asStringImpl(), qName.localName().impl());
299 }
300 #endif
301
302 struct ImageWithScale {
303     String imageURL;
304     float scaleFactor;
305     bool operator==(const ImageWithScale& image) const
306     {
307         return scaleFactor == image.scaleFactor && imageURL == image.imageURL;
308     }
309 };
310 typedef Vector<ImageWithScale> ImageCandidates;
311
312 static inline bool compareByScaleFactor(const ImageWithScale& first, const ImageWithScale& second)
313 {
314     return first.scaleFactor < second.scaleFactor;
315 }
316
317 static inline bool isHTMLSpaceOrComma(UChar character)
318 {
319     return isHTMLSpace(character) || character == ',';
320 }
321
322 // See the specifications for more details about the algorithm to follow.
323 // http://www.w3.org/TR/2013/WD-html-srcset-20130228/#processing-the-image-candidates.
324 static void parseImagesWithScaleFromSrcSetAttribute(const String& srcSetAttribute, ImageCandidates& imageCandidates)
325 {
326     size_t imageCandidateStart = 0;
327     unsigned srcSetLength = srcSetAttribute.length();
328
329     while (imageCandidateStart < srcSetLength) {
330         float imgScaleFactor = 1.0;
331         size_t separator;
332
333         // 4. Splitting loop: Skip whitespace.
334         size_t imageUrlStart = srcSetAttribute.find(isNotHTMLSpace, imageCandidateStart);
335         if (imageUrlStart == notFound)
336             break;
337         // If The current candidate is either totally empty or only contains space, skipping.
338         if (srcSetAttribute[imageUrlStart] == ',') {
339             imageCandidateStart = imageUrlStart + 1;
340             continue;
341         }
342         // 5. Collect a sequence of characters that are not space characters, and let that be url.
343         size_t imageUrlEnd = srcSetAttribute.find(isHTMLSpace, imageUrlStart + 1);
344         if (imageUrlEnd == notFound) {
345             imageUrlEnd = srcSetLength;
346             separator = srcSetLength;
347         } else if (srcSetAttribute[imageUrlEnd - 1] == ',') {
348             --imageUrlEnd;
349             separator = imageUrlEnd;
350         } else {
351             // 7. Collect a sequence of characters that are not "," (U+002C) characters, and let that be descriptors.
352             size_t imageScaleStart = srcSetAttribute.find(isNotHTMLSpace, imageUrlEnd + 1);
353             if (imageScaleStart == notFound)
354                 separator = srcSetLength;
355             else if (srcSetAttribute[imageScaleStart] == ',')
356                 separator = imageScaleStart;
357             else {
358                 // This part differs from the spec as the current implementation only supports pixel density descriptors for now.
359                 size_t imageScaleEnd = srcSetAttribute.find(isHTMLSpaceOrComma, imageScaleStart + 1);
360                 imageScaleEnd = (imageScaleEnd == notFound) ? srcSetLength : imageScaleEnd;
361                 size_t commaPosition = imageScaleEnd;
362                 // Make sure there are no other descriptors.
363                 while ((commaPosition < srcSetLength - 1) && isHTMLSpace(srcSetAttribute[commaPosition]))
364                     ++commaPosition;
365                 // If the first not html space character after the scale modifier is not a comma,
366                 // the current candidate is an invalid input.
367                 if ((commaPosition < srcSetLength - 1) && srcSetAttribute[commaPosition] != ',') {
368                     // Find the nearest comma and skip the input.
369                     commaPosition = srcSetAttribute.find(',', commaPosition + 1);
370                     if (commaPosition == notFound)
371                         break;
372                     imageCandidateStart = commaPosition + 1;
373                     continue;
374                 }
375                 separator = commaPosition;
376                 if (srcSetAttribute[imageScaleEnd - 1] != 'x') {
377                     imageCandidateStart = separator + 1;
378                     continue;
379                 }
380                 bool validScaleFactor = false;
381                 size_t scaleFactorLengthWithoutUnit = imageScaleEnd - imageScaleStart - 1;
382                 imgScaleFactor = charactersToFloat(srcSetAttribute.characters() + imageScaleStart, scaleFactorLengthWithoutUnit, &validScaleFactor);
383
384                 if (!validScaleFactor) {
385                     imageCandidateStart = separator + 1;
386                     continue;
387                 }
388             }
389         }
390         ImageWithScale image;
391         image.imageURL = StringImpl::createWithoutCopying(srcSetAttribute.characters() + imageUrlStart, imageUrlEnd - imageUrlStart);
392         image.scaleFactor = imgScaleFactor;
393
394         imageCandidates.append(image);
395         // 11. Return to the step labeled splitting loop.
396         imageCandidateStart = separator + 1;
397     }
398 }
399
400 String bestFitSourceForImageAttributes(float deviceScaleFactor, const String& srcAttribute, const String& srcSetAttribute)
401 {
402     ImageCandidates imageCandidates;
403
404     parseImagesWithScaleFromSrcSetAttribute(srcSetAttribute, imageCandidates);
405
406     const String src =  srcAttribute.simplifyWhiteSpace(isHTMLSpace);
407     if (!src.isEmpty()) {
408         ImageWithScale image;
409         image.imageURL = src;
410         image.scaleFactor = 1.0;
411
412         imageCandidates.append(image);
413     }
414
415     if (imageCandidates.isEmpty())
416         return String();
417
418     std::stable_sort(imageCandidates.begin(), imageCandidates.end(), compareByScaleFactor);
419
420     for (size_t i = 0; i < imageCandidates.size() - 1; ++i) {
421         if (imageCandidates[i].scaleFactor >= deviceScaleFactor)
422             return String(imageCandidates[i].imageURL);
423     }
424     return String(imageCandidates.last().imageURL);
425 }
426
427 }