VirtualBox

source: vbox/trunk/src/VBox/Runtime/common/misc/inifile.cpp@ 96014

Last change on this file since 96014 was 93115, checked in by vboxsync, 3 years ago

scm --update-copyright-year

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 26.9 KB
Line 
1/* $Id: inifile.cpp 93115 2022-01-01 11:31:46Z vboxsync $ */
2/** @file
3 * IPRT - INI-file parser.
4 */
5
6/*
7 * Copyright (C) 2017-2022 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 *
17 * The contents of this file may alternatively be used under the terms
18 * of the Common Development and Distribution License Version 1.0
19 * (CDDL) only, as it comes in the "COPYING.CDDL" file of the
20 * VirtualBox OSE distribution, in which case the provisions of the
21 * CDDL are applicable instead of those of the GPL.
22 *
23 * You may elect to license modified versions of this file under the
24 * terms and conditions of either the GPL or the CDDL or both.
25 */
26
27
28/*********************************************************************************************************************************
29* Header Files *
30*********************************************************************************************************************************/
31#include <iprt/inifile.h>
32#include "internal/iprt.h"
33
34#include <iprt/asm.h>
35#include <iprt/assert.h>
36#include <iprt/ctype.h>
37#include <iprt/err.h>
38#include <iprt/latin1.h>
39#include <iprt/mem.h>
40#include <iprt/string.h>
41#include <iprt/utf16.h>
42#include <iprt/vfs.h>
43
44#include "internal/magics.h"
45
46
47/*********************************************************************************************************************************
48* Defined Constants And Macros *
49*********************************************************************************************************************************/
50/** @def RTINIFILE_MAX_SIZE
51 * The maximum INI-file size we accept loading. */
52#if ARCH_BITS > 32
53# define RTINIFILE_MAX_SIZE (_64M - 2U)
54#elif ARCH_BITS > 16
55# define RTINIFILE_MAX_SIZE (_16M - 2U)
56#else
57# define RTINIFILE_MAX_SIZE (_64K - 2U)
58#endif
59
60/** @def RTINIFILE_MAX_SECTIONS
61 * The maximum number of sections we accept in an INI-file. */
62#if ARCH_BITS > 32
63# define RTINIFILE_MAX_SECTIONS (_1M)
64#elif ARCH_BITS > 16
65# define RTINIFILE_MAX_SECTIONS (_256K)
66#else
67# define RTINIFILE_MAX_SECTIONS (_1K)
68#endif
69
70
71/*********************************************************************************************************************************
72* Structures and Typedefs *
73*********************************************************************************************************************************/
74/**
75 * File encoding types.
76 */
77typedef enum RTINIFILEENCODING
78{
79 /** The customary invalid zero value. */
80 RTINIFILEENCODING_INVALID = 0,
81 /** We treat this as latin-1. */
82 RTINIFILEENCODING_ANSI,
83 /** UTF-8. */
84 RTINIFILEENCODING_UTF8,
85 /** Little endian UTF-16. */
86 RTINIFILEENCODING_UTF16LE,
87 /** Big endian UTF-16. */
88 RTINIFILEENCODING_UTF16BE,
89 /** End of valid encoding types. */
90 RTINIFILEENCODING_END
91} RTINIFILEENCODING;
92
93
94/**
95 * Preparsed section info.
96 */
97typedef struct RTINIFILESECTION
98{
99 /** The section name offset (byte). */
100 uint32_t offName;
101 /** The section length in bytes starting with the name. */
102 uint32_t cchSection;
103 /** The UTF-8 length of the section name. */
104 uint32_t cchName;
105 /** Offset into the section where to start looking for values. */
106 uint32_t cchSkipToValues : 24;
107 /** @todo use 4 bits for flags and stuff. like escaped name. */
108} RTINIFILESECTION;
109/** Pointer to preparsed section info. */
110typedef RTINIFILESECTION *PRTINIFILESECTION;
111
112
113/**
114 * INI-file instance data.
115 */
116typedef struct RTINIFILEINT
117{
118 /** Magic value (RTINIFILEINT_MAGIC). */
119 uint32_t u32Magic;
120 /** Reference counter. */
121 uint32_t volatile cRefs;
122 /** The file we're working on. */
123 RTVFSFILE hVfsFile;
124 /** Flags, RTINIFILE_F_XXX. */
125 uint32_t fFlags;
126
127 /** The original file encoding. */
128 RTINIFILEENCODING enmEncoding;
129 /** Pointer to the file content (converted to UTF-8). */
130 char *pszFile;
131 /** The file size. */
132 uint32_t cbFile;
133 /** Number of sections. */
134 uint32_t cSections;
135 /** Sections in the loaded file. */
136 PRTINIFILESECTION paSections;
137
138} RTINIFILEINT;
139/** Pointer to an INI-file instance. */
140typedef RTINIFILEINT *PRTINIFILEINT;
141
142
143static int rtIniFileLoad(PRTINIFILEINT pThis)
144{
145 /*
146 * Load the entire file into memory, ensuring two terminating zeros.
147 */
148 uint64_t cbFile;
149 int rc = RTVfsFileQuerySize(pThis->hVfsFile, &cbFile);
150 AssertRCReturn(rc, rc);
151
152 if (cbFile > RTINIFILE_MAX_SIZE)
153 return VERR_TOO_MUCH_DATA;
154 if (cbFile == 0)
155 return VINF_SUCCESS; /* Nothing to do. */
156
157 pThis->cbFile = (uint32_t)cbFile;
158 pThis->pszFile = (char *)RTMemAllocZ(pThis->cbFile + 2);
159 if (!pThis->pszFile)
160 return VERR_NO_MEMORY;
161
162 rc = RTVfsFileReadAt(pThis->hVfsFile, 0, pThis->pszFile, pThis->cbFile, NULL);
163 AssertRCReturn(rc, rc);
164
165 /*
166 * Detect encoding and convert to BOM prefixed UTF-8.
167 */
168 if ( (uint8_t)pThis->pszFile[0] == UINT8_C(0xef)
169 && (uint8_t)pThis->pszFile[1] == UINT8_C(0xbb)
170 && (uint8_t)pThis->pszFile[2] == UINT8_C(0xbf))
171 {
172 pThis->enmEncoding = RTINIFILEENCODING_UTF8;
173 rc = RTStrValidateEncoding(&pThis->pszFile[3]);
174 if (RT_FAILURE(rc))
175 return rc;
176 }
177 else
178 {
179 size_t cchUtf8;
180 if ( (uint8_t)pThis->pszFile[0] == UINT8_C(0xfe)
181 && (uint8_t)pThis->pszFile[1] == UINT8_C(0xff))
182 {
183 pThis->enmEncoding = RTINIFILEENCODING_UTF16BE;
184 rc = RTUtf16BigCalcUtf8LenEx((PCRTUTF16)&pThis->pszFile[2], RTSTR_MAX, &cchUtf8);
185 }
186 else if ( (uint8_t)pThis->pszFile[0] == UINT8_C(0xff)
187 && (uint8_t)pThis->pszFile[1] == UINT8_C(0xfe))
188 {
189 pThis->enmEncoding = RTINIFILEENCODING_UTF16LE;
190 rc = RTUtf16LittleCalcUtf8LenEx((PCRTUTF16)&pThis->pszFile[2], RTSTR_MAX, &cchUtf8);
191 }
192 else
193 {
194 pThis->enmEncoding = RTINIFILEENCODING_ANSI;
195 rc = RTLatin1CalcUtf8LenEx(pThis->pszFile, RTSTR_MAX, &cchUtf8);
196 }
197 if (RT_FAILURE(rc))
198 return rc;
199
200 char *pszUtf8Bom = (char *)RTMemAllocZ(3 + cchUtf8 + 1);
201 if (!pszUtf8Bom)
202 return VERR_NO_MEMORY;
203 pszUtf8Bom[0] = '\xEF';
204 pszUtf8Bom[1] = '\xBB';
205 pszUtf8Bom[2] = '\xBF';
206
207 char *pszUtf8 = pszUtf8Bom + 3;
208 if (pThis->enmEncoding == RTINIFILEENCODING_UTF16BE)
209 rc = RTUtf16BigToUtf8Ex((PCRTUTF16)&pThis->pszFile[2], RTSTR_MAX, &pszUtf8, cchUtf8 + 1, NULL);
210 else if (pThis->enmEncoding == RTINIFILEENCODING_UTF16LE)
211 rc = RTUtf16LittleToUtf8Ex((PCRTUTF16)&pThis->pszFile[2], RTSTR_MAX, &pszUtf8, cchUtf8 + 1, NULL);
212 else
213 rc = RTLatin1ToUtf8Ex(pThis->pszFile, RTSTR_MAX, &pszUtf8, cchUtf8 + 1, NULL);
214 AssertRCReturnStmt(rc, RTMemFree(pszUtf8Bom), rc);
215
216 RTMemFree(pThis->pszFile);
217 pThis->pszFile = pszUtf8Bom;
218 pThis->cbFile = 3 + (uint32_t)cchUtf8;
219 }
220
221 /*
222 * Do a rough section count.
223 * Section zero is for unsectioned values at the start of the file.
224 */
225 uint32_t cSections = 1;
226 const char *psz = pThis->pszFile + 3;
227 char ch;
228 while ((ch = *psz) != '\0')
229 {
230 while (RT_C_IS_SPACE(ch))
231 ch = *++psz;
232 if (ch == '[')
233 cSections++;
234
235 /* next line. */
236 psz = strchr(psz, '\n');
237 if (psz)
238 psz++;
239 else
240 break;
241 }
242 if (cSections > RTINIFILE_MAX_SECTIONS)
243 return VERR_TOO_MUCH_DATA;
244
245 /*
246 * Allocation section array and do the preparsing.
247 */
248 pThis->paSections = (PRTINIFILESECTION)RTMemAllocZ(sizeof(pThis->paSections[0]) * cSections);
249 if (!pThis->paSections)
250 return VERR_NO_MEMORY;
251
252 uint32_t iSection = 0;
253 pThis->paSections[0].offName = 3;
254 pThis->paSections[0].cchName = 0;
255 pThis->paSections[0].cchSkipToValues = 0;
256 psz = pThis->pszFile + 3;
257 while ((ch = *psz) != '\0')
258 {
259 const char *const pszLine = psz;
260
261 while (RT_C_IS_SPACE(ch))
262 ch = *++psz;
263 if (ch == '[')
264 {
265 /* Complete previous section. */
266 pThis->paSections[iSection].cchSection = (uint32_t)(pszLine - &pThis->pszFile[pThis->paSections[iSection].offName]);
267
268 /* New section. */
269 iSection++;
270 AssertReturn(iSection < cSections, VERR_INTERNAL_ERROR_3);
271 const char * const pszName = ++psz;
272 pThis->paSections[iSection].offName = (uint32_t)(psz - pThis->pszFile);
273
274 /* Figure the name length. We're very very relaxed about terminating bracket. */
275 while ((ch = *psz) != '\0' && ch != ']' && ch != '\r' && ch != '\n')
276 psz++;
277 pThis->paSections[iSection].cchName = (uint32_t)(psz - pszName);
278
279 /* Set skip count to the start of the next line. */
280 while (ch != '\0' && ch != '\n')
281 ch = *++psz;
282 pThis->paSections[iSection].cchSkipToValues = (uint32_t)(psz - pszName + 1);
283
284 if (ch == '\n')
285 psz++;
286 else
287 break;
288 }
289 else
290 {
291 psz = strchr(psz, '\n');
292 if (psz)
293 psz++;
294 else
295 break;
296 }
297 }
298
299 /* Complete the final section. */
300 pThis->paSections[iSection].cchSection = pThis->cbFile - pThis->paSections[iSection].offName;
301 pThis->cSections = iSection + 1;
302
303 return VINF_SUCCESS;
304}
305
306
307/**
308 * Creates a INI-file instance from a VFS file handle.
309 *
310 * @returns IPRT status code
311 * @param phIniFile Where to return the INI-file handle.
312 * @param hVfsFile The VFS file handle (not consumed, additional
313 * reference is retained).
314 * @param fFlags Flags, RTINIFILE_F_XXX.
315 */
316RTDECL(int) RTIniFileCreateFromVfsFile(PRTINIFILE phIniFile, RTVFSFILE hVfsFile, uint32_t fFlags)
317{
318 /*
319 * Validate input, retaining a reference to the file.
320 */
321 AssertPtrReturn(phIniFile, VERR_INVALID_POINTER);
322 AssertReturn(!(fFlags & ~RTINIFILE_F_VALID_MASK), VERR_INVALID_FLAGS);
323
324 uint32_t cRefs = RTVfsFileRetain(hVfsFile);
325 AssertReturn(cRefs != UINT32_MAX, VERR_INVALID_HANDLE);
326
327 /*
328 * Create an instance.
329 */
330 PRTINIFILEINT pThis = (PRTINIFILEINT)RTMemAllocZ(sizeof(*pThis));
331 if (pThis)
332 {
333 pThis->u32Magic = RTINIFILE_MAGIC;
334 pThis->cRefs = 1;
335 pThis->hVfsFile = hVfsFile;
336 pThis->fFlags = fFlags;
337
338 int rc = rtIniFileLoad(pThis);
339 if (RT_SUCCESS(rc))
340 {
341
342 *phIniFile = pThis;
343 return VINF_SUCCESS;
344 }
345 RTIniFileRelease(pThis);
346 return rc;
347 }
348 RTVfsFileRelease(hVfsFile);
349 return VERR_NO_MEMORY;
350}
351
352
353/**
354 * Retains a reference to an INI-file instance.
355 *
356 * @returns New reference count, UINT32_MAX on failure.
357 * @param hIniFile The INI-file handle.
358 */
359RTDECL(uint32_t) RTIniFileRetain(RTINIFILE hIniFile)
360{
361 PRTINIFILEINT pThis = hIniFile;
362 AssertPtrReturn(pThis, UINT32_MAX);
363 AssertReturn(pThis->u32Magic == RTINIFILE_MAGIC, UINT32_MAX);
364
365 uint32_t cRefs = ASMAtomicIncU32(&pThis->cRefs);
366 Assert(cRefs > 1);
367 Assert(cRefs < _64K);
368 return cRefs;
369}
370
371
372/**
373 * Releases a reference to an INI-file instance, destroying it if the count
374 * reaches zero.
375 *
376 * @returns New reference count, UINT32_MAX on failure.
377 * @param hIniFile The INI-file handle. NIL is ignored.
378 */
379RTDECL(uint32_t) RTIniFileRelease(RTINIFILE hIniFile)
380{
381 if (hIniFile == NIL_RTINIFILE)
382 return 0;
383 PRTINIFILEINT pThis = hIniFile;
384 AssertPtrReturn(pThis, UINT32_MAX);
385 AssertReturn(pThis->u32Magic == RTINIFILE_MAGIC, UINT32_MAX);
386
387 uint32_t cRefs = ASMAtomicDecU32(&pThis->cRefs);
388 Assert(cRefs < _64K);
389 if (cRefs == 0)
390 {
391 AssertReturn(ASMAtomicCmpXchgU32(&pThis->u32Magic, RTINIFILE_MAGIC_DEAD, RTINIFILE_MAGIC), UINT32_MAX);
392 RTMemFree(pThis->paSections);
393 pThis->paSections = NULL;
394 RTMemFree(pThis->pszFile);
395 pThis->pszFile = NULL;
396 RTVfsFileRelease(pThis->hVfsFile);
397 pThis->hVfsFile = NIL_RTVFSFILE;
398 RTMemFree(pThis);
399 }
400 return cRefs;
401}
402
403
404/**
405 * Worker for RTIniFileQueryValue.
406 */
407static int rtIniFileQueryValueInSection(PRTINIFILEINT pThis, PRTINIFILESECTION pSection, const char *pszKey, size_t cchKey,
408 char *pszValue, size_t cbValue, size_t *pcbActual)
409{
410 /*
411 * Scan the section, looking for the matching key.
412 */
413 Assert(pSection->cchSkipToValues <= pSection->cchSection);
414 const char * const pszEnd = &pThis->pszFile[pSection->offName + pSection->cchSection];
415 const char * pszNext = pszEnd;
416 for (const char *psz = &pThis->pszFile[pSection->offName + pSection->cchSkipToValues];
417 (uintptr_t)psz < (uintptr_t)pszEnd;
418 psz = pszNext)
419 {
420 /* Find start of next line so we can use 'continue' to skip a line. */
421 pszNext = strchr(psz, '\n');
422 if (pszNext)
423 pszNext++;
424 else
425 pszNext = pszEnd;
426
427 /* Skip leading spaces. */
428 char ch;
429 while ((ch = *psz) != '\0' && RT_C_IS_SPACE(ch))
430 psz++;
431 if ( ch != ';' /* comment line */
432 && ch != '\n' /* empty line */
433 && ch != '\r' /* empty line */
434 && (uintptr_t)psz < (uintptr_t)pszEnd)
435 {
436 /* Find end of key name, if any. */
437 const char *pszCurKey = psz;
438 size_t cchCurKey;
439 const char *pszEqual;
440 if (ch != '=')
441 {
442 /** @todo deal with escaped equal signs? */
443 pszEqual = strchr(psz, '=');
444 if (pszEqual)
445 {
446 if ((uintptr_t)pszEqual < (uintptr_t)pszNext)
447 cchCurKey = pszEqual - pszCurKey;
448 else
449 continue;
450 }
451 else
452 break;
453
454 /* Strip trailing spaces from the current key name. */
455 while (cchCurKey > 0 && RT_C_IS_SPACE(pszCurKey[cchCurKey - 1]))
456 cchCurKey--;
457 }
458 else
459 {
460 cchCurKey = 0;
461 pszEqual = psz;
462 }
463
464 /* Match the keys. */
465 /** @todo escape sequences? */
466 if ( cchCurKey == cchKey
467 && RTStrNICmp(pszCurKey, pszKey, cchKey) == 0)
468 {
469 /*
470 * Copy out the return value, without quotes.
471 */
472
473 /* Skip leading blanks. */
474 psz = pszEqual + 1;
475 while ((ch = *psz) && RT_C_IS_SPACE(ch) && ch != '\n')
476 psz++;
477
478 /* Strip trailing spaces. */
479 size_t cchCurValue = pszNext - psz;
480 while (cchCurValue > 1 && RT_C_IS_SPACE(psz[cchCurValue - 1]))
481 cchCurValue--;
482
483 /* Strip quotes. */
484 if ( cchCurValue > 2
485 && ( (ch = *psz) == '"'
486 || ch == '\'' )
487 && psz[cchCurValue - 1] == ch)
488 {
489 cchCurValue -= 2;
490 psz++;
491 }
492
493 /* Do the copying. */
494 if (cchCurValue < cbValue)
495 {
496 memcpy(pszValue, psz, cchCurValue);
497 pszValue[cchCurValue] = '\0';
498 if (pcbActual)
499 *pcbActual = cchCurValue;
500 return VINF_SUCCESS;
501 }
502
503 if (cbValue > 0)
504 {
505 memcpy(pszValue, psz, cbValue - 1);
506 pszValue[cbValue - 1] = '\0';
507 }
508 if (pcbActual)
509 *pcbActual = cchCurValue + 1;
510 return VERR_BUFFER_OVERFLOW;
511 }
512 }
513 }
514 return VERR_NOT_FOUND;
515}
516
517
518/**
519 * Queries a value in a section.
520 *
521 * The first matching value is returned. The matching is by default case
522 * insensitive.
523 *
524 * @returns IPRT status code.
525 * @retval VERR_NOT_FOUND if section or key not found.
526 *
527 * @param hIniFile The INI-file handle.
528 * @param pszSection The section name. Pass NULL to refer to the
529 * unsectioned key space at the top of the file.
530 * @param pszKey The key name.
531 * @param pszValue Where to return the value.
532 * @param cbValue Size of the buffer @a pszValue points to.
533 * @param pcbActual Where to return the actual value size excluding
534 * terminator on success. On VERR_BUFFER_OVERFLOW this
535 * will be set to the buffer size needed to hold the
536 * value, terminator included. Optional.
537 */
538RTDECL(int) RTIniFileQueryValue(RTINIFILE hIniFile, const char *pszSection, const char *pszKey,
539 char *pszValue, size_t cbValue, size_t *pcbActual)
540{
541 /*
542 * Validate input.
543 */
544 PRTINIFILEINT pThis = hIniFile;
545 AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
546 AssertReturn(pThis->u32Magic == RTINIFILE_MAGIC, VERR_INVALID_HANDLE);
547 AssertPtrNullReturn(pszSection, VERR_INVALID_POINTER);
548 AssertPtrReturn(pszKey, VERR_INVALID_POINTER);
549 size_t const cchKey = strlen(pszKey);
550 if (cbValue)
551 AssertPtrReturn(pszValue, VERR_INVALID_POINTER);
552 AssertPtrNullReturn(pcbActual, VERR_INVALID_POINTER);
553
554 /*
555 * Search relevant sections.
556 */
557 int rc;
558 if (pszSection == NULL)
559 rc = rtIniFileQueryValueInSection(pThis, &pThis->paSections[0], pszKey, cchKey, pszValue, cbValue, pcbActual);
560 else
561 {
562 rc = VERR_NOT_FOUND;
563 uint32_t const cchSection = (uint32_t)strlen(pszSection);
564 for (uint32_t iSection = 1; iSection < pThis->cSections; iSection++)
565 if ( pThis->paSections[iSection].cchName == cchSection
566 && RTStrNICmp(&pThis->pszFile[pThis->paSections[iSection].offName], pszSection, cchSection) == 0)
567 {
568 rc = rtIniFileQueryValueInSection(pThis, &pThis->paSections[iSection], pszKey, cchKey,
569 pszValue, cbValue, pcbActual);
570 if (rc != VERR_NOT_FOUND)
571 break;
572 }
573 }
574 return rc;
575}
576
577
578/**
579 * Worker for RTIniFileQueryPair.
580 *
581 * This can also be used to count the number of pairs in a section.
582 */
583static int rtIniFileQueryPairInSection(PRTINIFILEINT pThis, PRTINIFILESECTION pSection, uint32_t *pidxPair,
584 char *pszKey, size_t cbKey, size_t *pcbKeyActual,
585 char *pszValue, size_t cbValue, size_t *pcbValueActual)
586{
587 uint32_t idxPair = *pidxPair;
588
589 /*
590 * Scan the section, looking for the matching key.
591 */
592 Assert(pSection->cchSkipToValues <= pSection->cchSection);
593 const char * const pszEnd = &pThis->pszFile[pSection->offName + pSection->cchSection];
594 const char * pszNext = pszEnd;
595 for (const char *psz = &pThis->pszFile[pSection->offName + pSection->cchSkipToValues];
596 (uintptr_t)psz < (uintptr_t)pszEnd;
597 psz = pszNext)
598 {
599 /* Find start of next line so we can use 'continue' to skip a line. */
600 pszNext = strchr(psz, '\n');
601 if (pszNext)
602 pszNext++;
603 else
604 pszNext = pszEnd;
605
606 /* Skip leading spaces. */
607 char ch;
608 while ((ch = *psz) != '\0' && RT_C_IS_SPACE(ch))
609 psz++;
610 if ( ch != ';' /* comment line */
611 && ch != '\n' /* empty line */
612 && ch != '\r' /* empty line */
613 && (uintptr_t)psz < (uintptr_t)pszEnd)
614 {
615 /* Find end of key name, if any. */
616 const char *pszCurKey = psz;
617 size_t cchCurKey;
618 const char *pszEqual;
619 if (ch != '=')
620 {
621 /** @todo deal with escaped equal signs? */
622 pszEqual = strchr(psz, '=');
623 if (pszEqual)
624 {
625 if ((uintptr_t)pszEqual < (uintptr_t)pszNext)
626 cchCurKey = pszEqual - pszCurKey;
627 else
628 continue;
629 }
630 else
631 break;
632 }
633 else
634 {
635 cchCurKey = 0;
636 pszEqual = psz;
637 }
638
639 /* Is this the pair we're looking for? */
640 if (idxPair > 0)
641 idxPair--;
642 else
643 {
644 /*
645 * Yes it's the stuff we're looking for.
646 * Prepare the the return stuff.
647 */
648
649 /* Strip trailing spaces from the key name. */
650 while (cchCurKey > 0 && RT_C_IS_SPACE(pszCurKey[cchCurKey - 1]))
651 cchCurKey--;
652
653 /* Skip leading blanks from the value. */
654 psz = pszEqual + 1;
655 while ((ch = *psz) && RT_C_IS_SPACE(ch) && ch != '\n')
656 psz++;
657
658 /* Strip trailing spaces from the value. */
659 size_t cchCurValue = pszNext - psz;
660 while (cchCurValue > 1 && RT_C_IS_SPACE(psz[cchCurValue - 1]))
661 cchCurValue--;
662
663 /* Strip value quotes. */
664 if ( cchCurValue > 2
665 && ( (ch = *psz) == '"'
666 || ch == '\'' )
667 && psz[cchCurValue - 1] == ch)
668 {
669 cchCurValue -= 2;
670 psz++;
671 }
672
673 /*
674 * Copy the stuff out.
675 */
676 if ( cchCurValue < cbValue
677 && cchCurKey < cbKey)
678 {
679 memcpy(pszKey, pszCurKey, cchCurKey);
680 pszKey[cchCurKey] = '\0';
681 if (pcbKeyActual)
682 *pcbKeyActual = cchCurKey;
683
684 memcpy(pszValue, psz, cchCurValue);
685 pszValue[cchCurValue] = '\0';
686 if (pcbValueActual)
687 *pcbValueActual = cchCurValue;
688
689 *pidxPair = 0;
690 return VINF_SUCCESS;
691 }
692
693 /* Buffer overflow. Copy out what we can. */
694 if (cbKey > 0)
695 {
696 if (cchCurKey < cbKey)
697 cbKey = cchCurKey + 1;
698 memcpy(pszKey, pszCurKey, cbKey - 1);
699 pszKey[cbKey - 1] = '\0';
700 }
701 if (pcbKeyActual)
702 *pcbKeyActual = cchCurKey + 1;
703
704 if (cbValue > 0)
705 {
706 if (cchCurValue < cbValue)
707 cbValue = cchCurValue + 1;
708 memcpy(pszValue, psz, cbValue - 1);
709 pszValue[cbValue - 1] = '\0';
710 }
711 if (pcbValueActual)
712 *pcbValueActual = cchCurValue + 1;
713
714 *pidxPair = 0;
715 return VERR_BUFFER_OVERFLOW;
716 }
717 }
718 }
719 *pidxPair = idxPair;
720 return VERR_NOT_FOUND;
721}
722
723
724/**
725 * Queries a key-value pair in a section by ordinal.
726 *
727 * @returns IPRT status code.
728 * @retval VERR_NOT_FOUND if the section wasn't found or if it contains no pair
729 * with the given ordinal value.
730 *
731 * @param hIniFile The INI-file handle.
732 * @param pszSection The section name. Pass NULL to refer to the
733 * unsectioned key space at the top of the file.
734 * @param idxPair The pair to fetch (counting from 0).
735 *
736 * @param pszKey Where to return the key name.
737 * @param cbKey Size of the buffer @a pszKey points to.
738 * @param pcbKeyActual Where to return the actual key size excluding
739 * terminator on success. On VERR_BUFFER_OVERFLOW this
740 * will be set to the buffer size needed to hold the
741 * value, terminator included. Optional.
742 *
743 * @param pszValue Where to return the value.
744 * @param cbValue Size of the buffer @a pszValue points to.
745 * @param pcbValueActual Where to return the actual value size excluding
746 * terminator on success. On VERR_BUFFER_OVERFLOW this
747 * will be set to the buffer size needed to hold the
748 * value, terminator included. Optional.
749 */
750RTDECL(int) RTIniFileQueryPair(RTINIFILE hIniFile, const char *pszSection, uint32_t idxPair,
751 char *pszKey, size_t cbKey, size_t *pcbKeyActual,
752 char *pszValue, size_t cbValue, size_t *pcbValueActual)
753{
754 /*
755 * Validate input.
756 */
757 PRTINIFILEINT pThis = hIniFile;
758 AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
759 AssertReturn(pThis->u32Magic == RTINIFILE_MAGIC, VERR_INVALID_HANDLE);
760 AssertPtrNullReturn(pszSection, VERR_INVALID_POINTER);
761 if (cbKey)
762 AssertPtrReturn(pszKey, VERR_INVALID_POINTER);
763 AssertPtrNullReturn(pcbKeyActual, VERR_INVALID_POINTER);
764 if (cbValue)
765 AssertPtrReturn(pszValue, VERR_INVALID_POINTER);
766 AssertPtrNullReturn(pcbValueActual, VERR_INVALID_POINTER);
767
768 /*
769 * Search relevant sections.
770 */
771 int rc;
772 if (pszSection == NULL)
773 rc = rtIniFileQueryPairInSection(pThis, &pThis->paSections[0], &idxPair,
774 pszKey, cbKey, pcbKeyActual, pszValue, cbValue, pcbValueActual);
775 else
776 {
777 rc = VERR_NOT_FOUND;
778 uint32_t const cchSection = (uint32_t)strlen(pszSection);
779 for (uint32_t iSection = 1; iSection < pThis->cSections; iSection++)
780 if ( pThis->paSections[iSection].cchName == cchSection
781 && RTStrNICmp(&pThis->pszFile[pThis->paSections[iSection].offName], pszSection, cchSection) == 0)
782 {
783 rc = rtIniFileQueryPairInSection(pThis, &pThis->paSections[iSection], &idxPair,
784 pszKey, cbKey, pcbKeyActual, pszValue, cbValue, pcbValueActual);
785 if (rc != VERR_NOT_FOUND)
786 break;
787 }
788 }
789 return rc;
790}
791
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette