Bug 1945855 - Show error page for 4xx/5xx responses with no content, whether the Content-Length header is missing or lying r=dveditz,necko-reviewers,valentin

Differential Revision: https://phabricator.services.mozilla.com/D240385
This commit is contained in:
Sean
2025-03-21 13:09:40 +00:00
parent 702cd76018
commit e654940ab2
4 changed files with 121 additions and 3 deletions

View File

@@ -25,6 +25,7 @@ function handleRequest(request, response) {
const status = parseInt(params.status);
const message = params.message;
const header = params.header;
// Set default if missing parameters
if (!status || !message) {
@@ -33,6 +34,15 @@ function handleRequest(request, response) {
return;
}
if (header === "hide") {
response.setStatusLine(request.httpVersion, status, message);
return;
}
if (header === "lie") {
response.setStatusLine(request.httpVersion, status, message);
response.setHeader("Content-Length", "100", false);
return;
}
response.setStatusLine(request.httpVersion, status, message);
response.setHeader("Content-Length", "0", false);
}

View File

@@ -17,7 +17,8 @@ async function test_blankPage(
page,
expectedL10nID,
responseStatus,
responseStatusText
responseStatusText,
header = "show" // show (zero content-length), hide (no content-length), or lie (non-empty content-length)
) {
await SpecialPowers.pushPrefEnv({
set: [["browser.http.blank_page_with_error_response.enabled", false]],
@@ -27,7 +28,7 @@ async function test_blankPage(
let pageLoaded;
const uri = `${page}?status=${encodeURIComponent(
responseStatus
)}&message=${encodeURIComponent(responseStatusText)}`;
)}&message=${encodeURIComponent(responseStatusText)}&header=${encodeURIComponent(header)}`;
// Simulating loading the page
await BrowserTestUtils.openNewForegroundTab(
@@ -89,6 +90,46 @@ add_task(async function test_blankPage_5xx() {
);
});
add_task(async function test_blankPage_withoutHeader_4xx() {
await test_blankPage(
BLANK_PAGE,
"httpErrorPage-title",
400,
"Bad Request",
"hide"
);
});
add_task(async function test_blankPage_withoutHeader_5xx() {
await test_blankPage(
BLANK_PAGE,
"serverError-title",
503,
"Service Unavailable",
"hide"
);
});
add_task(async function test_blankPage_lyingHeader_4xx() {
await test_blankPage(
BLANK_PAGE,
"httpErrorPage-title",
400,
"Bad Request",
"lie"
);
});
add_task(async function test_blankPage_lyingHeader_5xx() {
await test_blankPage(
BLANK_PAGE,
"serverError-title",
503,
"Service Unavailable",
"lie"
);
});
add_task(async function test_emptyPage_viewSource() {
await SpecialPowers.pushPrefEnv({
set: [["browser.http.blank_page_with_error_response.enabled", false]],
@@ -96,7 +137,7 @@ add_task(async function test_emptyPage_viewSource() {
let tab = await BrowserTestUtils.openNewForegroundTab(
gBrowser,
`view-source:${BLANK_PAGE}?status=503&message=Service%20Unavailable`,
`view-source:${BLANK_PAGE}?status=503&message=Service%20Unavailable&header=show`,
true // wait for the load to complete
);
let browser = tab.linkedBrowser;

View File

@@ -218,6 +218,7 @@ nsDocumentOpenInfo::OnDataAvailable(nsIRequest* request, nsIInputStream* inStr,
// if we have retarged to the end stream listener, then forward the call....
// otherwise, don't do anything
mReceivedData = true;
nsresult rv = NS_OK;
if (m_targetStreamListener)
@@ -240,10 +241,64 @@ nsDocumentOpenInfo::OnDataFinished(nsresult aStatus) {
return NS_OK;
}
/* static */
nsresult nsDocumentOpenInfo::CheckContentLengthDiscrepancy(
nsIRequest* request) {
// Blank page with content length discrepancy is allowed
if (mReceivedData ||
mozilla::StaticPrefs::
browser_http_blank_page_with_error_response_enabled()) {
return NS_OK;
}
nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(request));
if (!httpChannel) {
return NS_OK;
}
uint64_t decodedBodySize;
if (NS_FAILED(httpChannel->GetDecodedBodySize(&decodedBodySize)) ||
decodedBodySize != 0) {
return NS_OK;
}
nsCOMPtr<nsIURI> uri;
if (NS_SUCCEEDED(httpChannel->GetURI(getter_AddRefs(uri))) &&
uri->SchemeIs("view-source")) {
return NS_OK;
}
uint32_t responseCode = 0;
if (NS_SUCCEEDED(httpChannel->GetResponseStatus(&responseCode))) {
if (responseCode >= 500) {
LOG(
(" Returning NS_ERROR_NET_ERROR_RESPONSE from "
"nsDocumentOpenInfo::CheckContentLengthDiscrepancy due to 5xx "
"responses with no content"));
return NS_ERROR_NET_ERROR_RESPONSE;
}
if (responseCode >= 400) {
LOG(
(" Returning NS_ERROR_NET_EMPTY_RESPONSE from "
"nsDocumentOpenInfo::CheckContentLengthDiscrepancy due to 4xx "
"responses with no content"));
return NS_ERROR_NET_EMPTY_RESPONSE;
}
}
return NS_OK;
}
NS_IMETHODIMP nsDocumentOpenInfo::OnStopRequest(nsIRequest* request,
nsresult aStatus) {
LOG(("[0x%p] nsDocumentOpenInfo::OnStopRequest", this));
// Bug 1945855 - Show error page for 4xx/5xx responses with no content,
// whether the Content-Length header is missing or lying
nsresult rv = CheckContentLengthDiscrepancy(request);
if (NS_FAILED(rv)) {
aStatus = rv;
}
if (m_targetStreamListener) {
nsCOMPtr<nsIStreamListener> listener(m_targetStreamListener);

View File

@@ -80,6 +80,11 @@ class nsDocumentOpenInfo : public nsIThreadRetargetableStreamListener {
*/
nsresult Prepare();
// Check for a page with empty body + missing/any content-length.
// Return NS_ERROR_NET_ERROR_RESPONSE for 5xx and NS_ERROR_NET_EMPTY_RESPONSE
// for 4xx, respectively. Otherwise, return NS_OK.
nsresult CheckContentLengthDiscrepancy(nsIRequest* request);
// Call this (from OnStartRequest) to attempt to find an nsIStreamListener to
// take the data off our hands.
nsresult DispatchContent(nsIRequest* request);
@@ -213,6 +218,13 @@ class nsDocumentOpenInfo : public nsIThreadRetargetableStreamListener {
* docshell
*/
bool mAllowListenerConversions = true;
/**
* Tracks whether any data was received in OnDataAvailable.
* Used to detect cases where the response has no Content-Length
* header and an empty body.
*/
bool mReceivedData = false;
};
#endif /* nsURILoader_h__ */