/* -*- Mode: C++; tab-width: 2; indent-tabs-mode:nil; c-basic-offset: 2 -*- */ /* vim:set ts=2 sts=2 sw=2 et cin: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// // Deal with "special" HTTP responses: // // - In the case of a 204 (No Content) or 205 (Reset Content) response, do // not try to find a content handler. Return NS_BINDING_ABORTED to cancel // the request. This has the effect of ensuring that the DocLoader does // not try to interpret this as a real request. //
nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(request, &rv));
if (NS_SUCCEEDED(rv)) {
uint32_t responseCode = 0;
if (!mozilla::StaticPrefs::
browser_http_blank_page_with_error_response_enabled()) { // Bug 1325876: Show internal error page for HTTP responses with error // codes (4xx, 5xx) and "Content-Length": 0 instead of blank page
int64_t contentLength = 0;
rv = httpChannel->GetContentLength(&contentLength);
if (NS_FAILED(rv) || contentLength <= 0) { if (responseCode >= 500) { return NS_ERROR_NET_ERROR_RESPONSE;
} if (responseCode >= 400) { return NS_ERROR_NET_EMPTY_RESPONSE;
}
}
}
}
// // Make sure that the transaction has succeeded, so far... //
nsresult status;
rv = request->GetStatus(&status);
NS_ASSERTION(NS_SUCCEEDED(rv), "Unable to get request status!"); if (NS_FAILED(rv)) return rv;
if (NS_FAILED(status)) {
LOG_ERROR((" Request failed, status: 0x%08" PRIX32, static_cast<uint32_t>(status)));
// // The transaction has already reported an error - so it will be torn // down. Therefore, it is not necessary to return an error code... // return NS_OK;
}
rv = DispatchContent(request);
LOG((" After dispatch, m_targetStreamListener: 0x%p, rv: 0x%08" PRIX32,
m_targetStreamListener.get(), static_cast<uint32_t>(rv)));
NS_ASSERTION(
NS_SUCCEEDED(rv) || !m_targetStreamListener, "Must not have an m_targetStreamListener with a failure return!");
NS_ENSURE_SUCCESS(rv, rv);
if (m_targetStreamListener)
rv = m_targetStreamListener->OnStartRequest(request);
NS_IMETHODIMP
nsDocumentOpenInfo::CheckListenerChain() {
NS_ASSERTION(NS_IsMainThread(), "Should be on the main thread!");
nsresult rv = NS_OK;
nsCOMPtr<nsIThreadRetargetableStreamListener> retargetableListener =
do_QueryInterface(m_targetStreamListener, &rv); if (retargetableListener) {
rv = retargetableListener->CheckListenerChain();
}
LOG(
("[0x%p] nsDocumentOpenInfo::CheckListenerChain %s listener %p rv " "%" PRIx32, this, (NS_SUCCEEDED(rv) ? "success" : "failure"),
(nsIStreamListener*)m_targetStreamListener, static_cast<uint32_t>(rv))); return rv;
}
NS_IMETHODIMP
nsDocumentOpenInfo::OnDataAvailable(nsIRequest* request, nsIInputStream* inStr,
uint64_t sourceOffset, uint32_t count) { // if we have retarged to the end stream listener, then forward the call.... // otherwise, don't do anything
if (m_targetStreamListener) {
nsCOMPtr<nsIStreamListener> listener(m_targetStreamListener);
// If this is a multipart stream, we could get another // OnStartRequest after this... reset state.
m_targetStreamListener = nullptr;
mContentType.Truncate();
listener->OnStopRequest(request, aStatus);
}
mUsedContentHandler = false;
// Remember... // In the case of multiplexed streams (such as multipart/x-mixed-replace) // these stream listener methods could be called again :-) // return NS_OK;
}
nsresult nsDocumentOpenInfo::DispatchContent(nsIRequest* request) {
LOG(("[0x%p] nsDocumentOpenInfo::DispatchContent for type '%s'", this,
mContentType.get()));
MOZ_ASSERT(!m_targetStreamListener, "Why do we already have a target stream listener?");
nsresult rv;
nsCOMPtr<nsIChannel> aChannel = do_QueryInterface(request); if (!aChannel) {
LOG_ERROR((" Request is not a channel. Bailing.")); return NS_ERROR_FAILURE;
}
constexpr auto anyType = "*/*"_ns; if (mContentType.IsEmpty() || mContentType == anyType) {
rv = aChannel->GetContentType(mContentType); if (NS_FAILED(rv)) return rv;
LOG((" Got type from channel: '%s'", mContentType.get()));
}
bool isGuessFromExt =
mContentType.LowerCaseEqualsASCII(APPLICATION_GUESS_FROM_EXT); if (isGuessFromExt) { // Reset to application/octet-stream for now; no one other than the // external helper app service should see APPLICATION_GUESS_FROM_EXT.
mContentType = APPLICATION_OCTET_STREAM;
aChannel->SetContentType(nsLiteralCString(APPLICATION_OCTET_STREAM));
}
// Check whether the data should be forced to be handled externally. This // could happen because the Content-Disposition header is set so, or, in the // future, because the user has specified external handling for the MIME // type. // // If we're not going to be able to retarget to an external handler, ignore // content-disposition, and unconditionally try to display the content. // This is used for object/embed tags, which expect to display subresources // marked with an attachment disposition. bool forceExternalHandling = false; if (!(mFlags & nsIURILoader::DONT_RETARGET)) {
uint32_t disposition;
rv = aChannel->GetContentDisposition(&disposition);
if (forceExternalHandling &&
mozilla::StaticPrefs::browser_download_open_pdf_attachments_inline()) { // Check if this is a PDF which should be opened internally. We also handle // octet-streams that look like they might be PDFs based on their extension. bool isPDF = mContentType.LowerCaseEqualsASCII(APPLICATION_PDF); if (!isPDF &&
(mContentType.LowerCaseEqualsASCII(APPLICATION_OCTET_STREAM) ||
mContentType.IsEmpty())) {
nsAutoString flname;
aChannel->GetContentDispositionFilename(flname);
isPDF = StringEndsWith(flname, u".pdf"_ns); if (!isPDF) {
nsCOMPtr<nsIURI> uri;
aChannel->GetURI(getter_AddRefs(uri));
nsCOMPtr<nsIURL> url(do_QueryInterface(uri)); if (url) {
nsAutoCString ext;
url->GetFileExtension(ext);
isPDF = ext.EqualsLiteral("pdf");
}
}
}
// For a PDF, check if the preference is set that forces attachments to be // opened inline. If so, treat it as a non-attachment by clearing // 'forceExternalHandling' again. This allows it open a PDF directly // instead of downloading it first. It may still end up being handled by // a helper app depending anyway on the later checks. if (isPDF) {
nsCOMPtr<nsILoadInfo> loadInfo;
aChannel->GetLoadInfo(getter_AddRefs(loadInfo));
if (!forceExternalHandling) { // // First step: See whether m_contentListener wants to handle this // content type. // if (TryDefaultContentListener(aChannel)) {
LOG((" Success! Our default listener likes this type")); // All done here return NS_OK;
}
// If we aren't allowed to try other listeners, just skip through to // trying to convert the data. if (!(mFlags & nsIURILoader::DONT_RETARGET)) { // // Second step: See whether some other registered listener wants // to handle this content type. //
int32_t count = mURILoader ? mURILoader->m_listeners.Count() : 0;
nsCOMPtr<nsIURIContentListener> listener; for (int32_t i = 0; i < count; i++) {
listener = do_QueryReferent(mURILoader->m_listeners[i]); if (listener) { if (TryContentListener(listener, aChannel)) {
LOG((" Found listener registered on the URILoader")); return NS_OK;
}
} else { // remove from the listener list, reset i and update count
mURILoader->m_listeners.RemoveObjectAt(i--);
--count;
}
}
// // Third step: Try to find an nsIContentHandler for our type. //
nsAutoCString handlerContractID(NS_CONTENT_HANDLER_CONTRACTID_PREFIX);
handlerContractID += mContentType;
nsCOMPtr<nsIContentHandler> contentHandler =
do_CreateInstance(handlerContractID.get()); if (contentHandler) {
LOG((" Content handler found")); // Note that m_originalContext can be nullptr when running this in // the parent process on behalf on a docshell in the content process, // and in that case we only support content handlers that don't need // the context.
rv = contentHandler->HandleContent(mContentType.get(),
m_originalContext, request); // XXXbz returning an error code to represent handling the // content is just bizarre! if (rv != NS_ERROR_WONT_HANDLE_CONTENT) { if (NS_FAILED(rv)) { // The content handler has unexpectedly failed. Cancel the request // just in case the handler didn't...
LOG((" Content handler failed. Aborting load"));
request->Cancel(rv);
} else {
LOG((" Content handler taking over load"));
mUsedContentHandler = true;
}
return rv;
}
}
} else {
LOG(
(" DONT_RETARGET flag set, so skipped over random other content " "listeners and content handlers"));
}
// // Fourth step: If no listener prefers this type, see if any stream // converters exist to transform this content type into // some other. // // Don't do this if the server sent us a MIME type of "*/*" because they saw // it in our Accept header and got confused. // XXXbz have to be careful here; may end up in some sort of bizarre // infinite decoding loop. if (mContentType != anyType) {
rv = TryStreamConversion(aChannel); if (NS_SUCCEEDED(rv)) { return NS_OK;
}
}
}
NS_ASSERTION(!m_targetStreamListener, "If we found a listener, why are we not using it?");
// Before dispatching to the external helper app service, check for an HTTP // error page. If we got one, we don't want to handle it with a helper app, // really.
nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(request)); if (httpChannel) { bool requestSucceeded;
rv = httpChannel->GetRequestSucceeded(&requestSucceeded); if (NS_FAILED(rv) || !requestSucceeded) {
LOG(
(" Returning NS_ERROR_NET_ERROR_RESPONSE from " "nsDocumentOpenInfo::DispatchContent due to failed HTTP response")); return NS_ERROR_NET_ERROR_RESPONSE;
}
}
if (mFlags & nsIURILoader::DONT_RETARGET) {
LOG(
(" External handling forced or (listener not interested and no " "stream converter exists), and retargeting disallowed -> aborting")); return NS_ERROR_WONT_HANDLE_CONTENT;
}
// Fifth step: // // All attempts to dispatch this content have failed. Just pass it off to // the helper app service. //
nsCOMPtr<nsIExternalHelperAppService> helperAppService =
do_GetService(NS_EXTERNALHELPERAPPSERVICE_CONTRACTID, &rv); if (helperAppService) {
LOG((" Passing load off to helper app service"));
// Set these flags to indicate that the channel has been targeted and that // we are not using the original consumer.
nsLoadFlags loadFlags = 0;
request->GetLoadFlags(&loadFlags);
request->SetLoadFlags(loadFlags | nsIChannel::LOAD_RETARGETED_DOCUMENT_URI |
nsIChannel::LOAD_TARGETED);
if (isGuessFromExt || mContentType.IsEmpty()) {
mContentType = APPLICATION_GUESS_FROM_EXT;
aChannel->SetContentType(nsLiteralCString(APPLICATION_GUESS_FROM_EXT));
}
NS_ASSERTION(m_targetStreamListener || NS_FAILED(rv), "There is no way we should be successful at this point without " "a m_targetStreamListener"); return rv;
}
if (mDataConversionDepthLimit == 0) {
LOG(
("[0x%p] nsDocumentOpenInfo::ConvertData - reached the recursion " "limit!", this)); // This will fall back to external helper app handling. return NS_ERROR_ABORT;
}
MOZ_ASSERT(aSrcContentType != aOutContentType, "ConvertData called when the two types are the same!");
nsresult rv = NS_OK;
nsCOMPtr<nsIStreamConverterService> StreamConvService =
do_GetService(NS_STREAMCONVERTERSERVICE_CONTRACTID, &rv); if (NS_FAILED(rv)) return rv;
LOG((" Got converter service"));
// When applying stream decoders, it is necessary to "insert" an // intermediate nsDocumentOpenInfo instance to handle the targeting of // the "final" stream or streams. // // For certain content types (ie. multi-part/x-mixed-replace) the input // stream is split up into multiple destination streams. This // intermediate instance is used to target these "decoded" streams... //
RefPtr<nsDocumentOpenInfo> nextLink = Clone();
LOG((" Downstream DocumentOpenInfo would be: 0x%p", nextLink.get()));
// Decrease the conversion recursion limit by one to prevent infinite loops.
nextLink->mDataConversionDepthLimit = mDataConversionDepthLimit - 1;
// Make sure nextLink starts with the contentListener that said it wanted // the results of this decode.
nextLink->m_contentListener = aListener; // Also make sure it has to look for a stream listener to pump data into.
nextLink->m_targetStreamListener = nullptr;
// Make sure that nextLink treats the data as aOutContentType when // dispatching; that way even if the stream converters don't change the type // on the channel we will still do the right thing. If aOutContentType is // */*, that's OK -- that will just indicate to nextLink that it should get // the type off the channel.
nextLink->mContentType = aOutContentType;
// The following call sets m_targetStreamListener to the input end of the // stream converter and sets the output end of the stream converter to // nextLink. As we pump data into m_targetStreamListener the stream // converter will convert it and pass the converted data to nextLink. return StreamConvService->AsyncConvertData(
PromiseFlatCString(aSrcContentType).get(),
PromiseFlatCString(aOutContentType).get(), nextLink, request,
getter_AddRefs(m_targetStreamListener));
}
nsresult nsDocumentOpenInfo::TryStreamConversion(nsIChannel* aChannel) {
constexpr auto anyType = "*/*"_ns;
// A empty content type should be treated like the unknown content type.
nsCString srcContentType(mContentType); if (srcContentType.IsEmpty()) {
srcContentType.AssignLiteral(UNKNOWN_CONTENT_TYPE);
}
nsresult rv =
ConvertData(aChannel, m_contentListener, srcContentType, anyType); if (NS_FAILED(rv)) {
m_targetStreamListener = nullptr;
} elseif (m_targetStreamListener) { // We found a converter for this MIME type. We'll just pump data into // it and let the downstream nsDocumentOpenInfo handle things.
LOG((" Converter taking over now"));
} return rv;
}
if (NS_FAILED(rv)) { // No conversion path -- we don't want this listener, if we got one
m_targetStreamListener = nullptr;
}
LOG((" Found conversion: %s", m_targetStreamListener ? "yes" : "no"));
// m_targetStreamListener is now the input end of the converter, and we can // just pump the data in there, if it exists. If it does not, we need to // try other nsIURIContentListeners. return m_targetStreamListener != nullptr;
}
// At this point, aListener wants data of type mContentType. Let 'em have // it. But first, if we are retargeting, set an appropriate flag on the // channel
nsLoadFlags loadFlags = 0;
aChannel->GetLoadFlags(&loadFlags);
// Set this flag to indicate that the channel has been targeted at a final // consumer. This load flag is tested in nsDocLoader::OnProgress.
nsLoadFlags newLoadFlags = nsIChannel::LOAD_TARGETED;
if (NS_FAILED(rv)) {
LOG_ERROR((" DoContent failed"));
// Unset the RETARGETED_DOCUMENT_URI flag if we set it...
aChannel->SetLoadFlags(loadFlags);
m_targetStreamListener = nullptr; returnfalse;
}
if (abort) { // Nothing else to do here -- aListener is handling it all. Make // sure m_targetStreamListener is null so we don't do anything // after this point.
LOG((" Listener has aborted the load"));
m_targetStreamListener = nullptr;
}
NS_ASSERTION(abort || m_targetStreamListener, "DoContent returned no listener?");
// aListener is handling the load from this point on. returntrue;
}
if (LOG_ENABLED()) {
nsCOMPtr<nsIURI> uri;
channel->GetURI(getter_AddRefs(uri));
nsAutoCString spec;
uri->GetAsciiSpec(spec);
LOG(("nsURILoader::OpenURI for %s", spec.get()));
}
nsCOMPtr<nsIStreamListener> loader;
nsresult rv = OpenChannel(channel, aFlags, aWindowContext, false,
getter_AddRefs(loader)); if (NS_FAILED(rv)) { if (rv == NS_ERROR_WONT_HANDLE_CONTENT) { // Not really an error, from this method's point of view return NS_OK;
}
}
// This method is not complete. Eventually, we should first go // to the content listener and ask them for a protocol handler... // if they don't give us one, we need to go to the registry and get // the preferred protocol handler.
// But for now, I'm going to let necko do the work for us....
rv = channel->AsyncOpen(loader);
// no content from this load - that's OK. if (rv == NS_ERROR_NO_CONTENT) {
LOG((" rv is NS_ERROR_NO_CONTENT -- doing nothing")); return NS_OK;
} return rv;
}
nsresult nsURILoader::OpenChannel(nsIChannel* channel, uint32_t aFlags,
nsIInterfaceRequestor* aWindowContext, bool aChannelIsOpen,
nsIStreamListener** aListener) {
NS_ASSERTION(channel, "Trying to open a null channel!");
NS_ASSERTION(aWindowContext, "Window context must not be null");
if (LOG_ENABLED()) {
nsCOMPtr<nsIURI> uri;
channel->GetURI(getter_AddRefs(uri));
nsAutoCString spec;
uri->GetAsciiSpec(spec);
LOG(("nsURILoader::OpenChannel for %s", spec.get()));
}
// we need to create a DocumentOpenInfo object which will go ahead and open // the url and discover the content type....
RefPtr<nsDocumentOpenInfo> loader = new nsDocumentOpenInfo(aWindowContext, aFlags, this);
// Set the correct loadgroup on the channel
nsCOMPtr<nsILoadGroup> loadGroup(do_GetInterface(aWindowContext));
if (!loadGroup) { // XXXbz This context is violating what we'd like to be the new uriloader // api.... Set up a nsDocLoader to handle the loadgroup for this context. // This really needs to go away!
nsCOMPtr<nsIURIContentListener> listener(do_GetInterface(aWindowContext)); if (listener) {
nsCOMPtr<nsISupports> cookie;
listener->GetLoadCookie(getter_AddRefs(cookie)); if (!cookie) {
RefPtr<nsDocLoader> newDocLoader = new nsDocLoader();
nsresult rv = newDocLoader->Init(); if (NS_FAILED(rv)) return rv;
rv = nsDocLoader::AddDocLoaderAsChildOfRoot(newDocLoader); if (NS_FAILED(rv)) return rv;
cookie = nsDocLoader::GetAsSupports(newDocLoader);
listener->SetLoadCookie(cookie);
}
loadGroup = do_GetInterface(cookie);
}
}
// If the channel is pending, then we need to remove it from its current // loadgroup
nsCOMPtr<nsILoadGroup> oldGroup;
channel->GetLoadGroup(getter_AddRefs(oldGroup)); if (aChannelIsOpen && !SameCOMIdentity(oldGroup, loadGroup)) { // It is important to add the channel to the new group before // removing it from the old one, so that the load isn't considered // done as soon as the request is removed.
loadGroup->AddRequest(channel, nullptr);
if (oldGroup) {
oldGroup->RemoveRequest(channel, nullptr, NS_BINDING_RETARGETED);
}
}
channel->SetLoadGroup(loadGroup);
// prepare the loader for receiving data
nsresult rv = loader->Prepare(); if (NS_SUCCEEDED(rv)) NS_ADDREF(*aListener = loader); return rv;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.