1/*
2 * Copyright (C) 2010-2018 Apple Inc. All rights reserved.
3 * Copyright (C) 2012 Google Inc. All rights reserved.
4 * Copyright (C) 2017 Yusuke Suzuki <[email protected]>. All rights reserved.
5 * Copyright (C) 2017 Mozilla Foundation. All rights reserved.
6 *
7 * This Source Code Form is subject to the terms of the Mozilla Public
8 * License, v. 2.0. If a copy of the MPL was not distributed with this
9 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
10 */
11
12#include "config.h"
13#include <wtf/text/StringBuilder.h>
14
15#include <wtf/text/WTFString.h>
16
17namespace WTF {
18
19// This table driven escaping is ported from SpiderMonkey.
20static const constexpr LChar escapedFormsForJSON[0x100] = {
21 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u',
22 'b', 't', 'n', 'u', 'f', 'r', 'u', 'u',
23 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u',
24 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u',
25 0, 0, '\"', 0, 0, 0, 0, 0,
26 0, 0, 0, 0, 0, 0, 0, 0,
27 0, 0, 0, 0, 0, 0, 0, 0,
28 0, 0, 0, 0, 0, 0, 0, 0,
29 0, 0, 0, 0, 0, 0, 0, 0,
30 0, 0, 0, 0, 0, 0, 0, 0,
31 0, 0, 0, 0, 0, 0, 0, 0,
32 0, 0, 0, 0, '\\', 0, 0, 0,
33 0, 0, 0, 0, 0, 0, 0, 0,
34 0, 0, 0, 0, 0, 0, 0, 0,
35 0, 0, 0, 0, 0, 0, 0, 0,
36 0, 0, 0, 0, 0, 0, 0, 0,
37 0, 0, 0, 0, 0, 0, 0, 0,
38 0, 0, 0, 0, 0, 0, 0, 0,
39 0, 0, 0, 0, 0, 0, 0, 0,
40 0, 0, 0, 0, 0, 0, 0, 0,
41 0, 0, 0, 0, 0, 0, 0, 0,
42 0, 0, 0, 0, 0, 0, 0, 0,
43 0, 0, 0, 0, 0, 0, 0, 0,
44 0, 0, 0, 0, 0, 0, 0, 0,
45 0, 0, 0, 0, 0, 0, 0, 0,
46 0, 0, 0, 0, 0, 0, 0, 0,
47 0, 0, 0, 0, 0, 0, 0, 0,
48 0, 0, 0, 0, 0, 0, 0, 0,
49 0, 0, 0, 0, 0, 0, 0, 0,
50 0, 0, 0, 0, 0, 0, 0, 0,
51 0, 0, 0, 0, 0, 0, 0, 0,
52 0, 0, 0, 0, 0, 0, 0, 0,
53};
54
55template<typename OutputCharacterType, typename InputCharacterType>
56ALWAYS_INLINE static void appendQuotedJSONStringInternal(OutputCharacterType*& output, const InputCharacterType* input, unsigned length)
57{
58 for (auto* end = input + length; input != end; ++input) {
59 auto character = *input;
60 if (LIKELY(character <= 0xFF)) {
61 auto escaped = escapedFormsForJSON[character];
62 if (LIKELY(!escaped)) {
63 *output++ = character;
64 continue;
65 }
66
67 *output++ = '\\';
68 *output++ = escaped;
69 if (UNLIKELY(escaped == 'u')) {
70 *output++ = '0';
71 *output++ = '0';
72 *output++ = upperNibbleToLowercaseASCIIHexDigit(character);
73 *output++ = lowerNibbleToLowercaseASCIIHexDigit(character);
74 }
75 continue;
76 }
77
78 if (LIKELY(!U16_IS_SURROGATE(character))) {
79 *output++ = character;
80 continue;
81 }
82
83 auto next = input + 1;
84 bool isValidSurrogatePair = U16_IS_SURROGATE_LEAD(character) && next != end && U16_IS_TRAIL(*next);
85 if (isValidSurrogatePair) {
86 *output++ = character;
87 *output++ = *next;
88 ++input;
89 continue;
90 }
91
92 uint8_t upper = static_cast<uint32_t>(character) >> 8;
93 uint8_t lower = static_cast<uint8_t>(character);
94 *output++ = '\\';
95 *output++ = 'u';
96 *output++ = upperNibbleToLowercaseASCIIHexDigit(upper);
97 *output++ = lowerNibbleToLowercaseASCIIHexDigit(upper);
98 *output++ = upperNibbleToLowercaseASCIIHexDigit(lower);
99 *output++ = lowerNibbleToLowercaseASCIIHexDigit(lower);
100 continue;
101 }
102}
103
104void StringBuilder::appendQuotedJSONString(const String& string)
105{
106 if (hasOverflowed())
107 return;
108 // Make sure we have enough buffer space to append this string without having
109 // to worry about reallocating in the middle.
110 // The 2 is for the '"' quotes on each end.
111 // The 6 is for characters that need to be \uNNNN encoded.
112 Checked<unsigned, RecordOverflow> stringLength = string.length();
113 Checked<unsigned, RecordOverflow> maximumCapacityRequired = length();
114 maximumCapacityRequired += 2 + stringLength * 6;
115 unsigned allocationSize;
116 if (CheckedState::DidOverflow == maximumCapacityRequired.safeGet(allocationSize))
117 return didOverflow();
118 // This max() is here to allow us to allocate sizes between the range [2^31, 2^32 - 2] because roundUpToPowerOfTwo(1<<31 + some int smaller than 1<<31) == 0.
119 // FIXME: roundUpToPowerOfTwo should take Checked<unsigned> and abort if it fails to round up.
120 // https://bugs.webkit.org/show_bug.cgi?id=176086
121 allocationSize = std::max(allocationSize, roundUpToPowerOfTwo(allocationSize));
122
123 // Allocating this much will definitely fail.
124 if (allocationSize > String::MaxLength)
125 return didOverflow();
126
127 if (is8Bit() && !string.is8Bit())
128 allocateBufferUpConvert(m_bufferCharacters8, allocationSize);
129 else
130 reserveCapacity(allocationSize);
131 if (UNLIKELY(hasOverflowed()))
132 return;
133 ASSERT(m_buffer->length() >= allocationSize);
134
135 if (is8Bit()) {
136 ASSERT(string.is8Bit());
137 LChar* output = m_bufferCharacters8 + m_length.unsafeGet<unsigned>();
138 *output++ = '"';
139 appendQuotedJSONStringInternal(output, string.characters8(), string.length());
140 *output++ = '"';
141 m_length = output - m_bufferCharacters8;
142 } else {
143 UChar* output = m_bufferCharacters16 + m_length.unsafeGet<unsigned>();
144 *output++ = '"';
145 if (string.is8Bit())
146 appendQuotedJSONStringInternal(output, string.characters8(), string.length());
147 else
148 appendQuotedJSONStringInternal(output, string.characters16(), string.length());
149 *output++ = '"';
150 m_length = output - m_bufferCharacters16;
151 }
152 ASSERT(!hasOverflowed());
153 ASSERT(m_buffer->length() >= m_length.unsafeGet<unsigned>());
154}
155
156} // namespace WTF
157