| /* | 
 |  * 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" | 
 | #include "nsIDOMWindowInternal.h" | 
 | #include "nsIDOMLocation.h" | 
 | #include "nsXPCOMStrings.h" | 
 | #include "nsICategoryManager.h" | 
 | #include "nsIJSContextStack.h" | 
 | #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) { | 
 |   // 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; | 
 |   } | 
 |   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 | 
 | NS_IMETHODIMP ExternalWrapper::Init(nsIDOMWindow* suppliedWindow, | 
 |     PRBool *_retval) { | 
 |   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)"); | 
 |   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; | 
 | } | 
 |  | 
 | // TODO(jat): remove suppliedUrl and update hosted.html API | 
 | NS_IMETHODIMP ExternalWrapper::Connect(const nsACString& suppliedUrl, | 
 |                 const nsACString& sessionKey, const nsACString& aAddr, | 
 |                 const nsACString& aModuleName, const nsACString& hostedHtmlVersion, | 
 |                 PRBool *_retval) { | 
 |   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 = nsnull; | 
 |   } | 
 |   return NS_OK; | 
 | } | 
 |  | 
 | NS_IMETHODIMP ExternalWrapper::CanGetProperty(const nsIID * iid, | 
 |     const PRUnichar *propertyName, char **_retval) { | 
 |   Debug::log(Debug::Spam) << "ExternalWrapper::CanGetProperty" << Debug::flush; | 
 |   *_retval = nsnull; | 
 |   return NS_OK; | 
 | } | 
 | NS_IMETHODIMP ExternalWrapper::CanSetProperty(const nsIID * iid, | 
 |     const PRUnichar *propertyName, char **_retval) { | 
 |   Debug::log(Debug::Spam) << "ExternalWrapper::CanSetProperty" << Debug::flush; | 
 |   *_retval = nsnull; | 
 |   return NS_OK; | 
 | } |