michael@0: //* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: /** michael@0: * Implementation of moz-anno: URLs for accessing favicons. The urls are sent michael@0: * to the favicon service. If the favicon service doesn't have the michael@0: * data, a stream containing the default favicon will be returned. michael@0: * michael@0: * The reference to annotations ("moz-anno") is a leftover from previous michael@0: * iterations of this component. As of now the moz-anno protocol is independent michael@0: * of annotations. michael@0: */ michael@0: michael@0: #include "nsAnnoProtocolHandler.h" michael@0: #include "nsFaviconService.h" michael@0: #include "nsIChannel.h" michael@0: #include "nsIInputStreamChannel.h" michael@0: #include "nsILoadGroup.h" michael@0: #include "nsIStandardURL.h" michael@0: #include "nsIStringStream.h" michael@0: #include "nsISupportsUtils.h" michael@0: #include "nsIURI.h" michael@0: #include "nsNetUtil.h" michael@0: #include "nsServiceManagerUtils.h" michael@0: #include "nsStringStream.h" michael@0: #include "mozilla/storage.h" michael@0: #include "nsIPipe.h" michael@0: #include "Helpers.h" michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::places; michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: //// Global Functions michael@0: michael@0: /** michael@0: * Creates a channel to obtain the default favicon. michael@0: */ michael@0: static michael@0: nsresult michael@0: GetDefaultIcon(nsIChannel **aChannel) michael@0: { michael@0: nsCOMPtr defaultIconURI; michael@0: nsresult rv = NS_NewURI(getter_AddRefs(defaultIconURI), michael@0: NS_LITERAL_CSTRING(FAVICON_DEFAULT_URL)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: return NS_NewChannel(aChannel, defaultIconURI); michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: //// faviconAsyncLoader michael@0: michael@0: namespace { michael@0: michael@0: /** michael@0: * An instance of this class is passed to the favicon service as the callback michael@0: * for getting favicon data from the database. We'll get this data back in michael@0: * HandleResult, and on HandleCompletion, we'll close our output stream which michael@0: * will close the original channel for the favicon request. michael@0: * michael@0: * However, if an error occurs at any point, we do not set mReturnDefaultIcon to michael@0: * false, so we will open up another channel to get the default favicon, and michael@0: * pass that along to our output stream in HandleCompletion. If anything michael@0: * happens at that point, the world must be against us, so we return nothing. michael@0: */ michael@0: class faviconAsyncLoader : public AsyncStatementCallback michael@0: , public nsIRequestObserver michael@0: { michael@0: public: michael@0: NS_DECL_ISUPPORTS_INHERITED michael@0: michael@0: faviconAsyncLoader(nsIChannel *aChannel, nsIOutputStream *aOutputStream) : michael@0: mChannel(aChannel) michael@0: , mOutputStream(aOutputStream) michael@0: , mReturnDefaultIcon(true) michael@0: { michael@0: NS_ASSERTION(aChannel, michael@0: "Not providing a channel will result in crashes!"); michael@0: NS_ASSERTION(aOutputStream, michael@0: "Not providing an output stream will result in crashes!"); michael@0: } michael@0: michael@0: ////////////////////////////////////////////////////////////////////////////// michael@0: //// mozIStorageStatementCallback michael@0: michael@0: NS_IMETHOD HandleResult(mozIStorageResultSet *aResultSet) michael@0: { michael@0: // We will only get one row back in total, so we do not need to loop. michael@0: nsCOMPtr row; michael@0: nsresult rv = aResultSet->GetNextRow(getter_AddRefs(row)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // We do not allow favicons without a MIME type, so we'll return the default michael@0: // icon. michael@0: nsAutoCString mimeType; michael@0: (void)row->GetUTF8String(1, mimeType); michael@0: NS_ENSURE_FALSE(mimeType.IsEmpty(), NS_OK); michael@0: michael@0: // Set our mimeType now that we know it. michael@0: rv = mChannel->SetContentType(mimeType); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Obtain the binary blob that contains our favicon data. michael@0: uint8_t *favicon; michael@0: uint32_t size = 0; michael@0: rv = row->GetBlob(0, &size, &favicon); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: uint32_t totalWritten = 0; michael@0: do { michael@0: uint32_t bytesWritten; michael@0: rv = mOutputStream->Write( michael@0: &(reinterpret_cast(favicon)[totalWritten]), michael@0: size - totalWritten, michael@0: &bytesWritten michael@0: ); michael@0: if (NS_FAILED(rv) || !bytesWritten) michael@0: break; michael@0: totalWritten += bytesWritten; michael@0: } while (size != totalWritten); michael@0: NS_ASSERTION(NS_FAILED(rv) || size == totalWritten, michael@0: "Failed to write all of our data out to the stream!"); michael@0: michael@0: // Free our favicon array. michael@0: NS_Free(favicon); michael@0: michael@0: // Handle an error to write if it occurred, but only after we've freed our michael@0: // favicon. michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // At this point, we should have written out all of our data to our stream. michael@0: // HandleCompletion will close the output stream, so we are done here. michael@0: mReturnDefaultIcon = false; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHOD HandleCompletion(uint16_t aReason) michael@0: { michael@0: if (!mReturnDefaultIcon) michael@0: return mOutputStream->Close(); michael@0: michael@0: // We need to return our default icon, so we'll open up a new channel to get michael@0: // that data, and push it to our output stream. If at any point we get an michael@0: // error, we can't do anything, so we'll just close our output stream. michael@0: nsCOMPtr listener; michael@0: nsresult rv = NS_NewSimpleStreamListener(getter_AddRefs(listener), michael@0: mOutputStream, this); michael@0: NS_ENSURE_SUCCESS(rv, mOutputStream->Close()); michael@0: michael@0: nsCOMPtr newChannel; michael@0: rv = GetDefaultIcon(getter_AddRefs(newChannel)); michael@0: NS_ENSURE_SUCCESS(rv, mOutputStream->Close()); michael@0: michael@0: rv = newChannel->AsyncOpen(listener, nullptr); michael@0: NS_ENSURE_SUCCESS(rv, mOutputStream->Close()); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: ////////////////////////////////////////////////////////////////////////////// michael@0: //// nsIRequestObserver michael@0: michael@0: NS_IMETHOD OnStartRequest(nsIRequest *, nsISupports *) michael@0: { michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHOD OnStopRequest(nsIRequest *, nsISupports *, nsresult aStatusCode) michael@0: { michael@0: // We always need to close our output stream, regardless of the status code. michael@0: (void)mOutputStream->Close(); michael@0: michael@0: // But, we'll warn about it not being successful if it wasn't. michael@0: NS_WARN_IF_FALSE(NS_SUCCEEDED(aStatusCode), michael@0: "Got an error when trying to load our default favicon!"); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: private: michael@0: nsCOMPtr mChannel; michael@0: nsCOMPtr mOutputStream; michael@0: bool mReturnDefaultIcon; michael@0: }; michael@0: michael@0: NS_IMPL_ISUPPORTS_INHERITED( michael@0: faviconAsyncLoader, michael@0: AsyncStatementCallback, michael@0: nsIRequestObserver michael@0: ) michael@0: michael@0: } // anonymous namespace michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: //// nsAnnoProtocolHandler michael@0: michael@0: NS_IMPL_ISUPPORTS(nsAnnoProtocolHandler, nsIProtocolHandler) michael@0: michael@0: // nsAnnoProtocolHandler::GetScheme michael@0: michael@0: NS_IMETHODIMP michael@0: nsAnnoProtocolHandler::GetScheme(nsACString& aScheme) michael@0: { michael@0: aScheme.AssignLiteral("moz-anno"); michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: // nsAnnoProtocolHandler::GetDefaultPort michael@0: // michael@0: // There is no default port for annotation URLs michael@0: michael@0: NS_IMETHODIMP michael@0: nsAnnoProtocolHandler::GetDefaultPort(int32_t *aDefaultPort) michael@0: { michael@0: *aDefaultPort = -1; michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: // nsAnnoProtocolHandler::GetProtocolFlags michael@0: michael@0: NS_IMETHODIMP michael@0: nsAnnoProtocolHandler::GetProtocolFlags(uint32_t *aProtocolFlags) michael@0: { michael@0: *aProtocolFlags = (URI_NORELATIVE | URI_NOAUTH | URI_DANGEROUS_TO_LOAD | michael@0: URI_IS_LOCAL_RESOURCE); michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: // nsAnnoProtocolHandler::NewURI michael@0: michael@0: NS_IMETHODIMP michael@0: nsAnnoProtocolHandler::NewURI(const nsACString& aSpec, michael@0: const char *aOriginCharset, michael@0: nsIURI *aBaseURI, nsIURI **_retval) michael@0: { michael@0: nsCOMPtr uri = do_CreateInstance(NS_SIMPLEURI_CONTRACTID); michael@0: if (!uri) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: nsresult rv = uri->SetSpec(aSpec); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: *_retval = nullptr; michael@0: uri.swap(*_retval); michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: // nsAnnoProtocolHandler::NewChannel michael@0: // michael@0: michael@0: NS_IMETHODIMP michael@0: nsAnnoProtocolHandler::NewChannel(nsIURI *aURI, nsIChannel **_retval) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aURI); michael@0: michael@0: // annotation info michael@0: nsCOMPtr annoURI; michael@0: nsAutoCString annoName; michael@0: nsresult rv = ParseAnnoURI(aURI, getter_AddRefs(annoURI), annoName); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Only favicon annotation are supported. michael@0: if (!annoName.EqualsLiteral(FAVICON_ANNOTATION_NAME)) michael@0: return NS_ERROR_INVALID_ARG; michael@0: michael@0: return NewFaviconChannel(aURI, annoURI, _retval); michael@0: } michael@0: michael@0: michael@0: // nsAnnoProtocolHandler::AllowPort michael@0: // michael@0: // Don't override any bans on bad ports. michael@0: michael@0: NS_IMETHODIMP michael@0: nsAnnoProtocolHandler::AllowPort(int32_t port, const char *scheme, michael@0: bool *_retval) michael@0: { michael@0: *_retval = false; michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: // nsAnnoProtocolHandler::ParseAnnoURI michael@0: // michael@0: // Splits an annotation URL into its URI and name parts michael@0: michael@0: nsresult michael@0: nsAnnoProtocolHandler::ParseAnnoURI(nsIURI* aURI, michael@0: nsIURI** aResultURI, nsCString& aName) michael@0: { michael@0: nsresult rv; michael@0: nsAutoCString path; michael@0: rv = aURI->GetPath(path); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: int32_t firstColon = path.FindChar(':'); michael@0: if (firstColon <= 0) michael@0: return NS_ERROR_MALFORMED_URI; michael@0: michael@0: rv = NS_NewURI(aResultURI, Substring(path, firstColon + 1)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: aName = Substring(path, 0, firstColon); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsAnnoProtocolHandler::NewFaviconChannel(nsIURI *aURI, nsIURI *aAnnotationURI, michael@0: nsIChannel **_channel) michael@0: { michael@0: // Create our pipe. This will give us our input stream and output stream michael@0: // that will be written to when we get data from the database. michael@0: nsCOMPtr inputStream; michael@0: nsCOMPtr outputStream; michael@0: nsresult rv = NS_NewPipe(getter_AddRefs(inputStream), michael@0: getter_AddRefs(outputStream), michael@0: MAX_FAVICON_SIZE, MAX_FAVICON_SIZE, true, michael@0: true); michael@0: NS_ENSURE_SUCCESS(rv, GetDefaultIcon(_channel)); michael@0: michael@0: // Create our channel. We'll call SetContentType with the right type when michael@0: // we know what it actually is. michael@0: nsCOMPtr channel; michael@0: rv = NS_NewInputStreamChannel(getter_AddRefs(channel), aURI, inputStream, michael@0: EmptyCString()); michael@0: NS_ENSURE_SUCCESS(rv, GetDefaultIcon(_channel)); michael@0: michael@0: // Now we go ahead and get our data asynchronously for the favicon. michael@0: nsCOMPtr callback = michael@0: new faviconAsyncLoader(channel, outputStream); michael@0: NS_ENSURE_TRUE(callback, GetDefaultIcon(_channel)); michael@0: nsFaviconService* faviconService = nsFaviconService::GetFaviconService(); michael@0: NS_ENSURE_TRUE(faviconService, GetDefaultIcon(_channel)); michael@0: michael@0: rv = faviconService->GetFaviconDataAsync(aAnnotationURI, callback); michael@0: NS_ENSURE_SUCCESS(rv, GetDefaultIcon(_channel)); michael@0: michael@0: channel.forget(_channel); michael@0: return NS_OK; michael@0: }