gecko-dev/xpcom/base/nsINIParser.cpp
Dave Townsend 6403c58cd7 Bug 1527704: Store install information in profiles.ini and use installs.ini as a backup in case an earlier Firefox throws it away. r=froydnj
Originally we stored the new information about installation defaults in
installs.ini since older versions of Firefox would throw away any new data in
profiles.ini any time they made changes to the profiles. That does however mean
we have to load two files on startup.

This changes things so that we save all the data in profiles.ini as well as a
version tag and still save the install data into installs.ini. An older version
will throw away the install data and version tag from profiles.ini but leave
installs.ini alone. On startup if the version tag is gone from profiles.ini then
we reload the install data from installs.ini and put it back into profiles.ini.

At some point in the future where we don't care about supporting older versions
of Firefox we can just drop installs.ini entirely.

A lot of the changes here involve moving to loading profiles.ini into an
in-memory ini, keeping it up to date and flushing it to disk. This means that we
no longer throw away any information in the ini file that this version does not
understand allowing the possibility of adding new data to this file in the
future.

Differential Revision: https://phabricator.services.mozilla.com/D22576

--HG--
extra : rebase_source : d00edf1ceb200a73a60bb1a90afabcdf95b01acf
extra : intermediate-source : e1c9790cd3bee060da99ffe37026721e36bc46c3
extra : source : d4feb17faf013134f5eac8b5e19b714c56410973
2019-03-27 14:41:04 -07:00

320 lines
7.7 KiB
C++

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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/. */
// Moz headers (alphabetical)
#include "nsCRTGlue.h"
#include "nsError.h"
#include "nsIFile.h"
#include "nsINIParser.h"
#include "mozilla/ResultExtensions.h"
#include "mozilla/URLPreloader.h"
using namespace mozilla;
nsresult nsINIParser::Init(nsIFile* aFile) {
nsCString result;
MOZ_TRY_VAR(result, URLPreloader::ReadFile(aFile));
return InitFromString(result);
}
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.
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), fileContents);
} else {
fileContents.Append(aStr);
}
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;
}
token = (char*)NS_strspnp(kWhitespace, token);
if (!*token) { // empty line
continue;
}
if (token[0] == '[') { // section header!
++token;
currSection = token;
char* rb = NS_strtok(kRBracket, &token);
if (!rb || NS_strtok(kWhitespace, &token)) {
// there's either an unclosed [Section or a [Section]Moretext!
// we could frankly decide that this INI file is malformed right
// here and stop, but we won't... keep going, looking for
// a well-formed [section] to continue working with
currSection = nullptr;
}
continue;
}
if (!currSection) {
// If we haven't found a section header (or we found a malformed
// section header), don't bother parsing this line.
continue;
}
char* key = token;
char* e = NS_strtok(kEquals, &token);
if (!e || !token) {
continue;
}
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;
}
val = val->next.get();
}
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) {
return NS_ERROR_LOSS_OF_SIGNIFICANT_DATA;
}
return NS_OK;
}
val = val->next.get();
}
return NS_ERROR_FAILURE;
}
nsresult nsINIParser::GetSections(INISectionCallback aCB, void* aClosure) {
for (auto iter = mSections.Iter(); !iter.Done(); iter.Next()) {
if (!aCB(iter.Key(), aClosure)) {
break;
}
}
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::RenameSection(const char* aSection,
const char* aNewName) {
if (!IsValidSection(aSection) || !IsValidSection(aNewName)) {
return NS_ERROR_INVALID_ARG;
}
if (mSections.Get(aNewName, nullptr)) {
return NS_ERROR_ILLEGAL_VALUE;
}
nsAutoPtr<INIValue> val;
if (mSections.Remove(aSection, &val)) {
mSections.Put(aNewName, val.forget());
} else {
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;
}