diff --git a/netwerk/dns/TRR.cpp b/netwerk/dns/TRR.cpp index 54ddef6ec4fa..c5e027caa512 100644 --- a/netwerk/dns/TRR.cpp +++ b/netwerk/dns/TRR.cpp @@ -7,7 +7,6 @@ #include "DNS.h" #include "nsCharSeparatedTokenizer.h" #include "nsContentUtils.h" -#include "nsHostResolver.h" #include "nsHttpHandler.h" #include "nsIHttpChannel.h" #include "nsIHttpChannelInternal.h" @@ -24,6 +23,7 @@ #include "nsURLHelper.h" #include "TRR.h" #include "TRRService.h" +#include "TRRLoadInfo.h" #include "mozilla/Base64.h" #include "mozilla/DebugOnly.h" @@ -219,11 +219,13 @@ nsresult TRR::CreateChannelHelper(nsIURI* aUri, nsIChannel** aResult) { return NS_ERROR_UNEXPECTED; } + RefPtr loadInfo = + new TRRLoadInfo(aUri, nsIContentPolicy::TYPE_OTHER); return gHttpHandler->CreateTRRServiceChannel(aUri, - nullptr, // givenProxyInfo - 0, // proxyResolveFlags - nullptr, // proxyURI - nullptr, // aLoadInfo + nullptr, // givenProxyInfo + 0, // proxyResolveFlags + nullptr, // proxyURI + loadInfo, // aLoadInfo aResult); } @@ -299,7 +301,7 @@ nsresult TRR::SendHTTPRequest() { if (query.IsEmpty()) { query.Assign(NS_LITERAL_CSTRING("?dns=")); - } else { + } else { query.Append(NS_LITERAL_CSTRING("&dns=")); } query.Append(body); @@ -323,21 +325,22 @@ nsresult TRR::SendHTTPRequest() { return rv; } - rv = CreateChannelHelper(dnsURI, getter_AddRefs(mChannel)); - if (NS_FAILED(rv)) { + nsCOMPtr channel; + rv = CreateChannelHelper(dnsURI, getter_AddRefs(channel)); + if (NS_FAILED(rv) || !channel) { LOG(("TRR:SendHTTPRequest: NewChannel failed!\n")); return rv; } - mChannel->SetLoadFlags( + channel->SetLoadFlags( nsIRequest::LOAD_ANONYMOUS | nsIRequest::INHIBIT_CACHING | nsIRequest::LOAD_BYPASS_CACHE | nsIChannel::LOAD_BYPASS_URL_CLASSIFIER); NS_ENSURE_SUCCESS(rv, rv); - rv = mChannel->SetNotificationCallbacks(this); + rv = channel->SetNotificationCallbacks(this); NS_ENSURE_SUCCESS(rv, rv); - nsCOMPtr httpChannel = do_QueryInterface(mChannel); + nsCOMPtr httpChannel = do_QueryInterface(channel); if (!httpChannel) { return NS_ERROR_UNEXPECTED; } @@ -361,8 +364,7 @@ nsresult TRR::SendHTTPRequest() { NS_ENSURE_SUCCESS(rv, rv); } - nsCOMPtr internalChannel = - do_QueryInterface(mChannel); + nsCOMPtr internalChannel = do_QueryInterface(channel); if (!internalChannel) { return NS_ERROR_UNEXPECTED; } @@ -380,9 +382,6 @@ nsresult TRR::SendHTTPRequest() { rv = httpChannel->SetRequestMethod(NS_LITERAL_CSTRING("GET")); NS_ENSURE_SUCCESS(rv, rv); } else { - rv = httpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Cache-Control"), - NS_LITERAL_CSTRING("no-store"), false); - NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr uploadChannel = do_QueryInterface(httpChannel); if (!uploadChannel) { return NS_ERROR_UNEXPECTED; @@ -399,6 +398,37 @@ nsresult TRR::SendHTTPRequest() { NS_ENSURE_SUCCESS(rv, rv); } + rv = SetupTRRServiceChannelInternal(httpChannel, useGet); + if (NS_FAILED(rv)) { + return rv; + } + + rv = httpChannel->AsyncOpen(this); + if (NS_FAILED(rv)) { + return rv; + } + + NS_NewTimerWithCallback(getter_AddRefs(mTimeout), this, + gTRRService->GetRequestTimeout(), + nsITimer::TYPE_ONE_SHOT); + + mChannel = channel; + return NS_OK; +} + +// static +nsresult TRR::SetupTRRServiceChannelInternal(nsIHttpChannel* aChannel, + bool aUseGet) { + nsCOMPtr httpChannel = aChannel; + MOZ_ASSERT(httpChannel); + + nsresult rv = NS_OK; + if (!aUseGet) { + rv = httpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Cache-Control"), + NS_LITERAL_CSTRING("no-store"), false); + NS_ENSURE_SUCCESS(rv, rv); + } + // Sanitize the request by removing the Accept-Language header so we minimize // the amount of fingerprintable information we send to the server. if (!StaticPrefs::network_trr_send_accept_language_headers()) { @@ -423,20 +453,15 @@ nsresult TRR::SendHTTPRequest() { // set the *default* response content type if (NS_FAILED(httpChannel->SetContentType( NS_LITERAL_CSTRING("application/dns-message")))) { - LOG(("TRR::SendHTTPRequest: couldn't set content-type!\n")); + LOG(("TRR::SetupTRRServiceChannelInternal: couldn't set content-type!\n")); } nsCOMPtr timedChan(do_QueryInterface(httpChannel)); - timedChan->SetTimingEnabled(true); - - if (NS_SUCCEEDED(httpChannel->AsyncOpen(this))) { - NS_NewTimerWithCallback(getter_AddRefs(mTimeout), this, - gTRRService->GetRequestTimeout(), - nsITimer::TYPE_ONE_SHOT); - return NS_OK; + if (timedChan) { + timedChan->SetTimingEnabled(true); } - mChannel = nullptr; - return NS_ERROR_UNEXPECTED; + + return NS_OK; } NS_IMETHODIMP diff --git a/netwerk/dns/TRR.h b/netwerk/dns/TRR.h index 023b6a79576b..0537dc2fdc28 100644 --- a/netwerk/dns/TRR.h +++ b/netwerk/dns/TRR.h @@ -12,6 +12,7 @@ #include "nsIHttpPushListener.h" #include "nsIInterfaceRequestor.h" #include "nsIStreamListener.h" +#include "nsHostResolver.h" #include "nsXULAppAPI.h" namespace mozilla { @@ -33,6 +34,7 @@ class DOHaddr : public LinkedListElement { }; class TRRService; +class TRRServiceChannel; extern TRRService* gTRRService; class DOHresp { @@ -167,6 +169,10 @@ class TRR : public Runnable, nsresult CreateChannelHelper(nsIURI* aUri, nsIChannel** aResult); + friend class TRRServiceChannel; + static nsresult SetupTRRServiceChannelInternal(nsIHttpChannel* aChannel, + bool aUseGet); + nsCOMPtr mChannel; enum TrrType mType; TimeStamp mStartTime; diff --git a/netwerk/protocol/http/TRRServiceChannel.cpp b/netwerk/protocol/http/TRRServiceChannel.cpp index 36b6573a0e60..cd3322450eef 100644 --- a/netwerk/protocol/http/TRRServiceChannel.cpp +++ b/netwerk/protocol/http/TRRServiceChannel.cpp @@ -18,7 +18,9 @@ #include "nsIOService.h" #include "nsISeekableStream.h" #include "nsURLHelper.h" +#include "TRRLoadInfo.h" #include "ReferrerInfo.h" +#include "TRR.h" namespace mozilla { namespace net { @@ -675,9 +677,11 @@ nsresult TRRServiceChannel::OnPush(uint32_t aPushedStreamId, return NS_ERROR_FAILURE; } + nsCOMPtr loadInfo = + static_cast(mLoadInfo.get())->Clone(); nsCOMPtr pushHttpChannel; rv = gHttpHandler->CreateTRRServiceChannel(pushResource, nullptr, 0, nullptr, - nullptr, + loadInfo, getter_AddRefs(pushHttpChannel)); NS_ENSURE_SUCCESS(rv, rv); @@ -945,6 +949,18 @@ TRRServiceChannel::OnStartRequest(nsIRequest* request) { if ((httpStatus < 500) && (httpStatus != 421) && (httpStatus != 407)) { ProcessAltService(); } + + if (httpStatus == 300 || httpStatus == 301 || httpStatus == 302 || + httpStatus == 303 || httpStatus == 307 || httpStatus == 308) { + nsresult rv = SyncProcessRedirection(httpStatus); + if (NS_SUCCEEDED(rv)) { + return rv; + } + + mStatus = rv; + DoNotifyListener(); + return rv; + } } else { NS_WARNING("No response head in OnStartRequest"); } @@ -959,6 +975,124 @@ TRRServiceChannel::OnStartRequest(nsIRequest* request) { return CallOnStartRequest(); } +nsresult TRRServiceChannel::SyncProcessRedirection(uint32_t aHttpStatus) { + nsAutoCString location; + + // if a location header was not given, then we can't perform the redirect, + // so just carry on as though this were a normal response. + if (NS_FAILED(mResponseHead->GetHeader(nsHttp::Location, location))) { + return NS_ERROR_FAILURE; + } + + // make sure non-ASCII characters in the location header are escaped. + nsAutoCString locationBuf; + if (NS_EscapeURL(location.get(), -1, esc_OnlyNonASCII | esc_Spaces, + locationBuf)) { + location = locationBuf; + } + + LOG(("redirecting to: %s [redirection-limit=%u]\n", location.get(), + uint32_t(mRedirectionLimit))); + + nsCOMPtr redirectURI; + nsresult rv = NS_NewURI(getter_AddRefs(redirectURI), location); + + if (NS_FAILED(rv)) { + LOG(("Invalid URI for redirect: Location: %s\n", location.get())); + return NS_ERROR_CORRUPTED_CONTENT; + } + + // move the reference of the old location to the new one if the new + // one has none. + PropagateReferenceIfNeeded(mURI, redirectURI); + + bool rewriteToGET = + ShouldRewriteRedirectToGET(aHttpStatus, mRequestHead.ParsedMethod()); + + // Let's not rewrite the method to GET for TRR requests. + if (rewriteToGET) { + return NS_ERROR_FAILURE; + } + + // If the method is not safe (such as POST, PUT, DELETE, ...) + if (!mRequestHead.IsSafeMethod()) { + LOG(("TRRServiceChannel: unsafe redirect to:%s\n", location.get())); + } + + uint32_t redirectFlags; + if (nsHttp::IsPermanentRedirect(aHttpStatus)) { + redirectFlags = nsIChannelEventSink::REDIRECT_PERMANENT; + } else { + redirectFlags = nsIChannelEventSink::REDIRECT_TEMPORARY; + } + + nsCOMPtr newChannel; + nsCOMPtr redirectLoadInfo = + static_cast(mLoadInfo.get())->Clone(); + rv = gHttpHandler->CreateTRRServiceChannel(redirectURI, nullptr, 0, nullptr, + redirectLoadInfo, + getter_AddRefs(newChannel)); + if (NS_FAILED(rv)) { + return rv; + } + + rv = SetupReplacementChannel(redirectURI, newChannel, !rewriteToGET, + redirectFlags); + if (NS_FAILED(rv)) { + return rv; + } + + // Make sure to do this after we received redirect veto answer, + // i.e. after all sinks had been notified + newChannel->SetOriginalURI(mOriginalURI); + + rv = newChannel->AsyncOpen(mListener); + LOG((" new channel AsyncOpen returned %" PRIX32, static_cast(rv))); + + // close down this channel + Cancel(NS_BINDING_REDIRECTED); + + ReleaseListeners(); + + return NS_OK; +} + +nsresult TRRServiceChannel::SetupReplacementChannel(nsIURI* aNewURI, + nsIChannel* aNewChannel, + bool aPreserveMethod, + uint32_t aRedirectFlags) { + LOG( + ("TRRServiceChannel::SetupReplacementChannel " + "[this=%p newChannel=%p preserveMethod=%d]", + this, aNewChannel, aPreserveMethod)); + + nsresult rv = HttpBaseChannel::SetupReplacementChannel( + aNewURI, aNewChannel, aPreserveMethod, aRedirectFlags); + if (NS_FAILED(rv)) { + return rv; + } + + rv = CheckRedirectLimit(aRedirectFlags); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr httpChannel = do_QueryInterface(aNewChannel); + if (!httpChannel) { + MOZ_ASSERT(false); + return NS_ERROR_FAILURE; + } + + // convey the mApplyConversion flag (bug 91862) + nsCOMPtr encodedChannel = do_QueryInterface(httpChannel); + if (encodedChannel) { + encodedChannel->SetApplyConversion(mApplyConversion); + } + + // Apply TRR specific settings. + return TRR::SetupTRRServiceChannelInternal( + httpChannel, + mRequestHead.ParsedMethod() == nsHttpRequestHead::kMethod_Get); +} + NS_IMETHODIMP TRRServiceChannel::OnDataAvailable(nsIRequest* request, nsIInputStream* input, uint64_t offset, uint32_t count) { @@ -1306,5 +1440,16 @@ TRRServiceChannel::GetResponseEnd(TimeStamp* _retval) { return NS_OK; } +NS_IMETHODIMP TRRServiceChannel::SetLoadGroup(nsILoadGroup* aLoadGroup) { + return NS_OK; +} + +NS_IMETHODIMP +TRRServiceChannel::TimingAllowCheck(nsIPrincipal* aOrigin, bool* aResult) { + NS_ENSURE_ARG_POINTER(aResult); + *aResult = true; + return NS_OK; +} + } // namespace net } // namespace mozilla diff --git a/netwerk/protocol/http/TRRServiceChannel.h b/netwerk/protocol/http/TRRServiceChannel.h index 9999bc93af26..98e2142e9af8 100644 --- a/netwerk/protocol/http/TRRServiceChannel.h +++ b/netwerk/protocol/http/TRRServiceChannel.h @@ -101,6 +101,8 @@ class TRRServiceChannel : public HttpBaseChannel, NS_IMETHOD GetRequestStart(mozilla::TimeStamp* aRequestStart) override; NS_IMETHOD GetResponseStart(mozilla::TimeStamp* aResponseStart) override; NS_IMETHOD GetResponseEnd(mozilla::TimeStamp* aResponseEnd) override; + NS_IMETHOD SetLoadGroup(nsILoadGroup* aLoadGroup) override; + NS_IMETHOD TimingAllowCheck(nsIPrincipal* aOrigin, bool* aResult) override; protected: TRRServiceChannel(); @@ -125,6 +127,10 @@ class TRRServiceChannel : public HttpBaseChannel, nsresult ResolveProxy(); void AfterApplyContentConversions(nsresult aResult, nsIStreamListener* aListener); + nsresult SyncProcessRedirection(uint32_t aHttpStatus); + virtual MOZ_MUST_USE nsresult SetupReplacementChannel( + nsIURI* aNewURI, nsIChannel* aNewChannel, bool aPreserveMethod, + uint32_t aRedirectFlags) override; // True only when we have computed the value of the top window origin. bool mTopWindowOriginComputed; diff --git a/netwerk/protocol/http/moz.build b/netwerk/protocol/http/moz.build index 8974871c4954..4c6a2cf5fbbe 100644 --- a/netwerk/protocol/http/moz.build +++ b/netwerk/protocol/http/moz.build @@ -161,6 +161,7 @@ LOCAL_INCLUDES += [ '/extensions/auth', '/netwerk/base', '/netwerk/cookie', + '/netwerk/dns', '/netwerk/ipc', '/netwerk/socket/neqo_glue', '/netwerk/url-classifier', diff --git a/netwerk/test/unit/test_trr.js b/netwerk/test/unit/test_trr.js index bfdda19cfc55..3e5d96296853 100644 --- a/netwerk/test/unit/test_trr.js +++ b/netwerk/test/unit/test_trr.js @@ -1686,6 +1686,32 @@ add_task(async function test_content_encoding_gzip() { await new DNSListener("bar.example.com", "2.2.2.2"); }); +add_task(async function test_redirect_get() { + dns.clearCache(true); + Services.prefs.setIntPref("network.trr.mode", 3); // TRR-only + Services.prefs.setCharPref( + "network.trr.uri", + `https://foo.example.com:${h2Port}/doh?redirect=4.4.4.4{&dns}` + ); + Services.prefs.clearUserPref("network.trr.allow-rfc1918"); + Services.prefs.setBoolPref("network.trr.useGET", true); + Services.prefs.setBoolPref("network.trr.disable-ECS", true); + await new DNSListener("ecs.example.com", "4.4.4.4"); +}); + +// test redirect +add_task(async function test_redirect_post() { + dns.clearCache(true); + Services.prefs.setIntPref("network.trr.mode", 3); + Services.prefs.setBoolPref("network.trr.useGET", false); + Services.prefs.setCharPref( + "network.trr.uri", + `https://foo.example.com:${h2Port}/doh?redirect=4.4.4.4` + ); + + await new DNSListener("bar.example.com", "4.4.4.4"); +}); + // confirmationNS set without confirmed NS yet // checks that we properly fall back to DNS is confirmation is not ready yet add_task(async function test_resolve_not_confirmed() { diff --git a/testing/xpcshell/moz-http2/moz-http2.js b/testing/xpcshell/moz-http2/moz-http2.js index afcfad5ce6ca..ff4ea1238cd8 100644 --- a/testing/xpcshell/moz-http2/moz-http2.js +++ b/testing/xpcshell/moz-http2/moz-http2.js @@ -580,6 +580,30 @@ function handleRequest(req, res) { responseIP = "5.5.5.5"; } + let redirect = u.query.redirect; + if (redirect) { + responseIP = redirect; + if (u.query.dns) { + res.setHeader( + "Location", + "https://localhost:" + + serverPort + + "/doh?responseIP=" + + responseIP + + "&dns=" + + u.query.dns + ); + } else { + res.setHeader( + "Location", + "https://localhost:" + serverPort + "/doh?responseIP=" + responseIP + ); + } + res.writeHead(307); + res.end(""); + return; + } + if (u.query.auth) { // There's a Set-Cookie: header in the response for "/dns" , which this // request subsequently would include if the http channel wasn't @@ -758,7 +782,10 @@ function handleRequest(req, res) { payload = Buffer.concat([payload, chunk]); }); req.on("end", function finishedData() { - emitResponse(res, payload); + // parload is empty when we send redirect response. + if (payload.length) { + emitResponse(res, payload); + } }); return; } else if (u.pathname === "/dns-cname-a") {