webapprt/win/webapprt.cpp

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/webapprt/win/webapprt.cpp	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,527 @@
     1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
     1.5 +
     1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this file,
     1.8 + * You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.9 +
    1.10 +// System headers (alphabetical)
    1.11 +#include <fcntl.h>
    1.12 +#include <io.h>
    1.13 +#include <share.h>
    1.14 +#include <sys/stat.h>
    1.15 +#include <windows.h>
    1.16 +
    1.17 +// Mozilla headers (alphabetical)
    1.18 +#include "nsIFile.h"
    1.19 +#include "nsINIParser.h"
    1.20 +#include "nsWindowsWMain.cpp"   // we want a wmain entry point
    1.21 +#include "nsXPCOMGlue.h"
    1.22 +#include "nsXPCOMPrivate.h"     // for MAXPATHLEN and XPCOM_DLL
    1.23 +#include "nsXULAppAPI.h"
    1.24 +#include "mozilla/AppData.h"
    1.25 +
    1.26 +using namespace mozilla;
    1.27 +
    1.28 +XRE_GetFileFromPathType XRE_GetFileFromPath;
    1.29 +XRE_CreateAppDataType XRE_CreateAppData;
    1.30 +XRE_FreeAppDataType XRE_FreeAppData;
    1.31 +XRE_mainType XRE_main;
    1.32 +
    1.33 +namespace {
    1.34 +  const char kAPP_INI[] = "application.ini";
    1.35 +  const char kWEBAPP_INI[] = "webapp.ini";
    1.36 +  const char kWEBAPPRT_INI[] = "webapprt.ini";
    1.37 +  const char kWEBAPPRT_PATH[] = "webapprt";
    1.38 +  const char kAPP_ENV_PREFIX[] = "XUL_APP_FILE=";
    1.39 +  const char kAPP_RT[] = "webapprt-stub.exe";
    1.40 +
    1.41 +  const wchar_t kAPP_RT_BACKUP[] = L"webapprt.old";
    1.42 +
    1.43 +  wchar_t curExePath[MAXPATHLEN];
    1.44 +  wchar_t backupFilePath[MAXPATHLEN];
    1.45 +  wchar_t iconPath[MAXPATHLEN];
    1.46 +  char profile[MAXPATHLEN];
    1.47 +  bool isProfileOverridden = false;
    1.48 +  int* pargc;
    1.49 +  char*** pargv;
    1.50 +
    1.51 +  nsresult
    1.52 +  joinPath(char* const dest,
    1.53 +           char const* const dir,
    1.54 +           char const* const leaf,
    1.55 +           size_t bufferSize)
    1.56 +  {
    1.57 +    size_t dirLen = strlen(dir);
    1.58 +    size_t leafLen = strlen(leaf);
    1.59 +    bool needsSeparator = (dirLen != 0
    1.60 +                        && dir[dirLen-1] != '\\'
    1.61 +                        && leafLen != 0
    1.62 +                        && leaf[0] != '\\');
    1.63 +
    1.64 +    if (dirLen + (needsSeparator? 1 : 0) + leafLen >= bufferSize) {
    1.65 +      return NS_ERROR_FAILURE;
    1.66 +    }
    1.67 +
    1.68 +    strncpy(dest, dir, bufferSize);
    1.69 +    char* destEnd = dest + dirLen;
    1.70 +    if (needsSeparator) {
    1.71 +      *(destEnd++) = '\\';
    1.72 +    }
    1.73 +
    1.74 +    strncpy(destEnd, leaf, leafLen);
    1.75 +    return NS_OK;
    1.76 +  }
    1.77 +
    1.78 +  /**
    1.79 +   * A helper class which calls NS_LogInit/NS_LogTerm in its scope.
    1.80 +   */
    1.81 +  class ScopedLogging
    1.82 +  {
    1.83 +    public:
    1.84 +      ScopedLogging() { NS_LogInit(); }
    1.85 +      ~ScopedLogging() { NS_LogTerm(); }
    1.86 +  };
    1.87 +
    1.88 +  /**
    1.89 +   * A helper class for scope-guarding nsXREAppData.
    1.90 +   */
    1.91 +  class ScopedXREAppData
    1.92 +  {
    1.93 +    public:
    1.94 +      ScopedXREAppData()
    1.95 +        : mAppData(nullptr) { }
    1.96 +
    1.97 +      nsresult
    1.98 +      create(nsIFile* aINIFile)
    1.99 +      {
   1.100 +        return XRE_CreateAppData(aINIFile, &mAppData);
   1.101 +      }
   1.102 +
   1.103 +      ~ScopedXREAppData()
   1.104 +      {
   1.105 +        if (nullptr != mAppData) {
   1.106 +          XRE_FreeAppData(mAppData);
   1.107 +        }
   1.108 +      }
   1.109 +
   1.110 +      nsXREAppData* const
   1.111 +      operator->()
   1.112 +      {
   1.113 +        return get();
   1.114 +      }
   1.115 +
   1.116 +      nsXREAppData
   1.117 +      operator*()
   1.118 +      {
   1.119 +        return *get();
   1.120 +      }
   1.121 +
   1.122 +      operator
   1.123 +      nsXREAppData*()
   1.124 +      {
   1.125 +        return get();
   1.126 +      }
   1.127 +    private:
   1.128 +      nsXREAppData* mAppData;
   1.129 +      nsXREAppData* const get() { return mAppData; }
   1.130 +  };
   1.131 +
   1.132 +  void
   1.133 +  Output(const wchar_t *fmt, ... )
   1.134 +  {
   1.135 +    va_list ap;
   1.136 +    va_start(ap, fmt);
   1.137 +
   1.138 +    wchar_t msg[1024];
   1.139 +    _vsnwprintf_s(msg, _countof(msg), _countof(msg), fmt, ap);
   1.140 +
   1.141 +    MessageBoxW(nullptr, msg, L"Web Runtime", MB_OK);
   1.142 +
   1.143 +    va_end(ap);
   1.144 +  }
   1.145 +
   1.146 +  void
   1.147 +  Output(const char *fmt, ... )
   1.148 +  {
   1.149 +    va_list ap;
   1.150 +    va_start(ap, fmt);
   1.151 +
   1.152 +    char msg[1024];
   1.153 +    vsnprintf(msg, sizeof(msg), fmt, ap);
   1.154 +
   1.155 +    wchar_t wide_msg[1024];
   1.156 +    MultiByteToWideChar(CP_UTF8,
   1.157 +                        0,
   1.158 +                        msg,
   1.159 +                        -1,
   1.160 +                        wide_msg,
   1.161 +                        _countof(wide_msg));
   1.162 +    Output(wide_msg);
   1.163 +
   1.164 +    va_end(ap);
   1.165 +  }
   1.166 +
   1.167 +  const nsDynamicFunctionLoad kXULFuncs[] = {
   1.168 +      { "XRE_GetFileFromPath", (NSFuncPtr*) &XRE_GetFileFromPath },
   1.169 +      { "XRE_CreateAppData", (NSFuncPtr*) &XRE_CreateAppData },
   1.170 +      { "XRE_FreeAppData", (NSFuncPtr*) &XRE_FreeAppData },
   1.171 +      { "XRE_main", (NSFuncPtr*) &XRE_main },
   1.172 +      { nullptr, nullptr }
   1.173 +  };
   1.174 +
   1.175 +  bool
   1.176 +  AttemptCopyAndLaunch(wchar_t* src)
   1.177 +  {
   1.178 +    // Rename the old app executable
   1.179 +    if (FALSE == ::MoveFileExW(curExePath,
   1.180 +                               backupFilePath,
   1.181 +                               MOVEFILE_REPLACE_EXISTING)) {
   1.182 +      return false;
   1.183 +    }
   1.184 +
   1.185 +    // Copy webapprt-stub.exe from the Firefox dir to the app's dir
   1.186 +    if (FALSE == ::CopyFileW(src,
   1.187 +                             curExePath,
   1.188 +                             TRUE)) {
   1.189 +      // Try to move the old file back to its original location
   1.190 +      ::MoveFileW(backupFilePath,
   1.191 +                  curExePath);
   1.192 +      return false;
   1.193 +    }
   1.194 +
   1.195 +    // XXX: We will soon embed the app's icon in the EXE here
   1.196 +
   1.197 +    STARTUPINFOW si;
   1.198 +    PROCESS_INFORMATION pi;
   1.199 +
   1.200 +    ::ZeroMemory(&si, sizeof(si));
   1.201 +    si.cb = sizeof(si);
   1.202 +    ::ZeroMemory(&pi, sizeof(pi));
   1.203 +
   1.204 +    if (!CreateProcessW(curExePath, // Module name
   1.205 +                        nullptr,    // Command line
   1.206 +                        nullptr,    // Process handle not inheritable
   1.207 +                        nullptr,    // Thread handle not inheritable
   1.208 +                        FALSE,      // Set handle inheritance to FALSE
   1.209 +                        0,          // No creation flags
   1.210 +                        nullptr,    // Use parent's environment block
   1.211 +                        nullptr,    // Use parent's starting directory
   1.212 +                        &si,
   1.213 +                        &pi)) {
   1.214 +      return false;
   1.215 +    }
   1.216 +
   1.217 +    // Close process and thread handles.
   1.218 +    CloseHandle( pi.hProcess );
   1.219 +    CloseHandle( pi.hThread );
   1.220 +
   1.221 +    return true;
   1.222 +  }
   1.223 +
   1.224 +  bool
   1.225 +  AttemptCopyAndLaunch(char* srcUtf8)
   1.226 +  {
   1.227 +    wchar_t src[MAXPATHLEN];
   1.228 +    if (0 == MultiByteToWideChar(CP_UTF8,
   1.229 +                                 0,
   1.230 +                                 srcUtf8,
   1.231 +                                 -1,
   1.232 +                                 src,
   1.233 +                                 MAXPATHLEN)) {
   1.234 +      return false;
   1.235 +    }
   1.236 +
   1.237 +    return AttemptCopyAndLaunch(src);
   1.238 +  }
   1.239 +
   1.240 +  bool
   1.241 +  AttemptGRELoadAndLaunch(char* greDir)
   1.242 +  {
   1.243 +    nsresult rv;
   1.244 +
   1.245 +    char xpcomDllPath[MAXPATHLEN];
   1.246 +    rv = joinPath(xpcomDllPath, greDir, XPCOM_DLL, MAXPATHLEN);
   1.247 +    NS_ENSURE_SUCCESS(rv, false);
   1.248 +
   1.249 +    rv = XPCOMGlueStartup(xpcomDllPath);
   1.250 +    NS_ENSURE_SUCCESS(rv, false);
   1.251 +
   1.252 +    rv = XPCOMGlueLoadXULFunctions(kXULFuncs);
   1.253 +    NS_ENSURE_SUCCESS(rv, false);
   1.254 +
   1.255 +    // NOTE: The GRE has successfully loaded, so we can use XPCOM now
   1.256 +    { // Scope for any XPCOM stuff we create
   1.257 +
   1.258 +      ScopedLogging log;
   1.259 +
   1.260 +      // Get the path to the runtime.
   1.261 +      char rtPath[MAXPATHLEN];
   1.262 +      rv = joinPath(rtPath, greDir, kWEBAPPRT_PATH, MAXPATHLEN);
   1.263 +      NS_ENSURE_SUCCESS(rv, false);
   1.264 +
   1.265 +      // Get the path to the runtime's INI file.
   1.266 +      char rtIniPath[MAXPATHLEN];
   1.267 +      rv = joinPath(rtIniPath, rtPath, kWEBAPPRT_INI, MAXPATHLEN);
   1.268 +      NS_ENSURE_SUCCESS(rv, false);
   1.269 +
   1.270 +      // Load the runtime's INI from its path.
   1.271 +      nsCOMPtr<nsIFile> rtINI;
   1.272 +      rv = XRE_GetFileFromPath(rtIniPath, getter_AddRefs(rtINI));
   1.273 +      NS_ENSURE_SUCCESS(rv, false);
   1.274 +
   1.275 +      bool exists;
   1.276 +      rv = rtINI->Exists(&exists);
   1.277 +      if (NS_FAILED(rv) || !exists)
   1.278 +        return false;
   1.279 +
   1.280 +      ScopedXREAppData webShellAppData;
   1.281 +      rv = webShellAppData.create(rtINI);
   1.282 +      NS_ENSURE_SUCCESS(rv, false);
   1.283 +
   1.284 +      if (!isProfileOverridden) {
   1.285 +        SetAllocatedString(webShellAppData->profile, profile);
   1.286 +        SetAllocatedString(webShellAppData->name, profile);
   1.287 +      }
   1.288 +
   1.289 +      nsCOMPtr<nsIFile> directory;
   1.290 +      rv = XRE_GetFileFromPath(rtPath, getter_AddRefs(directory));
   1.291 +      NS_ENSURE_SUCCESS(rv, false);
   1.292 +
   1.293 +      nsCOMPtr<nsIFile> xreDir;
   1.294 +      rv = XRE_GetFileFromPath(greDir, getter_AddRefs(xreDir));
   1.295 +      NS_ENSURE_SUCCESS(rv, false);
   1.296 +
   1.297 +      xreDir.forget(&webShellAppData->xreDirectory);
   1.298 +      NS_IF_RELEASE(webShellAppData->directory);
   1.299 +      directory.forget(&webShellAppData->directory);
   1.300 +
   1.301 +      // There is only XUL.
   1.302 +      XRE_main(*pargc, *pargv, webShellAppData, 0);
   1.303 +    }
   1.304 +
   1.305 +    return true;
   1.306 +  }
   1.307 +
   1.308 +  bool
   1.309 +  AttemptLoadFromDir(char* firefoxDir)
   1.310 +  {
   1.311 +    nsresult rv;
   1.312 +
   1.313 +    // Here we're going to open Firefox's application.ini
   1.314 +    char appIniPath[MAXPATHLEN];
   1.315 +    rv = joinPath(appIniPath, firefoxDir, kAPP_INI, MAXPATHLEN);
   1.316 +    NS_ENSURE_SUCCESS(rv, false);
   1.317 +
   1.318 +    nsINIParser parser;
   1.319 +    rv = parser.Init(appIniPath);
   1.320 +    NS_ENSURE_SUCCESS(rv, false);
   1.321 +
   1.322 +    // Get buildid of FF we're trying to load
   1.323 +    char buildid[MAXPATHLEN]; // This isn't a path, so MAXPATHLEN doesn't
   1.324 +                              // necessarily make sense, but it's a
   1.325 +                              // convenient number to use.
   1.326 +    rv = parser.GetString("App",
   1.327 +                          "BuildID",
   1.328 +                          buildid,
   1.329 +                          MAXPATHLEN);
   1.330 +    NS_ENSURE_SUCCESS(rv, false);
   1.331 +
   1.332 +    if (0 == strcmp(buildid, NS_STRINGIFY(GRE_BUILDID))) {
   1.333 +      return AttemptGRELoadAndLaunch(firefoxDir);
   1.334 +    }
   1.335 +
   1.336 +    char webAppRTExe[MAXPATHLEN];
   1.337 +    rv = joinPath(webAppRTExe, firefoxDir, kAPP_RT, MAXPATHLEN);
   1.338 +    NS_ENSURE_SUCCESS(rv, false);
   1.339 +
   1.340 +    return AttemptCopyAndLaunch(webAppRTExe);
   1.341 +  }
   1.342 +
   1.343 +  bool
   1.344 +  GetFirefoxDirFromRegistry(char* firefoxDir)
   1.345 +  {
   1.346 +    HKEY key;
   1.347 +    wchar_t wideGreDir[MAXPATHLEN];
   1.348 +
   1.349 +    if (ERROR_SUCCESS !=
   1.350 +                RegOpenKeyExW(HKEY_LOCAL_MACHINE,
   1.351 +                              L"SOFTWARE\\Microsoft\\Windows"
   1.352 +                              L"\\CurrentVersion\\App paths\\firefox.exe",
   1.353 +                              0,
   1.354 +                              KEY_READ,
   1.355 +                              &key)) {
   1.356 +      return false;
   1.357 +    }
   1.358 +
   1.359 +    DWORD length = MAXPATHLEN * sizeof(wchar_t);
   1.360 +    // XXX: When Vista/XP64 become our minimum supported client, we can use
   1.361 +    //      RegGetValue instead
   1.362 +    if (ERROR_SUCCESS != RegQueryValueExW(key,
   1.363 +                                          L"Path",
   1.364 +                                          nullptr,
   1.365 +                                          nullptr,
   1.366 +                                          reinterpret_cast<BYTE*>(wideGreDir),
   1.367 +                                          &length)) {
   1.368 +      RegCloseKey(key);
   1.369 +      return false;
   1.370 +    };
   1.371 +    RegCloseKey(key);
   1.372 +
   1.373 +    // According to this article, we need to write our own null terminator:
   1.374 +    // http://msdn.microsoft.com/en-us/library/ms724911%28v=vs.85%29.aspx
   1.375 +    length = length / sizeof(wchar_t);
   1.376 +    if (wideGreDir[length] != L'\0') {
   1.377 +      if (length >= MAXPATHLEN) {
   1.378 +        return false;
   1.379 +      }
   1.380 +      wideGreDir[length] = L'\0';
   1.381 +    }
   1.382 +
   1.383 +    if (0 == WideCharToMultiByte(CP_UTF8,
   1.384 +                                 0,
   1.385 +                                 wideGreDir,
   1.386 +                                 -1,
   1.387 +                                 firefoxDir,
   1.388 +                                 MAXPATHLEN,
   1.389 +                                 nullptr,
   1.390 +                                 nullptr)) {
   1.391 +      return false;
   1.392 +    }
   1.393 +
   1.394 +    return true;
   1.395 +  }
   1.396 +};
   1.397 +
   1.398 +
   1.399 +
   1.400 +//////////////////////////////////////////////////////////////////////////////
   1.401 +// main
   1.402 +//
   1.403 +// Note: XPCOM cannot be used until AttemptGRELoad has returned successfully.
   1.404 +int
   1.405 +main(int argc, char* argv[])
   1.406 +{
   1.407 +  pargc = &argc;
   1.408 +  pargv = &argv;
   1.409 +  nsresult rv;
   1.410 +  char buffer[MAXPATHLEN];
   1.411 +  wchar_t wbuffer[MAXPATHLEN];
   1.412 +
   1.413 +  // Set up curEXEPath
   1.414 +  if (!GetModuleFileNameW(0, wbuffer, MAXPATHLEN)) {
   1.415 +    Output("Couldn't calculate the application directory.");
   1.416 +    return 255;
   1.417 +  }
   1.418 +  wcsncpy(curExePath, wbuffer, MAXPATHLEN);
   1.419 +
   1.420 +  // Get the current directory into wbuffer
   1.421 +  wchar_t* lastSlash = wcsrchr(wbuffer, L'\\');
   1.422 +  if (!lastSlash) {
   1.423 +    Output("Application directory format not understood.");
   1.424 +    return 255;
   1.425 +  }
   1.426 +  *(++lastSlash) = L'\0';
   1.427 +
   1.428 +  // Set up backup file path
   1.429 +  if (wcslen(wbuffer) + _countof(kAPP_RT_BACKUP) >= MAXPATHLEN) {
   1.430 +    Output("Application directory path is too long (couldn't set up backup file path).");
   1.431 +  }
   1.432 +  wcsncpy(lastSlash, kAPP_RT_BACKUP, _countof(kAPP_RT_BACKUP));
   1.433 +  wcsncpy(backupFilePath, wbuffer, MAXPATHLEN);
   1.434 +
   1.435 +  *lastSlash = L'\0';
   1.436 +
   1.437 +  // Convert current directory to utf8 and stuff it in buffer
   1.438 +  if (0 == WideCharToMultiByte(CP_UTF8,
   1.439 +                               0,
   1.440 +                               wbuffer,
   1.441 +                               -1,
   1.442 +                               buffer,
   1.443 +                               MAXPATHLEN,
   1.444 +                               nullptr,
   1.445 +                               nullptr)) {
   1.446 +    Output("Application directory could not be processed.");
   1.447 +    return 255;
   1.448 +  }
   1.449 +
   1.450 +  // Check if the runtime was executed with the "-profile" argument
   1.451 +  for (int i = 1; i < argc; i++) {
   1.452 +    if (!strcmp(argv[i], "-profile")) {
   1.453 +      isProfileOverridden = true;
   1.454 +      break;
   1.455 +    }
   1.456 +  }
   1.457 +
   1.458 +  // First attempt at loading Firefox binaries:
   1.459 +  //   Check if the webapprt is in the same directory as the Firefox binary.
   1.460 +  //   This is the case during WebappRT chrome and content tests.
   1.461 +  if (AttemptLoadFromDir(buffer)) {
   1.462 +    return 0;
   1.463 +  }
   1.464 +
   1.465 +  // Set up appIniPath with path to webapp.ini.
   1.466 +  // This should be in the same directory as the running executable.
   1.467 +  char appIniPath[MAXPATHLEN];
   1.468 +  if (NS_FAILED(joinPath(appIniPath, buffer, kWEBAPP_INI, MAXPATHLEN))) {
   1.469 +    Output("Path to webapp.ini could not be processed.");
   1.470 +    return 255;
   1.471 +  }
   1.472 +
   1.473 +  // Open webapp.ini as an INI file (as opposed to using the
   1.474 +  // XRE webapp.ini-specific processing we do later)
   1.475 +  nsINIParser parser;
   1.476 +  if (NS_FAILED(parser.Init(appIniPath))) {
   1.477 +    Output("Could not open webapp.ini");
   1.478 +    return 255;
   1.479 +  }
   1.480 +
   1.481 +  // Set up our environment to know where webapp.ini was loaded from.
   1.482 +  char appEnv[MAXPATHLEN + _countof(kAPP_ENV_PREFIX)];
   1.483 +  strcpy(appEnv, kAPP_ENV_PREFIX);
   1.484 +  strcpy(appEnv + _countof(kAPP_ENV_PREFIX) - 1, appIniPath);
   1.485 +  if (putenv(appEnv)) {
   1.486 +    Output("Couldn't set up app environment");
   1.487 +    return 255;
   1.488 +  }
   1.489 +
   1.490 +  if (!isProfileOverridden) {
   1.491 +    // Get profile dir from webapp.ini
   1.492 +    if (NS_FAILED(parser.GetString("Webapp",
   1.493 +                                   "Profile",
   1.494 +                                   profile,
   1.495 +                                   MAXPATHLEN))) {
   1.496 +      Output("Unable to retrieve profile from web app INI file");
   1.497 +      return 255;
   1.498 +    }
   1.499 +  }
   1.500 +
   1.501 +  char firefoxDir[MAXPATHLEN];
   1.502 +
   1.503 +  // Second attempt at loading Firefox binaries:
   1.504 +  //   Get the location of Firefox from our webapp.ini
   1.505 +
   1.506 +  // XXX: This string better be UTF-8...
   1.507 +  rv = parser.GetString("WebappRT",
   1.508 +                        "InstallDir",
   1.509 +                        firefoxDir,
   1.510 +                        MAXPATHLEN);
   1.511 +  if (NS_SUCCEEDED(rv)) {
   1.512 +    if (AttemptLoadFromDir(firefoxDir)) {
   1.513 +      return 0;
   1.514 +    }
   1.515 +  }
   1.516 +
   1.517 +  // Third attempt at loading Firefox binaries:
   1.518 +  //   Get the location of Firefox from the registry
   1.519 +  if (GetFirefoxDirFromRegistry(firefoxDir)) {
   1.520 +    if (AttemptLoadFromDir(firefoxDir)) {
   1.521 +      // XXX: Write gre dir location to webapp.ini
   1.522 +      return 0;
   1.523 +    }
   1.524 +  }
   1.525 +
   1.526 +  // We've done all we know how to do to try to find and launch FF
   1.527 +  Output("This app requires that Firefox version 16 or above is installed."
   1.528 +         " Firefox 16+ has not been detected.");
   1.529 +  return 255;
   1.530 +}

mercurial