browser/components/migration/nsEdgeReadingListExtractor.cpp
author Jared Wein <jwein@mozilla.com>
Tue, 08 Sep 2015 08:52:32 -0400
changeset 293931 b14d437728aaf5f232830d8e10de80caa62b22e0
parent 292956 395305136abd0d6ae82b25f7ef55d43669a8903b
child 293998 0a9064a3b26e5c21291167a47d868a056c157e02
permissions -rw-r--r--
Bug 1200656 - Reading list import from Edge leaks. r=jimm

/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "nsEdgeReadingListExtractor.h"

#include <windows.h>

#include "nsCOMPtr.h"
#include "nsIConsoleService.h"
#include "nsIMutableArray.h"
#include "nsIWritablePropertyBag2.h"
#include "nsNetUtil.h"
#include "nsServiceManagerUtils.h"
#include "nsWindowsMigrationUtils.h"

#define NS_HANDLE_JET_ERROR(err) { \
  if (err < JET_errSuccess) {	\
    rv = ConvertJETError(err); \
    goto CloseDB; \
  } \
}

#define MAX_URL_LENGTH 4168
#define MAX_TITLE_LENGTH 1024

NS_IMPL_ISUPPORTS(nsEdgeReadingListExtractor, nsIEdgeReadingListExtractor)

NS_IMETHODIMP
nsEdgeReadingListExtractor::Extract(const nsAString& aDBPath, nsIArray** aItems)
{
  nsresult rv = NS_OK;
  *aItems = nullptr;

  if (!aDBPath.Length()) {
    return NS_ERROR_FAILURE;
  }

  JET_ERR err;
  JET_INSTANCE instance;
  JET_SESID sesid;
  JET_DBID dbid;
  JET_TABLEID tableid;
  JET_COLUMNDEF urlColumnInfo = { 0 };
  JET_COLUMNDEF titleColumnInfo = { 0 };
  JET_COLUMNDEF addedDateColumnInfo = { 0 };

  // Need to ensure this happens before we skip ahead to CloseDB,
  // otherwise the compiler complains.
  nsCOMPtr<nsIMutableArray> items = do_CreateInstance(NS_ARRAY_CONTRACTID);

  // JET does not throw exceptions, and so error handling and ensuring we close
  // the DB is a bit finnicky. Keep track of how far we got so we guarantee closing
  // the right things
  bool instanceCreated, sessionCreated, dbOpened, tableOpened;

  // Check for the right page size and initialize with that
  unsigned long pageSize;
  err = JetGetDatabaseFileInfoW(aDBPath.BeginReading(), &pageSize, sizeof(pageSize), JET_DbInfoPageSize);
  NS_HANDLE_JET_ERROR(err)
  err = JetSetSystemParameter(&instance, NULL, JET_paramDatabasePageSize, pageSize, NULL);
  NS_HANDLE_JET_ERROR(err)

  // Turn off recovery, because otherwise we will create log files in either the cwd or
  // overwrite Edge's own logfiles, which is useless at best and at worst might mess with
  // Edge actually using the DB
  err = JetSetSystemParameter(&instance, NULL, JET_paramRecovery, NULL, "Off");
  NS_HANDLE_JET_ERROR(err)

  // Start our session:
  err = JetCreateInstance(&instance, "edge_readinglist_migration");
  NS_HANDLE_JET_ERROR(err)
  instanceCreated = true;

  err = JetInit(&instance);
  NS_HANDLE_JET_ERROR(err)
  err = JetBeginSession(instance, &sesid, 0, 0);
  NS_HANDLE_JET_ERROR(err)
  sessionCreated = true;

  // Actually open the DB, and make sure to do so readonly:
  err = JetAttachDatabaseW(sesid, aDBPath.BeginReading(), JET_bitDbReadOnly);
  NS_HANDLE_JET_ERROR(err)
  dbOpened = true;
  err = JetOpenDatabaseW(sesid, aDBPath.BeginReading(), NULL, &dbid, JET_bitDbReadOnly);
  NS_HANDLE_JET_ERROR(err)

  // Open the readinglist table and get information on the columns we are interested in:
  err = JetOpenTable(sesid, dbid, "ReadingList", NULL, 0, JET_bitTableReadOnly, &tableid);
  NS_HANDLE_JET_ERROR(err)
  tableOpened = true;
  err = JetGetColumnInfo(sesid, dbid, "ReadingList", "URL", &urlColumnInfo,
                         sizeof(urlColumnInfo), JET_ColInfo);
  NS_HANDLE_JET_ERROR(err)
  if (urlColumnInfo.cbMax > MAX_URL_LENGTH) {
    nsCOMPtr<nsIConsoleService> consoleService = do_GetService(NS_CONSOLESERVICE_CONTRACTID);
    if (consoleService) {
      consoleService->LogStringMessage(NS_LITERAL_STRING("Edge migration: URL column size increased").get());
    }
  }
  err = JetGetColumnInfo(sesid, dbid, "ReadingList", "Title", &titleColumnInfo,
                         sizeof(titleColumnInfo), JET_ColInfo);
  NS_HANDLE_JET_ERROR(err)
  if (titleColumnInfo.cbMax > MAX_TITLE_LENGTH) {
    nsCOMPtr<nsIConsoleService> consoleService = do_GetService(NS_CONSOLESERVICE_CONTRACTID);
    if (consoleService) {
      consoleService->LogStringMessage(NS_LITERAL_STRING("Edge migration: Title column size increased").get());
    }
  }
  err = JetGetColumnInfo(sesid, dbid, "ReadingList", "AddedDate", &addedDateColumnInfo,
                         sizeof(addedDateColumnInfo), JET_ColInfo);
  NS_HANDLE_JET_ERROR(err)

  // verify the column types are what we expect:
  if (urlColumnInfo.coltyp != JET_coltypLongText ||
      titleColumnInfo.coltyp != JET_coltypLongText ||
      addedDateColumnInfo.coltyp != JET_coltypLongLong) {
    rv = NS_ERROR_NOT_IMPLEMENTED;
    goto CloseDB;
  }

  JET_COLUMNID urlColumnId = urlColumnInfo.columnid;
  JET_COLUMNID titleColumnId = titleColumnInfo.columnid;
  JET_COLUMNID addedDateColumnId = addedDateColumnInfo.columnid;

  // If we got here, we've found our table and column information

  err = JetMove(sesid, tableid, JET_MoveFirst, 0);
  // It's possible there are 0 items in this table, in which case we want to
  // not fail:
  if (err == JET_errNoCurrentRecord) {
    items.forget(aItems);
    goto CloseDB;
  }
  // Check for any other errors
  NS_HANDLE_JET_ERROR(err)

  FILETIME addedDate;
  wchar_t urlBuffer[MAX_URL_LENGTH] = { 0 };
  wchar_t titleBuffer[MAX_TITLE_LENGTH] = { 0 };
  do {
    err = JetRetrieveColumn(sesid, tableid, urlColumnId, &urlBuffer,
                            sizeof(urlBuffer), NULL, 0, NULL);
    NS_HANDLE_JET_ERROR(err)
    err = JetRetrieveColumn(sesid, tableid, titleColumnId, &titleBuffer,
                            sizeof(titleBuffer), NULL, 0, NULL);
    NS_HANDLE_JET_ERROR(err)
    err = JetRetrieveColumn(sesid, tableid, addedDateColumnId, &addedDate,
                            sizeof(addedDate), NULL, 0, NULL);
    NS_HANDLE_JET_ERROR(err)
    nsCOMPtr<nsIWritablePropertyBag2> pbag = do_CreateInstance("@mozilla.org/hash-property-bag;1");
    bool dateIsValid;
    PRTime prAddedDate = WinMigrationFileTimeToPRTime(&addedDate, &dateIsValid);
    nsDependentString url(urlBuffer);
    nsDependentString title(titleBuffer);
    pbag->SetPropertyAsAString(NS_LITERAL_STRING("uri"), url);
    pbag->SetPropertyAsAString(NS_LITERAL_STRING("title"), title);
    if (dateIsValid) {
      pbag->SetPropertyAsInt64(NS_LITERAL_STRING("time"), prAddedDate);
    }
    items->AppendElement(pbag, false);
    memset(urlBuffer, 0, sizeof(urlBuffer));
    memset(titleBuffer, 0, sizeof(titleBuffer));
  } while (JET_errSuccess == JetMove(sesid, tableid, JET_MoveNext, 0));

  items.forget(aItems);

CloseDB:
  // Terminate ESENT. This performs a clean shutdown.
  // Ignore errors while closing:
  if (tableOpened)
    JetCloseTable(sesid, tableid);
  if (dbOpened)
    JetCloseDatabase(sesid, dbid, 0);
  if (sessionCreated)
    JetEndSession(sesid, 0);
  if (instanceCreated)
    JetTerm(instance);

  return rv;
}

nsresult
nsEdgeReadingListExtractor::ConvertJETError(const JET_ERR &aError)
{
  switch (aError) {
    case JET_errPageSizeMismatch:
    case JET_errInvalidName:
    case JET_errColumnNotFound:
      // The DB format has changed and we haven't updated this migration code:
      return NS_ERROR_NOT_IMPLEMENTED;
    case JET_errDatabaseLocked:
      return NS_ERROR_FILE_IS_LOCKED;
    case JET_errPermissionDenied:
    case JET_errAccessDenied:
      return NS_ERROR_FILE_ACCESS_DENIED;
    case JET_errInvalidFilename:
      return NS_ERROR_FILE_INVALID_PATH;
    case JET_errFileNotFound:
      return NS_ERROR_FILE_NOT_FOUND;
    default:
      return NS_ERROR_FAILURE;
  }
}