michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ 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: #include "mozilla/chrome/RegistryMessageUtils.h" michael@0: michael@0: #include "nsResProtocolHandler.h" michael@0: #include "nsIIOService.h" michael@0: #include "nsIFile.h" michael@0: #include "nsNetUtil.h" michael@0: #include "nsURLHelper.h" michael@0: #include "nsEscape.h" michael@0: michael@0: #include "mozilla/Omnijar.h" michael@0: michael@0: static NS_DEFINE_CID(kResURLCID, NS_RESURL_CID); michael@0: michael@0: static nsResProtocolHandler *gResHandler = nullptr; michael@0: michael@0: #if defined(PR_LOGGING) michael@0: // michael@0: // Log module for Resource Protocol logging... michael@0: // michael@0: // To enable logging (see prlog.h for full details): michael@0: // michael@0: // set NSPR_LOG_MODULES=nsResProtocol:5 michael@0: // set NSPR_LOG_FILE=log.txt michael@0: // michael@0: // this enables PR_LOG_ALWAYS level information and places all output in michael@0: // the file log.txt michael@0: // michael@0: static PRLogModuleInfo *gResLog; michael@0: #endif michael@0: michael@0: #define kAPP NS_LITERAL_CSTRING("app") michael@0: #define kGRE NS_LITERAL_CSTRING("gre") michael@0: michael@0: //---------------------------------------------------------------------------- michael@0: // nsResURL : overrides nsStandardURL::GetFile to provide nsIFile resolution michael@0: //---------------------------------------------------------------------------- michael@0: michael@0: nsresult michael@0: nsResURL::EnsureFile() michael@0: { michael@0: nsresult rv; michael@0: michael@0: NS_ENSURE_TRUE(gResHandler, NS_ERROR_NOT_AVAILABLE); michael@0: michael@0: nsAutoCString spec; michael@0: rv = gResHandler->ResolveURI(this, spec); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: nsAutoCString scheme; michael@0: rv = net_ExtractURLScheme(spec, nullptr, nullptr, &scheme); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: // Bug 585869: michael@0: // In most cases, the scheme is jar if it's not file. michael@0: // Regardless, net_GetFileFromURLSpec should be avoided michael@0: // when the scheme isn't file. michael@0: if (!scheme.Equals(NS_LITERAL_CSTRING("file"))) michael@0: return NS_ERROR_NO_INTERFACE; michael@0: michael@0: rv = net_GetFileFromURLSpec(spec, getter_AddRefs(mFile)); michael@0: #ifdef DEBUG_bsmedberg michael@0: if (NS_SUCCEEDED(rv)) { michael@0: bool exists = true; michael@0: mFile->Exists(&exists); michael@0: if (!exists) { michael@0: printf("resource %s doesn't exist!\n", spec.get()); michael@0: } michael@0: } michael@0: #endif michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: /* virtual */ nsStandardURL* michael@0: nsResURL::StartClone() michael@0: { michael@0: nsResURL *clone = new nsResURL(); michael@0: return clone; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsResURL::GetClassIDNoAlloc(nsCID *aClassIDNoAlloc) michael@0: { michael@0: *aClassIDNoAlloc = kResURLCID; michael@0: return NS_OK; michael@0: } michael@0: michael@0: //---------------------------------------------------------------------------- michael@0: // nsResProtocolHandler michael@0: //---------------------------------------------------------------------------- michael@0: michael@0: nsResProtocolHandler::nsResProtocolHandler() michael@0: : mSubstitutions(32) michael@0: { michael@0: #if defined(PR_LOGGING) michael@0: gResLog = PR_NewLogModule("nsResProtocol"); michael@0: #endif michael@0: michael@0: NS_ASSERTION(!gResHandler, "res handler already created!"); michael@0: gResHandler = this; michael@0: } michael@0: michael@0: nsResProtocolHandler::~nsResProtocolHandler() michael@0: { michael@0: gResHandler = nullptr; michael@0: } michael@0: michael@0: nsresult michael@0: nsResProtocolHandler::Init() michael@0: { michael@0: nsresult rv; michael@0: michael@0: mIOService = do_GetIOService(&rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsAutoCString appURI, greURI; michael@0: rv = mozilla::Omnijar::GetURIString(mozilla::Omnijar::APP, appURI); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = mozilla::Omnijar::GetURIString(mozilla::Omnijar::GRE, greURI); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // michael@0: // make resource:/// point to the application directory or omnijar michael@0: // michael@0: nsCOMPtr uri; michael@0: rv = NS_NewURI(getter_AddRefs(uri), appURI.Length() ? appURI : greURI); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = SetSubstitution(EmptyCString(), uri); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // michael@0: // make resource://app/ point to the application directory or omnijar michael@0: // michael@0: rv = SetSubstitution(kAPP, uri); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // michael@0: // make resource://gre/ point to the GRE directory michael@0: // michael@0: if (appURI.Length()) { // We already have greURI in uri if appURI.Length() is 0. michael@0: rv = NS_NewURI(getter_AddRefs(uri), greURI); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: rv = SetSubstitution(kGRE, uri); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: //XXXbsmedberg Neil wants a resource://pchrome/ for the profile chrome dir... michael@0: // but once I finish multiple chrome registration I'm not sure that it is needed michael@0: michael@0: // XXX dveditz: resource://pchrome/ defeats profile directory salting michael@0: // if web content can load it. Tread carefully. michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: static PLDHashOperator michael@0: EnumerateSubstitution(const nsACString& aKey, michael@0: nsIURI* aURI, michael@0: void* aArg) michael@0: { michael@0: nsTArray* resources = michael@0: static_cast*>(aArg); michael@0: SerializedURI uri; michael@0: if (aURI) { michael@0: aURI->GetSpec(uri.spec); michael@0: aURI->GetOriginCharset(uri.charset); michael@0: } michael@0: michael@0: ResourceMapping resource = { michael@0: nsCString(aKey), uri michael@0: }; michael@0: resources->AppendElement(resource); michael@0: return (PLDHashOperator)PL_DHASH_NEXT; michael@0: } michael@0: michael@0: void michael@0: nsResProtocolHandler::CollectSubstitutions(InfallibleTArray& aResources) michael@0: { michael@0: mSubstitutions.EnumerateRead(&EnumerateSubstitution, &aResources); michael@0: } michael@0: michael@0: //---------------------------------------------------------------------------- michael@0: // nsResProtocolHandler::nsISupports michael@0: //---------------------------------------------------------------------------- michael@0: michael@0: NS_IMPL_ISUPPORTS(nsResProtocolHandler, michael@0: nsIResProtocolHandler, michael@0: nsIProtocolHandler, michael@0: nsISupportsWeakReference) michael@0: michael@0: //---------------------------------------------------------------------------- michael@0: // nsResProtocolHandler::nsIProtocolHandler michael@0: //---------------------------------------------------------------------------- michael@0: michael@0: NS_IMETHODIMP michael@0: nsResProtocolHandler::GetScheme(nsACString &result) michael@0: { michael@0: result.AssignLiteral("resource"); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsResProtocolHandler::GetDefaultPort(int32_t *result) michael@0: { michael@0: *result = -1; // no port for res: URLs michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsResProtocolHandler::GetProtocolFlags(uint32_t *result) michael@0: { michael@0: // XXXbz Is this really true for all resource: URIs? Could we michael@0: // somehow give different flags to some of them? michael@0: *result = URI_STD | URI_IS_UI_RESOURCE | URI_IS_LOCAL_RESOURCE; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsResProtocolHandler::NewURI(const nsACString &aSpec, michael@0: const char *aCharset, michael@0: nsIURI *aBaseURI, michael@0: nsIURI **result) michael@0: { michael@0: nsresult rv; michael@0: michael@0: nsResURL *resURL = new nsResURL(); michael@0: if (!resURL) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: NS_ADDREF(resURL); michael@0: michael@0: // unescape any %2f and %2e to make sure nsStandardURL coalesces them. michael@0: // Later net_GetFileFromURLSpec() will do a full unescape and we want to michael@0: // treat them the same way the file system will. (bugs 380994, 394075) michael@0: nsAutoCString spec; michael@0: const char *src = aSpec.BeginReading(); michael@0: const char *end = aSpec.EndReading(); michael@0: const char *last = src; michael@0: michael@0: spec.SetCapacity(aSpec.Length()+1); michael@0: for ( ; src < end; ++src) { michael@0: if (*src == '%' && (src < end-2) && *(src+1) == '2') { michael@0: char ch = '\0'; michael@0: if (*(src+2) == 'f' || *(src+2) == 'F') michael@0: ch = '/'; michael@0: else if (*(src+2) == 'e' || *(src+2) == 'E') michael@0: ch = '.'; michael@0: michael@0: if (ch) { michael@0: if (last < src) michael@0: spec.Append(last, src-last); michael@0: spec.Append(ch); michael@0: src += 2; michael@0: last = src+1; // src will be incremented by the loop michael@0: } michael@0: } michael@0: } michael@0: if (last < src) michael@0: spec.Append(last, src-last); michael@0: michael@0: rv = resURL->Init(nsIStandardURL::URLTYPE_STANDARD, -1, spec, aCharset, aBaseURI); michael@0: if (NS_SUCCEEDED(rv)) michael@0: rv = CallQueryInterface(resURL, result); michael@0: NS_RELEASE(resURL); michael@0: return rv; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsResProtocolHandler::NewChannel(nsIURI* uri, nsIChannel* *result) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(uri); michael@0: nsresult rv; michael@0: nsAutoCString spec; michael@0: michael@0: rv = ResolveURI(uri, spec); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: rv = mIOService->NewChannel(spec, nullptr, nullptr, result); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: nsLoadFlags loadFlags = 0; michael@0: (*result)->GetLoadFlags(&loadFlags); michael@0: (*result)->SetLoadFlags(loadFlags & ~nsIChannel::LOAD_REPLACE); michael@0: return (*result)->SetOriginalURI(uri); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsResProtocolHandler::AllowPort(int32_t port, const char *scheme, bool *_retval) michael@0: { michael@0: // don't override anything. michael@0: *_retval = false; michael@0: return NS_OK; michael@0: } michael@0: michael@0: //---------------------------------------------------------------------------- michael@0: // nsResProtocolHandler::nsIResProtocolHandler michael@0: //---------------------------------------------------------------------------- michael@0: michael@0: NS_IMETHODIMP michael@0: nsResProtocolHandler::SetSubstitution(const nsACString& root, nsIURI *baseURI) michael@0: { michael@0: if (!baseURI) { michael@0: mSubstitutions.Remove(root); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // If baseURI isn't a resource URI, we can set the substitution immediately. michael@0: nsAutoCString scheme; michael@0: nsresult rv = baseURI->GetScheme(scheme); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: if (!scheme.Equals(NS_LITERAL_CSTRING("resource"))) { michael@0: mSubstitutions.Put(root, baseURI); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // baseURI is a resource URI, let's resolve it first. michael@0: nsAutoCString newBase; michael@0: rv = ResolveURI(baseURI, newBase); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr newBaseURI; michael@0: rv = mIOService->NewURI(newBase, nullptr, nullptr, michael@0: getter_AddRefs(newBaseURI)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: mSubstitutions.Put(root, newBaseURI); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsResProtocolHandler::GetSubstitution(const nsACString& root, nsIURI **result) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(result); michael@0: michael@0: if (mSubstitutions.Get(root, result)) michael@0: return NS_OK; michael@0: michael@0: // try invoking the directory service for "resource:root" michael@0: michael@0: nsAutoCString key; michael@0: key.AssignLiteral("resource:"); michael@0: key.Append(root); michael@0: michael@0: nsCOMPtr file; michael@0: nsresult rv = NS_GetSpecialDirectory(key.get(), getter_AddRefs(file)); michael@0: if (NS_FAILED(rv)) michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: michael@0: rv = mIOService->NewFileURI(file, result); michael@0: if (NS_FAILED(rv)) michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsResProtocolHandler::HasSubstitution(const nsACString& root, bool *result) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(result); michael@0: michael@0: *result = mSubstitutions.Get(root, nullptr); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsResProtocolHandler::ResolveURI(nsIURI *uri, nsACString &result) michael@0: { michael@0: nsresult rv; michael@0: michael@0: nsAutoCString host; michael@0: nsAutoCString path; michael@0: michael@0: rv = uri->GetAsciiHost(host); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: rv = uri->GetPath(path); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: // Unescape the path so we can perform some checks on it. michael@0: nsAutoCString unescapedPath(path); michael@0: NS_UnescapeURL(unescapedPath); michael@0: michael@0: // Don't misinterpret the filepath as an absolute URI. michael@0: if (unescapedPath.FindChar(':') != -1) michael@0: return NS_ERROR_MALFORMED_URI; michael@0: michael@0: if (unescapedPath.FindChar('\\') != -1) michael@0: return NS_ERROR_MALFORMED_URI; michael@0: michael@0: const char *p = path.get() + 1; // path always starts with a slash michael@0: NS_ASSERTION(*(p-1) == '/', "Path did not begin with a slash!"); michael@0: michael@0: if (*p == '/') michael@0: return NS_ERROR_MALFORMED_URI; michael@0: michael@0: nsCOMPtr baseURI; michael@0: rv = GetSubstitution(host, getter_AddRefs(baseURI)); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: rv = baseURI->Resolve(nsDependentCString(p, path.Length()-1), result); michael@0: michael@0: #if defined(PR_LOGGING) michael@0: if (PR_LOG_TEST(gResLog, PR_LOG_DEBUG)) { michael@0: nsAutoCString spec; michael@0: uri->GetAsciiSpec(spec); michael@0: PR_LOG(gResLog, PR_LOG_DEBUG, michael@0: ("%s\n -> %s\n", spec.get(), PromiseFlatCString(result).get())); michael@0: } michael@0: #endif michael@0: return rv; michael@0: }