Fixed img src URLS with multiple spaces
[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     ASSERT(imageCandidates.isEmpty());
327
328     size_t imageCandidateStart = 0;
329     unsigned srcsetAttributeLength = srcsetAttribute.length();
330
331     while (imageCandidateStart < srcsetAttributeLength) {
332         float imageScaleFactor = 1;
333         size_t separator;
334
335         // 4. Splitting loop: Skip whitespace.
336         size_t imageURLStart = srcsetAttribute.find(isNotHTMLSpace, imageCandidateStart);
337         if (imageURLStart == notFound)
338             break;
339         // If The current candidate is either totally empty or only contains space, skipping.
340         if (srcsetAttribute[imageURLStart] == ',') {
341             imageCandidateStart = imageURLStart + 1;
342             continue;
343         }
344         // 5. Collect a sequence of characters that are not space characters, and let that be url.
345         size_t imageURLEnd = srcsetAttribute.find(isHTMLSpace, imageURLStart + 1);
346         if (imageURLEnd == notFound) {
347             imageURLEnd = srcsetAttributeLength;
348             separator = srcsetAttributeLength;
349         } else if (srcsetAttribute[imageURLEnd - 1] == ',') {
350             --imageURLEnd;
351             separator = imageURLEnd;
352         } else {
353             // 7. Collect a sequence of characters that are not "," (U+002C) characters, and let that be descriptors.
354             size_t imageScaleStart = srcsetAttribute.find(isNotHTMLSpace, imageURLEnd + 1);
355             if (imageScaleStart == notFound)
356                 separator = srcsetAttributeLength;
357             else if (srcsetAttribute[imageScaleStart] == ',')
358                 separator = imageScaleStart;
359             else {
360                 // This part differs from the spec as the current implementation only supports pixel density descriptors for now.
361                 size_t imageScaleEnd = srcsetAttribute.find(isHTMLSpaceOrComma, imageScaleStart + 1);
362                 imageScaleEnd = (imageScaleEnd == notFound) ? srcsetAttributeLength : imageScaleEnd;
363                 size_t commaPosition = imageScaleEnd;
364                 // Make sure there are no other descriptors.
365                 while ((commaPosition < srcsetAttributeLength - 1) && isHTMLSpace(srcsetAttribute[commaPosition]))
366                     ++commaPosition;
367                 // If the first not html space character after the scale modifier is not a comma,
368                 // the current candidate is an invalid input.
369                 if ((commaPosition < srcsetAttributeLength - 1) && srcsetAttribute[commaPosition] != ',') {
370                     // Find the nearest comma and skip the input.
371                     commaPosition = srcsetAttribute.find(',', commaPosition + 1);
372                     if (commaPosition == notFound)
373                         break;
374                     imageCandidateStart = commaPosition + 1;
375                     continue;
376                 }
377                 separator = commaPosition;
378                 if (srcsetAttribute[imageScaleEnd - 1] != 'x') {
379                     imageCandidateStart = separator + 1;
380                     continue;
381                 }
382                 bool validScaleFactor = false;
383                 size_t scaleFactorLengthWithoutUnit = imageScaleEnd - imageScaleStart - 1;
384                 imageScaleFactor = charactersToFloat(srcsetAttribute.characters() + imageScaleStart, scaleFactorLengthWithoutUnit, &validScaleFactor);
385
386                 if (!validScaleFactor) {
387                     imageCandidateStart = separator + 1;
388                     continue;
389                 }
390             }
391         }
392         ImageWithScale image;
393         image.imageURL = String(srcsetAttribute.characters() + imageURLStart, imageURLEnd - imageURLStart);
394         image.scaleFactor = imageScaleFactor;
395
396         imageCandidates.append(image);
397         // 11. Return to the step labeled splitting loop.
398         imageCandidateStart = separator + 1;
399     }
400 }
401
402 String bestFitSourceForImageAttributes(float deviceScaleFactor, const String& srcAttribute, const String& srcsetAttribute)
403 {
404     ImageCandidates imageCandidates;
405
406     parseImagesWithScaleFromSrcsetAttribute(srcsetAttribute, imageCandidates);
407
408     if (!srcAttribute.isEmpty()) {
409         ImageWithScale image;
410         image.imageURL = srcAttribute;
411         image.scaleFactor = 1.0;
412
413         imageCandidates.append(image);
414     }
415
416     if (imageCandidates.isEmpty())
417         return String();
418
419     std::stable_sort(imageCandidates.begin(), imageCandidates.end(), compareByScaleFactor);
420
421     for (size_t i = 0; i < imageCandidates.size() - 1; ++i) {
422         if (imageCandidates[i].scaleFactor >= deviceScaleFactor)
423             return String(imageCandidates[i].imageURL);
424     }
425     return String(imageCandidates.last().imageURL);
426 }
427
428 }