michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: // Moz headers (alphabetical) michael@0: #include "nsCRTGlue.h" michael@0: #include "nsError.h" michael@0: #include "nsIFile.h" michael@0: #include "nsINIParser.h" michael@0: #include "mozilla/FileUtils.h" // AutoFILE michael@0: michael@0: // System headers (alphabetical) michael@0: #include michael@0: #include michael@0: #ifdef XP_WIN michael@0: #include michael@0: #endif michael@0: michael@0: #if defined(XP_WIN) michael@0: #define READ_BINARYMODE L"rb" michael@0: #else michael@0: #define READ_BINARYMODE "r" michael@0: #endif michael@0: michael@0: #ifdef XP_WIN michael@0: inline FILE *TS_tfopen (const char *path, const wchar_t *mode) michael@0: { michael@0: wchar_t wPath[MAX_PATH]; michael@0: MultiByteToWideChar(CP_UTF8, 0, path, -1, wPath, MAX_PATH); michael@0: return _wfopen(wPath, mode); michael@0: } michael@0: #else michael@0: inline FILE *TS_tfopen (const char *path, const char *mode) michael@0: { michael@0: return fopen(path, mode); michael@0: } michael@0: #endif michael@0: michael@0: // Stack based FILE wrapper to ensure that fclose is called, copied from michael@0: // toolkit/mozapps/update/updater/readstrings.cpp michael@0: michael@0: class AutoFILE { michael@0: public: michael@0: AutoFILE(FILE *fp = nullptr) : fp_(fp) {} michael@0: ~AutoFILE() { if (fp_) fclose(fp_); } michael@0: operator FILE *() { return fp_; } michael@0: FILE** operator &() { return &fp_; } michael@0: void operator=(FILE *fp) { fp_ = fp; } michael@0: private: michael@0: FILE *fp_; michael@0: }; michael@0: michael@0: nsresult michael@0: nsINIParser::Init(nsIFile* aFile) michael@0: { michael@0: /* open the file. Don't use OpenANSIFileDesc, because you mustn't michael@0: pass FILE* across shared library boundaries, which may be using michael@0: different CRTs */ michael@0: michael@0: AutoFILE fd; michael@0: michael@0: #ifdef XP_WIN michael@0: nsAutoString path; michael@0: nsresult rv = aFile->GetPath(path); michael@0: if (NS_WARN_IF(NS_FAILED(rv))) michael@0: return rv; michael@0: michael@0: fd = _wfopen(path.get(), READ_BINARYMODE); michael@0: #else michael@0: nsAutoCString path; michael@0: aFile->GetNativePath(path); michael@0: michael@0: fd = fopen(path.get(), READ_BINARYMODE); michael@0: #endif michael@0: michael@0: if (!fd) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: return InitFromFILE(fd); michael@0: } michael@0: michael@0: nsresult michael@0: nsINIParser::Init(const char *aPath) michael@0: { michael@0: /* open the file */ michael@0: AutoFILE fd = TS_tfopen(aPath, READ_BINARYMODE); michael@0: michael@0: if (!fd) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: return InitFromFILE(fd); michael@0: } michael@0: michael@0: static const char kNL[] = "\r\n"; michael@0: static const char kEquals[] = "="; michael@0: static const char kWhitespace[] = " \t"; michael@0: static const char kRBracket[] = "]"; michael@0: michael@0: nsresult michael@0: nsINIParser::InitFromFILE(FILE *fd) michael@0: { michael@0: /* get file size */ michael@0: if (fseek(fd, 0, SEEK_END) != 0) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: long flen = ftell(fd); michael@0: if (flen == 0) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: /* malloc an internal buf the size of the file */ michael@0: mFileContents = new char[flen + 2]; michael@0: if (!mFileContents) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: /* read the file in one swoop */ michael@0: if (fseek(fd, 0, SEEK_SET) != 0) michael@0: return NS_BASE_STREAM_OSERROR; michael@0: michael@0: int rd = fread(mFileContents, sizeof(char), flen, fd); michael@0: if (rd != flen) michael@0: return NS_BASE_STREAM_OSERROR; michael@0: michael@0: // We write a UTF16 null so that the file is easier to convert to UTF8 michael@0: mFileContents[flen] = mFileContents[flen + 1] = '\0'; michael@0: michael@0: char *buffer = &mFileContents[0]; michael@0: michael@0: if (flen >= 3 michael@0: && mFileContents[0] == static_cast(0xEF) michael@0: && mFileContents[1] == static_cast(0xBB) michael@0: && mFileContents[2] == static_cast(0xBF)) { michael@0: // Someone set us up the Utf-8 BOM michael@0: // This case is easy, since we assume that BOM-less michael@0: // files are Utf-8 anyway. Just skip the BOM and process as usual. michael@0: buffer = &mFileContents[3]; michael@0: } michael@0: michael@0: #ifdef XP_WIN michael@0: if (flen >= 2 michael@0: && mFileContents[0] == static_cast(0xFF) michael@0: && mFileContents[1] == static_cast(0xFE)) { michael@0: // Someone set us up the Utf-16LE BOM michael@0: buffer = &mFileContents[2]; michael@0: // Get the size required for our Utf8 buffer michael@0: flen = WideCharToMultiByte(CP_UTF8, michael@0: 0, michael@0: reinterpret_cast(buffer), michael@0: -1, michael@0: nullptr, michael@0: 0, michael@0: nullptr, michael@0: nullptr); michael@0: if (0 == flen) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: nsAutoArrayPtr utf8Buffer(new char[flen]); michael@0: if (0 == WideCharToMultiByte(CP_UTF8, michael@0: 0, michael@0: reinterpret_cast(buffer), michael@0: -1, michael@0: utf8Buffer, michael@0: flen, michael@0: nullptr, michael@0: nullptr)) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: mFileContents = utf8Buffer.forget(); michael@0: buffer = mFileContents; michael@0: } michael@0: #endif michael@0: michael@0: char *currSection = nullptr; michael@0: michael@0: // outer loop tokenizes into lines michael@0: while (char *token = NS_strtok(kNL, &buffer)) { michael@0: if (token[0] == '#' || token[0] == ';') // it's a comment michael@0: continue; michael@0: michael@0: token = (char*) NS_strspnp(kWhitespace, token); michael@0: if (!*token) // empty line michael@0: continue; michael@0: michael@0: if (token[0] == '[') { // section header! michael@0: ++token; michael@0: currSection = token; michael@0: michael@0: char *rb = NS_strtok(kRBracket, &token); michael@0: if (!rb || NS_strtok(kWhitespace, &token)) { michael@0: // there's either an unclosed [Section or a [Section]Moretext! michael@0: // we could frankly decide that this INI file is malformed right michael@0: // here and stop, but we won't... keep going, looking for michael@0: // a well-formed [section] to continue working with michael@0: currSection = nullptr; michael@0: } michael@0: michael@0: continue; michael@0: } michael@0: michael@0: if (!currSection) { michael@0: // If we haven't found a section header (or we found a malformed michael@0: // section header), don't bother parsing this line. michael@0: continue; michael@0: } michael@0: michael@0: char *key = token; michael@0: char *e = NS_strtok(kEquals, &token); michael@0: if (!e || !token) michael@0: continue; michael@0: michael@0: INIValue *v; michael@0: if (!mSections.Get(currSection, &v)) { michael@0: v = new INIValue(key, token); michael@0: if (!v) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: mSections.Put(currSection, v); michael@0: continue; michael@0: } michael@0: michael@0: // Check whether this key has already been specified; overwrite michael@0: // if so, or append if not. michael@0: while (v) { michael@0: if (!strcmp(key, v->key)) { michael@0: v->value = token; michael@0: break; michael@0: } michael@0: if (!v->next) { michael@0: v->next = new INIValue(key, token); michael@0: if (!v->next) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: break; michael@0: } michael@0: v = v->next; michael@0: } michael@0: NS_ASSERTION(v, "v should never be null coming out of this loop"); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsINIParser::GetString(const char *aSection, const char *aKey, michael@0: nsACString &aResult) michael@0: { michael@0: INIValue *val; michael@0: mSections.Get(aSection, &val); michael@0: michael@0: while (val) { michael@0: if (strcmp(val->key, aKey) == 0) { michael@0: aResult.Assign(val->value); michael@0: return NS_OK; michael@0: } michael@0: michael@0: val = val->next; michael@0: } michael@0: michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: nsresult michael@0: nsINIParser::GetString(const char *aSection, const char *aKey, michael@0: char *aResult, uint32_t aResultLen) michael@0: { michael@0: INIValue *val; michael@0: mSections.Get(aSection, &val); michael@0: michael@0: while (val) { michael@0: if (strcmp(val->key, aKey) == 0) { michael@0: strncpy(aResult, val->value, aResultLen); michael@0: aResult[aResultLen - 1] = '\0'; michael@0: if (strlen(val->value) >= aResultLen) michael@0: return NS_ERROR_LOSS_OF_SIGNIFICANT_DATA; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: val = val->next; michael@0: } michael@0: michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: PLDHashOperator michael@0: nsINIParser::GetSectionsCB(const char *aKey, INIValue *aData, michael@0: void *aClosure) michael@0: { michael@0: GSClosureStruct *cs = reinterpret_cast(aClosure); michael@0: michael@0: return cs->usercb(aKey, cs->userclosure) ? PL_DHASH_NEXT : PL_DHASH_STOP; michael@0: } michael@0: michael@0: nsresult michael@0: nsINIParser::GetSections(INISectionCallback aCB, void *aClosure) michael@0: { michael@0: GSClosureStruct gs = { michael@0: aCB, michael@0: aClosure michael@0: }; michael@0: michael@0: mSections.EnumerateRead(GetSectionsCB, &gs); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsINIParser::GetStrings(const char *aSection, michael@0: INIStringCallback aCB, void *aClosure) michael@0: { michael@0: INIValue *val; michael@0: michael@0: for (mSections.Get(aSection, &val); michael@0: val; michael@0: val = val->next) { michael@0: michael@0: if (!aCB(val->key, val->value, aClosure)) michael@0: return NS_OK; michael@0: } michael@0: michael@0: return NS_OK; michael@0: }