From 51554934bebfe8aa62b6a728ae0bbd39758f5532 Mon Sep 17 00:00:00 2001 From: theodab Date: Tue, 25 Apr 2023 15:56:37 -0700 Subject: [PATCH] fix(net): Fix HEAD requests in new Chromium (#5180) This makes it so that we no longer try to download the body of a head response, in the http fetch plugin. This is necessary due to an upcoming change to Chromium, where the body object of such responses is null. Fixes #5164 --- lib/net/http_fetch_plugin.js | 126 +++++++++++++++++++---------------- 1 file changed, 69 insertions(+), 57 deletions(-) diff --git a/lib/net/http_fetch_plugin.js b/lib/net/http_fetch_plugin.js index b73178be0c..249fdfdcab 100644 --- a/lib/net/http_fetch_plugin.js +++ b/lib/net/http_fetch_plugin.js @@ -120,65 +120,77 @@ shaka.net.HttpFetchPlugin = class { // not the body yet. headersReceived(shaka.net.HttpFetchPlugin.headersToGenericObject_( response.headers)); - // Getting the reader in this way allows us to observe the process of - // downloading the body, instead of just waiting for an opaque promise to - // resolve. - // We first clone the response because calling getReader locks the body - // stream; if we didn't clone it here, we would be unable to get the - // response's arrayBuffer later. - const reader = response.clone().body.getReader(); - - const contentLengthRaw = response.headers.get('Content-Length'); - const contentLength = - contentLengthRaw ? parseInt(contentLengthRaw, 10) : 0; - - const start = (controller) => { - const push = async () => { - let readObj; - try { - readObj = await reader.read(); - } catch (e) { - // If we abort the request, we'll get an error here. Just ignore it - // since real errors will be reported when we read the buffer below. - shaka.log.v1('error reading from stream', e.message); - return; - } - - if (!readObj.done) { - loaded += readObj.value.byteLength; - if (streamDataCallback) { - await streamDataCallback(readObj.value); + + // In new versions of Chromium, HEAD requests now have a response body + // that is null. + // So just don't try to download the body at all, if it's a HEAD request, + // to avoid null reference errors. + // See: https://crbug.com/1297060 + if (init.method != 'HEAD') { + goog.asserts.assert(response.body, + 'non-HEAD responses should have a body'); + + // Getting the reader in this way allows us to observe the process of + // downloading the body, instead of just waiting for an opaque promise + // to resolve. + // We first clone the response because calling getReader locks the body + // stream; if we didn't clone it here, we would be unable to get the + // response's arrayBuffer later. + const reader = response.clone().body.getReader(); + + const contentLengthRaw = response.headers.get('Content-Length'); + const contentLength = + contentLengthRaw ? parseInt(contentLengthRaw, 10) : 0; + + const start = (controller) => { + const push = async () => { + let readObj; + try { + readObj = await reader.read(); + } catch (e) { + // If we abort the request, we'll get an error here. Just ignore + // it since real errors will be reported when we read the buffer + // below. + shaka.log.v1('error reading from stream', e.message); + return; + } + + if (!readObj.done) { + loaded += readObj.value.byteLength; + if (streamDataCallback) { + await streamDataCallback(readObj.value); + } + } + + const currentTime = Date.now(); + // If the time between last time and this time we got progress event + // is long enough, or if a whole segment is downloaded, call + // progressUpdated(). + if (currentTime - lastTime > 100 || readObj.done) { + progressUpdated(currentTime - lastTime, loaded - lastLoaded, + contentLength - loaded); + lastLoaded = loaded; + lastTime = currentTime; + } + + if (readObj.done) { + goog.asserts.assert(!readObj.value, + 'readObj should be unset when "done" is true.'); + controller.close(); + } else { + controller.enqueue(readObj.value); + push(); } - } - - const currentTime = Date.now(); - // If the time between last time and this time we got progress event - // is long enough, or if a whole segment is downloaded, call - // progressUpdated(). - if (currentTime - lastTime > 100 || readObj.done) { - progressUpdated(currentTime - lastTime, loaded - lastLoaded, - contentLength - loaded); - lastLoaded = loaded; - lastTime = currentTime; - } - - if (readObj.done) { - goog.asserts.assert(!readObj.value, - 'readObj should be unset when "done" is true.'); - controller.close(); - } else { - controller.enqueue(readObj.value); - push(); - } + }; + push(); }; - push(); - }; - // Create a ReadableStream to use the reader. We don't need to use the - // actual stream for anything, though, as we are using the response's - // arrayBuffer method to get the body, so we don't store the - // ReadableStream. - new ReadableStream({start}); // eslint-disable-line no-new - arrayBuffer = await response.arrayBuffer(); + // Create a ReadableStream to use the reader. We don't need to use the + // actual stream for anything, though, as we are using the response's + // arrayBuffer method to get the body, so we don't store the + // ReadableStream. + new ReadableStream({start}); // eslint-disable-line no-new + arrayBuffer = await response.arrayBuffer(); + } } catch (error) { if (abortStatus.canceled) { throw new shaka.util.Error(