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 | |
17 | namespace WTF { |
18 | |
19 | // This table driven escaping is ported from SpiderMonkey. |
20 | static 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 | |
55 | template<typename OutputCharacterType, typename InputCharacterType> |
56 | ALWAYS_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 | |
104 | void 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 | |