Bug 1484844: Make nsINIParser mutable. r=froydnj
authorDave Townsend <dtownsend@oxymoronical.com>
Thu, 12 Oct 2017 14:20:57 -0700
changeset 487725 ec7da761af74dacb9a4172ac2e2a42de032bd4ef
parent 487724 d8a7212c51a48253bfa9551ecc9318a9536c62d4
child 487726 1cba447409917569d3d44d814fd13ba2e6244006
push id9719
push userffxbld-merge
push dateFri, 24 Aug 2018 17:49:46 +0000
treeherdermozilla-beta@719ec98fba77 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersfroydnj
bugs1484844
milestone63.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1484844: Make nsINIParser mutable. r=froydnj In order to implement profile-per-install we need a mutable INI parser in early startup. The current one is implemented in JavaScript and thus not available. This makes the current read-only C++ INI parser mutable and removes the JavaScript implementation. It turns out that the two different implementations of nsIINIParserFactory and nsIINIParser behaved slightly differently but only in ways that the single test cared about so I've adjusted things a little to make it work. The existing C++ implementation did not do validity checks on arguments, this adds that making empty sections and values illegal. Differential Revision: https://phabricator.services.mozilla.com/D3851
browser/installer/package-manifest.in
toolkit/crashreporter/nsExceptionHandler.cpp
xpcom/base/nsINIParser.cpp
xpcom/base/nsINIParser.h
xpcom/ds/moz.build
xpcom/ds/nsIINIParser.idl
xpcom/ds/nsINIParserImpl.cpp
xpcom/ds/nsINIProcessor.js
xpcom/ds/nsINIProcessor.manifest
xpcom/tests/unit/data/iniparser17.ini
xpcom/tests/unit/test_iniParser.js
xpcom/tests/unit/test_iniProcessor.js
xpcom/tests/unit/xpcshell.ini
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -280,18 +280,16 @@
 #ifdef XP_WIN
 @RESPATH@/browser/components/360seProfileMigrator.js
 @RESPATH@/browser/components/EdgeProfileMigrator.js
 @RESPATH@/browser/components/IEProfileMigrator.js
 #endif
 #ifdef XP_MACOSX
 @RESPATH@/browser/components/SafariProfileMigrator.js
 #endif
-@RESPATH@/components/nsINIProcessor.manifest
-@RESPATH@/components/nsINIProcessor.js
 @RESPATH@/components/nsPrompter.manifest
 @RESPATH@/components/nsPrompter.js
 @RESPATH@/components/SyncComponents.manifest
 @RESPATH@/components/Weave.js
 @RESPATH@/components/FxAccountsComponents.manifest
 @RESPATH@/components/FxAccountsPush.js
 @RESPATH@/components/CaptivePortalDetectComponents.manifest
 @RESPATH@/components/captivedetect.js
--- a/toolkit/crashreporter/nsExceptionHandler.cpp
+++ b/toolkit/crashreporter/nsExceptionHandler.cpp
@@ -2563,17 +2563,17 @@ static nsresult PrefSubmitReports(bool* 
         return NS_OK;
     }
     // Create the file so the INI processor can write to it.
     rv = reporterINI->Create(nsIFile::NORMAL_FILE_TYPE, 0600);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   nsCOMPtr<nsIINIParserFactory> iniFactory =
-    do_GetService("@mozilla.org/xpcom/ini-processor-factory;1", &rv);
+    do_GetService("@mozilla.org/xpcom/ini-parser-factory;1", &rv);
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsCOMPtr<nsIINIParser> iniParser;
   rv = iniFactory->CreateINIParser(reporterINI,
                                    getter_AddRefs(iniParser));
   NS_ENSURE_SUCCESS(rv, rv);
 
   // If we're writing the pref, just set and we're done.
@@ -2581,17 +2581,17 @@ static nsresult PrefSubmitReports(bool* 
     nsCOMPtr<nsIINIParserWriter> iniWriter = do_QueryInterface(iniParser);
     NS_ENSURE_TRUE(iniWriter, NS_ERROR_FAILURE);
 
     rv = iniWriter->SetString(NS_LITERAL_CSTRING("Crash Reporter"),
                               NS_LITERAL_CSTRING("SubmitReport"),
                               *aSubmitReports ?  NS_LITERAL_CSTRING("1") :
                                                  NS_LITERAL_CSTRING("0"));
     NS_ENSURE_SUCCESS(rv, rv);
-    rv = iniWriter->WriteFile(nullptr, 0);
+    rv = iniWriter->WriteFile(reporterINI);
     return rv;
   }
 
   nsAutoCString submitReportValue;
   rv = iniParser->GetString(NS_LITERAL_CSTRING("Crash Reporter"),
                             NS_LITERAL_CSTRING("SubmitReport"),
                             submitReportValue);
 
--- a/xpcom/base/nsINIParser.cpp
+++ b/xpcom/base/nsINIParser.cpp
@@ -4,65 +4,21 @@
  * 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/. */
 
 // Moz headers (alphabetical)
 #include "nsCRTGlue.h"
 #include "nsError.h"
 #include "nsIFile.h"
 #include "nsINIParser.h"
-#include "mozilla/FileUtils.h" // AutoFILE
 #include "mozilla/ResultExtensions.h"
 #include "mozilla/URLPreloader.h"
 
-// System headers (alphabetical)
-#include <stdio.h>
-#include <stdlib.h>
-#ifdef XP_WIN
-#include <windows.h>
-#endif
-
 using namespace mozilla;
 
-#ifdef XP_WIN
-inline FILE*
-TS_tfopen(const char* aPath, const wchar_t* aMode)
-{
-  wchar_t wPath[MAX_PATH];
-  MultiByteToWideChar(CP_UTF8, 0, aPath, -1, wPath, MAX_PATH);
-  return _wfopen(wPath, aMode);
-}
-#else
-inline FILE*
-TS_tfopen(const char* aPath, const char* aMode)
-{
-  return fopen(aPath, aMode);
-}
-#endif
-
-// Stack based FILE wrapper to ensure that fclose is called, copied from
-// toolkit/mozapps/update/updater/readstrings.cpp
-
-class AutoFILE
-{
-public:
-  explicit AutoFILE(FILE* aFp = nullptr) : fp_(aFp) {}
-  ~AutoFILE()
-  {
-    if (fp_) {
-      fclose(fp_);
-    }
-  }
-  operator FILE*() { return fp_; }
-  FILE** operator&() { return &fp_; }
-  void operator=(FILE* aFp) { fp_ = aFp; }
-private:
-  FILE* fp_;
-};
-
 nsresult
 nsINIParser::Init(nsIFile* aFile)
 {
   nsCString result;
   MOZ_TRY_VAR(result, URLPreloader::ReadFile(aFile));
 
   return InitFromString(result);
 }
@@ -70,36 +26,37 @@ nsINIParser::Init(nsIFile* aFile)
 static const char kNL[] = "\r\n";
 static const char kEquals[] = "=";
 static const char kWhitespace[] = " \t";
 static const char kRBracket[] = "]";
 
 nsresult
 nsINIParser::InitFromString(const nsCString& aStr)
 {
+  nsCString fileContents;
   char* buffer;
 
   if (StringHead(aStr, 3) == "\xEF\xBB\xBF") {
     // Someone set us up the Utf-8 BOM
     // This case is easy, since we assume that BOM-less
     // files are Utf-8 anyway.  Just skip the BOM and process as usual.
-    mFileContents.Append(aStr);
-    buffer = mFileContents.BeginWriting() + 3;
+    fileContents.Append(aStr);
+    buffer = fileContents.BeginWriting() + 3;
   } else {
     if (StringHead(aStr, 2) == "\xFF\xFE") {
       // Someone set us up the Utf-16LE BOM
       nsDependentSubstring str(reinterpret_cast<const char16_t*>(aStr.get()),
                                aStr.Length() / 2);
 
-      AppendUTF16toUTF8(Substring(str, 1), mFileContents);
+      AppendUTF16toUTF8(Substring(str, 1), fileContents);
     } else {
-      mFileContents.Append(aStr);
+      fileContents.Append(aStr);
     }
 
-    buffer = mFileContents.BeginWriting();
+    buffer = fileContents.BeginWriting();
   }
 
   char* currSection = nullptr;
 
   // outer loop tokenizes into lines
   while (char* token = NS_strtok(kNL, &buffer)) {
     if (token[0] == '#' || token[0] == ';') { // it's a comment
       continue;
@@ -133,53 +90,59 @@ nsINIParser::InitFromString(const nsCStr
     }
 
     char* key = token;
     char* e = NS_strtok(kEquals, &token);
     if (!e || !token) {
       continue;
     }
 
-    INIValue* v;
-    if (!mSections.Get(currSection, &v)) {
-      v = new INIValue(key, token);
-      if (!v) {
-        return NS_ERROR_OUT_OF_MEMORY;
-      }
-
-      mSections.Put(currSection, v);
-      continue;
-    }
-
-    // Check whether this key has already been specified; overwrite
-    // if so, or append if not.
-    while (v) {
-      if (!strcmp(key, v->key)) {
-        v->value = token;
-        break;
-      }
-      if (!v->next) {
-        v->next = MakeUnique<INIValue>(key, token);
-        if (!v->next) {
-          return NS_ERROR_OUT_OF_MEMORY;
-        }
-        break;
-      }
-      v = v->next.get();
-    }
-    NS_ASSERTION(v, "v should never be null coming out of this loop");
+    SetString(currSection, key, token);
   }
 
   return NS_OK;
 }
 
+bool
+nsINIParser::IsValidSection(const char* aSection)
+{
+  if (aSection[0] == '\0') {
+    return false;
+  }
+
+  const char* found = strpbrk(aSection, "\r\n[]");
+  return found == nullptr;
+}
+
+bool
+nsINIParser::IsValidKey(const char* aKey)
+{
+  if (aKey[0] == '\0') {
+    return false;
+  }
+
+  const char* found = strpbrk(aKey, "\r\n=");
+  return found == nullptr;
+}
+
+bool
+nsINIParser::IsValidValue(const char* aValue)
+{
+  const char* found = strpbrk(aValue, "\r\n");
+  return found == nullptr;
+}
+
 nsresult
 nsINIParser::GetString(const char* aSection, const char* aKey,
                        nsACString& aResult)
 {
+  if (!IsValidSection(aSection) || !IsValidKey(aKey)) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
   INIValue* val;
   mSections.Get(aSection, &val);
 
   while (val) {
     if (strcmp(val->key, aKey) == 0) {
       aResult.Assign(val->value);
       return NS_OK;
     }
@@ -189,16 +152,20 @@ nsINIParser::GetString(const char* aSect
 
   return NS_ERROR_FAILURE;
 }
 
 nsresult
 nsINIParser::GetString(const char* aSection, const char* aKey,
                        char* aResult, uint32_t aResultLen)
 {
+  if (!IsValidSection(aSection) || !IsValidKey(aKey)) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
   INIValue* val;
   mSections.Get(aSection, &val);
 
   while (val) {
     if (strcmp(val->key, aKey) == 0) {
       strncpy(aResult, val->value, aResultLen);
       aResult[aResultLen - 1] = '\0';
       if (strlen(val->value) >= aResultLen) {
@@ -224,21 +191,136 @@ nsINIParser::GetSections(INISectionCallb
   }
   return NS_OK;
 }
 
 nsresult
 nsINIParser::GetStrings(const char* aSection,
                         INIStringCallback aCB, void* aClosure)
 {
+  if (!IsValidSection(aSection)) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
   INIValue* val;
 
   for (mSections.Get(aSection, &val);
        val;
        val = val->next.get()) {
 
     if (!aCB(val->key, val->value, aClosure)) {
       return NS_OK;
     }
   }
 
   return NS_OK;
 }
+
+nsresult
+nsINIParser::SetString(const char* aSection, const char* aKey, const char* aValue)
+{
+  if (!IsValidSection(aSection) || !IsValidKey(aKey) || !IsValidValue(aValue)) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  INIValue* v;
+  if (!mSections.Get(aSection, &v)) {
+    v = new INIValue(aKey, aValue);
+
+    mSections.Put(aSection, v);
+    return NS_OK;
+  }
+
+  // Check whether this key has already been specified; overwrite
+  // if so, or append if not.
+  while (v) {
+    if (!strcmp(aKey, v->key)) {
+      v->SetValue(aValue);
+      break;
+    }
+    if (!v->next) {
+      v->next = MakeUnique<INIValue>(aKey, aValue);
+      break;
+    }
+    v = v->next.get();
+  }
+  NS_ASSERTION(v, "v should never be null coming out of this loop");
+
+  return NS_OK;
+}
+
+nsresult
+nsINIParser::DeleteString(const char* aSection, const char* aKey)
+{
+  if (!IsValidSection(aSection) || !IsValidKey(aKey)) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  INIValue* val;
+  if (!mSections.Get(aSection, &val)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  // Special case the first result
+  if (strcmp(val->key, aKey) == 0) {
+    if (!val->next) {
+      mSections.Remove(aSection);
+    } else {
+      mSections.Put(aSection, val->next.release());
+      delete val;
+    }
+    return NS_OK;
+  }
+
+  while (val->next) {
+    if (strcmp(val->next->key, aKey) == 0) {
+      val->next = std::move(val->next->next);
+
+      return NS_OK;
+    }
+
+    val = val->next.get();
+  }
+
+  return NS_ERROR_FAILURE;
+}
+
+nsresult
+nsINIParser::DeleteSection(const char* aSection)
+{
+  if (!IsValidSection(aSection)) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  if (!mSections.Remove(aSection)) {
+    return NS_ERROR_FAILURE;
+  }
+  return NS_OK;
+}
+
+nsresult
+nsINIParser::WriteToFile(nsIFile *aFile) {
+  nsCString buffer;
+
+  for (auto iter = mSections.Iter(); !iter.Done(); iter.Next()) {
+    buffer.AppendPrintf("[%s]\n", iter.Key());
+    INIValue* val = iter.Data();
+    while (val) {
+      buffer.AppendPrintf("%s=%s\n", val->key, val->value);
+      val = val->next.get();
+    }
+    buffer.AppendLiteral("\n");
+  }
+
+  FILE* writeFile;
+  nsresult rv = aFile->OpenANSIFileDesc("w", &writeFile);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  unsigned int length = buffer.Length();
+
+  if (fwrite(buffer.get(), sizeof(char), length, writeFile) != length) {
+      fclose(writeFile);
+      return NS_ERROR_UNEXPECTED;
+  }
+
+  fclose(writeFile);
+  return NS_OK;
+}
--- a/xpcom/base/nsINIParser.h
+++ b/xpcom/base/nsINIParser.h
@@ -82,29 +82,79 @@ public:
    *         large enough for the data. aResult will be filled with as
    *         much data as possible.
    *
    * @see GetString [1]
    */
   nsresult GetString(const char* aSection, const char* aKey,
                      char* aResult, uint32_t aResultLen);
 
+  /**
+   * Sets the value of the specified key in the specified section. The section
+   * is created if it does not already exist.
+   *
+   * @oaram aSection      section name
+   * @param aKey          key name
+   * @param aValue        the value to set
+   */
+  nsresult SetString(const char* aSection, const char* aKey, const char* aValue);
+
+  /**
+   * Deletes the value of the specified key in the specified section.
+   *
+   * @param aSection      section name
+   * @param aKey          key name
+   *
+   * @throws NS_ERROR_FAILURE if the string was not set.
+   */
+  nsresult DeleteString(const char* aSection, const char* aKey);
+
+  /**
+   * Deletes the specified section.
+   *
+   * @param aSection      section name
+   *
+   * @throws NS_ERROR_FAILURE if the section did not exist.
+   */
+  nsresult DeleteSection(const char* aSection);
+
+  /**
+   * Writes the ini data to disk.
+   * @param aFile         the file to write to
+   * @throws NS_ERROR_FAILURE on failure.
+   */
+  nsresult WriteToFile(nsIFile *aFile);
+
 private:
   struct INIValue
   {
     INIValue(const char* aKey, const char* aValue)
-      : key(aKey)
-      , value(aValue)
+      : key(strdup(aKey))
+      , value(strdup(aValue))
+    {
+    }
+
+    ~INIValue()
     {
+      delete key;
+      delete value;
+    }
+
+    void SetValue(const char* aValue) {
+      delete value;
+      value = strdup(aValue);
     }
 
     const char* key;
     const char* value;
     mozilla::UniquePtr<INIValue> next;
   };
 
-  nsClassHashtable<nsDepCharHashKey, INIValue> mSections;
-  nsCString mFileContents;
+  nsClassHashtable<nsCharPtrHashKey, INIValue> mSections;
 
   nsresult InitFromString(const nsCString& aStr);
+
+  bool IsValidSection(const char* aSection);
+  bool IsValidKey(const char* aKey);
+  bool IsValidValue(const char* aValue);
 };
 
 #endif /* nsINIParser_h__ */
--- a/xpcom/ds/moz.build
+++ b/xpcom/ds/moz.build
@@ -124,21 +124,16 @@ SOURCES += [
 ]
 if CONFIG['CC_TYPE'] == 'msvc':
     # Needed for gGkAtoms.
     SOURCES['nsGkAtoms.cpp'].flags += [
         '-constexpr:steps300000',
         '-Zc:externConstexpr',
     ]
 
-EXTRA_COMPONENTS += [
-    'nsINIProcessor.js',
-    'nsINIProcessor.manifest',
-]
-
 LOCAL_INCLUDES += [
     '../io',
 ]
 
 GENERATED_FILES += ['nsGkAtomList.h']
 GENERATED_FILES['nsGkAtomList.h'].script = 'StaticAtoms.py:generate_nsgkatomlist_h'
 GENERATED_FILES['nsGkAtomList.h'].inputs = ['Atom.py', 'HTMLAtoms.py']
 
--- a/xpcom/ds/nsIINIParser.idl
+++ b/xpcom/ds/nsIINIParser.idl
@@ -25,34 +25,26 @@ interface nsIINIParser : nsISupports
    */
   AUTF8String getString(in AUTF8String aSection, in AUTF8String aKey);
 };
 
 [scriptable, uuid(b67bb24b-31a3-4a6a-a5d9-0485c9af5a04)]
 interface nsIINIParserWriter : nsISupports
 {
   /**
-   * Windows and the NSIS installer code sometimes expect INI files to be in
-   * UTF-16 encoding. On Windows only, this flag to writeFile can be used to
-   * change the encoding from its default UTF-8.
-   */
-  const unsigned long WRITE_UTF16 = 0x1;
-
-  /**
    * Set the value of a string for a particular section and key.
    */
   void setString(in AUTF8String aSection, in AUTF8String aKey, in AUTF8String aValue);
 
   /**
    * Write to the INI file.
    */
-  void writeFile([optional] in nsIFile aINIFile,
-                 [optional] in unsigned long aFlags);
+  void writeFile(in nsIFile aINIFile);
 };
 
 [scriptable, uuid(ccae7ea5-1218-4b51-aecb-c2d8ecd46af9)]
 interface nsIINIParserFactory : nsISupports
 {
   /**
    * Create an iniparser instance from a local file.
    */
-  nsIINIParser createINIParser(in nsIFile aINIFile);
+  nsIINIParser createINIParser([optional] in nsIFile aINIFile);
 };
--- a/xpcom/ds/nsINIParserImpl.cpp
+++ b/xpcom/ds/nsINIParserImpl.cpp
@@ -8,27 +8,30 @@
 
 #include "nsINIParser.h"
 #include "nsStringEnumerator.h"
 #include "nsTArray.h"
 #include "mozilla/Attributes.h"
 
 class nsINIParserImpl final
   : public nsIINIParser
+  , public nsIINIParserWriter
 {
   ~nsINIParserImpl() {}
 
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIINIPARSER
+  NS_DECL_NSIINIPARSERWRITER
 
   nsresult Init(nsIFile* aINIFile) { return mParser.Init(aINIFile); }
 
 private:
   nsINIParser mParser;
+  bool ContainsNull(const nsACString& aStr);
 };
 
 NS_IMPL_ISUPPORTS(nsINIParserFactory,
                   nsIINIParserFactory,
                   nsIFactory)
 
 NS_IMETHODIMP
 nsINIParserFactory::CreateINIParser(nsIFile* aINIFile,
@@ -36,23 +39,25 @@ nsINIParserFactory::CreateINIParser(nsIF
 {
   *aResult = nullptr;
 
   RefPtr<nsINIParserImpl> p(new nsINIParserImpl());
   if (!p) {
     return NS_ERROR_OUT_OF_MEMORY;
   }
 
-  nsresult rv = p->Init(aINIFile);
-
-  if (NS_SUCCEEDED(rv)) {
-    NS_ADDREF(*aResult = p);
+  if (aINIFile) {
+    nsresult rv = p->Init(aINIFile);
+    if (NS_FAILED(rv)) {
+      return rv;
+    }
   }
 
-  return rv;
+   p.forget(aResult);
+  return NS_OK;
 }
 
 NS_IMETHODIMP
 nsINIParserFactory::CreateInstance(nsISupports* aOuter,
                                    REFNSIID aIID,
                                    void** aResult)
 {
   if (NS_WARN_IF(aOuter)) {
@@ -65,17 +70,23 @@ nsINIParserFactory::CreateInstance(nsISu
 
 NS_IMETHODIMP
 nsINIParserFactory::LockFactory(bool aLock)
 {
   return NS_OK;
 }
 
 NS_IMPL_ISUPPORTS(nsINIParserImpl,
-                  nsIINIParser)
+                  nsIINIParser,
+                  nsIINIParserWriter)
+
+bool
+nsINIParserImpl::ContainsNull(const nsACString& aStr) {
+  return aStr.CountChar('\0') > 0;
+}
 
 static bool
 SectionCB(const char* aSection, void* aClosure)
 {
   nsTArray<nsCString>* strings = static_cast<nsTArray<nsCString>*>(aClosure);
   strings->AppendElement()->Assign(aSection);
   return true;
 }
@@ -107,16 +118,20 @@ KeyCB(const char* aKey, const char* aVal
   strings->AppendElement()->Assign(aKey);
   return true;
 }
 
 NS_IMETHODIMP
 nsINIParserImpl::GetKeys(const nsACString& aSection,
                          nsIUTF8StringEnumerator** aResult)
 {
+  if (ContainsNull(aSection)) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
   nsTArray<nsCString>* strings = new nsTArray<nsCString>;
   if (!strings) {
     return NS_ERROR_OUT_OF_MEMORY;
   }
 
   nsresult rv = mParser.GetStrings(PromiseFlatCString(aSection).get(),
                                    KeyCB, strings);
   if (NS_SUCCEEDED(rv)) {
@@ -131,12 +146,36 @@ nsINIParserImpl::GetKeys(const nsACStrin
 
 }
 
 NS_IMETHODIMP
 nsINIParserImpl::GetString(const nsACString& aSection,
                            const nsACString& aKey,
                            nsACString& aResult)
 {
+  if (ContainsNull(aSection) || ContainsNull(aKey)) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
   return mParser.GetString(PromiseFlatCString(aSection).get(),
                            PromiseFlatCString(aKey).get(),
                            aResult);
 }
+
+NS_IMETHODIMP
+nsINIParserImpl::SetString(const nsACString& aSection,
+                           const nsACString& aKey,
+                           const nsACString& aValue)
+{
+  if (ContainsNull(aSection) || ContainsNull(aKey) || ContainsNull(aValue)) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  return mParser.SetString(PromiseFlatCString(aSection).get(),
+                           PromiseFlatCString(aKey).get(),
+                           PromiseFlatCString(aValue).get());
+}
+
+NS_IMETHODIMP
+nsINIParserImpl::WriteFile(nsIFile* aINIFile)
+{
+  return mParser.WriteToFile(aINIFile);
+}
deleted file mode 100644
--- a/xpcom/ds/nsINIProcessor.js
+++ /dev/null
@@ -1,187 +0,0 @@
-/* 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/. */
-
-
-ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
-
-function INIProcessorFactory() {
-}
-
-INIProcessorFactory.prototype = {
-    classID: Components.ID("{6ec5f479-8e13-4403-b6ca-fe4c2dca14fd}"),
-    QueryInterface: ChromeUtils.generateQI([Ci.nsIINIParserFactory]),
-
-    createINIParser(aINIFile) {
-        return new INIProcessor(aINIFile);
-    }
-
-}; // end of INIProcessorFactory implementation
-
-const MODE_WRONLY = 0x02;
-const MODE_CREATE = 0x08;
-const MODE_TRUNCATE = 0x20;
-
-// nsIINIParser implementation
-function INIProcessor(aFile) {
-    this._iniFile = aFile;
-    this._iniData = {};
-    this._readFile();
-}
-
-INIProcessor.prototype = {
-    QueryInterface: ChromeUtils.generateQI([Ci.nsIINIParser, Ci.nsIINIParserWriter]),
-
-    __utf8Converter: null, // UCS2 <--> UTF8 string conversion
-    get _utf8Converter() {
-        if (!this.__utf8Converter) {
-            this.__utf8Converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].
-                                  createInstance(Ci.nsIScriptableUnicodeConverter);
-            this.__utf8Converter.charset = "UTF-8";
-        }
-        return this.__utf8Converter;
-    },
-
-    __utf16leConverter: null, // UCS2 <--> UTF16LE string conversion
-    get _utf16leConverter() {
-        if (!this.__utf16leConverter) {
-            this.__utf16leConverter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].
-                                  createInstance(Ci.nsIScriptableUnicodeConverter);
-            this.__utf16leConverter.charset = "UTF-16LE";
-        }
-        return this.__utf16leConverter;
-    },
-
-    _utfConverterReset() {
-        this.__utf8Converter = null;
-        this.__utf16leConverter = null;
-    },
-
-    _iniFile: null,
-    _iniData: null,
-
-    /*
-     * Reads the INI file and stores the data internally.
-     */
-    _readFile() {
-        // If file doesn't exist, there's nothing to do.
-        if (!this._iniFile.exists() || 0 == this._iniFile.fileSize)
-            return;
-
-        let iniParser = Cc["@mozilla.org/xpcom/ini-parser-factory;1"]
-            .getService(Ci.nsIINIParserFactory).createINIParser(this._iniFile);
-        for (let section of XPCOMUtils.IterStringEnumerator(iniParser.getSections())) {
-            this._iniData[section] = {};
-            for (let key of XPCOMUtils.IterStringEnumerator(iniParser.getKeys(section))) {
-                this._iniData[section][key] = iniParser.getString(section, key);
-            }
-        }
-    },
-
-    // nsIINIParser
-
-    getSections() {
-        let sections = [];
-        for (let section in this._iniData)
-            sections.push(section);
-        return new stringEnumerator(sections);
-    },
-
-    getKeys(aSection) {
-        let keys = [];
-        if (aSection in this._iniData)
-            for (let key in this._iniData[aSection])
-                keys.push(key);
-        return new stringEnumerator(keys);
-    },
-
-    getString(aSection, aKey) {
-        if (!(aSection in this._iniData))
-            throw Cr.NS_ERROR_FAILURE;
-        if (!(aKey in this._iniData[aSection]))
-            throw Cr.NS_ERROR_FAILURE;
-        return this._iniData[aSection][aKey];
-    },
-
-
-    // nsIINIParserWriter
-
-    setString(aSection, aKey, aValue) {
-        const isSectionIllegal = /[\0\r\n\[\]]/;
-        const isKeyValIllegal  = /[\0\r\n=]/;
-
-        if (isSectionIllegal.test(aSection))
-            throw Components.Exception("bad character in section name",
-                                       Cr.ERROR_ILLEGAL_VALUE);
-        if (isKeyValIllegal.test(aKey) || isKeyValIllegal.test(aValue))
-            throw Components.Exception("bad character in key/value",
-                                       Cr.ERROR_ILLEGAL_VALUE);
-
-        if (!(aSection in this._iniData))
-            this._iniData[aSection] = {};
-
-        this._iniData[aSection][aKey] = aValue;
-    },
-
-    writeFile(aFile, aFlags) {
-
-        let converter;
-        function writeLine(data) {
-            data += "\n";
-            data = converter.ConvertFromUnicode(data);
-            data += converter.Finish();
-            outputStream.write(data, data.length);
-        }
-
-        if (!aFile)
-            aFile = this._iniFile;
-
-        let safeStream = Cc["@mozilla.org/network/safe-file-output-stream;1"].
-                         createInstance(Ci.nsIFileOutputStream);
-        safeStream.init(aFile, MODE_WRONLY | MODE_CREATE | MODE_TRUNCATE,
-                        0o600, null);
-
-        var outputStream = Cc["@mozilla.org/network/buffered-output-stream;1"].
-                           createInstance(Ci.nsIBufferedOutputStream);
-        outputStream.init(safeStream, 8192);
-        outputStream.QueryInterface(Ci.nsISafeOutputStream); // for .finish()
-
-        if (Ci.nsIINIParserWriter.WRITE_UTF16 == aFlags
-         && "nsIWindowsRegKey" in Ci) {
-            outputStream.write("\xFF\xFE", 2);
-            converter = this._utf16leConverter;
-        } else {
-            converter = this._utf8Converter;
-        }
-
-        for (let section in this._iniData) {
-            writeLine("[" + section + "]");
-            for (let key in this._iniData[section]) {
-                writeLine(key + "=" + this._iniData[section][key]);
-            }
-        }
-
-        outputStream.finish();
-    }
-};
-
-function stringEnumerator(stringArray) {
-    this._strings = stringArray;
-}
-stringEnumerator.prototype = {
-    QueryInterface: ChromeUtils.generateQI([Ci.nsIUTF8StringEnumerator]),
-
-    _strings: null,
-    _enumIndex: 0,
-
-    hasMore() {
-        return (this._enumIndex < this._strings.length);
-    },
-
-    getNext() {
-        return this._strings[this._enumIndex++];
-    }
-};
-
-var component = [INIProcessorFactory];
-this.NSGetFactory = XPCOMUtils.generateNSGetFactory(component);
deleted file mode 100644
--- a/xpcom/ds/nsINIProcessor.manifest
+++ /dev/null
@@ -1,2 +0,0 @@
-component {6ec5f479-8e13-4403-b6ca-fe4c2dca14fd} nsINIProcessor.js
-contract @mozilla.org/xpcom/ini-processor-factory;1 {6ec5f479-8e13-4403-b6ca-fe4c2dca14fd}
new file mode 100644
--- /dev/null
+++ b/xpcom/tests/unit/data/iniparser17.ini
@@ -0,0 +1,7 @@
+[section]
+key=
+
+[]
+
+[empty]
+=foo
rename from xpcom/tests/unit/test_iniProcessor.js
rename to xpcom/tests/unit/test_iniParser.js
--- a/xpcom/tests/unit/test_iniProcessor.js
+++ b/xpcom/tests/unit/test_iniParser.js
@@ -84,17 +84,17 @@ var testdata = [
                     { section1: { name1: "value1", name2: "value2" },
                       section2: { name1: "value1", name2: "foopy"  }} },
     { filename: "data/iniparser15.ini", reference:
                     { section1: { name1: "newValue1" },
                       section2: { name1: "foopy"     }} },
     { filename: "data/iniparser16.ini", reference:
                     { "☺♫": { "♫": "☻", "♪": "♥"  },
                        "☼": { "♣": "♠", "♦": "♥"  }} },
-
+    { filename: "data/iniparser17.ini", reference: { section: { key: "" } } },
     ];
 
     testdata.push( { filename: "data/iniparser01-utf8BOM.ini",
                      reference: testdata[0].reference } );
     testdata.push( { filename: "data/iniparser02-utf8BOM.ini",
                      reference: testdata[1].reference } );
     testdata.push( { filename: "data/iniparser03-utf8BOM.ini",
                      reference: testdata[2].reference } );
@@ -160,17 +160,17 @@ var testdata = [
                          reference: testdata[13].reference } );
         testdata.push( { filename: "data/iniparser15-utf16leBOM.ini",
                          reference: testdata[14].reference } );
         testdata.push( { filename: "data/iniparser16-utf16leBOM.ini",
                          reference: testdata[15].reference } );
     }
 
 /* ========== 0 ========== */
-factory = Cc["@mozilla.org/xpcom/ini-processor-factory;1"].
+factory = Cc["@mozilla.org/xpcom/ini-parser-factory;1"].
           getService(Ci.nsIINIParserFactory);
 Assert.ok(!!factory);
 
 // Test reading from a variety of files. While we're at it, write out each one
 // and read it back to ensure that nothing changed.
 while (testnum < testdata.length) {
     dump("\nINFO | test #" + ++testnum);
     let filename = testdata[testnum - 1].filename;
@@ -196,31 +196,38 @@ dump("INFO | test #" + ++testnum + "\n")
 
 // test writing to a new file.
 var newfile = do_get_file("data/");
 newfile.append("nonexistent-file.ini");
 if (newfile.exists())
     newfile.remove(false);
 Assert.ok(!newfile.exists());
 
-var parser = factory.createINIParser(newfile);
+try {
+    var parser = factory.createINIParser(newfile);
+    Assert.ok(false, "Should have thrown an exception");
+} catch (e) {
+    Assert.equal(e.result, Cr.NS_ERROR_FILE_NOT_FOUND, "Caught a file not found exception");
+}
+parser = factory.createINIParser();
 Assert.ok(!!parser);
 Assert.ok(parser instanceof Ci.nsIINIParserWriter);
 checkParserOutput(parser, {});
-parser.writeFile();
+parser.writeFile(newfile);
 Assert.ok(newfile.exists());
 
 // test adding a new section and new key
 parser.setString("section", "key", "value");
-parser.writeFile();
+parser.setString("section", "key2", "");
+parser.writeFile(newfile);
 Assert.ok(newfile.exists());
-checkParserOutput(parser, {section: {key: "value"} });
+checkParserOutput(parser, {section: {key: "value", key2: ""} });
 // read it in again, check for same data.
 parser = parserForFile("data/nonexistent-file.ini");
-checkParserOutput(parser, {section: {key: "value"} });
+checkParserOutput(parser, {section: {key: "value", key2: ""} });
 // cleanup after the test
 newfile.remove(false);
 
 dump("INFO | test #" + ++testnum + "\n");
 
 // test modifying a existing key's value (in an existing section)
 parser = parserForFile("data/iniparser09.ini");
 checkParserOutput(parser, {section1: {name1: "value1"} });
@@ -228,59 +235,80 @@ checkParserOutput(parser, {section1: {na
 Assert.ok(parser instanceof Ci.nsIINIParserWriter);
 parser.setString("section1", "name1", "value2");
 checkParserOutput(parser, {section1: {name1: "value2"} });
 
 dump("INFO | test #" + ++testnum + "\n");
 
 // test trying to set illegal characters
 var caughtError;
-caughtError = false;
+caughtError = null;
 checkParserOutput(parser, {section1: {name1: "value2"} });
 
 // Bad characters in section name
-try { parser.SetString("bad\0", "ok", "ok"); } catch (e) { caughtError = true; }
+try { parser.setString("bad\0", "ok", "ok"); } catch (e) { caughtError = e; }
 Assert.ok(caughtError);
-caughtError = false;
-try { parser.SetString("bad\r", "ok", "ok"); } catch (e) { caughtError = true; }
+Assert.equal(caughtError.result, Cr.NS_ERROR_INVALID_ARG);
+caughtError = null;
+try { parser.setString("bad\r", "ok", "ok"); } catch (e) { caughtError = e; }
+Assert.ok(caughtError);
+Assert.equal(caughtError.result, Cr.NS_ERROR_INVALID_ARG);
+caughtError = null;
+try { parser.setString("bad\n", "ok", "ok"); } catch (e) { caughtError = e; }
 Assert.ok(caughtError);
-caughtError = false;
-try { parser.SetString("bad\n", "ok", "ok"); } catch (e) { caughtError = true; }
+Assert.equal(caughtError.result, Cr.NS_ERROR_INVALID_ARG);
+caughtError = null;
+try { parser.setString("bad[", "ok", "ok"); } catch (e) { caughtError = e; }
 Assert.ok(caughtError);
-caughtError = false;
-try { parser.SetString("bad[", "ok", "ok"); } catch (e) { caughtError = true; }
+Assert.equal(caughtError.result, Cr.NS_ERROR_INVALID_ARG);
+caughtError = null;
+try { parser.setString("bad]", "ok", "ok"); } catch (e) { caughtError = e; }
 Assert.ok(caughtError);
-caughtError = false;
-try { parser.SetString("bad]", "ok", "ok"); } catch (e) { caughtError = true; }
+Assert.equal(caughtError.result, Cr.NS_ERROR_INVALID_ARG);
+caughtError = null;
+try { parser.setString("", "ok", "ok"); } catch (e) { caughtError = e; }
 Assert.ok(caughtError);
+Assert.equal(caughtError.result, Cr.NS_ERROR_INVALID_ARG);
 
 // Bad characters in key name
-caughtError = false;
-try { parser.SetString("ok", "bad\0", "ok"); } catch (e) { caughtError = true; }
+caughtError = null;
+try { parser.setString("ok", "bad\0", "ok"); } catch (e) { caughtError = e; }
 Assert.ok(caughtError);
-caughtError = false;
-try { parser.SetString("ok", "bad\r", "ok"); } catch (e) { caughtError = true; }
+Assert.equal(caughtError.result, Cr.NS_ERROR_INVALID_ARG);
+caughtError = null;
+try { parser.setString("ok", "bad\r", "ok"); } catch (e) { caughtError = e; }
 Assert.ok(caughtError);
-caughtError = false;
-try { parser.SetString("ok", "bad\n", "ok"); } catch (e) { caughtError = true; }
+Assert.equal(caughtError.result, Cr.NS_ERROR_INVALID_ARG);
+caughtError = null;
+try { parser.setString("ok", "bad\n", "ok"); } catch (e) { caughtError = e; }
 Assert.ok(caughtError);
-caughtError = false;
-try { parser.SetString("ok", "bad=", "ok"); } catch (e) { caughtError = true; }
+Assert.equal(caughtError.result, Cr.NS_ERROR_INVALID_ARG);
+caughtError = null;
+try { parser.setString("ok", "bad=", "ok"); } catch (e) { caughtError = e; }
 Assert.ok(caughtError);
+Assert.equal(caughtError.result, Cr.NS_ERROR_INVALID_ARG);
+caughtError = null;
+try { parser.setString("ok", "", "ok"); } catch (e) { caughtError = e; }
+Assert.ok(caughtError);
+Assert.equal(caughtError.result, Cr.NS_ERROR_INVALID_ARG);
 
 // Bad characters in value
-caughtError = false;
-try { parser.SetString("ok", "ok", "bad\0"); } catch (e) { caughtError = true; }
+caughtError = null;
+try { parser.setString("ok", "ok", "bad\0"); } catch (e) { caughtError = e; }
 Assert.ok(caughtError);
-caughtError = false;
-try { parser.SetString("ok", "ok", "bad\r"); } catch (e) { caughtError = true; }
+Assert.equal(caughtError.result, Cr.NS_ERROR_INVALID_ARG);
+caughtError = null;
+try { parser.setString("ok", "ok", "bad\r"); } catch (e) { caughtError = e; }
 Assert.ok(caughtError);
-caughtError = false;
-try { parser.SetString("ok", "ok", "bad\n"); } catch (e) { caughtError = true; }
+Assert.equal(caughtError.result, Cr.NS_ERROR_INVALID_ARG);
+caughtError = null;
+try { parser.setString("ok", "ok", "bad\n"); } catch (e) { caughtError = e; }
 Assert.ok(caughtError);
-caughtError = false;
-try { parser.SetString("ok", "ok", "bad="); } catch (e) { caughtError = true; }
-Assert.ok(caughtError);
+Assert.equal(caughtError.result, Cr.NS_ERROR_INVALID_ARG);
+caughtError = null;
+try { parser.setString("ok", "ok", "good="); } catch (e) { caughtError = e; }
+Assert.ok(!caughtError);
+caughtError = null;
 
 } catch (e) {
     throw "FAILED in test #" + testnum + " -- " + e;
 }
 }
--- a/xpcom/tests/unit/xpcshell.ini
+++ b/xpcom/tests/unit/xpcshell.ini
@@ -25,17 +25,17 @@ fail-if = os == "android"
 [test_bug1434856.js]
 [test_debugger_malloc_size_of.js]
 [test_file_createUnique.js]
 [test_file_equality.js]
 [test_hidden_files.js]
 [test_home.js]
 # Bug 676998: test fails consistently on Android
 fail-if = os == "android"
-[test_iniProcessor.js]
+[test_iniParser.js]
 [test_ioutil.js]
 [test_localfile.js]
 [test_mac_bundle.js]
 [test_nsIMutableArray.js]
 [test_nsIProcess.js]
 skip-if = os == "win" || os == "linux" # bug 582821, bug 1325609
 # Bug 676998: test fails consistently on Android
 fail-if = os == "android"