response-consume.html (20189B)
1 <!doctype html> 2 <html> 3 <head> 4 <meta charset="utf-8"> 5 <title>Response consume</title> 6 <meta name="help" href="https://fetch.spec.whatwg.org/#response"> 7 <meta name="help" href="https://fetch.spec.whatwg.org/#body-mixin"> 8 <meta name="author" title="Canon Research France" href="https://www.crf.canon.fr"> 9 <script src="/resources/testharness.js"></script> 10 <script src="/resources/testharnessreport.js"></script> 11 <script src="../resources/utils.js"></script> 12 </head> 13 <body> 14 <script> 15 function blobToFormDataResponse(name, blob) { 16 var formData = new FormData(); 17 formData.append(name, blob); 18 return new Response(formData); 19 } 20 21 function readBlobAsArrayBuffer(blob) { 22 return new Promise(function(resolve, reject) { 23 var reader = new FileReader(); 24 reader.onload = function(evt) { 25 resolve(reader.result); 26 }; 27 reader.onerror = function(evt) { 28 reject("Blob's reader failed"); 29 }; 30 reader.readAsArrayBuffer(blob); 31 }); 32 } 33 34 function blobToTypeViaFetch(blob) { 35 var url = URL.createObjectURL(blob); 36 return fetch(url).then(function(response) { 37 return response.headers.get('Content-Type'); 38 }); 39 } 40 41 function responsePromise(body, responseInit) { 42 return new Promise(function(resolve, reject) { 43 resolve(new Response(body, responseInit)); 44 }); 45 } 46 47 function responseStringToMultipartFormTextData(response, name, value) { 48 assert_true(response.headers.has("Content-Type"), "Response contains Content-Type header"); 49 var boundaryMatches = response.headers.get("Content-Type").match(/;\s*boundary=("?)([^";\s]*)\1/); 50 assert_true(!!boundaryMatches, "Response contains boundary parameter"); 51 return stringToMultipartFormTextData(boundaryMatches[2], name, value); 52 } 53 54 function streamResponsePromise(streamData, responseInit) { 55 return new Promise(function(resolve, reject) { 56 var stream = new ReadableStream({ 57 start: function(controller) { 58 controller.enqueue(stringToArray(streamData)); 59 controller.close(); 60 } 61 }); 62 resolve(new Response(stream, responseInit)); 63 }); 64 } 65 66 function stringToMultipartFormTextData(multipartBoundary, name, value) { 67 return ('--' + multipartBoundary + '\r\n' + 68 'Content-Disposition: form-data;name="' + name + '"\r\n' + 69 '\r\n' + 70 value + '\r\n' + 71 '--' + multipartBoundary + '--\r\n'); 72 } 73 74 function checkBodyText(test, response, expectedBody) { 75 return response.text().then( function(bodyAsText) { 76 assert_equals(bodyAsText, expectedBody, "Retrieve and verify response's body"); 77 assert_true(response.bodyUsed, "body as text: bodyUsed turned true"); 78 }); 79 } 80 81 function checkBodyBlob(test, response, expectedBody, expectedType) { 82 return response.blob().then(function(bodyAsBlob) { 83 assert_equals(bodyAsBlob.type, expectedType || "text/plain", "Blob body type should be computed from the response Content-Type"); 84 85 var promise = blobToTypeViaFetch(bodyAsBlob).then(function(type) { 86 assert_equals(type, expectedType || "text/plain", 'Type via blob URL'); 87 return new Promise( function (resolve, reject) { 88 var reader = new FileReader(); 89 reader.onload = function(evt) { 90 resolve(reader.result) 91 }; 92 reader.onerror = function () { 93 reject("Blob's reader failed"); 94 }; 95 reader.readAsText(bodyAsBlob); 96 }); 97 }); 98 return promise.then(function(body) { 99 assert_equals(body, expectedBody, "Retrieve and verify response's body"); 100 assert_true(response.bodyUsed, "body as blob: bodyUsed turned true"); 101 }); 102 }); 103 } 104 105 function checkBodyArrayBuffer(test, response, expectedBody) { 106 return response.arrayBuffer().then( function(bodyAsArrayBuffer) { 107 validateBufferFromString(bodyAsArrayBuffer, expectedBody, "Retrieve and verify response's body"); 108 assert_true(response.bodyUsed, "body as arrayBuffer: bodyUsed turned true"); 109 }); 110 } 111 112 function checkBodyJSON(test, response, expectedBody) { 113 return response.json().then(function(bodyAsJSON) { 114 var strBody = JSON.stringify(bodyAsJSON) 115 assert_equals(strBody, expectedBody, "Retrieve and verify response's body"); 116 assert_true(response.bodyUsed, "body as json: bodyUsed turned true"); 117 }); 118 } 119 120 function checkBodyFormDataMultipart(test, response, expectedBody) { 121 return response.formData().then(function(bodyAsFormData) { 122 assert_true(bodyAsFormData instanceof FormData, "Should receive a FormData"); 123 var entryName = "name"; 124 var strBody = responseStringToMultipartFormTextData(response, entryName, bodyAsFormData.get(entryName)); 125 assert_equals(strBody, expectedBody, "Retrieve and verify response's body"); 126 assert_true(response.bodyUsed, "body as formData: bodyUsed turned true"); 127 }); 128 } 129 130 function checkBodyFormDataUrlencoded(test, response, expectedBody) { 131 return response.formData().then(function(bodyAsFormData) { 132 assert_true(bodyAsFormData instanceof FormData, "Should receive a FormData"); 133 var entryName = "name"; 134 var strBody = entryName + "=" + bodyAsFormData.get(entryName); 135 assert_equals(strBody, expectedBody, "Retrieve and verify response's body"); 136 assert_true(response.bodyUsed, "body as formData: bodyUsed turned true"); 137 }); 138 } 139 140 function checkBodyFormDataError(test, response, expectedBody) { 141 return promise_rejects_js(test, TypeError, response.formData()).then(function() { 142 assert_true(response.bodyUsed, "body as formData: bodyUsed turned true"); 143 }); 144 } 145 146 function checkResponseBody(responsePromise, expectedBody, checkFunction, bodyTypes) { 147 promise_test(function(test) { 148 return responsePromise.then(function(response) { 149 assert_false(response.bodyUsed, "bodyUsed is false at init"); 150 return checkFunction(test, response, expectedBody); 151 }); 152 }, "Consume response's body: " + bodyTypes); 153 } 154 155 var textData = JSON.stringify("This is response's body"); 156 var textResponseInit = { "headers": [["Content-Type", "text/PLAIN"]] }; 157 var blob = new Blob([textData], { "type": "application/octet-stream" }); 158 var multipartBoundary = "boundary-" + Math.random(); 159 var formData = new FormData(); 160 var formTextResponseInit = { "headers": [["Content-Type", 'multipart/FORM-data; boundary="' + multipartBoundary + '"']] }; 161 var formTextData = stringToMultipartFormTextData(multipartBoundary, "name", textData); 162 var formBlob = new Blob([formTextData]); 163 var urlSearchParamsData = "name=value"; 164 var urlSearchParams = new URLSearchParams(urlSearchParamsData); 165 var urlSearchParamsType = "application/x-www-form-urlencoded;charset=UTF-8"; 166 var urlSearchParamsResponseInit = { "headers": [["Content-Type", urlSearchParamsType]] }; 167 var urlSearchParamsBlob = new Blob([urlSearchParamsData], { "type": urlSearchParamsType }); 168 formData.append("name", textData); 169 170 // https://fetch.spec.whatwg.org/#concept-body-package-data 171 // "UTF-8 decoded without BOM" is used for formData(), either in 172 // "multipart/form-data" and "application/x-www-form-urlencoded" cases, 173 // so BOMs in the values should be kept. 174 // (The "application/x-www-form-urlencoded" cases are tested in 175 // url/urlencoded-parser.any.js) 176 var textDataWithBom = "\uFEFFquick\uFEFFfox\uFEFF"; 177 var formTextDataWithBom = stringToMultipartFormTextData(multipartBoundary, "name", textDataWithBom); 178 var formTextDataWithBomExpectedForMultipartFormData = stringToMultipartFormTextData(multipartBoundary, "name", textDataWithBom); 179 180 checkResponseBody(responsePromise(textData, textResponseInit), textData, checkBodyText, "from text to text"); 181 checkResponseBody(responsePromise(textData, textResponseInit), textData, checkBodyBlob, "from text to blob"); 182 checkResponseBody(responsePromise(textData, textResponseInit), textData, checkBodyArrayBuffer, "from text to arrayBuffer"); 183 checkResponseBody(responsePromise(textData, textResponseInit), textData, checkBodyJSON, "from text to json"); 184 checkResponseBody(responsePromise(formTextData, formTextResponseInit), formTextData, checkBodyFormDataMultipart, "from text with correct multipart type to formData"); 185 checkResponseBody(responsePromise(formTextDataWithBom, formTextResponseInit), formTextDataWithBomExpectedForMultipartFormData, checkBodyFormDataMultipart, "from text with correct multipart type to formData with BOM"); 186 checkResponseBody(responsePromise(formTextData, textResponseInit), undefined, checkBodyFormDataError, "from text without correct multipart type to formData (error case)"); 187 checkResponseBody(responsePromise(urlSearchParamsData, urlSearchParamsResponseInit), urlSearchParamsData, checkBodyFormDataUrlencoded, "from text with correct urlencoded type to formData"); 188 checkResponseBody(responsePromise(urlSearchParamsData, textResponseInit), undefined, checkBodyFormDataError, "from text without correct urlencoded type to formData (error case)"); 189 190 checkResponseBody(responsePromise(blob, textResponseInit), textData, checkBodyBlob, "from blob to blob"); 191 checkResponseBody(responsePromise(blob), textData, checkBodyText, "from blob to text"); 192 checkResponseBody(responsePromise(blob), textData, checkBodyArrayBuffer, "from blob to arrayBuffer"); 193 checkResponseBody(responsePromise(blob), textData, checkBodyJSON, "from blob to json"); 194 checkResponseBody(responsePromise(formBlob, formTextResponseInit), formTextData, checkBodyFormDataMultipart, "from blob with correct multipart type to formData"); 195 checkResponseBody(responsePromise(formBlob, textResponseInit), undefined, checkBodyFormDataError, "from blob without correct multipart type to formData (error case)"); 196 checkResponseBody(responsePromise(urlSearchParamsBlob, urlSearchParamsResponseInit), urlSearchParamsData, checkBodyFormDataUrlencoded, "from blob with correct urlencoded type to formData"); 197 checkResponseBody(responsePromise(urlSearchParamsBlob, textResponseInit), undefined, checkBodyFormDataError, "from blob without correct urlencoded type to formData (error case)"); 198 199 function checkFormDataResponseBody(responsePromise, expectedName, expectedValue, checkFunction, bodyTypes) { 200 promise_test(function(test) { 201 return responsePromise.then(function(response) { 202 assert_false(response.bodyUsed, "bodyUsed is false at init"); 203 var expectedBody = responseStringToMultipartFormTextData(response, expectedName, expectedValue); 204 return Promise.resolve().then(function() { 205 if (checkFunction === checkBodyFormDataMultipart) 206 return expectedBody; 207 // Modify expectedBody to use the same spacing for 208 // Content-Disposition parameters as Response and FormData does. 209 var response2 = new Response(formData); 210 return response2.text().then(function(formDataAsText) { 211 var reName = /[ \t]*;[ \t]*name=/; 212 var nameMatches = formDataAsText.match(reName); 213 return expectedBody.replace(reName, nameMatches[0]); 214 }); 215 }).then(function(expectedBody) { 216 return checkFunction(test, response, expectedBody); 217 }); 218 }); 219 }, "Consume response's body: " + bodyTypes); 220 } 221 222 checkFormDataResponseBody(responsePromise(formData), "name", textData, checkBodyFormDataMultipart, "from FormData to formData"); 223 checkResponseBody(responsePromise(formData, textResponseInit), undefined, checkBodyFormDataError, "from FormData without correct type to formData (error case)"); 224 checkFormDataResponseBody(responsePromise(formData), "name", textData, function(test, response, expectedBody) { return checkBodyBlob(test, response, expectedBody, response.headers.get('Content-Type').toLowerCase()); }, "from FormData to blob"); 225 checkFormDataResponseBody(responsePromise(formData), "name", textData, checkBodyText, "from FormData to text"); 226 checkFormDataResponseBody(responsePromise(formData), "name", textData, checkBodyArrayBuffer, "from FormData to arrayBuffer"); 227 228 checkResponseBody(responsePromise(urlSearchParams), urlSearchParamsData, checkBodyFormDataUrlencoded, "from URLSearchParams to formData"); 229 checkResponseBody(responsePromise(urlSearchParams, textResponseInit), urlSearchParamsData, checkBodyFormDataError, "from URLSearchParams without correct type to formData (error case)"); 230 checkResponseBody(responsePromise(urlSearchParams), urlSearchParamsData, function(test, response, expectedBody) { return checkBodyBlob(test, response, expectedBody, "application/x-www-form-urlencoded;charset=utf-8"); }, "from URLSearchParams to blob"); 231 checkResponseBody(responsePromise(urlSearchParams), urlSearchParamsData, checkBodyText, "from URLSearchParams to text"); 232 checkResponseBody(responsePromise(urlSearchParams), urlSearchParamsData, checkBodyArrayBuffer, "from URLSearchParams to arrayBuffer"); 233 234 checkResponseBody(streamResponsePromise(textData, textResponseInit), textData, checkBodyBlob, "from stream to blob"); 235 checkResponseBody(streamResponsePromise(textData), textData, checkBodyText, "from stream to text"); 236 checkResponseBody(streamResponsePromise(textData), textData, checkBodyArrayBuffer, "from stream to arrayBuffer"); 237 checkResponseBody(streamResponsePromise(textData), textData, checkBodyJSON, "from stream to json"); 238 checkResponseBody(streamResponsePromise(formTextData, formTextResponseInit), formTextData, checkBodyFormDataMultipart, "from stream with correct multipart type to formData"); 239 checkResponseBody(streamResponsePromise(formTextData), formTextData, checkBodyFormDataError, "from stream without correct multipart type to formData (error case)"); 240 checkResponseBody(streamResponsePromise(urlSearchParamsData, urlSearchParamsResponseInit), urlSearchParamsData, checkBodyFormDataUrlencoded, "from stream with correct urlencoded type to formData"); 241 checkResponseBody(streamResponsePromise(urlSearchParamsData), urlSearchParamsData, checkBodyFormDataError, "from stream without correct urlencoded type to formData (error case)"); 242 243 checkResponseBody(fetch("../resources/top.txt"), "top", checkBodyBlob, "from fetch to blob"); 244 checkResponseBody(fetch("../resources/top.txt"), "top", checkBodyText, "from fetch to text"); 245 checkResponseBody(fetch("../resources/top.txt"), "top", checkBodyArrayBuffer, "from fetch to arrayBuffer"); 246 checkResponseBody(fetch("../resources/top.txt"), "top", checkBodyFormDataError, "from fetch without correct type to formData (error case)"); 247 248 promise_test(function(test) { 249 var response = new Response(new Blob([ 250 "--boundary\r\n", 251 "Content-Disposition: form-data; name=string\r\n", 252 "\r\nvalue", new Uint8Array([0xC2, 0xA0]), "1\r\n", 253 "--boundary\r\n", 254 "Content-Disposition: form-data; name=string-with-default-charset\r\n", 255 "Content-Type: text/plain; charset=utf-8\r\n", 256 "\r\nvalue", new Uint8Array([0xC2, 0xA0]), "2\r\n", 257 "--boundary\r\n", 258 "Content-Disposition: form-data; name=string-with-non-default-charset\r\n", 259 "Content-Type: text/plain; charset=iso-8859-1\r\n", 260 "\r\nvalue", new Uint8Array([0xC2, 0xA0]), "3\r\n", 261 "--boundary\r\n", 262 "Content-Disposition: form-data; name=string-with-non-default-type\r\n", 263 "Content-Type: application/octet-stream\r\n", 264 "\r\nvalue", new Uint8Array([0xC2, 0xA0]), "4\r\n", 265 "--boundary\r\n", 266 "Content-Disposition: form-data; name=file; filename=file1\r\n", 267 "Content-Type: application/octet-stream; x-param=x-value\r\n", 268 "\r\n", new Uint8Array([5, 0x0, 0xFF]), "\r\n", 269 "--boundary\r\n", 270 "Content-Disposition: form-data; name=\"file-without-type\"; filename=\"file2\"\r\n", 271 "\r\n", new Uint8Array([6, 0x0, 0x7F, 0xFF]), "\r\n", 272 "--boundary--\r\n" 273 ]), { "headers": [["Content-Type", 'multipart/form-data; boundary="boundary"']] }); 274 return response.formData().then(function(bodyAsFormData) { 275 // Non-file parts must always be decoded using utf-8 encoding. 276 assert_equals(bodyAsFormData.get("string"), "value\u00A01", "Retrieve and verify response's 1st entry value"); 277 assert_equals(bodyAsFormData.get("string-with-default-charset"), "value\u00A02", "Retrieve and verify response's 2nd entry value"); 278 assert_equals(bodyAsFormData.get("string-with-non-default-charset"), "value\u00A03", "Retrieve and verify response's 3rd entry value"); 279 assert_equals(bodyAsFormData.get("string-with-non-default-type"), "value\u00A04", "Retrieve and verify response's 4th entry value"); 280 // The name of a File must be taken from the filename parameter in 281 // the Content-Disposition header field. 282 assert_equals(bodyAsFormData.get("file").name, "file1", "Retrieve and verify response's 5th entry name property"); 283 assert_equals(bodyAsFormData.get("file-without-type").name, "file2", "Retrieve and verify response's 6th entry name property"); 284 // The type of a File must be taken from the Content-Type header field 285 // which defaults to "text/plain". 286 assert_equals(bodyAsFormData.get("file").type, "application/octet-stream; x-param=x-value", "Retrieve and verify response's 5th entry type property"); 287 assert_equals(bodyAsFormData.get("file-without-type").type, "text/plain", "Retrieve and verify response's 6th entry type property"); 288 289 return Promise.resolve().then(function() { 290 return blobToFormDataResponse("file", bodyAsFormData.get("file")).text().then(function(bodyAsText) { 291 // Verify that filename, name and type are preserved. 292 assert_regexp_match(bodyAsText, /\r\nContent-Disposition: *form-data;([^\r\n]*;)* *filename=("?)file1\2[;\r]/i, "Retrieve and verify response's 5th entry filename parameter"); 293 assert_regexp_match(bodyAsText, /\r\nContent-Disposition: *form-data;([^\r\n]*;)* *name=("?)file\2[;\r]/i, "Retrieve and verify response's 5th entry name parameter"); 294 assert_regexp_match(bodyAsText, /\r\nContent-Type: *application\/octet-stream; x-param=x-value\r\n/i, "Retrieve and verify response's 5th entry type field"); 295 // Verify that the content is preserved. 296 return readBlobAsArrayBuffer(bodyAsFormData.get("file")).then(function(arrayBuffer) { 297 assert_array_equals(new Uint8Array(arrayBuffer), new Uint8Array([5, 0x0, 0xFF]), "Retrieve and verify response's 5th entry content"); 298 }); 299 }); 300 }).then(function() { 301 return blobToFormDataResponse("file-without-type", bodyAsFormData.get("file-without-type")).text().then(function(bodyAsText) { 302 // Verify that filename, name and type are preserved. 303 assert_regexp_match(bodyAsText, /\r\nContent-Disposition: *form-data;([^\r\n]*;)* *filename=("?)file2\2[;\r]/i, "Retrieve and verify response's 6th entry filename parameter"); 304 assert_regexp_match(bodyAsText, /\r\nContent-Disposition: *form-data;([^\r\n]*;)* *name=("?)file-without-type\2[;\r]/i, "Retrieve and verify response's 6th entry name parameter"); 305 assert_regexp_match(bodyAsText, /\r\nContent-Type: *text\/plain\r\n/i, "Retrieve and verify response's 6th entry type field"); 306 // Verify that the content is preserved. 307 return readBlobAsArrayBuffer(bodyAsFormData.get("file-without-type")).then(function(arrayBuffer) { 308 assert_array_equals(new Uint8Array(arrayBuffer), new Uint8Array([6, 0x0, 0x7F, 0xFF]), "Retrieve and verify response's 6th entry content"); 309 }); 310 }); 311 }); 312 }); 313 }, "Consume response's body: from multipart form data blob to formData"); 314 315 </script> 316 </body> 317 </html>