uniapp,h5

test-utf8-tearing.js 7.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. /******************************************************************************************
  2. * Assume a web server serves up the utf8 encoding of a random Uint8Array,
  3. * so that xhr.responseText is a string corresponding to the in-memory
  4. * representation of the Uint8Array. This test demonstrates a bug in xmlhttprequest-ssl,
  5. * where the utf8 endcoding of a byte with 0x80 <= byte <= 0xff, is torn across 2 chunks.
  6. *
  7. * Consider a code point 0x80. The utf8 encoding has 2 bytes 0xc2 and 0x80.
  8. * It is possible for one chunk to end with 0xc2 and the next chunk starts with 0x80.
  9. * This is what is meant by tearing. The fix is to remove
  10. * self.responseText += data.toString('utf8');
  11. * from the response 'data' handler and add the following to the response 'end' handler
  12. * // Construct responseText from response
  13. * self.responseText = self.response.toString('utf8');
  14. */
  15. // @ts-check
  16. 'use strict';
  17. const assert = require("assert");
  18. const http = require("http");
  19. const useLocalXHR = true;
  20. const XHRModule = useLocalXHR ? "../lib/XMLHttpRequest" : "xmlhttprequest-ssl";
  21. const { XMLHttpRequest } = require(XHRModule);
  22. const supressConsoleOutput = true;
  23. function log (...args) {
  24. if ( !supressConsoleOutput)
  25. console.debug(...args);
  26. }
  27. var serverProcess;
  28. /******************************************************************************************
  29. * This section produces a web server that serves up
  30. * 1) Buffer.from(ta.buffer) using url = "http://localhost:8888/binary";
  31. * 2) utf8 encoding of ta_to_hexStr(ta) using url = "http://localhost:8888/binaryUtf8";
  32. * where ta is a Float32Array.
  33. * Note: In order to repro utf8 tearing ta.length needs to be pretty big
  34. * N = 1 * 1000 * 1000;
  35. */
  36. /**
  37. * Create a string corresponding to the in-memory representation of Float32Array ta.
  38. *
  39. * @param {Float32Array} ta
  40. * @returns {string}
  41. */
  42. function ta_to_hexStr(ta) {
  43. const u8 = new Uint8Array(ta.buffer);
  44. return u8.reduce((acc, cur) => acc + String.fromCharCode(cur), "");
  45. }
  46. /**
  47. * Create a random Float32Array of length N.
  48. *
  49. * @param {number} N
  50. * @returns {Float32Array}
  51. */
  52. function createFloat32Array(N) {
  53. assert(N > 0);
  54. let ta = new Float32Array(N);
  55. for (let k = 0; k < ta.length; k++)
  56. ta[k] = Math.random();
  57. //ta = new Float32Array([1, 5, 6, 7]); // Use to debug
  58. return ta;
  59. }
  60. const N = 1 * 1000 * 1000; // Needs to be big enough to tear a few utf8 sequences.
  61. const f32 = createFloat32Array(N);
  62. /**
  63. * From a Float32Array f32 transform into:
  64. * 1) buffer: Buffer.from(ta.buffer)
  65. * 2) bufferUtf8: utf8 encoding of ta_to_hexStr(ta)
  66. *
  67. * @param {Float32Array} f32
  68. * @returns {{ buffer: Buffer, bufferUtf8: Buffer }}
  69. */
  70. function createBuffers(f32) {
  71. const buffer = Buffer.from(f32.buffer);
  72. const ss = ta_to_hexStr(f32);
  73. const bufferUtf8 = Buffer.from(ss, 'utf8'); // Encode ss in utf8
  74. return { buffer, bufferUtf8 };
  75. }
  76. const { buffer, bufferUtf8 } = createBuffers(f32);
  77. /**
  78. * Serves up buffer at
  79. * url = "http://localhost:8888/binary";
  80. * Serves up bufferUtf8 at
  81. * url = "http://localhost:8888/binaryUtf8";
  82. *
  83. * @param {Buffer} buffer
  84. * @param {Buffer} bufferUtf8
  85. */
  86. function createServer(buffer, bufferUtf8) {
  87. serverProcess = http.createServer(function (req, res) {
  88. switch (req.url) {
  89. case "/binary":
  90. return res
  91. .writeHead(200, {"Content-Type": "application/octet-stream"})
  92. .end(buffer);
  93. case "/binaryUtf8":
  94. return res
  95. .writeHead(200, {"Content-Type": "application/octet-stream"})
  96. .end(bufferUtf8);
  97. default:
  98. return res
  99. .writeHead(404, {"Content-Type": "text/plain"})
  100. .end("Not found");
  101. }
  102. }).listen(8888);
  103. process.on("SIGINT", function () {
  104. if (serverProcess)
  105. serverProcess.close();
  106. serverProcess = null;
  107. });
  108. }
  109. createServer(buffer, bufferUtf8);
  110. /******************************************************************************************
  111. * This section tests the above web server and verifies the correct Float32Array can be
  112. * successfully reconstituted for both
  113. * 1) url = "http://localhost:8888/binary";
  114. * 2) url = "http://localhost:8888/binaryUtf8";
  115. */
  116. /**
  117. * Assumes hexStr is the in-memory representation of a Float32Array.
  118. * Relies on the fact that the char codes in hexStr are all <= 0xFF.
  119. * Returns Float32Array corresponding to hexStr.
  120. *
  121. * @param {string} hexStr
  122. * @returns {Float32Array}
  123. */
  124. function hexStr_to_ta(hexStr) {
  125. const u8 = new Uint8Array(hexStr.length);
  126. for (let k = 0; k < hexStr.length; k++)
  127. u8[k] = Number(hexStr.charCodeAt(k));
  128. return new Float32Array(u8.buffer);
  129. }
  130. /**
  131. * Verify ta1 and ta2 are the same kind of view.
  132. * Verify the first count elements of ta1 and ta2 are equal.
  133. *
  134. * @param {Float32Array} ta1
  135. * @param {Float32Array} ta2
  136. * @param {number} [count=1000]
  137. * @returns {boolean}
  138. */
  139. function checkEnough(ta1, ta2, count = 1000) {
  140. assert(ta1 && ta2);
  141. if (ta1.constructor.name !== ta2.constructor.name) return false;
  142. if (ta1.length !== ta2.length) return false;
  143. if (ta1.byteOffset !== ta2.byteOffset) return false;
  144. for (let k = 0; k < Math.min(count, ta1.length); k++) {
  145. if (ta1[k] !== ta2[k]) {
  146. log('checkEnough: Not Equal!', k, ta1[k], ta2[k]);
  147. return false;
  148. }
  149. }
  150. return true;
  151. }
  152. const xhr = new XMLHttpRequest();
  153. const url = "http://localhost:8888/binary";
  154. const urlUtf8 = "http://localhost:8888/binaryUtf8";
  155. /**
  156. * Send a GET request to the server.
  157. * When isUtf8 is true, assume that xhr.response is already
  158. * utf8 encoded so that xhr.responseText.
  159. *
  160. * @param {string} url
  161. * @param {boolean} isUtf8
  162. * @returns {Promise<Float32Array>}
  163. */
  164. function Get(url, isUtf8) {
  165. return new Promise((resolve, reject) => {
  166. xhr.open("GET", url, true);
  167. xhr.onloadend = function(event) {
  168. log('xhr.status:', xhr.status);
  169. if (xhr.status >= 200 && xhr.status < 300) {
  170. const contentType = xhr.getResponseHeader('content-type');
  171. assert.equal(contentType, 'application/octet-stream');
  172. const dataTxt = xhr.responseText;
  173. const data = xhr.response;
  174. assert(dataTxt && data);
  175. log('XHR GET:', contentType, dataTxt.length, data.length, data.toString('utf8').length);
  176. log('XHR GET:', data.constructor.name, dataTxt.constructor.name);
  177. if (isUtf8 && dataTxt.length !== data.toString('utf8').length)
  178. throw new Error("xhr.responseText !== xhr.response.toString('utf8')");
  179. const ta = isUtf8 ? new Float32Array(hexStr_to_ta(dataTxt)) : new Float32Array(data.buffer);
  180. log('XHR GET:', ta.constructor.name, ta.length, ta[0], ta[1]);
  181. if (!checkEnough(ta, f32))
  182. throw new Error("Unable to correctly reconstitute Float32Array");
  183. resolve(ta);
  184. }
  185. reject(new Error(`Request failed: xhr.status ${xhr.status}`));
  186. }
  187. xhr.send();
  188. });
  189. }
  190. /**
  191. * Test function which gets utf8 encoded bytes of the typed array
  192. * new Uint8Array(new Float32Array(N).buffer),
  193. * then it gets the raw bytes from
  194. * new Uint8Array(new Float32Array(N).buffer).
  195. * Before the utf8 tearing bug is fixed,
  196. * Get(urlUtf8, true)
  197. * will fail with the exception:
  198. * Error: xhr.responseText !== xhr.response.toString('utf8').
  199. *
  200. * @returns {Promise<Float32Array>}
  201. */
  202. function runTest() {
  203. return Get(urlUtf8, true)
  204. .then(() => { return Get(url, false); });
  205. }
  206. /**
  207. * Run the test.
  208. */
  209. setTimeout(function () {
  210. runTest()
  211. .then((ta) => { console.log("done", ta?.length); })
  212. .finally(() => {
  213. if (serverProcess)
  214. serverProcess.close();
  215. serverProcess = null;
  216. });
  217. }, 100);