michael@0: /* -*- Mode: C++; tab-width: 20; 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 michael@0: #include michael@0: michael@0: #include "prprf.h" michael@0: michael@0: #include "nsIServiceManager.h" michael@0: michael@0: #include "nsIConsoleService.h" michael@0: #include "nsIDOMCanvasRenderingContext2D.h" michael@0: #include "nsICanvasRenderingContextInternal.h" michael@0: #include "nsIHTMLCollection.h" michael@0: #include "mozilla/dom/HTMLCanvasElement.h" michael@0: #include "nsIPrincipal.h" michael@0: michael@0: #include "nsGfxCIID.h" michael@0: michael@0: #include "nsTArray.h" michael@0: michael@0: #include "CanvasUtils.h" michael@0: #include "mozilla/gfx/Matrix.h" michael@0: michael@0: using namespace mozilla::gfx; michael@0: michael@0: #include "nsIScriptObjectPrincipal.h" michael@0: #include "nsIPermissionManager.h" michael@0: #include "nsIObserverService.h" michael@0: #include "mozilla/Services.h" michael@0: #include "mozIThirdPartyUtil.h" michael@0: #include "nsContentUtils.h" michael@0: #include "nsUnicharUtils.h" michael@0: #include "nsPrintfCString.h" michael@0: #include "nsIConsoleService.h" michael@0: #include "jsapi.h" michael@0: michael@0: #define TOPIC_CANVAS_PERMISSIONS_PROMPT "canvas-permissions-prompt" michael@0: #define PERMISSION_CANVAS_EXTRACT_DATA "canvas/extractData" michael@0: michael@0: namespace mozilla { michael@0: namespace CanvasUtils { michael@0: michael@0: // Check site-specific permission and display prompt if appropriate. michael@0: bool IsImageExtractionAllowed(nsIDocument *aDocument, JSContext *aCx) michael@0: { michael@0: if (!aDocument || !aCx) michael@0: return false; michael@0: michael@0: nsPIDOMWindow *win = aDocument->GetWindow(); michael@0: nsCOMPtr sop(do_QueryInterface(win)); michael@0: if (sop && nsContentUtils::IsSystemPrincipal(sop->GetPrincipal())) michael@0: return true; michael@0: michael@0: // Don't show canvas prompt for chrome scripts (e.g. Page Inspector) michael@0: if (nsContentUtils::IsCallerChrome()) michael@0: return true; michael@0: michael@0: JS::AutoFilename scriptFile; michael@0: unsigned scriptLine = 0; michael@0: bool isScriptKnown = false; michael@0: if (JS::DescribeScriptedCaller(aCx, &scriptFile, &scriptLine)) { michael@0: isScriptKnown = true; michael@0: // Don't show canvas prompt for PDF.js michael@0: if (scriptFile.get() && michael@0: strcmp(scriptFile.get(), "resource://pdf.js/build/pdf.js") == 0) michael@0: return true; michael@0: } michael@0: bool isAllowed = false; michael@0: nsCOMPtr thirdPartyUtil = michael@0: do_GetService(THIRDPARTYUTIL_CONTRACTID); michael@0: nsCOMPtr permissionManager = michael@0: do_GetService(NS_PERMISSIONMANAGER_CONTRACTID); michael@0: if (thirdPartyUtil && permissionManager) { michael@0: nsCOMPtr uri; michael@0: nsresult rv = thirdPartyUtil->GetFirstPartyURI(NULL, aDocument, michael@0: getter_AddRefs(uri)); michael@0: uint32_t permission = nsIPermissionManager::UNKNOWN_ACTION; michael@0: if (NS_SUCCEEDED(rv)) { michael@0: // Allow local files to access canvas data; check content permissions michael@0: // for remote pages. michael@0: bool isFileURL = false; michael@0: (void)uri->SchemeIs("file", &isFileURL); michael@0: if (isFileURL) michael@0: permission = nsIPermissionManager::ALLOW_ACTION; michael@0: else { michael@0: rv = permissionManager->TestPermission(uri, michael@0: PERMISSION_CANVAS_EXTRACT_DATA, &permission); michael@0: } michael@0: } michael@0: michael@0: if (NS_SUCCEEDED(rv)) { michael@0: isAllowed = (permission == nsIPermissionManager::ALLOW_ACTION); michael@0: michael@0: if (!isAllowed && (permission != nsIPermissionManager::DENY_ACTION)) { michael@0: // Log all attempted canvas access and block access by third parties. michael@0: bool isThirdParty = true; michael@0: nsIURI *docURI = aDocument->GetDocumentURI(); michael@0: rv = thirdPartyUtil->IsThirdPartyURI(uri, docURI, &isThirdParty); michael@0: NS_ENSURE_SUCCESS(rv, false); michael@0: michael@0: nsCString firstPartySpec; michael@0: rv = uri->GetSpec(firstPartySpec); michael@0: nsCString docSpec; michael@0: docURI->GetSpec(docSpec); michael@0: nsPrintfCString msg("On %s: blocked access to canvas image data" michael@0: " from document %s, ", // L10n michael@0: firstPartySpec.get(), docSpec.get()); michael@0: if (isScriptKnown && scriptFile.get()) { michael@0: msg += nsPrintfCString("script from %s:%u", // L10n michael@0: scriptFile.get(), scriptLine); michael@0: } else { michael@0: msg += nsPrintfCString("unknown script"); // L10n michael@0: } michael@0: nsCOMPtr console michael@0: (do_GetService(NS_CONSOLESERVICE_CONTRACTID)); michael@0: if (console) michael@0: console->LogStringMessage(NS_ConvertUTF8toUTF16(msg).get()); michael@0: michael@0: // Log every canvas access attempt to stdout if debugging: michael@0: #ifdef DEBUG michael@0: printf("%s\n", msg.get()); michael@0: #endif michael@0: // Ensure URI is valid after logging, but before trying to notify the michael@0: // user: michael@0: NS_ENSURE_SUCCESS(rv, false); michael@0: michael@0: if (!isThirdParty) { michael@0: // Send notification so that a prompt is displayed. michael@0: nsCOMPtr obs = michael@0: mozilla::services::GetObserverService(); michael@0: obs->NotifyObservers(win, TOPIC_CANVAS_PERMISSIONS_PROMPT, michael@0: NS_ConvertUTF8toUTF16(firstPartySpec).get()); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: return isAllowed; michael@0: } michael@0: michael@0: void michael@0: DoDrawImageSecurityCheck(dom::HTMLCanvasElement *aCanvasElement, michael@0: nsIPrincipal *aPrincipal, michael@0: bool forceWriteOnly, michael@0: bool CORSUsed) michael@0: { michael@0: NS_PRECONDITION(aPrincipal, "Must have a principal here"); michael@0: michael@0: // Callers should ensure that mCanvasElement is non-null before calling this michael@0: if (!aCanvasElement) { michael@0: NS_WARNING("DoDrawImageSecurityCheck called without canvas element!"); michael@0: return; michael@0: } michael@0: michael@0: if (aCanvasElement->IsWriteOnly()) michael@0: return; michael@0: michael@0: // If we explicitly set WriteOnly just do it and get out michael@0: if (forceWriteOnly) { michael@0: aCanvasElement->SetWriteOnly(); michael@0: return; michael@0: } michael@0: michael@0: // No need to do a security check if the image used CORS for the load michael@0: if (CORSUsed) michael@0: return; michael@0: michael@0: if (aCanvasElement->NodePrincipal()->Subsumes(aPrincipal)) { michael@0: // This canvas has access to that image anyway michael@0: return; michael@0: } michael@0: michael@0: aCanvasElement->SetWriteOnly(); michael@0: } michael@0: michael@0: bool michael@0: CoerceDouble(JS::Value v, double* d) michael@0: { michael@0: if (JSVAL_IS_DOUBLE(v)) { michael@0: *d = JSVAL_TO_DOUBLE(v); michael@0: } else if (JSVAL_IS_INT(v)) { michael@0: *d = double(JSVAL_TO_INT(v)); michael@0: } else if (JSVAL_IS_VOID(v)) { michael@0: *d = 0.0; michael@0: } else { michael@0: return false; michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: } // namespace CanvasUtils michael@0: } // namespace mozilla