main
cpp 770 lines 24.7 KB
Raw
1 // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3 #include "precomp.h"
4
5
6 // Exit macros
7 #define IniExitOnLastError(x, s, ...) ExitOnLastErrorSource(DUTIL_SOURCE_INIUTIL, x, s, __VA_ARGS__)
8 #define IniExitOnLastErrorDebugTrace(x, s, ...) ExitOnLastErrorDebugTraceSource(DUTIL_SOURCE_INIUTIL, x, s, __VA_ARGS__)
9 #define IniExitWithLastError(x, s, ...) ExitWithLastErrorSource(DUTIL_SOURCE_INIUTIL, x, s, __VA_ARGS__)
10 #define IniExitOnFailure(x, s, ...) ExitOnFailureSource(DUTIL_SOURCE_INIUTIL, x, s, __VA_ARGS__)
11 #define IniExitOnRootFailure(x, s, ...) ExitOnRootFailureSource(DUTIL_SOURCE_INIUTIL, x, s, __VA_ARGS__)
12 #define IniExitOnFailureDebugTrace(x, s, ...) ExitOnFailureDebugTraceSource(DUTIL_SOURCE_INIUTIL, x, s, __VA_ARGS__)
13 #define IniExitOnNull(p, x, e, s, ...) ExitOnNullSource(DUTIL_SOURCE_INIUTIL, p, x, e, s, __VA_ARGS__)
14 #define IniExitOnNullWithLastError(p, x, s, ...) ExitOnNullWithLastErrorSource(DUTIL_SOURCE_INIUTIL, p, x, s, __VA_ARGS__)
15 #define IniExitOnNullDebugTrace(p, x, e, s, ...) ExitOnNullDebugTraceSource(DUTIL_SOURCE_INIUTIL, p, x, e, s, __VA_ARGS__)
16 #define IniExitOnInvalidHandleWithLastError(p, x, s, ...) ExitOnInvalidHandleWithLastErrorSource(DUTIL_SOURCE_INIUTIL, p, x, s, __VA_ARGS__)
17 #define IniExitOnWin32Error(e, x, s, ...) ExitOnWin32ErrorSource(DUTIL_SOURCE_INIUTIL, e, x, s, __VA_ARGS__)
18 #define IniExitOnGdipFailure(g, x, s, ...) ExitOnGdipFailureSource(DUTIL_SOURCE_INIUTIL, g, x, s, __VA_ARGS__)
19
20 const LPCWSTR wzSectionSeparator = L"\\";
21
22 struct INI_STRUCT
23 {
24 LPWSTR sczPath; // the path to the INI file to be parsed
25
26 LPWSTR sczOpenTagPrefix; // For regular ini, this would be '['
27 LPWSTR sczOpenTagPostfix; // For regular ini, this would be ']'
28
29 LPWSTR sczValuePrefix; // for regular ini, this would be NULL
30 LPWSTR sczValueSeparator; // for regular ini, this would be '='
31
32 LPWSTR *rgsczValueSeparatorExceptions;
33 DWORD cValueSeparatorExceptions;
34
35 LPWSTR sczCommentLinePrefix; // for regular ini, this would be ';'
36
37 INI_VALUE *rgivValues;
38 DWORD cValues;
39
40 LPWSTR *rgsczLines;
41 DWORD cLines;
42
43 FILE_ENCODING feEncoding;
44 BOOL fModified;
45 };
46
47 const int INI_HANDLE_BYTES = sizeof(INI_STRUCT);
48
49 static HRESULT GetSectionPrefixFromName(
50 __in_z LPCWSTR wzName,
51 __deref_inout_z LPWSTR* psczOutput
52 );
53 static void UninitializeIniValue(
54 INI_VALUE *pivValue
55 );
56
57 extern "C" HRESULT DAPI IniInitialize(
58 __out_bcount(INI_HANDLE_BYTES) INI_HANDLE* piHandle
59 )
60 {
61 HRESULT hr = S_OK;
62
63 // Allocate the handle
64 *piHandle = static_cast<INI_HANDLE>(MemAlloc(sizeof(INI_STRUCT), TRUE));
65 IniExitOnNull(*piHandle, hr, E_OUTOFMEMORY, "Failed to allocate ini object");
66
67 LExit:
68 return hr;
69 }
70
71 extern "C" void DAPI IniUninitialize(
72 __in_bcount(INI_HANDLE_BYTES) INI_HANDLE piHandle
73 )
74 {
75 INI_STRUCT *pi = static_cast<INI_STRUCT *>(piHandle);
76
77 ReleaseStr(pi->sczPath);
78 ReleaseStr(pi->sczOpenTagPrefix);
79 ReleaseStr(pi->sczOpenTagPostfix);
80 ReleaseStr(pi->sczValuePrefix);
81 ReleaseStr(pi->sczValueSeparator);
82
83 for (DWORD i = 0; i < pi->cValueSeparatorExceptions; ++i)
84 {
85 ReleaseStr(pi->rgsczValueSeparatorExceptions + i);
86 }
87
88 ReleaseStr(pi->sczCommentLinePrefix);
89
90 for (DWORD i = 0; i < pi->cValues; ++i)
91 {
92 UninitializeIniValue(pi->rgivValues + i);
93 }
94 ReleaseMem(pi->rgivValues);
95
96 ReleaseStrArray(pi->rgsczLines, pi->cLines);
97
98 ReleaseMem(pi);
99 }
100
101 extern "C" HRESULT DAPI IniSetOpenTag(
102 __inout_bcount(INI_HANDLE_BYTES) INI_HANDLE piHandle,
103 __in_z_opt LPCWSTR wzOpenTagPrefix,
104 __in_z_opt LPCWSTR wzOpenTagPostfix
105 )
106 {
107 HRESULT hr = S_OK;
108
109 INI_STRUCT *pi = static_cast<INI_STRUCT *>(piHandle);
110
111 if (wzOpenTagPrefix)
112 {
113 hr = StrAllocString(&pi->sczOpenTagPrefix, wzOpenTagPrefix, 0);
114 IniExitOnFailure(hr, "Failed to copy open tag prefix to ini struct: %ls", wzOpenTagPrefix);
115 }
116 else
117 {
118 ReleaseNullStr(pi->sczOpenTagPrefix);
119 }
120
121 if (wzOpenTagPostfix)
122 {
123 hr = StrAllocString(&pi->sczOpenTagPostfix, wzOpenTagPostfix, 0);
124 IniExitOnFailure(hr, "Failed to copy open tag postfix to ini struct: %ls", wzOpenTagPostfix);
125 }
126 else
127 {
128 ReleaseNullStr(pi->sczOpenTagPrefix);
129 }
130
131 LExit:
132 return hr;
133 }
134
135 extern "C" HRESULT DAPI IniSetValueStyle(
136 __inout_bcount(INI_HANDLE_BYTES) INI_HANDLE piHandle,
137 __in_z_opt LPCWSTR wzValuePrefix,
138 __in_z_opt LPCWSTR wzValueSeparator
139 )
140 {
141 HRESULT hr = S_OK;
142
143 INI_STRUCT *pi = static_cast<INI_STRUCT *>(piHandle);
144
145 if (wzValuePrefix)
146 {
147 hr = StrAllocString(&pi->sczValuePrefix, wzValuePrefix, 0);
148 IniExitOnFailure(hr, "Failed to copy value prefix to ini struct: %ls", wzValuePrefix);
149 }
150 else
151 {
152 ReleaseNullStr(pi->sczValuePrefix);
153 }
154
155 if (wzValueSeparator)
156 {
157 hr = StrAllocString(&pi->sczValueSeparator, wzValueSeparator, 0);
158 IniExitOnFailure(hr, "Failed to copy value separator to ini struct: %ls", wzValueSeparator);
159 }
160 else
161 {
162 ReleaseNullStr(pi->sczValueSeparator);
163 }
164
165 LExit:
166 return hr;
167 }
168
169 extern "C" HRESULT DAPI IniSetValueSeparatorException(
170 __inout_bcount(INI_HANDLE_BYTES) INI_HANDLE piHandle,
171 __in_z LPCWSTR wzValueNamePrefix
172 )
173 {
174 HRESULT hr = S_OK;
175 DWORD dwInsertedIndex = 0;
176
177 INI_STRUCT *pi = static_cast<INI_STRUCT *>(piHandle);
178
179 hr = MemEnsureArraySize(reinterpret_cast<void **>(&pi->rgsczValueSeparatorExceptions), pi->cValueSeparatorExceptions + 1, sizeof(LPWSTR), 5);
180 IniExitOnFailure(hr, "Failed to increase array size for value separator exceptions");
181 dwInsertedIndex = pi->cValueSeparatorExceptions;
182 ++pi->cValueSeparatorExceptions;
183
184 hr = StrAllocString(&pi->rgsczValueSeparatorExceptions[dwInsertedIndex], wzValueNamePrefix, 0);
185 IniExitOnFailure(hr, "Failed to copy value separator exception");
186
187 LExit:
188 return hr;
189 }
190
191 extern "C" HRESULT DAPI IniSetCommentStyle(
192 __inout_bcount(INI_HANDLE_BYTES) INI_HANDLE piHandle,
193 __in_z_opt LPCWSTR wzLinePrefix
194 )
195 {
196 HRESULT hr = S_OK;
197
198 INI_STRUCT *pi = static_cast<INI_STRUCT *>(piHandle);
199
200 if (wzLinePrefix)
201 {
202 hr = StrAllocString(&pi->sczCommentLinePrefix, wzLinePrefix, 0);
203 IniExitOnFailure(hr, "Failed to copy comment line prefix to ini struct: %ls", wzLinePrefix);
204 }
205 else
206 {
207 ReleaseNullStr(pi->sczCommentLinePrefix);
208 }
209
210 LExit:
211 return hr;
212 }
213
214 extern "C" HRESULT DAPI IniParse(
215 __inout_bcount(INI_HANDLE_BYTES) INI_HANDLE piHandle,
216 __in LPCWSTR wzPath,
217 __out_opt FILE_ENCODING *pfeEncodingFound
218 )
219 {
220 HRESULT hr = S_OK;
221 DWORD dwValuePrefixLength = 0;
222 DWORD dwValueSeparatorExceptionLength = 0;
223 LPWSTR sczContents = NULL;
224 LPWSTR sczCurrentSection = NULL;
225 LPWSTR sczName = NULL;
226 LPWSTR sczNameTrimmed = NULL;
227 LPWSTR sczValue = NULL;
228 LPWSTR sczValueTrimmed = NULL;
229 LPWSTR wzOpenTagPrefix = NULL;
230 LPWSTR wzOpenTagPostfix = NULL;
231 LPWSTR wzValuePrefix = NULL;
232 LPWSTR wzValueNameStart = NULL;
233 LPWSTR wzValueSeparator = NULL;
234 LPWSTR wzCommentLinePrefix = NULL;
235 LPWSTR wzValueBegin = NULL;
236 LPCWSTR wzTemp = NULL;
237
238 INI_STRUCT *pi = static_cast<INI_STRUCT *>(piHandle);
239
240 BOOL fSections = (NULL != pi->sczOpenTagPrefix) && (NULL != pi->sczOpenTagPostfix);
241 BOOL fValuePrefix = (NULL != pi->sczValuePrefix);
242
243 hr = StrAllocString(&pi->sczPath, wzPath, 0);
244 IniExitOnFailure(hr, "Failed to copy path to ini struct: %ls", wzPath);
245
246 hr = FileToString(pi->sczPath, &sczContents, &pi->feEncoding);
247 IniExitOnFailure(hr, "Failed to convert file to string: %ls", pi->sczPath);
248
249 if (pfeEncodingFound)
250 {
251 *pfeEncodingFound = pi->feEncoding;
252 }
253
254 if (!sczContents || !*sczContents)
255 {
256 // Empty string, nothing to parse
257 ExitFunction1(hr = S_OK);
258 }
259
260 dwValuePrefixLength = lstrlenW(pi->sczValuePrefix);
261 hr = StrSplitAllocArray(&pi->rgsczLines, reinterpret_cast<UINT *>(&pi->cLines), sczContents, L"\n");
262 IniExitOnFailure(hr, "Failed to split INI file into lines");
263
264 for (DWORD i = 0; i < pi->cLines; ++i)
265 {
266 if (!*pi->rgsczLines[i] || '\r' == *pi->rgsczLines[i])
267 {
268 continue;
269 }
270
271 if (pi->sczCommentLinePrefix)
272 {
273 wzCommentLinePrefix = wcsstr(pi->rgsczLines[i], pi->sczCommentLinePrefix);
274
275 if (wzCommentLinePrefix && wzCommentLinePrefix <= pi->rgsczLines[i] + 1)
276 {
277 continue;
278 }
279 }
280
281 if (pi->sczOpenTagPrefix)
282 {
283 wzOpenTagPrefix = wcsstr(pi->rgsczLines[i], pi->sczOpenTagPrefix);
284 if (wzOpenTagPrefix)
285 {
286 // If there is an open tag prefix but there is anything but whitespace before it, then it's NOT an open tag prefix
287 // This is important, for example, to support values with names like "Array[0]=blah" in INI format
288 for (wzTemp = pi->rgsczLines[i]; wzTemp < wzOpenTagPrefix; ++wzTemp)
289 {
290 if (*wzTemp != L' ' && *wzTemp != L'\t')
291 {
292 wzOpenTagPrefix = NULL;
293 break;
294 }
295 }
296 }
297 }
298
299 if (pi->sczOpenTagPostfix)
300 {
301 wzOpenTagPostfix = wcsstr(pi->rgsczLines[i], pi->sczOpenTagPostfix);
302 }
303
304 wzValueNameStart = NULL;
305
306 if (pi->sczValuePrefix)
307 {
308 wzValuePrefix = wcsstr(pi->rgsczLines[i], pi->sczValuePrefix);
309 if (wzValuePrefix != NULL)
310 {
311 wzValueNameStart = wzValuePrefix + dwValuePrefixLength;
312 }
313 }
314 else
315 {
316 wzValueNameStart = pi->rgsczLines[i];
317 }
318
319 if (pi->sczValueSeparator && NULL != wzValueNameStart && *wzValueNameStart != L'\0')
320 {
321 dwValueSeparatorExceptionLength = 0;
322 for (DWORD j = 0; j < pi->cValueSeparatorExceptions; ++j)
323 {
324 if (pi->rgsczLines[i] == wcsstr(pi->rgsczLines[i], pi->rgsczValueSeparatorExceptions[j]))
325 {
326 dwValueSeparatorExceptionLength = lstrlenW(pi->rgsczValueSeparatorExceptions[j]);
327 break;
328 }
329 }
330
331 wzValueSeparator = wcsstr(wzValueNameStart + dwValueSeparatorExceptionLength, pi->sczValueSeparator);
332 }
333
334 // Don't keep the endline
335 if (pi->rgsczLines[i][lstrlenW(pi->rgsczLines[i])-1] == L'\r')
336 {
337 pi->rgsczLines[i][lstrlenW(pi->rgsczLines[i])-1] = L'\0';
338 }
339
340 if (fSections && wzOpenTagPrefix && wzOpenTagPostfix && wzOpenTagPrefix < wzOpenTagPostfix && (NULL == wzCommentLinePrefix || wzOpenTagPrefix < wzCommentLinePrefix))
341 {
342 // There is an section starting here, let's keep track of it and move on
343 hr = StrAllocString(&sczCurrentSection, wzOpenTagPrefix + lstrlenW(pi->sczOpenTagPrefix), wzOpenTagPostfix - (wzOpenTagPrefix + lstrlenW(pi->sczOpenTagPrefix)));
344 IniExitOnFailure(hr, "Failed to record section name for line: %ls of INI file: %ls", pi->rgsczLines[i], pi->sczPath);
345
346 // Sections will be calculated dynamically after any set operations, so don't include this in the list of lines to remember for output
347 ReleaseNullStr(pi->rgsczLines[i]);
348 }
349 else if (wzValueSeparator && (NULL == wzCommentLinePrefix || wzValueSeparator < wzCommentLinePrefix)
350 && (!fValuePrefix || wzValuePrefix))
351 {
352 if (fValuePrefix)
353 {
354 wzValueBegin = wzValuePrefix + lstrlenW(pi->sczValuePrefix);
355 }
356 else
357 {
358 wzValueBegin = pi->rgsczLines[i];
359 }
360
361 hr = MemEnsureArraySize(reinterpret_cast<void **>(&pi->rgivValues), pi->cValues + 1, sizeof(INI_VALUE), 100);
362 IniExitOnFailure(hr, "Failed to increase array size for value array");
363
364 if (sczCurrentSection)
365 {
366 hr = StrAllocString(&sczName, sczCurrentSection, 0);
367 IniExitOnFailure(hr, "Failed to copy current section name");
368
369 hr = StrAllocConcat(&sczName, wzSectionSeparator, 0);
370 IniExitOnFailure(hr, "Failed to copy current section name");
371 }
372
373 hr = StrAllocConcat(&sczName, wzValueBegin, wzValueSeparator - wzValueBegin);
374 IniExitOnFailure(hr, "Failed to copy name");
375
376 hr = StrAllocString(&sczValue, wzValueSeparator + lstrlenW(pi->sczValueSeparator), 0);
377 IniExitOnFailure(hr, "Failed to copy value");
378
379 hr = StrTrimWhitespace(&sczNameTrimmed, sczName);
380 IniExitOnFailure(hr, "Failed to trim whitespace from name");
381
382 hr = StrTrimWhitespace(&sczValueTrimmed, sczValue);
383 IniExitOnFailure(hr, "Failed to trim whitespace from value");
384
385 pi->rgivValues[pi->cValues].wzName = const_cast<LPCWSTR>(sczNameTrimmed);
386 sczNameTrimmed = NULL;
387 pi->rgivValues[pi->cValues].wzValue = const_cast<LPCWSTR>(sczValueTrimmed);
388 sczValueTrimmed = NULL;
389 pi->rgivValues[pi->cValues].dwLineNumber = i + 1;
390
391 ++pi->cValues;
392
393 // Values will be calculated dynamically after any set operations, so don't include this in the list of lines to remember for output
394 ReleaseNullStr(pi->rgsczLines[i]);
395 }
396 else
397 {
398 // Must be a comment, so ignore it and keep it in the list to output
399 }
400
401 ReleaseNullStr(sczName);
402 }
403
404 LExit:
405 ReleaseStr(sczCurrentSection);
406 ReleaseStr(sczContents);
407 ReleaseStr(sczName);
408 ReleaseStr(sczNameTrimmed);
409 ReleaseStr(sczValue);
410 ReleaseStr(sczValueTrimmed);
411
412 return hr;
413 }
414
415 extern "C" HRESULT DAPI IniGetValueList(
416 __in_bcount(INI_HANDLE_BYTES) INI_HANDLE piHandle,
417 __deref_out_ecount_opt(*pcValues) INI_VALUE** prgivValues,
418 __out DWORD *pcValues
419 )
420 {
421 HRESULT hr = S_OK;
422
423 INI_STRUCT *pi = static_cast<INI_STRUCT *>(piHandle);
424
425 *prgivValues = pi->rgivValues;
426 *pcValues = pi->cValues;
427
428 return hr;
429 }
430
431 extern "C" HRESULT DAPI IniGetValue(
432 __in_bcount(INI_HANDLE_BYTES) INI_HANDLE piHandle,
433 __in LPCWSTR wzValueName,
434 __deref_out_z LPWSTR* psczValue
435 )
436 {
437 HRESULT hr = S_OK;
438
439 INI_STRUCT *pi = static_cast<INI_STRUCT *>(piHandle);
440 INI_VALUE *pValue = NULL;
441
442 for (DWORD i = 0; i < pi->cValues; ++i)
443 {
444 if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, pi->rgivValues[i].wzName, -1, wzValueName, -1))
445 {
446 pValue = pi->rgivValues + i;
447 break;
448 }
449 }
450
451 if (NULL == pValue)
452 {
453 hr = E_NOTFOUND;
454 IniExitOnFailure(hr, "Failed to check for INI value: %ls", wzValueName);
455 }
456
457 if (NULL == pValue->wzValue)
458 {
459 ExitFunction1(hr = E_NOTFOUND);
460 }
461
462 hr = StrAllocString(psczValue, pValue->wzValue, 0);
463 IniExitOnFailure(hr, "Failed to make copy of value while looking up INI value named: %ls", wzValueName);
464
465 LExit:
466 return hr;
467 }
468
469 extern "C" HRESULT DAPI IniSetValue(
470 __in_bcount(INI_HANDLE_BYTES) INI_HANDLE piHandle,
471 __in LPCWSTR wzValueName,
472 __in_z_opt LPCWSTR wzValue
473 )
474 {
475 HRESULT hr = S_OK;
476 LPWSTR sczSectionPrefix = NULL; // includes section name and backslash
477 LPWSTR sczName = NULL;
478 LPWSTR sczValue = NULL;
479 DWORD dwInsertIndex = DWORD_MAX;
480
481 INI_STRUCT *pi = static_cast<INI_STRUCT *>(piHandle);
482 INI_VALUE *pValue = NULL;
483
484 for (DWORD i = 0; i < pi->cValues; ++i)
485 {
486 if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, pi->rgivValues[i].wzName, -1, wzValueName, -1))
487 {
488 pValue = pi->rgivValues + i;
489 break;
490 }
491 }
492
493 // We're killing the value
494 if (NULL == wzValue)
495 {
496 if (pValue && pValue->wzValue)
497 {
498 pi->fModified = TRUE;
499 sczValue = const_cast<LPWSTR>(pValue->wzValue);
500 pValue->wzValue = NULL;
501 ReleaseNullStr(sczValue);
502 }
503
504 ExitFunction();
505 }
506 else
507 {
508 if (pValue)
509 {
510 if (CSTR_EQUAL != ::CompareStringW(LOCALE_INVARIANT, 0, pValue->wzValue, -1, wzValue, -1))
511 {
512 pi->fModified = TRUE;
513 hr = StrAllocString(const_cast<LPWSTR *>(&pValue->wzValue), wzValue, 0);
514 IniExitOnFailure(hr, "Failed to update value INI value named: %ls", wzValueName);
515 }
516
517 ExitFunction1(hr = S_OK);
518 }
519 else
520 {
521 if (wzValueName)
522 {
523 hr = GetSectionPrefixFromName(wzValueName, &sczSectionPrefix);
524 IniExitOnFailure(hr, "Failed to get section prefix from value name: %ls", wzValueName);
525 }
526
527 // If we have a section prefix, figure out the index to insert it (at the end of the section it belongs in)
528 if (sczSectionPrefix)
529 {
530 for (DWORD i = 0; i < pi->cValues; ++i)
531 {
532 if (0 == wcsncmp(pi->rgivValues[i].wzName, sczSectionPrefix, lstrlenW(sczSectionPrefix)))
533 {
534 dwInsertIndex = i;
535 }
536 else if (DWORD_MAX != dwInsertIndex)
537 {
538 break;
539 }
540 }
541 }
542 else
543 {
544 for (DWORD i = 0; i < pi->cValues; ++i)
545 {
546 if (NULL == wcsstr(pi->rgivValues[i].wzName, wzSectionSeparator))
547 {
548 dwInsertIndex = i;
549 }
550 else if (DWORD_MAX != dwInsertIndex)
551 {
552 break;
553 }
554 }
555 }
556
557 // Otherwise, just add it to the end
558 if (DWORD_MAX == dwInsertIndex)
559 {
560 dwInsertIndex = pi->cValues;
561 }
562
563 pi->fModified = TRUE;
564 hr = MemInsertIntoArray(reinterpret_cast<void **>(&pi->rgivValues), dwInsertIndex, 1, pi->cValues + 1, sizeof(INI_VALUE), 100);
565 IniExitOnFailure(hr, "Failed to insert value into array");
566
567 hr = StrAllocString(&sczName, wzValueName, 0);
568 IniExitOnFailure(hr, "Failed to copy name");
569
570 hr = StrAllocString(&sczValue, wzValue, 0);
571 IniExitOnFailure(hr, "Failed to copy value");
572
573 pi->rgivValues[dwInsertIndex].wzName = const_cast<LPCWSTR>(sczName);
574 sczName = NULL;
575 pi->rgivValues[dwInsertIndex].wzValue = const_cast<LPCWSTR>(sczValue);
576 sczValue = NULL;
577
578 ++pi->cValues;
579 }
580 }
581
582 LExit:
583 ReleaseStr(sczName);
584 ReleaseStr(sczValue);
585
586 return hr;
587 }
588
589 extern "C" HRESULT DAPI IniWriteFile(
590 __in_bcount(INI_HANDLE_BYTES) INI_HANDLE piHandle,
591 __in_z_opt LPCWSTR wzPath,
592 __in FILE_ENCODING feOverrideEncoding
593 )
594 {
595 HRESULT hr = S_OK;
596 LPWSTR sczCurrentSectionPrefix = NULL;
597 LPWSTR sczNewSectionPrefix = NULL;
598 LPWSTR sczContents = NULL;
599 LPCWSTR wzName = NULL;
600 DWORD dwLineArrayIndex = 1;
601 FILE_ENCODING feEncoding;
602
603 INI_STRUCT *pi = static_cast<INI_STRUCT *>(piHandle);
604
605 if (FILE_ENCODING_UNSPECIFIED == feOverrideEncoding)
606 {
607 feEncoding = pi->feEncoding;
608 }
609 else
610 {
611 feEncoding = feOverrideEncoding;
612 }
613
614 if (FILE_ENCODING_UNSPECIFIED == feEncoding)
615 {
616 feEncoding = FILE_ENCODING_UTF16_WITH_BOM;
617 }
618
619 if (!pi->fModified)
620 {
621 ExitFunction1(hr = S_OK);
622 }
623 if (NULL == wzPath && NULL == pi->sczPath)
624 {
625 ExitFunction1(hr = E_NOTFOUND);
626 }
627
628 BOOL fSections = (pi->sczOpenTagPrefix) && (pi->sczOpenTagPostfix);
629
630 hr = StrAllocString(&sczContents, L"", 0);
631 IniExitOnFailure(hr, "Failed to begin contents string as empty string");
632
633 // Insert any beginning lines we didn't understand like comments
634 if (0 < pi->cLines)
635 {
636 while (pi->rgsczLines[dwLineArrayIndex])
637 {
638 hr = StrAllocConcat(&sczContents, pi->rgsczLines[dwLineArrayIndex], 0);
639 IniExitOnFailure(hr, "Failed to add previous line to ini output buffer in-memory");
640
641 hr = StrAllocConcat(&sczContents, L"\r\n", 2);
642 IniExitOnFailure(hr, "Failed to add endline to ini output buffer in-memory");
643
644 ++dwLineArrayIndex;
645 }
646 }
647
648 for (DWORD i = 0; i < pi->cValues; ++i)
649 {
650 // Skip if this value was killed off
651 if (NULL == pi->rgivValues[i].wzValue)
652 {
653 continue;
654 }
655
656 // Now generate any lines for the current value like value line and maybe also a new section line before it
657
658 // First see if we need to write a section line
659 hr = GetSectionPrefixFromName(pi->rgivValues[i].wzName, &sczNewSectionPrefix);
660 IniExitOnFailure(hr, "Failed to get section prefix from name: %ls", pi->rgivValues[i].wzName);
661
662 // If the new section prefix is different, write a section out for it
663 if (fSections && sczNewSectionPrefix && (NULL == sczCurrentSectionPrefix || CSTR_EQUAL != ::CompareStringW(LOCALE_INVARIANT, 0, sczNewSectionPrefix, -1, sczCurrentSectionPrefix, -1)))
664 {
665 hr = StrAllocConcat(&sczContents, pi->sczOpenTagPrefix, 0);
666 IniExitOnFailure(hr, "Failed to concat open tag prefix to string");
667
668 // Exclude section separator (i.e. backslash) from new section prefix
669 hr = StrAllocConcat(&sczContents, sczNewSectionPrefix, lstrlenW(sczNewSectionPrefix)-lstrlenW(wzSectionSeparator));
670 IniExitOnFailure(hr, "Failed to concat section name to string");
671
672 hr = StrAllocConcat(&sczContents, pi->sczOpenTagPostfix, 0);
673 IniExitOnFailure(hr, "Failed to concat open tag postfix to string");
674
675 hr = StrAllocConcat(&sczContents, L"\r\n", 2);
676 IniExitOnFailure(hr, "Failed to add endline to ini output buffer in-memory");
677
678 ReleaseNullStr(sczCurrentSectionPrefix);
679 sczCurrentSectionPrefix = sczNewSectionPrefix;
680 sczNewSectionPrefix = NULL;
681 }
682
683 // Inserting lines we read before the current value if appropriate
684 while (pi->rgivValues[i].dwLineNumber > dwLineArrayIndex)
685 {
686 // Skip any lines were purposely forgot
687 if (NULL == pi->rgsczLines[dwLineArrayIndex])
688 {
689 ++dwLineArrayIndex;
690 continue;
691 }
692
693 hr = StrAllocConcat(&sczContents, pi->rgsczLines[dwLineArrayIndex++], 0);
694 IniExitOnFailure(hr, "Failed to add previous line to ini output buffer in-memory");
695
696 hr = StrAllocConcat(&sczContents, L"\r\n", 2);
697 IniExitOnFailure(hr, "Failed to add endline to ini output buffer in-memory");
698 }
699
700 wzName = pi->rgivValues[i].wzName;
701 if (fSections)
702 {
703 wzName += lstrlenW(sczCurrentSectionPrefix);
704 }
705
706 // OK, now just write the name/value pair, if it isn't deleted
707 if (pi->sczValuePrefix)
708 {
709 hr = StrAllocConcat(&sczContents, pi->sczValuePrefix, 0);
710 IniExitOnFailure(hr, "Failed to concat value prefix to ini output buffer");
711 }
712
713 hr = StrAllocConcat(&sczContents, wzName, 0);
714 IniExitOnFailure(hr, "Failed to concat value name to ini output buffer");
715
716 hr = StrAllocConcat(&sczContents, pi->sczValueSeparator, 0);
717 IniExitOnFailure(hr, "Failed to concat value separator to ini output buffer");
718
719 hr = StrAllocConcat(&sczContents, pi->rgivValues[i].wzValue, 0);
720 IniExitOnFailure(hr, "Failed to concat value to ini output buffer");
721
722 hr = StrAllocConcat(&sczContents, L"\r\n", 2);
723 IniExitOnFailure(hr, "Failed to add endline to ini output buffer in-memory");
724 }
725
726 // If no path was specified, use the path to the file we parsed
727 if (NULL == wzPath)
728 {
729 wzPath = pi->sczPath;
730 }
731
732 hr = FileFromString(wzPath, 0, sczContents, feEncoding);
733 IniExitOnFailure(hr, "Failed to write INI contents out to file: %ls", wzPath);
734
735 LExit:
736 ReleaseStr(sczContents);
737 ReleaseStr(sczCurrentSectionPrefix);
738 ReleaseStr(sczNewSectionPrefix);
739
740 return hr;
741 }
742
743 static void UninitializeIniValue(
744 INI_VALUE *pivValue
745 )
746 {
747 ReleaseStr(const_cast<LPWSTR>(pivValue->wzName));
748 ReleaseStr(const_cast<LPWSTR>(pivValue->wzValue));
749 }
750
751 static HRESULT GetSectionPrefixFromName(
752 __in_z LPCWSTR wzName,
753 __deref_inout_z LPWSTR* psczOutput
754 )
755 {
756 HRESULT hr = S_OK;
757 LPCWSTR wzSectionDelimiter = NULL;
758
759 ReleaseNullStr(*psczOutput);
760
761 wzSectionDelimiter = wcsstr(wzName, wzSectionSeparator);
762 if (wzSectionDelimiter && wzSectionDelimiter != wzName)
763 {
764 hr = StrAllocString(psczOutput, wzName, wzSectionDelimiter - wzName + 1);
765 IniExitOnFailure(hr, "Failed to copy section prefix");
766 }
767
768 LExit:
769 return hr;
770 }