JSONString.h 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250
  1. //-------------------------------------------------------------------------------------------------------
  2. // Copyright (C) Microsoft. All rights reserved.
  3. // Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
  4. //-------------------------------------------------------------------------------------------------------
  5. #pragma once
  6. namespace Js
  7. {
  8. enum EscapingOperation : BYTE
  9. {
  10. EscapingOperation_NotEscape,
  11. EscapingOperation_Escape,
  12. EscapingOperation_Count
  13. };
  14. class WritableStringBuffer
  15. {
  16. public:
  17. WritableStringBuffer(_In_count_(length) wchar_t* str, _In_ charcount_t length) : m_pszString(str), m_pszCurrentPtr(str), m_length(length) {}
  18. void Append(wchar_t c);
  19. void Append(const wchar_t * str, charcount_t countNeeded);
  20. void AppendLarge(const wchar_t * str, charcount_t countNeeded);
  21. private:
  22. wchar_t* m_pszString;
  23. wchar_t* m_pszCurrentPtr;
  24. charcount_t m_length;
  25. #if DBG
  26. charcount_t GetCount()
  27. {
  28. Assert(m_pszCurrentPtr >= m_pszString);
  29. Assert(m_pszCurrentPtr - m_pszString <= MaxCharCount);
  30. return static_cast<charcount_t>(m_pszCurrentPtr - m_pszString);
  31. }
  32. #endif
  33. };
  34. class JSONString : public JavascriptString
  35. {
  36. public:
  37. static JSONString* New(JavascriptString* originalString, charcount_t start, charcount_t extraChars);
  38. virtual const wchar_t* GetSz() override;
  39. protected:
  40. DEFINE_VTABLE_CTOR(JSONString, JavascriptString);
  41. DECLARE_CONCRETE_STRING_CLASS;
  42. private:
  43. JavascriptString* m_originalString;
  44. charcount_t m_start; /* start of the escaping operation */
  45. private:
  46. JSONString(JavascriptString* originalString, charcount_t start, charcount_t length);
  47. static const WCHAR escapeMap[128];
  48. static const BYTE escapeMapCount[128];
  49. public:
  50. template <EscapingOperation op>
  51. static Js::JavascriptString* Escape(Js::JavascriptString* value, uint start = 0, WritableStringBuffer* outputString = nullptr)
  52. {
  53. uint len = value->GetLength();
  54. if (0 == len)
  55. {
  56. Js::ScriptContext* scriptContext = value->GetScriptContext();
  57. return scriptContext->GetLibrary()->GetQuotesString();
  58. }
  59. else
  60. {
  61. const wchar_t* szValue = value->GetSz();
  62. return EscapeNonEmptyString<op, Js::JSONString, Js::ConcatStringWrapping<L'"', L'"'>, Js::JavascriptString*>(value, szValue, start, len, outputString);
  63. }
  64. }
  65. template <EscapingOperation op, class TJSONString, class TConcatStringWrapping, class TJavascriptString>
  66. static TJavascriptString EscapeNonEmptyString(Js::JavascriptString* value, const wchar_t* szValue, uint start, charcount_t len, WritableStringBuffer* outputString)
  67. {
  68. charcount_t extra = 0;
  69. TJavascriptString result;
  70. // Optimize for the case when we don't need to change anything, just wrap with quotes.
  71. // If we realize we need to change the inside of the string, start over in "modification needed" mode.
  72. if (op == EscapingOperation_Escape)
  73. {
  74. outputString->Append(L'\"');
  75. if (start != 0)
  76. {
  77. outputString->AppendLarge(szValue, start);
  78. }
  79. }
  80. const wchar* endSz = szValue + len;
  81. const wchar* startSz = szValue + start;
  82. const wchar* lastFlushSz = startSz;
  83. for (const wchar* current = startSz; current < endSz; current++)
  84. {
  85. WCHAR wch = *current;
  86. if (op == EscapingOperation_Count)
  87. {
  88. if (wch < _countof(escapeMap))
  89. {
  90. extra = UInt32Math::Add(extra, escapeMapCount[(char)wch]);
  91. }
  92. }
  93. else
  94. {
  95. WCHAR specialChar;
  96. if (wch < _countof(escapeMap))
  97. {
  98. specialChar = escapeMap[(char)wch];
  99. }
  100. else
  101. {
  102. specialChar = '\0';
  103. }
  104. if (specialChar != '\0')
  105. {
  106. if (op == EscapingOperation_Escape)
  107. {
  108. outputString->AppendLarge(lastFlushSz, (charcount_t)(current - lastFlushSz));
  109. lastFlushSz = current + 1;
  110. outputString->Append(L'\\');
  111. outputString->Append(specialChar);
  112. if (specialChar == L'u')
  113. {
  114. wchar_t bf[5];
  115. _ltow_s(wch, bf, _countof(bf), 16);
  116. size_t count = wcslen(bf);
  117. if (count < 4)
  118. {
  119. if (count == 1)
  120. {
  121. outputString->Append(L"000", 3);
  122. }
  123. else if (count == 2)
  124. {
  125. outputString->Append(L"00", 2);
  126. }
  127. else
  128. {
  129. outputString->Append(L"0", 1);
  130. }
  131. }
  132. outputString->Append(bf, (charcount_t)count);
  133. }
  134. }
  135. else
  136. {
  137. charcount_t i = (charcount_t)(current - startSz);
  138. return EscapeNonEmptyString<EscapingOperation_Count, TJSONString, TConcatStringWrapping, TJavascriptString>(value, szValue, i ? i - 1 : 0, len, outputString);
  139. }
  140. }
  141. }
  142. } // for.
  143. if (op == EscapingOperation_Escape)
  144. {
  145. if (lastFlushSz < endSz)
  146. {
  147. outputString->AppendLarge(lastFlushSz, (charcount_t)(endSz - lastFlushSz));
  148. }
  149. outputString->Append(L'\"');
  150. result = nullptr;
  151. }
  152. else if (op == EscapingOperation_Count)
  153. {
  154. result = TJSONString::New(value, start, extra);
  155. }
  156. else
  157. {
  158. // If we got here, we don't need to change the inside, just wrap the string with quotes.
  159. result = TConcatStringWrapping::New(value);
  160. }
  161. return result;
  162. }
  163. static WCHAR* EscapeNonEmptyString(ArenaAllocator* allocator, const wchar_t* szValue)
  164. {
  165. WCHAR* result = nullptr;
  166. StringProxy::allocator = allocator;
  167. charcount_t len = (charcount_t)wcslen(szValue);
  168. StringProxy* proxy = EscapeNonEmptyString<EscapingOperation_NotEscape, StringProxy, StringProxy, StringProxy*>(nullptr, szValue, 0, len, nullptr);
  169. result = proxy->GetResult(szValue, len);
  170. StringProxy::allocator = nullptr;
  171. return result;
  172. }
  173. // This class has the same interface (with respect to the EscapeNonEmptyString method) as JSONString and TConcatStringWrapping
  174. // It is used in scenario where we want to use the JSON escaping capability without having a script context.
  175. class StringProxy
  176. {
  177. public:
  178. static ArenaAllocator* allocator;
  179. StringProxy()
  180. {
  181. this->m_needEscape = false;
  182. }
  183. StringProxy(int start, int extra) : m_start(start), m_extra(extra)
  184. {
  185. this->m_needEscape = true;
  186. }
  187. static StringProxy* New(Js::JavascriptString* value)
  188. {
  189. // Case 1: The string do not need to be escaped at all
  190. Assert(value == nullptr);
  191. Assert(allocator != nullptr);
  192. return Anew(allocator, StringProxy);
  193. }
  194. static StringProxy* New(Js::JavascriptString* value, uint start, uint length)
  195. {
  196. // Case 2: The string requires escaping, and the length is computed
  197. Assert(value == nullptr);
  198. Assert(allocator != nullptr);
  199. return Anew(allocator, StringProxy, start, length);
  200. }
  201. WCHAR* GetResult(const WCHAR* originalString, charcount_t originalLength)
  202. {
  203. if (this->m_needEscape)
  204. {
  205. charcount_t unescapedStringLength = originalLength + m_extra + 2 /* for the quotes */;
  206. WCHAR* buffer = AnewArray(allocator, WCHAR, unescapedStringLength + 1); /* for terminating null */
  207. buffer[unescapedStringLength] = '\0';
  208. WritableStringBuffer stringBuffer(buffer, unescapedStringLength);
  209. StringProxy* proxy = JSONString::EscapeNonEmptyString<EscapingOperation_Escape, StringProxy, StringProxy, StringProxy*>(nullptr, originalString, m_start, originalLength, &stringBuffer);
  210. Assert(proxy == nullptr);
  211. Assert(buffer[unescapedStringLength] == '\0');
  212. return buffer;
  213. }
  214. else
  215. {
  216. WCHAR* buffer = AnewArray(allocator, WCHAR, originalLength + 3); /* quotes and terminating null */
  217. buffer[0] = L'\"';
  218. buffer[originalLength + 1] = L'\"';
  219. buffer[originalLength + 2] = L'\0';
  220. js_wmemcpy_s(buffer + 1, originalLength, originalString, originalLength);
  221. return buffer;
  222. }
  223. }
  224. private:
  225. int m_extra;
  226. int m_start;
  227. bool m_needEscape;
  228. };
  229. };
  230. }