tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit 9f0b1c96f7918d1ead5cb1089ebfaf9bb6f63234
parent 3b6c1fee6bfd6b8732b99143a716c415405b1ce9
Author: Valentin Gosu <valentin.gosu@gmail.com>
Date:   Fri, 12 Dec 2025 14:06:16 +0000

Bug 1999247 - Make NEL implementation use ReportDeliver::Fetch to enqueue report r=necko-reviewers,jesup

Differential Revision: https://phabricator.services.mozilla.com/D271979

Diffstat:
Mnetwerk/protocol/http/NetworkErrorLogging.sys.mjs | 46++++++++++++++++++----------------------------
Mnetwerk/protocol/http/nsHttpChannel.cpp | 100++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------
Mnetwerk/protocol/http/nsHttpChannel.h | 2++
Mnetwerk/protocol/http/nsINetworkErrorLogging.idl | 10+++++++++-
Mtesting/web-platform/meta/network-error-logging/__dir__.ini | 6+++++-
Dtesting/web-platform/meta/network-error-logging/no-report-on-failed-cors-preflight.https.html.ini | 7-------
Mtesting/web-platform/meta/network-error-logging/no-report-on-unexpired-cached-response.https.html.ini | 2+-
7 files changed, 114 insertions(+), 59 deletions(-)

diff --git a/netwerk/protocol/http/NetworkErrorLogging.sys.mjs b/netwerk/protocol/http/NetworkErrorLogging.sys.mjs @@ -270,7 +270,7 @@ export class NetworkErrorLogging { !Services.scriptSecurityManager.getChannelResultPrincipal(aChannel) .isOriginPotentiallyTrustworthy ) { - return; + return null; } // 2. Let origin be request's origin. let origin = @@ -283,7 +283,7 @@ export class NetworkErrorLogging { origin: policyOrigin, } = this.choosePolicyForRequest(aChannel); if (!policy) { - return; + return null; } // 4. Determine the active sampling rate for this request: @@ -302,7 +302,7 @@ export class NetworkErrorLogging { // 5. Decide whether or not to report on this request. Let roll be a random number between 0.0 and 1.0, inclusive. If roll ≥ sampling rate, return null. if (Math.random() >= samplingRate) { - return; + return null; } // 6. Let report body be a new ECMAScript object with the following properties: @@ -342,7 +342,7 @@ export class NetworkErrorLogging { policy.subdomains && report_body.phase != "dns" ) { - return; + return null; } // 10. If report body's phase property is not dns, and report body's server_ip property is non-empty and not equal to policy's received IP address: @@ -378,35 +378,25 @@ export class NetworkErrorLogging { if (report_body.phase == "dns" || report_body.phase == "connection") { uriMutator.setPathQueryRef(""); } + if (aChannel.URI.hasUserPass) { + uriMutator.setUserPass(""); + } + let url = uriMutator.finalize().spec; // 4. Generate a network report given these parameters: - let report = { - type: "network-error", - url: uriMutator.finalize().spec, - user_agent: Cc["@mozilla.org/network/protocol;1?name=http"].getService( - Ci.nsIHttpProtocolHandler - ).userAgent, - body: report_body, + // nsINetworkErrorReport + let retObj = { + body: JSON.stringify(report_body), + group: policy.reportTo.group, + url, }; - // XXX: this would benefit from using the actual reporting API, - // but it's not clear how easy it is to: - // - use it in the parent process - // - have it use the Report-To header - // https://w3c.github.io/reporting/#queue-report + if (policy && policy.reportTo.group === policy.nel.report_to) { - // TODO: defer to later. - fetch(policy.reportTo.endpoints[0].url, { - method: "POST", - mode: "cors", - credentials: "omit", - headers: { - "Content-Type": "application/reports+json", - }, - body: JSON.stringify([report]), - triggeringPrincipal: - Services.scriptSecurityManager.getChannelResultPrincipal(aChannel), - }); + // nsHttpChannel will call ReportDeliver::Fetch + return retObj; } + + return null; } QueryInterface = ChromeUtils.generateQI(["nsINetworkErrorLogging"]); diff --git a/netwerk/protocol/http/nsHttpChannel.cpp b/netwerk/protocol/http/nsHttpChannel.cpp @@ -155,6 +155,9 @@ # include "mozilla/StaticPrefs_fuzzing.h" #endif +#include "mozilla/dom/ReportDeliver.h" +#include "mozilla/dom/ReportingHeader.h" + namespace mozilla { using namespace dom; @@ -4546,6 +4549,76 @@ bool nsHttpChannel::ShouldBypassProcessNotModified() { return false; } +void nsHttpChannel::MaybeGenerateNELReport() { + if (!StaticPrefs::network_http_network_error_logging_enabled()) { + return; + } + + nsCOMPtr<nsINetworkErrorReport> report; + if (nsCOMPtr<nsINetworkErrorLogging> nel = + components::NetworkErrorLogging::Service()) { + nel->GenerateNELReport(this, getter_AddRefs(report)); + } + + mReportedNEL = true; + + // https://www.w3.org/TR/2023/WD-network-error-logging-20231005/#deliver-a-network-report + // 4. Generate a network report given these parameters: + // type: network-error + // url + // user_agent + // body + + if (!report) { + return; + } + + nsCOMPtr<nsIScriptSecurityManager> ssm = nsContentUtils::GetSecurityManager(); + if (!ssm) { + return; + } + + nsCOMPtr<nsIPrincipal> channelPrincipal; + ssm->GetChannelResultPrincipal(this, getter_AddRefs(channelPrincipal)); + if (!channelPrincipal) { + return; + } + + nsAutoCString body; + nsAutoString group; + nsAutoString url; + + report->GetBody(body); + report->GetGroup(group); + report->GetUrl(url); + + nsAutoCString endpointURL; + ReportingHeader::GetEndpointForReportIncludeSubdomains( + group, channelPrincipal, /* includeSubdomains */ true, endpointURL); + if (endpointURL.IsEmpty()) { + return; + } + + ReportDeliver::ReportData data; + data.mType = u"network-error"_ns; + data.mGroupName = group; + data.mURL = url; + data.mFailures = 0; + data.mCreationTime = TimeStamp::Now(); + + data.mPrincipal = channelPrincipal; + data.mEndpointURL = endpointURL; + data.mReportBodyJSON = body; + nsAutoCString userAgent; + // XXX(valentin): Should this be the potentially user set value of the header + // or the current value of user_agent from http handler? + (void)mRequestHead.GetHeader(nsHttp::User_Agent, userAgent); + data.mUserAgent = NS_ConvertUTF8toUTF16(userAgent); + + // Enqueue the report to be delivered by the reporting API + ReportDeliver::Fetch(data); +} + nsresult nsHttpChannel::ProcessNotModified( const std::function<nsresult(nsHttpChannel*, nsresult)>& aContinueProcessResponseFunc) { @@ -4598,13 +4671,8 @@ nsresult nsHttpChannel::ProcessNotModified( rv = mCacheEntry->SetMetaDataElement("response-head", head.get()); if (NS_FAILED(rv)) return rv; - if (StaticPrefs::network_http_network_error_logging_enabled() && - LoadUsedNetwork() && !mReportedNEL) { - if (nsCOMPtr<nsINetworkErrorLogging> nel = - components::NetworkErrorLogging::Service()) { - nel->GenerateNELReport(this); - } - mReportedNEL = true; + if (LoadUsedNetwork() && !mReportedNEL) { + MaybeGenerateNELReport(); } // make the cached response be the current response @@ -7113,13 +7181,8 @@ nsresult nsHttpChannel::CancelInternal(nsresult status) { return NS_OK; } - if (StaticPrefs::network_http_network_error_logging_enabled() && - LoadUsedNetwork() && !mReportedNEL) { - if (nsCOMPtr<nsINetworkErrorLogging> nel = - components::NetworkErrorLogging::Service()) { - nel->GenerateNELReport(this); - } - mReportedNEL = true; + if (LoadUsedNetwork() && !mReportedNEL) { + MaybeGenerateNELReport(); } // We don't want the content process to see any header values @@ -10310,13 +10373,8 @@ nsresult nsHttpChannel::ContinueOnStopRequest(nsresult aStatus, bool aIsFromNet, mAuthRetryPending = false; } - if (StaticPrefs::network_http_network_error_logging_enabled() && - LoadUsedNetwork() && !mReportedNEL) { - if (nsCOMPtr<nsINetworkErrorLogging> nel = - components::NetworkErrorLogging::Service()) { - nel->GenerateNELReport(this); - } - mReportedNEL = true; + if (LoadUsedNetwork() && !mReportedNEL) { + MaybeGenerateNELReport(); } // notify "http-on-before-stop-request" observers diff --git a/netwerk/protocol/http/nsHttpChannel.h b/netwerk/protocol/http/nsHttpChannel.h @@ -845,6 +845,8 @@ class nsHttpChannel final : public HttpBaseChannel, void RecordOnStartTelemetry(nsresult aStatus, bool aIsNavigation); + void MaybeGenerateNELReport(); + // Timer used to delay the network request, or to trigger the network // request if retrieving the cache entry takes too long. nsCOMPtr<nsITimer> mNetworkTriggerTimer; diff --git a/netwerk/protocol/http/nsINetworkErrorLogging.idl b/netwerk/protocol/http/nsINetworkErrorLogging.idl @@ -7,6 +7,14 @@ #include "nsISupports.idl" interface nsIHttpChannel; +[scriptable, uuid(53b81d9c-3b62-4ecb-b8d2-5f5a17914c9e)] +interface nsINetworkErrorReport : nsISupports +{ + readonly attribute AUTF8String body; + readonly attribute AString group; + readonly attribute AString url; +}; + [scriptable, uuid(391ba410-0a68-42f7-b3e4-3ec26db645c0)] interface nsINetworkErrorLogging : nsISupports { @@ -18,5 +26,5 @@ interface nsINetworkErrorLogging : nsISupports /** * Maybe send error log payload if theres a policy. */ - void generateNELReport(in nsIHttpChannel aChannel); + nsINetworkErrorReport generateNELReport(in nsIHttpChannel aChannel); }; diff --git a/testing/web-platform/meta/network-error-logging/__dir__.ini b/testing/web-platform/meta/network-error-logging/__dir__.ini @@ -1,2 +1,6 @@ implementation-status: backlog -prefs: [dom.reporting.enabled:true, dom.reporting.featurePolicy.enabled:true, network.http.network_error_logging.enabled: true] +prefs: [dom.reporting.enabled:true, dom.reporting.featurePolicy.enabled:true, +dom.reporting.header.enabled:true, +dom.reporting.delivering.timeout:1, +network.http.rcwn.enabled:false, +network.http.network_error_logging.enabled: true] diff --git a/testing/web-platform/meta/network-error-logging/no-report-on-failed-cors-preflight.https.html.ini b/testing/web-platform/meta/network-error-logging/no-report-on-failed-cors-preflight.https.html.ini @@ -1,7 +0,0 @@ -[no-report-on-failed-cors-preflight.https.html] - expected: - if os == "win": [OK, TIMEOUT] - [\n Test that NEL reports are not sent if the CORS preflight fails\n ] - expected: - if os == "win": [FAIL, PASS, TIMEOUT] - FAIL diff --git a/testing/web-platform/meta/network-error-logging/no-report-on-unexpired-cached-response.https.html.ini b/testing/web-platform/meta/network-error-logging/no-report-on-unexpired-cached-response.https.html.ini @@ -1,3 +1,3 @@ [no-report-on-unexpired-cached-response.https.html] [\n Test that NEL reports are not sent for cached responses that don't hit the\n network\n ] - expected: FAIL + expected: [PASS, FAIL]