VirtualBox

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

Last change on this file since 75244 was 69062, checked in by vboxsync, 7 years ago

IPRT: Added RTIniFileQueryPair for key/value pair enumeration.

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