blob: d32f1fc119cd27b57c4f9dd846df72eff0a8155c [file] [log] [blame]
/*
* Copyright 2008 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
#include "ExternalWrapper.h"
#include "nsIHttpProtocolHandler.h"
#include "nsISupports.h"
#include "nsNetCID.h"
#include "nsCOMPtr.h"
#include "nsMemory.h"
#include "nsServiceManagerUtils.h"
#include "nsIPromptService.h"
#include "nsIDOMWindow.h"
#if GECKO_VERSION <= 7000
#include "nsIDOMWindowInternal.h"
#endif //GECKO_VERSION
#include "nsIDOMLocation.h"
#include "nsXPCOMStrings.h"
#include "nsICategoryManager.h"
#if GECKO_VERSION <= 22000
#include "nsIJSContextStack.h"
#else
#include "nsIXPConnect.h"
#endif //GECKO_VERSION
#include "nsIScriptContext.h"
#include "nsIScriptGlobalObject.h"
#include "nsPIDOMWindow.h"
#include "LoadModuleMessage.h"
#include "ServerMethods.h"
#include "BrowserChannel.h"
#include "AllowedConnections.h"
#if GECKO_VERSION >= 1900
#include "nsIClassInfoImpl.h"
#endif //GECKO_VERSION
#if GECKO_VERSION >= 2000
NS_IMPL_CLASSINFO(ExternalWrapper, NULL, 0, OOPHM_CID)
#endif //GECKO_VERSION
NS_IMPL_ISUPPORTS2_CI(ExternalWrapper, IOOPHM, nsISecurityCheckedComponent)
ExternalWrapper::ExternalWrapper() {
Debug::log(Debug::Debugging) << "ExternalWrapper::ExternalWrapper(this="
<< this << ")" << Debug::flush;
preferences = new Preferences();
windowWatcher = do_GetService(NS_WINDOWWATCHER_CONTRACTID);
if (!windowWatcher) {
Debug::log(Debug::Warning) << "Can't get WindowWatcher service"
<< Debug::flush;
return;
}
}
ExternalWrapper::~ExternalWrapper() {
Debug::log(Debug::Debugging) << "ExternalWrapper::~ExternalWrapper(this="
<< this << ")" << Debug::flush;
}
// define the CID for nsIHttpProtocolHandler
static NS_DEFINE_CID(kHttpHandlerCID, NS_HTTPPROTOCOLHANDLER_CID);
static nsresult getUserAgent(std::string& userAgent) {
nsresult res;
nsCOMPtr<nsIHttpProtocolHandler> http = do_GetService(kHttpHandlerCID, &res);
if (NS_FAILED(res)) {
return res;
}
nsCString userAgentStr;
res = http->GetUserAgent(userAgentStr);
if (NS_FAILED(res)) {
return res;
}
userAgent.assign(userAgentStr.get());
return NS_OK;
}
/**
* Get JS window object.
*
* @param win output parameter to store the window object
* @return true on success
*/
static bool getWindowObject(nsIDOMWindow** win) {
#if GECKO_VERSION <= 22000
// Get JSContext from stack.
nsCOMPtr<nsIJSContextStack> stack =
do_GetService("@mozilla.org/js/xpc/ContextStack;1");
if (!stack) {
Debug::log(Debug::Error) << "getWindowObject: no context stack"
<< Debug::flush;
return false;
}
JSContext *cx;
if (NS_FAILED(stack->Peek(&cx)) || !cx) {
Debug::log(Debug::Error) << "getWindowObject: no context on stack"
<< Debug::flush;
return false;
}
#else
nsresult rv;
nsCOMPtr<nsIXPConnect> xpc = do_GetService(nsIXPConnect::GetCID(), &rv);
if (!NS_SUCCEEDED(rv)) {
Debug::log(Debug::Error) << "getWindowObject: no context stack"
<< Debug::flush;
return false;
}
JSContext *cx = xpc->GetCurrentJSContext();
if (!cx) {
Debug::log(Debug::Error) << "getWindowObject: no context on stack"
<< Debug::flush;
return false;
}
#endif
if (!(::JS_GetOptions(cx) & JSOPTION_PRIVATE_IS_NSISUPPORTS)) {
Debug::log(Debug::Error)
<< "getWindowObject: context doesn't have nsISupports" << Debug::flush;
return false;
}
nsCOMPtr<nsIScriptContext> scx =
do_QueryInterface(static_cast<nsISupports *>
(::JS_GetContextPrivate(cx)));
if (!scx) {
Debug::log(Debug::Error) << "getWindowObject: no script context"
<< Debug::flush;
return false;
}
nsCOMPtr<nsIScriptGlobalObject> globalObj = scx->GetGlobalObject();
if (!globalObj) {
Debug::log(Debug::Error) << "getWindowObject: no global object"
<< Debug::flush;
return false;
}
nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(globalObj);
if (!window) {
Debug::log(Debug::Error) << "getWindowObject: window is null"
<< Debug::flush;
return false;
}
NS_ADDREF(*win = window);
return true;
}
/**
* Get the URL of a window.
*
* @param win DOMWindowInternal instance
* @param url output wide string for the URL
* @return true if successful
*/
static bool getWindowUrl(nsIDOMWindowInternal* win, nsAString& url) {
nsCOMPtr<nsIDOMLocation> loc;
if (win->GetLocation(getter_AddRefs(loc)) != NS_OK) {
Debug::log(Debug::Info) << "Unable to get location" << Debug::flush;
return false;
}
if (loc->GetHref(url) != NS_OK) {
Debug::log(Debug::Info) << "Unable to get URL" << Debug::flush;
return false;
}
return true;
}
/**
* Get the top-level window for a given window, and its URL.
*
* @param win window to start from
* @param topWinRet output parameter to store top window
* @param topUrl output parameter to store URL
* @return true on success, false on error (already logged)
*/
static bool getTopWindow(nsIDOMWindow* win, nsIDOMWindowInternal** topWinRet,
nsAString& topUrl) {
nsCOMPtr<nsIDOMWindow> topWin;
if (win->GetTop(getter_AddRefs(topWin)) != NS_OK) {
Debug::log(Debug::Error) << "Unable to get top window" << Debug::flush;
return false;
}
nsresult rv;
nsCOMPtr<nsIDOMWindowInternal> topWinInt = do_QueryInterface(topWin, &rv);
if (rv != NS_OK) {
Debug::log(Debug::Error) << "Unable to QI DOMWindowInternal"
<< Debug::flush;
return false;
}
if (!getWindowUrl(topWinInt, topUrl)) {
Debug::log(Debug::Error) << "Unable to get url of top window"
<< Debug::flush;
return false;
}
NS_ADDREF(*topWinRet = topWinInt);
return true;
}
std::string ExternalWrapper::computeTabIdentity() {
std::string returnVal;
if (!windowWatcher) {
return returnVal;
}
// The nsPIDOMWindow interface of our top-level window appears to be stable
// across refreshes, so we will use that for our tab ID.
nsCOMPtr<nsPIDOMWindow> privateWin = do_QueryInterface(topWindow);
if (!privateWin) {
return returnVal;
}
char buf[20]; // typically 8-16 hex digits plus 0x, not horrible if truncated
snprintf(buf, sizeof(buf), "%p", privateWin.get());
buf[19] = 0; // ensure null termination
returnVal = buf;
return returnVal;
}
// TODO(jat): remove suppliedWindow and update hosted.html API
#if GECKO_VERSION < 10000
NS_IMETHODIMP ExternalWrapper::Init(nsIDOMWindow* suppliedWindow,
PRBool *_retval) {
#else
NS_IMETHODIMP ExternalWrapper::Init(nsIDOMWindow* suppliedWindow,
bool *_retval) {
#endif //GECKO_VERSION
Debug::log(Debug::Debugging) << "Plugin initialized from hosted.html"
<< Debug::flush;
*_retval = false;
nsCOMPtr<nsIDOMWindow> computedWindow;
if (getWindowObject(getter_AddRefs(computedWindow))) {
Debug::log(Debug::Debugging) << " passed window=" << suppliedWindow
<< ", computed=" << computedWindow << Debug::flush;
domWindow = computedWindow;
} else {
Debug::log(Debug::Warning) << " using supplied window object"
<< Debug::flush;
// TODO(jat): remove this
domWindow = suppliedWindow;
}
if (getTopWindow(domWindow, getter_AddRefs(topWindow), url)) {
*_retval = true;
}
return NS_OK;
}
bool ExternalWrapper::askUserToAllow(const std::string& url) {
nsCOMPtr<nsIPromptService> promptService = do_GetService(
"@mozilla.org/embedcomp/prompt-service;1");
if (!promptService) {
return false;
}
NS_ConvertASCIItoUTF16 title("Allow GWT Developer Plugin Connection");
NS_ConvertASCIItoUTF16 text("The web and code server combination is unrecognized and requesting a GWT "
"developer plugin connection -- do you want to allow it?");
NS_ConvertASCIItoUTF16 checkMsg("Remember this decision for this server "
"(change in GWT Developer Plugin preferences)");
#if GECKO_VERSION < 10000
// Please see: https://bugzilla.mozilla.org/show_bug.cgi?id=681188
PRBool remember = false;
PRBool include = true;
if (promptService->ConfirmCheck(domWindow.get(), title.get(), text.get(),
checkMsg.get(), &remember, &include) != NS_OK) {
return false;
}
if (remember) {
std::string host = AllowedConnections::getHostFromUrl(url);
std::string server = AllowedConnections::getCodeServerFromUrl(url);
preferences->addNewRule(host + "/" + server, !include);
}
return include;
#else
bool remember = false;
bool include = true;
if (promptService->ConfirmCheck(domWindow.get(), title.get(), text.get(),
checkMsg.get(), &remember, &include) != NS_OK) {
return false;
}
if (remember) {
std::string host = AllowedConnections::getHostFromUrl(url);
std::string server = AllowedConnections::getCodeServerFromUrl(url);
preferences->addNewRule(host + "/" + server, !include);
}
return include;
#endif //GECKO_VERSION
}
// TODO(jat): remove suppliedUrl and update hosted.html API
#if GECKO_VERSION < 10000
NS_IMETHODIMP ExternalWrapper::Connect(const nsACString& suppliedUrl,
const nsACString& sessionKey, const nsACString& aAddr,
const nsACString& aModuleName, const nsACString& hostedHtmlVersion,
PRBool *_retval) {
#else
NS_IMETHODIMP ExternalWrapper::Connect(const nsACString& suppliedUrl,
const nsACString& sessionKey, const nsACString& aAddr,
const nsACString& aModuleName, const nsACString& hostedHtmlVersion,
bool *_retval) {
#endif //GECKO_VERSION
Debug::log(Debug::Info) << "Connect(url=" << url << ", sessionKey="
<< sessionKey << ", address=" << aAddr << ", module=" << aModuleName
<< ", hostedHtmlVersion=" << hostedHtmlVersion << Debug::flush;
// TODO: string utilities?
nsCString urlAutoStr;
NS_UTF16ToCString(url, NS_CSTRING_ENCODING_UTF8, urlAutoStr);
nsCString sessionKeyAutoStr(sessionKey);
nsCString addrAutoStr(aAddr);
nsCString moduleAutoStr(aModuleName);
nsCString hostedHtmlVersionAutoStr(hostedHtmlVersion);
std::string hostedUrl(addrAutoStr.get());
std::string urlStr(urlAutoStr.get());
bool allowed = false;
std::string webHost = AllowedConnections::getHostFromUrl(urlStr);
std::string codeServer = AllowedConnections::getCodeServerFromUrl(urlStr);
if (!AllowedConnections::matchesRule( webHost, codeServer, &allowed)) {
// If we didn't match an existing rule, prompt the user
allowed = askUserToAllow(urlStr);
}
if (!allowed) {
*_retval = false;
return NS_OK;
}
size_t index = hostedUrl.find(':');
if (index == std::string::npos) {
*_retval = false;
return NS_OK;
}
std::string hostPart = hostedUrl.substr(0, index);
std::string portPart = hostedUrl.substr(index + 1);
// TODO(jat): leaks HostChannel -- need to save it in a session object and
// return that so the host page can call a disconnect method on it at unload
// time or when it gets GC'd.
HostChannel* channel = new HostChannel();
Debug::log(Debug::Debugging) << "Connecting..." << Debug::flush;
if (!channel->connectToHost(hostPart.c_str(),
atoi(portPart.c_str()))) {
*_retval = false;
return NS_OK;
}
Debug::log(Debug::Debugging) << "...Connected" << Debug::flush;
sessionHandler.reset(new FFSessionHandler(channel/*, ctx*/));
std::string hostedHtmlVersionStr(hostedHtmlVersionAutoStr.get());
if (!channel->init(sessionHandler.get(), BROWSERCHANNEL_PROTOCOL_VERSION,
BROWSERCHANNEL_PROTOCOL_VERSION, hostedHtmlVersionStr)) {
*_retval = false;
return NS_OK;
}
std::string moduleName(moduleAutoStr.get());
std::string userAgent;
// get the user agent
nsresult res = getUserAgent(userAgent);
if (NS_FAILED(res)) {
return res;
}
std::string tabKeyStr = computeTabIdentity();
std::string sessionKeyStr(sessionKeyAutoStr.get());
LoadModuleMessage::send(*channel, urlStr, tabKeyStr, sessionKeyStr,
moduleName, userAgent, sessionHandler.get());
// TODO: return session object?
*_retval = true;
return NS_OK;
}
// nsISecurityCheckedComponent
static char* cloneAllAccess() {
static const char allAccess[] = "allAccess";
return static_cast<char*>(nsMemory::Clone(allAccess, sizeof(allAccess)));
}
static bool strEquals(const PRUnichar* utf16, const char* ascii) {
nsCString utf8;
NS_UTF16ToCString(nsDependentString(utf16), NS_CSTRING_ENCODING_UTF8, utf8);
return strcmp(ascii, utf8.get()) == 0;
}
NS_IMETHODIMP ExternalWrapper::CanCreateWrapper(const nsIID * iid,
char **_retval) {
Debug::log(Debug::Spam) << "ExternalWrapper::CanCreateWrapper"
<< Debug::flush;
*_retval = cloneAllAccess();
return NS_OK;
}
NS_IMETHODIMP ExternalWrapper::CanCallMethod(const nsIID * iid,
const PRUnichar *methodName, char **_retval) {
Debug::log(Debug::Spam) << "ExternalWrapper::CanCallMethod" << Debug::flush;
if (strEquals(methodName, "connect") || strEquals(methodName, "init")) {
*_retval = cloneAllAccess();
} else {
*_retval = nullptr;
}
return NS_OK;
}
NS_IMETHODIMP ExternalWrapper::CanGetProperty(const nsIID * iid,
const PRUnichar *propertyName, char **_retval) {
Debug::log(Debug::Spam) << "ExternalWrapper::CanGetProperty" << Debug::flush;
*_retval = nullptr;
return NS_OK;
}
NS_IMETHODIMP ExternalWrapper::CanSetProperty(const nsIID * iid,
const PRUnichar *propertyName, char **_retval) {
Debug::log(Debug::Spam) << "ExternalWrapper::CanSetProperty" << Debug::flush;
*_retval = nullptr;
return NS_OK;
}