1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 // Moz headers (alphabetical)
11 #include "nsINIParser.h"
12 #include "mozilla/ResultExtensions.h"
13 #include "mozilla/Try.h"
14 #include "mozilla/URLPreloader.h"
16 using namespace mozilla
;
18 nsresult
nsINIParser::Init(nsIFile
* aFile
) {
20 MOZ_TRY_VAR(result
, URLPreloader::ReadFile(aFile
));
22 return InitFromString(result
);
25 static const char kNL
[] = "\r\n";
26 static const char kEquals
[] = "=";
27 static const char kWhitespace
[] = " \t";
28 static const char kRBracket
[] = "]";
30 nsresult
nsINIParser::InitFromString(const nsCString
& aStr
) {
31 nsCString fileContents
;
34 if (StringHead(aStr
, 3) == "\xEF\xBB\xBF") {
35 // Someone set us up the Utf-8 BOM
36 // This case is easy, since we assume that BOM-less
37 // files are Utf-8 anyway. Just skip the BOM and process as usual.
38 fileContents
.Append(aStr
);
39 buffer
= fileContents
.BeginWriting() + 3;
41 if (StringHead(aStr
, 2) == "\xFF\xFE") {
42 // Someone set us up the Utf-16LE BOM
43 nsDependentSubstring
str(reinterpret_cast<const char16_t
*>(aStr
.get()),
46 AppendUTF16toUTF8(Substring(str
, 1), fileContents
);
48 fileContents
.Append(aStr
);
51 buffer
= fileContents
.BeginWriting();
54 char* currSection
= nullptr;
56 // outer loop tokenizes into lines
57 while (char* token
= NS_strtok(kNL
, &buffer
)) {
58 if (token
[0] == '#' || token
[0] == ';') { // it's a comment
62 token
= (char*)NS_strspnp(kWhitespace
, token
);
63 if (!*token
) { // empty line
67 if (token
[0] == '[') { // section header!
71 char* rb
= NS_strtok(kRBracket
, &token
);
72 if (!rb
|| NS_strtok(kWhitespace
, &token
)) {
73 // there's either an unclosed [Section or a [Section]Moretext!
74 // we could frankly decide that this INI file is malformed right
75 // here and stop, but we won't... keep going, looking for
76 // a well-formed [section] to continue working with
77 currSection
= nullptr;
84 // If we haven't found a section header (or we found a malformed
85 // section header), don't bother parsing this line.
90 char* e
= NS_strtok(kEquals
, &token
);
95 SetString(currSection
, key
, token
);
101 bool nsINIParser::IsValidSection(const char* aSection
) {
102 if (aSection
[0] == '\0') {
106 const char* found
= strpbrk(aSection
, "\r\n[]");
107 return found
== nullptr;
110 bool nsINIParser::IsValidKey(const char* aKey
) {
111 if (aKey
[0] == '\0') {
115 const char* found
= strpbrk(aKey
, "\r\n=");
116 return found
== nullptr;
119 bool nsINIParser::IsValidValue(const char* aValue
) {
120 const char* found
= strpbrk(aValue
, "\r\n");
121 return found
== nullptr;
124 nsresult
nsINIParser::GetString(const char* aSection
, const char* aKey
,
125 nsACString
& aResult
) {
126 if (!IsValidSection(aSection
) || !IsValidKey(aKey
)) {
127 return NS_ERROR_INVALID_ARG
;
131 mSections
.Get(aSection
, &val
);
134 if (strcmp(val
->key
, aKey
) == 0) {
135 aResult
.Assign(val
->value
);
139 val
= val
->next
.get();
142 return NS_ERROR_FAILURE
;
145 nsresult
nsINIParser::GetString(const char* aSection
, const char* aKey
,
146 char* aResult
, uint32_t aResultLen
) {
147 if (!IsValidSection(aSection
) || !IsValidKey(aKey
)) {
148 return NS_ERROR_INVALID_ARG
;
152 mSections
.Get(aSection
, &val
);
155 if (strcmp(val
->key
, aKey
) == 0) {
156 strncpy(aResult
, val
->value
, aResultLen
);
157 aResult
[aResultLen
- 1] = '\0';
158 if (strlen(val
->value
) >= aResultLen
) {
159 return NS_ERROR_LOSS_OF_SIGNIFICANT_DATA
;
165 val
= val
->next
.get();
168 return NS_ERROR_FAILURE
;
171 nsresult
nsINIParser::GetSections(INISectionCallback aCB
, void* aClosure
) {
172 for (const auto& key
: mSections
.Keys()) {
173 if (!aCB(key
, aClosure
)) {
180 nsresult
nsINIParser::GetStrings(const char* aSection
, INIStringCallback aCB
,
182 if (!IsValidSection(aSection
)) {
183 return NS_ERROR_INVALID_ARG
;
188 for (mSections
.Get(aSection
, &val
); val
; val
= val
->next
.get()) {
189 if (!aCB(val
->key
, val
->value
, aClosure
)) {
197 nsresult
nsINIParser::SetString(const char* aSection
, const char* aKey
,
198 const char* aValue
) {
199 if (!IsValidSection(aSection
) || !IsValidKey(aKey
) || !IsValidValue(aValue
)) {
200 return NS_ERROR_INVALID_ARG
;
203 mSections
.WithEntryHandle(aSection
, [&](auto&& entry
) {
205 entry
.Insert(MakeUnique
<INIValue
>(aKey
, aValue
));
209 INIValue
* v
= entry
->get();
211 // Check whether this key has already been specified; overwrite
212 // if so, or append if not.
214 if (!strcmp(aKey
, v
->key
)) {
219 v
->next
= MakeUnique
<INIValue
>(aKey
, aValue
);
224 NS_ASSERTION(v
, "v should never be null coming out of this loop");
230 nsresult
nsINIParser::DeleteString(const char* aSection
, const char* aKey
) {
231 if (!IsValidSection(aSection
) || !IsValidKey(aKey
)) {
232 return NS_ERROR_INVALID_ARG
;
236 if (!mSections
.Get(aSection
, &val
)) {
237 return NS_ERROR_FAILURE
;
240 // Special case the first result
241 if (strcmp(val
->key
, aKey
) == 0) {
243 mSections
.Remove(aSection
);
245 mSections
.InsertOrUpdate(aSection
, std::move(val
->next
));
252 if (strcmp(val
->next
->key
, aKey
) == 0) {
253 val
->next
= std::move(val
->next
->next
);
258 val
= val
->next
.get();
261 return NS_ERROR_FAILURE
;
264 nsresult
nsINIParser::DeleteSection(const char* aSection
) {
265 if (!IsValidSection(aSection
)) {
266 return NS_ERROR_INVALID_ARG
;
269 if (!mSections
.Remove(aSection
)) {
270 return NS_ERROR_FAILURE
;
275 nsresult
nsINIParser::RenameSection(const char* aSection
,
276 const char* aNewName
) {
277 if (!IsValidSection(aSection
) || !IsValidSection(aNewName
)) {
278 return NS_ERROR_INVALID_ARG
;
281 if (mSections
.Contains(aNewName
)) {
282 return NS_ERROR_ILLEGAL_VALUE
;
285 mozilla::UniquePtr
<INIValue
> val
;
286 if (mSections
.Remove(aSection
, &val
)) {
287 mSections
.InsertOrUpdate(aNewName
, std::move(val
));
289 return NS_ERROR_FAILURE
;
295 nsresult
nsINIParser::WriteToFile(nsIFile
* aFile
) {
298 WriteToString(buffer
);
301 nsresult rv
= aFile
->OpenANSIFileDesc("w", &writeFile
);
302 NS_ENSURE_SUCCESS(rv
, rv
);
304 unsigned int length
= buffer
.Length();
306 if (fwrite(buffer
.get(), sizeof(char), length
, writeFile
) != length
) {
308 return NS_ERROR_UNEXPECTED
;
315 void nsINIParser::WriteToString(nsACString
& aOutput
) {
316 for (const auto& entry
: mSections
) {
317 aOutput
.AppendPrintf("[%s]\n", entry
.GetKey());
318 INIValue
* val
= entry
.GetWeak();
320 aOutput
.AppendPrintf("%s=%s\n", val
->key
, val
->value
);
321 val
= val
->next
.get();
323 aOutput
.AppendLiteral("\n");