Tue, 06 Jan 2015 21:39:09 +0100
Conditionally force memory storage according to privacy.thirdparty.isolate;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "nsSMILParserUtils.h"
7 #include "nsSMILKeySpline.h"
8 #include "nsISMILAttr.h"
9 #include "nsSMILValue.h"
10 #include "nsSMILTimeValue.h"
11 #include "nsSMILTimeValueSpecParams.h"
12 #include "nsSMILTypes.h"
13 #include "nsSMILRepeatCount.h"
14 #include "nsContentUtils.h"
15 #include "nsCharSeparatedTokenizer.h"
16 #include "SVGContentUtils.h"
18 using namespace mozilla;
19 using namespace mozilla::dom;
20 //------------------------------------------------------------------------------
21 // Helper functions and Constants
23 namespace {
25 const uint32_t MSEC_PER_SEC = 1000;
26 const uint32_t MSEC_PER_MIN = 1000 * 60;
27 const uint32_t MSEC_PER_HOUR = 1000 * 60 * 60;
29 #define ACCESSKEY_PREFIX_LC NS_LITERAL_STRING("accesskey(") // SMIL2+
30 #define ACCESSKEY_PREFIX_CC NS_LITERAL_STRING("accessKey(") // SVG/SMIL ANIM
31 #define REPEAT_PREFIX NS_LITERAL_STRING("repeat(")
32 #define WALLCLOCK_PREFIX NS_LITERAL_STRING("wallclock(")
34 inline bool
35 SkipWhitespace(RangedPtr<const char16_t>& aIter,
36 const RangedPtr<const char16_t>& aEnd)
37 {
38 while (aIter != aEnd) {
39 if (!IsSVGWhitespace(*aIter)) {
40 return true;
41 }
42 ++aIter;
43 }
44 return false;
45 }
47 inline bool
48 ParseColon(RangedPtr<const char16_t>& aIter,
49 const RangedPtr<const char16_t>& aEnd)
50 {
51 if (aIter == aEnd || *aIter != ':') {
52 return false;
53 }
54 ++aIter;
55 return true;
56 }
58 /*
59 * Exactly two digits in the range 00 - 59 are expected.
60 */
61 bool
62 ParseSecondsOrMinutes(RangedPtr<const char16_t>& aIter,
63 const RangedPtr<const char16_t>& aEnd,
64 uint32_t& aValue)
65 {
66 if (aIter == aEnd || !SVGContentUtils::IsDigit(*aIter)) {
67 return false;
68 }
70 RangedPtr<const char16_t> iter(aIter);
72 if (++iter == aEnd || !SVGContentUtils::IsDigit(*iter)) {
73 return false;
74 }
76 uint32_t value = 10 * SVGContentUtils::DecimalDigitValue(*aIter) +
77 SVGContentUtils::DecimalDigitValue(*iter);
78 if (value > 59) {
79 return false;
80 }
81 if (++iter != aEnd && SVGContentUtils::IsDigit(*iter)) {
82 return false;
83 }
85 aValue = value;
86 aIter = iter;
87 return true;
88 }
90 inline bool
91 ParseClockMetric(RangedPtr<const char16_t>& aIter,
92 const RangedPtr<const char16_t>& aEnd,
93 uint32_t& aMultiplier)
94 {
95 if (aIter == aEnd) {
96 aMultiplier = MSEC_PER_SEC;
97 return true;
98 }
100 switch (*aIter) {
101 case 'h':
102 if (++aIter == aEnd) {
103 aMultiplier = MSEC_PER_HOUR;
104 return true;
105 }
106 return false;
107 case 'm':
108 {
109 const nsAString& metric = Substring(aIter.get(), aEnd.get());
110 if (metric.EqualsLiteral("min")) {
111 aMultiplier = MSEC_PER_MIN;
112 aIter = aEnd;
113 return true;
114 }
115 if (metric.EqualsLiteral("ms")) {
116 aMultiplier = 1;
117 aIter = aEnd;
118 return true;
119 }
120 }
121 return false;
122 case 's':
123 if (++aIter == aEnd) {
124 aMultiplier = MSEC_PER_SEC;
125 return true;
126 }
127 }
128 return false;
129 }
131 /**
132 * See http://www.w3.org/TR/SVG/animate.html#ClockValueSyntax
133 */
134 bool
135 ParseClockValue(RangedPtr<const char16_t>& aIter,
136 const RangedPtr<const char16_t>& aEnd,
137 nsSMILTimeValue* aResult)
138 {
139 if (aIter == aEnd) {
140 return false;
141 }
143 // TIMECOUNT_VALUE ::= Timecount ("." Fraction)? (Metric)?
144 // PARTIAL_CLOCK_VALUE ::= Minutes ":" Seconds ("." Fraction)?
145 // FULL_CLOCK_VALUE ::= Hours ":" Minutes ":" Seconds ("." Fraction)?
146 enum ClockType {
147 TIMECOUNT_VALUE,
148 PARTIAL_CLOCK_VALUE,
149 FULL_CLOCK_VALUE
150 };
152 int32_t clockType = TIMECOUNT_VALUE;
154 RangedPtr<const char16_t> iter(aIter);
156 // Determine which type of clock value we have by counting the number
157 // of colons in the string.
158 do {
159 switch (*iter) {
160 case ':':
161 if (clockType == FULL_CLOCK_VALUE) {
162 return false;
163 }
164 ++clockType;
165 break;
166 case 'e':
167 case 'E':
168 case '-':
169 case '+':
170 // Exclude anything invalid (for clock values)
171 // that number parsing might otherwise allow.
172 return false;
173 }
174 ++iter;
175 } while (iter != aEnd);
177 iter = aIter;
179 int32_t hours = 0, timecount;
180 double fraction = 0.0;
181 uint32_t minutes, seconds, multiplier;
183 switch (clockType) {
184 case FULL_CLOCK_VALUE:
185 if (!SVGContentUtils::ParseInteger(iter, aEnd, hours) ||
186 !ParseColon(iter, aEnd)) {
187 return false;
188 }
189 // intentional fall through
190 case PARTIAL_CLOCK_VALUE:
191 if (!ParseSecondsOrMinutes(iter, aEnd, minutes) ||
192 !ParseColon(iter, aEnd) ||
193 !ParseSecondsOrMinutes(iter, aEnd, seconds)) {
194 return false;
195 }
196 if (iter != aEnd &&
197 (*iter != '.' ||
198 !SVGContentUtils::ParseNumber(iter, aEnd, fraction))) {
199 return false;
200 }
201 aResult->SetMillis(nsSMILTime(hours) * MSEC_PER_HOUR +
202 minutes * MSEC_PER_MIN +
203 seconds * MSEC_PER_SEC +
204 NS_round(fraction * MSEC_PER_SEC));
205 aIter = iter;
206 return true;
207 case TIMECOUNT_VALUE:
208 if (!SVGContentUtils::ParseInteger(iter, aEnd, timecount)) {
209 return false;
210 }
211 if (iter != aEnd && *iter == '.' &&
212 !SVGContentUtils::ParseNumber(iter, aEnd, fraction)) {
213 return false;
214 }
215 if (!ParseClockMetric(iter, aEnd, multiplier)) {
216 return false;
217 }
218 aResult->SetMillis(nsSMILTime(timecount) * multiplier +
219 NS_round(fraction * multiplier));
220 aIter = iter;
221 return true;
222 }
224 return false;
225 }
227 bool
228 ParseOffsetValue(RangedPtr<const char16_t>& aIter,
229 const RangedPtr<const char16_t>& aEnd,
230 nsSMILTimeValue* aResult)
231 {
232 RangedPtr<const char16_t> iter(aIter);
234 int32_t sign;
235 if (!SVGContentUtils::ParseOptionalSign(iter, aEnd, sign) ||
236 !SkipWhitespace(iter, aEnd) ||
237 !ParseClockValue(iter, aEnd, aResult)) {
238 return false;
239 }
240 if (sign == -1) {
241 aResult->SetMillis(-aResult->GetMillis());
242 }
243 aIter = iter;
244 return true;
245 }
247 bool
248 ParseOffsetValue(const nsAString& aSpec,
249 nsSMILTimeValue* aResult)
250 {
251 RangedPtr<const char16_t> iter(SVGContentUtils::GetStartRangedPtr(aSpec));
252 const RangedPtr<const char16_t> end(SVGContentUtils::GetEndRangedPtr(aSpec));
254 return ParseOffsetValue(iter, end, aResult) && iter == end;
255 }
257 bool
258 ParseOptionalOffset(RangedPtr<const char16_t>& aIter,
259 const RangedPtr<const char16_t>& aEnd,
260 nsSMILTimeValue* aResult)
261 {
262 if (aIter == aEnd) {
263 aResult->SetMillis(0L);
264 return true;
265 }
267 return SkipWhitespace(aIter, aEnd) &&
268 ParseOffsetValue(aIter, aEnd, aResult);
269 }
271 bool
272 ParseAccessKey(const nsAString& aSpec, nsSMILTimeValueSpecParams& aResult)
273 {
274 NS_ABORT_IF_FALSE(StringBeginsWith(aSpec, ACCESSKEY_PREFIX_CC) ||
275 StringBeginsWith(aSpec, ACCESSKEY_PREFIX_LC),
276 "Calling ParseAccessKey on non-accesskey-type spec");
278 nsSMILTimeValueSpecParams result;
279 result.mType = nsSMILTimeValueSpecParams::ACCESSKEY;
281 NS_ABORT_IF_FALSE(
282 ACCESSKEY_PREFIX_LC.Length() == ACCESSKEY_PREFIX_CC.Length(),
283 "Case variations for accesskey prefix differ in length");
285 RangedPtr<const char16_t> iter(SVGContentUtils::GetStartRangedPtr(aSpec));
286 RangedPtr<const char16_t> end(SVGContentUtils::GetEndRangedPtr(aSpec));
288 iter += ACCESSKEY_PREFIX_LC.Length();
290 // Expecting at least <accesskey> + ')'
291 if (end - iter < 2)
292 return false;
294 uint32_t c = *iter++;
296 // Process 32-bit codepoints
297 if (NS_IS_HIGH_SURROGATE(c)) {
298 if (end - iter < 2) // Expecting at least low-surrogate + ')'
299 return false;
300 uint32_t lo = *iter++;
301 if (!NS_IS_LOW_SURROGATE(lo))
302 return false;
303 c = SURROGATE_TO_UCS4(c, lo);
304 // XML 1.1 says that 0xFFFE and 0xFFFF are not valid characters
305 } else if (NS_IS_LOW_SURROGATE(c) || c == 0xFFFE || c == 0xFFFF) {
306 return false;
307 }
309 result.mRepeatIterationOrAccessKey = c;
311 if (*iter++ != ')')
312 return false;
314 if (!ParseOptionalOffset(iter, end, &result.mOffset) || iter != end) {
315 return false;
316 }
317 aResult = result;
318 return true;
319 }
321 void
322 MoveToNextToken(RangedPtr<const char16_t>& aIter,
323 const RangedPtr<const char16_t>& aEnd,
324 bool aBreakOnDot,
325 bool& aIsAnyCharEscaped)
326 {
327 aIsAnyCharEscaped = false;
329 bool isCurrentCharEscaped = false;
331 while (aIter != aEnd && !IsSVGWhitespace(*aIter)) {
332 if (isCurrentCharEscaped) {
333 isCurrentCharEscaped = false;
334 } else {
335 if (*aIter == '+' || *aIter == '-' ||
336 (aBreakOnDot && *aIter == '.')) {
337 break;
338 }
339 if (*aIter == '\\') {
340 isCurrentCharEscaped = true;
341 aIsAnyCharEscaped = true;
342 }
343 }
344 ++aIter;
345 }
346 }
348 already_AddRefed<nsIAtom>
349 ConvertUnescapedTokenToAtom(const nsAString& aToken)
350 {
351 // Whether the token is an id-ref or event-symbol it should be a valid NCName
352 if (aToken.IsEmpty() || NS_FAILED(nsContentUtils::CheckQName(aToken, false)))
353 return nullptr;
354 return do_GetAtom(aToken);
355 }
357 already_AddRefed<nsIAtom>
358 ConvertTokenToAtom(const nsAString& aToken,
359 bool aUnescapeToken)
360 {
361 // Unescaping involves making a copy of the string which we'd like to avoid if possible
362 if (!aUnescapeToken) {
363 return ConvertUnescapedTokenToAtom(aToken);
364 }
366 nsAutoString token(aToken);
368 const char16_t* read = token.BeginReading();
369 const char16_t* const end = token.EndReading();
370 char16_t* write = token.BeginWriting();
371 bool escape = false;
373 while (read != end) {
374 NS_ABORT_IF_FALSE(write <= read, "Writing past where we've read");
375 if (!escape && *read == '\\') {
376 escape = true;
377 ++read;
378 } else {
379 *write++ = *read++;
380 escape = false;
381 }
382 }
383 token.Truncate(write - token.BeginReading());
385 return ConvertUnescapedTokenToAtom(token);
386 }
388 bool
389 ParseElementBaseTimeValueSpec(const nsAString& aSpec,
390 nsSMILTimeValueSpecParams& aResult)
391 {
392 nsSMILTimeValueSpecParams result;
394 //
395 // The spec will probably look something like one of these
396 //
397 // element-name.begin
398 // element-name.event-name
399 // event-name
400 // element-name.repeat(3)
401 // event\.name
402 //
403 // Technically `repeat(3)' is permitted but the behaviour in this case is not
404 // defined (for SMIL Animation) so we don't support it here.
405 //
407 RangedPtr<const char16_t> start(SVGContentUtils::GetStartRangedPtr(aSpec));
408 RangedPtr<const char16_t> end(SVGContentUtils::GetEndRangedPtr(aSpec));
410 if (start == end) {
411 return false;
412 }
414 RangedPtr<const char16_t> tokenEnd(start);
416 bool requiresUnescaping;
417 MoveToNextToken(tokenEnd, end, true, requiresUnescaping);
419 nsRefPtr<nsIAtom> atom =
420 ConvertTokenToAtom(Substring(start.get(), tokenEnd.get()),
421 requiresUnescaping);
422 if (atom == nullptr) {
423 return false;
424 }
426 // Parse the second token if there is one
427 if (tokenEnd != end && *tokenEnd == '.') {
428 result.mDependentElemID = atom;
430 ++tokenEnd;
431 start = tokenEnd;
432 MoveToNextToken(tokenEnd, end, false, requiresUnescaping);
434 const nsAString& token2 = Substring(start.get(), tokenEnd.get());
436 // element-name.begin
437 if (token2.EqualsLiteral("begin")) {
438 result.mType = nsSMILTimeValueSpecParams::SYNCBASE;
439 result.mSyncBegin = true;
440 // element-name.end
441 } else if (token2.EqualsLiteral("end")) {
442 result.mType = nsSMILTimeValueSpecParams::SYNCBASE;
443 result.mSyncBegin = false;
444 // element-name.repeat(digit+)
445 } else if (StringBeginsWith(token2, REPEAT_PREFIX)) {
446 start += REPEAT_PREFIX.Length();
447 int32_t repeatValue;
448 if (start == tokenEnd || *start == '+' || *start == '-' ||
449 !SVGContentUtils::ParseInteger(start, tokenEnd, repeatValue)) {
450 return false;
451 }
452 if (start == tokenEnd || *start != ')') {
453 return false;
454 }
455 result.mType = nsSMILTimeValueSpecParams::REPEAT;
456 result.mRepeatIterationOrAccessKey = repeatValue;
457 // element-name.event-symbol
458 } else {
459 atom = ConvertTokenToAtom(token2, requiresUnescaping);
460 if (atom == nullptr) {
461 return false;
462 }
463 result.mType = nsSMILTimeValueSpecParams::EVENT;
464 result.mEventSymbol = atom;
465 }
466 } else {
467 // event-symbol
468 result.mType = nsSMILTimeValueSpecParams::EVENT;
469 result.mEventSymbol = atom;
470 }
472 // We've reached the end of the token, so we should now be either looking at
473 // a '+', '-' (possibly with whitespace before it), or the end.
474 if (!ParseOptionalOffset(tokenEnd, end, &result.mOffset) || tokenEnd != end) {
475 return false;
476 }
477 aResult = result;
478 return true;
479 }
481 } // end anonymous namespace block
483 //------------------------------------------------------------------------------
484 // Implementation
486 const nsDependentSubstring
487 nsSMILParserUtils::TrimWhitespace(const nsAString& aString)
488 {
489 nsAString::const_iterator start, end;
491 aString.BeginReading(start);
492 aString.EndReading(end);
494 // Skip whitespace characters at the beginning
495 while (start != end && IsSVGWhitespace(*start)) {
496 ++start;
497 }
499 // Skip whitespace characters at the end.
500 while (end != start) {
501 --end;
503 if (!IsSVGWhitespace(*end)) {
504 // Step back to the last non-whitespace character.
505 ++end;
507 break;
508 }
509 }
511 return Substring(start, end);
512 }
514 bool
515 nsSMILParserUtils::ParseKeySplines(const nsAString& aSpec,
516 FallibleTArray<nsSMILKeySpline>& aKeySplines)
517 {
518 nsCharSeparatedTokenizerTemplate<IsSVGWhitespace> controlPointTokenizer(aSpec, ';');
519 while (controlPointTokenizer.hasMoreTokens()) {
521 nsCharSeparatedTokenizerTemplate<IsSVGWhitespace>
522 tokenizer(controlPointTokenizer.nextToken(), ',',
523 nsCharSeparatedTokenizer::SEPARATOR_OPTIONAL);
525 double values[4];
526 for (int i = 0 ; i < 4; i++) {
527 if (!tokenizer.hasMoreTokens() ||
528 !SVGContentUtils::ParseNumber(tokenizer.nextToken(), values[i]) ||
529 values[i] > 1.0 || values[i] < 0.0) {
530 return false;
531 }
532 }
533 if (tokenizer.hasMoreTokens() ||
534 tokenizer.separatorAfterCurrentToken() ||
535 !aKeySplines.AppendElement(nsSMILKeySpline(values[0],
536 values[1],
537 values[2],
538 values[3]))) {
539 return false;
540 }
541 }
543 return !aKeySplines.IsEmpty();
544 }
546 bool
547 nsSMILParserUtils::ParseSemicolonDelimitedProgressList(const nsAString& aSpec,
548 bool aNonDecreasing,
549 FallibleTArray<double>& aArray)
550 {
551 nsCharSeparatedTokenizerTemplate<IsSVGWhitespace> tokenizer(aSpec, ';');
553 double previousValue = -1.0;
555 while (tokenizer.hasMoreTokens()) {
556 double value;
557 if (!SVGContentUtils::ParseNumber(tokenizer.nextToken(), value)) {
558 return false;
559 }
561 if (value > 1.0 || value < 0.0 ||
562 (aNonDecreasing && value < previousValue)) {
563 return false;
564 }
566 if (!aArray.AppendElement(value)) {
567 return false;
568 }
569 previousValue = value;
570 }
572 return !aArray.IsEmpty();
573 }
575 // Helper class for ParseValues
576 class MOZ_STACK_CLASS SMILValueParser :
577 public nsSMILParserUtils::GenericValueParser
578 {
579 public:
580 SMILValueParser(const SVGAnimationElement* aSrcElement,
581 const nsISMILAttr* aSMILAttr,
582 FallibleTArray<nsSMILValue>* aValuesArray,
583 bool* aPreventCachingOfSandwich) :
584 mSrcElement(aSrcElement),
585 mSMILAttr(aSMILAttr),
586 mValuesArray(aValuesArray),
587 mPreventCachingOfSandwich(aPreventCachingOfSandwich)
588 {}
590 virtual bool Parse(const nsAString& aValueStr) MOZ_OVERRIDE {
591 nsSMILValue newValue;
592 bool tmpPreventCachingOfSandwich = false;
593 if (NS_FAILED(mSMILAttr->ValueFromString(aValueStr, mSrcElement, newValue,
594 tmpPreventCachingOfSandwich)))
595 return false;
597 if (!mValuesArray->AppendElement(newValue)) {
598 return false;
599 }
600 if (tmpPreventCachingOfSandwich) {
601 *mPreventCachingOfSandwich = true;
602 }
603 return true;
604 }
605 protected:
606 const SVGAnimationElement* mSrcElement;
607 const nsISMILAttr* mSMILAttr;
608 FallibleTArray<nsSMILValue>* mValuesArray;
609 bool* mPreventCachingOfSandwich;
610 };
612 bool
613 nsSMILParserUtils::ParseValues(const nsAString& aSpec,
614 const SVGAnimationElement* aSrcElement,
615 const nsISMILAttr& aAttribute,
616 FallibleTArray<nsSMILValue>& aValuesArray,
617 bool& aPreventCachingOfSandwich)
618 {
619 // Assume all results can be cached, until we find one that can't.
620 aPreventCachingOfSandwich = false;
621 SMILValueParser valueParser(aSrcElement, &aAttribute,
622 &aValuesArray, &aPreventCachingOfSandwich);
623 return ParseValuesGeneric(aSpec, valueParser);
624 }
626 bool
627 nsSMILParserUtils::ParseValuesGeneric(const nsAString& aSpec,
628 GenericValueParser& aParser)
629 {
630 nsCharSeparatedTokenizerTemplate<IsSVGWhitespace> tokenizer(aSpec, ';');
631 if (!tokenizer.hasMoreTokens()) { // Empty list
632 return false;
633 }
635 while (tokenizer.hasMoreTokens()) {
636 if (!aParser.Parse(tokenizer.nextToken())) {
637 return false;
638 }
639 }
641 return true;
642 }
644 bool
645 nsSMILParserUtils::ParseRepeatCount(const nsAString& aSpec,
646 nsSMILRepeatCount& aResult)
647 {
648 const nsAString& spec =
649 nsSMILParserUtils::TrimWhitespace(aSpec);
651 if (spec.EqualsLiteral("indefinite")) {
652 aResult.SetIndefinite();
653 return true;
654 }
656 double value;
657 if (!SVGContentUtils::ParseNumber(spec, value) || value <= 0.0) {
658 return false;
659 }
660 aResult = value;
661 return true;
662 }
664 bool
665 nsSMILParserUtils::ParseTimeValueSpecParams(const nsAString& aSpec,
666 nsSMILTimeValueSpecParams& aResult)
667 {
668 const nsAString& spec = TrimWhitespace(aSpec);
670 if (spec.EqualsLiteral("indefinite")) {
671 aResult.mType = nsSMILTimeValueSpecParams::INDEFINITE;
672 return true;
673 }
675 // offset type
676 if (ParseOffsetValue(spec, &aResult.mOffset)) {
677 aResult.mType = nsSMILTimeValueSpecParams::OFFSET;
678 return true;
679 }
681 // wallclock type
682 if (StringBeginsWith(spec, WALLCLOCK_PREFIX)) {
683 return false; // Wallclock times not implemented
684 }
686 // accesskey type
687 if (StringBeginsWith(spec, ACCESSKEY_PREFIX_LC) ||
688 StringBeginsWith(spec, ACCESSKEY_PREFIX_CC)) {
689 return ParseAccessKey(spec, aResult);
690 }
692 // event, syncbase, or repeat
693 return ParseElementBaseTimeValueSpec(spec, aResult);
694 }
696 bool
697 nsSMILParserUtils::ParseClockValue(const nsAString& aSpec,
698 nsSMILTimeValue* aResult)
699 {
700 RangedPtr<const char16_t> iter(SVGContentUtils::GetStartRangedPtr(aSpec));
701 RangedPtr<const char16_t> end(SVGContentUtils::GetEndRangedPtr(aSpec));
703 return ::ParseClockValue(iter, end, aResult) && iter == end;
704 }
706 int32_t
707 nsSMILParserUtils::CheckForNegativeNumber(const nsAString& aStr)
708 {
709 int32_t absValLocation = -1;
711 nsAString::const_iterator start, end;
712 aStr.BeginReading(start);
713 aStr.EndReading(end);
715 // Skip initial whitespace
716 while (start != end && IsSVGWhitespace(*start)) {
717 ++start;
718 }
720 // Check for dash
721 if (start != end && *start == '-') {
722 ++start;
723 // Check for numeric character
724 if (start != end && SVGContentUtils::IsDigit(*start)) {
725 absValLocation = start.get() - start.start();
726 }
727 }
728 return absValLocation;
729 }