|
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
|
2 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
3 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
5 |
|
6 // Moz headers (alphabetical) |
|
7 #include "nsCRTGlue.h" |
|
8 #include "nsError.h" |
|
9 #include "nsIFile.h" |
|
10 #include "nsINIParser.h" |
|
11 #include "mozilla/FileUtils.h" // AutoFILE |
|
12 |
|
13 // System headers (alphabetical) |
|
14 #include <stdio.h> |
|
15 #include <stdlib.h> |
|
16 #ifdef XP_WIN |
|
17 #include <windows.h> |
|
18 #endif |
|
19 |
|
20 #if defined(XP_WIN) |
|
21 #define READ_BINARYMODE L"rb" |
|
22 #else |
|
23 #define READ_BINARYMODE "r" |
|
24 #endif |
|
25 |
|
26 #ifdef XP_WIN |
|
27 inline FILE *TS_tfopen (const char *path, const wchar_t *mode) |
|
28 { |
|
29 wchar_t wPath[MAX_PATH]; |
|
30 MultiByteToWideChar(CP_UTF8, 0, path, -1, wPath, MAX_PATH); |
|
31 return _wfopen(wPath, mode); |
|
32 } |
|
33 #else |
|
34 inline FILE *TS_tfopen (const char *path, const char *mode) |
|
35 { |
|
36 return fopen(path, mode); |
|
37 } |
|
38 #endif |
|
39 |
|
40 // Stack based FILE wrapper to ensure that fclose is called, copied from |
|
41 // toolkit/mozapps/update/updater/readstrings.cpp |
|
42 |
|
43 class AutoFILE { |
|
44 public: |
|
45 AutoFILE(FILE *fp = nullptr) : fp_(fp) {} |
|
46 ~AutoFILE() { if (fp_) fclose(fp_); } |
|
47 operator FILE *() { return fp_; } |
|
48 FILE** operator &() { return &fp_; } |
|
49 void operator=(FILE *fp) { fp_ = fp; } |
|
50 private: |
|
51 FILE *fp_; |
|
52 }; |
|
53 |
|
54 nsresult |
|
55 nsINIParser::Init(nsIFile* aFile) |
|
56 { |
|
57 /* open the file. Don't use OpenANSIFileDesc, because you mustn't |
|
58 pass FILE* across shared library boundaries, which may be using |
|
59 different CRTs */ |
|
60 |
|
61 AutoFILE fd; |
|
62 |
|
63 #ifdef XP_WIN |
|
64 nsAutoString path; |
|
65 nsresult rv = aFile->GetPath(path); |
|
66 if (NS_WARN_IF(NS_FAILED(rv))) |
|
67 return rv; |
|
68 |
|
69 fd = _wfopen(path.get(), READ_BINARYMODE); |
|
70 #else |
|
71 nsAutoCString path; |
|
72 aFile->GetNativePath(path); |
|
73 |
|
74 fd = fopen(path.get(), READ_BINARYMODE); |
|
75 #endif |
|
76 |
|
77 if (!fd) |
|
78 return NS_ERROR_FAILURE; |
|
79 |
|
80 return InitFromFILE(fd); |
|
81 } |
|
82 |
|
83 nsresult |
|
84 nsINIParser::Init(const char *aPath) |
|
85 { |
|
86 /* open the file */ |
|
87 AutoFILE fd = TS_tfopen(aPath, READ_BINARYMODE); |
|
88 |
|
89 if (!fd) |
|
90 return NS_ERROR_FAILURE; |
|
91 |
|
92 return InitFromFILE(fd); |
|
93 } |
|
94 |
|
95 static const char kNL[] = "\r\n"; |
|
96 static const char kEquals[] = "="; |
|
97 static const char kWhitespace[] = " \t"; |
|
98 static const char kRBracket[] = "]"; |
|
99 |
|
100 nsresult |
|
101 nsINIParser::InitFromFILE(FILE *fd) |
|
102 { |
|
103 /* get file size */ |
|
104 if (fseek(fd, 0, SEEK_END) != 0) |
|
105 return NS_ERROR_FAILURE; |
|
106 |
|
107 long flen = ftell(fd); |
|
108 if (flen == 0) |
|
109 return NS_ERROR_FAILURE; |
|
110 |
|
111 /* malloc an internal buf the size of the file */ |
|
112 mFileContents = new char[flen + 2]; |
|
113 if (!mFileContents) |
|
114 return NS_ERROR_OUT_OF_MEMORY; |
|
115 |
|
116 /* read the file in one swoop */ |
|
117 if (fseek(fd, 0, SEEK_SET) != 0) |
|
118 return NS_BASE_STREAM_OSERROR; |
|
119 |
|
120 int rd = fread(mFileContents, sizeof(char), flen, fd); |
|
121 if (rd != flen) |
|
122 return NS_BASE_STREAM_OSERROR; |
|
123 |
|
124 // We write a UTF16 null so that the file is easier to convert to UTF8 |
|
125 mFileContents[flen] = mFileContents[flen + 1] = '\0'; |
|
126 |
|
127 char *buffer = &mFileContents[0]; |
|
128 |
|
129 if (flen >= 3 |
|
130 && mFileContents[0] == static_cast<char>(0xEF) |
|
131 && mFileContents[1] == static_cast<char>(0xBB) |
|
132 && mFileContents[2] == static_cast<char>(0xBF)) { |
|
133 // Someone set us up the Utf-8 BOM |
|
134 // This case is easy, since we assume that BOM-less |
|
135 // files are Utf-8 anyway. Just skip the BOM and process as usual. |
|
136 buffer = &mFileContents[3]; |
|
137 } |
|
138 |
|
139 #ifdef XP_WIN |
|
140 if (flen >= 2 |
|
141 && mFileContents[0] == static_cast<char>(0xFF) |
|
142 && mFileContents[1] == static_cast<char>(0xFE)) { |
|
143 // Someone set us up the Utf-16LE BOM |
|
144 buffer = &mFileContents[2]; |
|
145 // Get the size required for our Utf8 buffer |
|
146 flen = WideCharToMultiByte(CP_UTF8, |
|
147 0, |
|
148 reinterpret_cast<LPWSTR>(buffer), |
|
149 -1, |
|
150 nullptr, |
|
151 0, |
|
152 nullptr, |
|
153 nullptr); |
|
154 if (0 == flen) { |
|
155 return NS_ERROR_FAILURE; |
|
156 } |
|
157 |
|
158 nsAutoArrayPtr<char> utf8Buffer(new char[flen]); |
|
159 if (0 == WideCharToMultiByte(CP_UTF8, |
|
160 0, |
|
161 reinterpret_cast<LPWSTR>(buffer), |
|
162 -1, |
|
163 utf8Buffer, |
|
164 flen, |
|
165 nullptr, |
|
166 nullptr)) { |
|
167 return NS_ERROR_FAILURE; |
|
168 } |
|
169 mFileContents = utf8Buffer.forget(); |
|
170 buffer = mFileContents; |
|
171 } |
|
172 #endif |
|
173 |
|
174 char *currSection = nullptr; |
|
175 |
|
176 // outer loop tokenizes into lines |
|
177 while (char *token = NS_strtok(kNL, &buffer)) { |
|
178 if (token[0] == '#' || token[0] == ';') // it's a comment |
|
179 continue; |
|
180 |
|
181 token = (char*) NS_strspnp(kWhitespace, token); |
|
182 if (!*token) // empty line |
|
183 continue; |
|
184 |
|
185 if (token[0] == '[') { // section header! |
|
186 ++token; |
|
187 currSection = token; |
|
188 |
|
189 char *rb = NS_strtok(kRBracket, &token); |
|
190 if (!rb || NS_strtok(kWhitespace, &token)) { |
|
191 // there's either an unclosed [Section or a [Section]Moretext! |
|
192 // we could frankly decide that this INI file is malformed right |
|
193 // here and stop, but we won't... keep going, looking for |
|
194 // a well-formed [section] to continue working with |
|
195 currSection = nullptr; |
|
196 } |
|
197 |
|
198 continue; |
|
199 } |
|
200 |
|
201 if (!currSection) { |
|
202 // If we haven't found a section header (or we found a malformed |
|
203 // section header), don't bother parsing this line. |
|
204 continue; |
|
205 } |
|
206 |
|
207 char *key = token; |
|
208 char *e = NS_strtok(kEquals, &token); |
|
209 if (!e || !token) |
|
210 continue; |
|
211 |
|
212 INIValue *v; |
|
213 if (!mSections.Get(currSection, &v)) { |
|
214 v = new INIValue(key, token); |
|
215 if (!v) |
|
216 return NS_ERROR_OUT_OF_MEMORY; |
|
217 |
|
218 mSections.Put(currSection, v); |
|
219 continue; |
|
220 } |
|
221 |
|
222 // Check whether this key has already been specified; overwrite |
|
223 // if so, or append if not. |
|
224 while (v) { |
|
225 if (!strcmp(key, v->key)) { |
|
226 v->value = token; |
|
227 break; |
|
228 } |
|
229 if (!v->next) { |
|
230 v->next = new INIValue(key, token); |
|
231 if (!v->next) |
|
232 return NS_ERROR_OUT_OF_MEMORY; |
|
233 break; |
|
234 } |
|
235 v = v->next; |
|
236 } |
|
237 NS_ASSERTION(v, "v should never be null coming out of this loop"); |
|
238 } |
|
239 |
|
240 return NS_OK; |
|
241 } |
|
242 |
|
243 nsresult |
|
244 nsINIParser::GetString(const char *aSection, const char *aKey, |
|
245 nsACString &aResult) |
|
246 { |
|
247 INIValue *val; |
|
248 mSections.Get(aSection, &val); |
|
249 |
|
250 while (val) { |
|
251 if (strcmp(val->key, aKey) == 0) { |
|
252 aResult.Assign(val->value); |
|
253 return NS_OK; |
|
254 } |
|
255 |
|
256 val = val->next; |
|
257 } |
|
258 |
|
259 return NS_ERROR_FAILURE; |
|
260 } |
|
261 |
|
262 nsresult |
|
263 nsINIParser::GetString(const char *aSection, const char *aKey, |
|
264 char *aResult, uint32_t aResultLen) |
|
265 { |
|
266 INIValue *val; |
|
267 mSections.Get(aSection, &val); |
|
268 |
|
269 while (val) { |
|
270 if (strcmp(val->key, aKey) == 0) { |
|
271 strncpy(aResult, val->value, aResultLen); |
|
272 aResult[aResultLen - 1] = '\0'; |
|
273 if (strlen(val->value) >= aResultLen) |
|
274 return NS_ERROR_LOSS_OF_SIGNIFICANT_DATA; |
|
275 |
|
276 return NS_OK; |
|
277 } |
|
278 |
|
279 val = val->next; |
|
280 } |
|
281 |
|
282 return NS_ERROR_FAILURE; |
|
283 } |
|
284 |
|
285 PLDHashOperator |
|
286 nsINIParser::GetSectionsCB(const char *aKey, INIValue *aData, |
|
287 void *aClosure) |
|
288 { |
|
289 GSClosureStruct *cs = reinterpret_cast<GSClosureStruct*>(aClosure); |
|
290 |
|
291 return cs->usercb(aKey, cs->userclosure) ? PL_DHASH_NEXT : PL_DHASH_STOP; |
|
292 } |
|
293 |
|
294 nsresult |
|
295 nsINIParser::GetSections(INISectionCallback aCB, void *aClosure) |
|
296 { |
|
297 GSClosureStruct gs = { |
|
298 aCB, |
|
299 aClosure |
|
300 }; |
|
301 |
|
302 mSections.EnumerateRead(GetSectionsCB, &gs); |
|
303 return NS_OK; |
|
304 } |
|
305 |
|
306 nsresult |
|
307 nsINIParser::GetStrings(const char *aSection, |
|
308 INIStringCallback aCB, void *aClosure) |
|
309 { |
|
310 INIValue *val; |
|
311 |
|
312 for (mSections.Get(aSection, &val); |
|
313 val; |
|
314 val = val->next) { |
|
315 |
|
316 if (!aCB(val->key, val->value, aClosure)) |
|
317 return NS_OK; |
|
318 } |
|
319 |
|
320 return NS_OK; |
|
321 } |