Coverage Report

Created: 2025-11-16 06:25

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/port/cplstring.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  GDAL
4
 * Purpose:  CPLString implementation.
5
 * Author:   Frank Warmerdam, warmerdam@pobox.com
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2005, Frank Warmerdam <warmerdam@pobox.com>
9
 * Copyright (c) 2011, Even Rouault <even dot rouault at spatialys.com>
10
 *
11
 * SPDX-License-Identifier: MIT
12
 ****************************************************************************/
13
14
#include "cpl_port.h"
15
#include "cpl_string.h"
16
17
#include <cctype>
18
#include <cstdarg>
19
#include <cstddef>
20
#include <cstring>
21
#include <string>
22
23
#include "cpl_config.h"
24
#include "cpl_conv.h"
25
26
#if !defined(va_copy) && defined(__va_copy)
27
#define va_copy __va_copy
28
#endif
29
30
/*
31
 * The CPLString class is derived from std::string, so the vast majority
32
 * of the implementation comes from that.  This module is just the extensions
33
 * we add.
34
 */
35
36
/************************************************************************/
37
/*                               Printf()                               */
38
/************************************************************************/
39
40
/** Assign the content of the string using sprintf() */
41
CPLString &CPLString::Printf(CPL_FORMAT_STRING(const char *pszFormat), ...)
42
43
14.0k
{
44
14.0k
    va_list args;
45
46
14.0k
    va_start(args, pszFormat);
47
14.0k
    vPrintf(pszFormat, args);
48
14.0k
    va_end(args);
49
50
14.0k
    return *this;
51
14.0k
}
52
53
/************************************************************************/
54
/*                              vPrintf()                               */
55
/************************************************************************/
56
57
/** Assign the content of the string using vsprintf() */
58
CPLString &CPLString::vPrintf(CPL_FORMAT_STRING(const char *pszFormat),
59
                              va_list args)
60
61
14.0k
{
62
    /* -------------------------------------------------------------------- */
63
    /*      This implementation for platforms without vsnprintf() will      */
64
    /*      just plain fail if the formatted contents are too large.        */
65
    /* -------------------------------------------------------------------- */
66
67
#if !defined(HAVE_VSNPRINTF)
68
    char *pszBuffer = static_cast<char *>(CPLMalloc(30000));
69
    if (CPLvsnprintf(pszBuffer, 30000, pszFormat, args) > 29998)
70
    {
71
        CPLError(CE_Fatal, CPLE_AppDefined,
72
                 "CPLString::vPrintf() ... buffer overrun.");
73
    }
74
    *this = pszBuffer;
75
    CPLFree(pszBuffer);
76
77
/* -------------------------------------------------------------------- */
78
/*      This should grow a big enough buffer to hold any formatted      */
79
/*      result.                                                         */
80
/* -------------------------------------------------------------------- */
81
#else
82
14.0k
    va_list wrk_args;
83
84
14.0k
#ifdef va_copy
85
14.0k
    va_copy(wrk_args, args);
86
#else
87
    wrk_args = args;
88
#endif
89
90
14.0k
    char szModestBuffer[500] = {};
91
14.0k
    szModestBuffer[0] = '\0';
92
14.0k
    int nPR = CPLvsnprintf(szModestBuffer, sizeof(szModestBuffer), pszFormat,
93
14.0k
                           wrk_args);
94
14.0k
    if (nPR == -1 || nPR >= static_cast<int>(sizeof(szModestBuffer)) - 1)
95
0
    {
96
0
        int nWorkBufferSize = 2000;
97
0
        char *pszWorkBuffer = static_cast<char *>(CPLMalloc(nWorkBufferSize));
98
99
0
#ifdef va_copy
100
0
        va_end(wrk_args);
101
0
        va_copy(wrk_args, args);
102
#else
103
        wrk_args = args;
104
#endif
105
0
        while ((nPR = CPLvsnprintf(pszWorkBuffer, nWorkBufferSize, pszFormat,
106
0
                                   wrk_args)) >= nWorkBufferSize - 1 ||
107
0
               nPR == -1)
108
0
        {
109
0
            nWorkBufferSize *= 4;
110
0
            pszWorkBuffer =
111
0
                static_cast<char *>(CPLRealloc(pszWorkBuffer, nWorkBufferSize));
112
0
#ifdef va_copy
113
0
            va_end(wrk_args);
114
0
            va_copy(wrk_args, args);
115
#else
116
            wrk_args = args;
117
#endif
118
0
        }
119
0
        *this = pszWorkBuffer;
120
0
        CPLFree(pszWorkBuffer);
121
0
    }
122
14.0k
    else
123
14.0k
    {
124
14.0k
        *this = szModestBuffer;
125
14.0k
    }
126
14.0k
#ifdef va_copy
127
14.0k
    va_end(wrk_args);
128
14.0k
#endif
129
130
14.0k
#endif /* !defined(HAVE_VSNPRINTF) */
131
132
14.0k
    return *this;
133
14.0k
}
134
135
/************************************************************************/
136
/*                              FormatC()                               */
137
/************************************************************************/
138
139
/**
140
 * Format double in C locale.
141
 *
142
 * The passed value is formatted using the C locale (period as decimal
143
 * separator) and appended to the target CPLString.
144
 *
145
 * @param dfValue the value to format.
146
 * @param pszFormat the sprintf() style format to use or omit for default.
147
 * Note that this format string should only include one substitution argument
148
 * and it must be for a double (%f or %g).
149
 *
150
 * @return a reference to the CPLString.
151
 */
152
153
CPLString &CPLString::FormatC(double dfValue, const char *pszFormat)
154
155
0
{
156
0
    if (pszFormat == nullptr)
157
0
        pszFormat = "%g";
158
159
    // presumably long enough for any number.
160
0
    const size_t buf_size = 512;
161
0
    char szWork[buf_size] = {};
162
163
0
    CPLsnprintf(szWork, buf_size, pszFormat, dfValue);
164
165
0
    *this += szWork;
166
167
0
    return *this;
168
0
}
169
170
/************************************************************************/
171
/*                                Trim()                                */
172
/************************************************************************/
173
174
/**
175
 * Trim white space.
176
 *
177
 * Trims white space off the let and right of the string.  White space
178
 * is any of a space, a tab, a newline ('\\n') or a carriage control ('\\r').
179
 *
180
 * @return a reference to the CPLString.
181
 */
182
183
CPLString &CPLString::Trim()
184
185
0
{
186
0
    constexpr char szWhitespace[] = " \t\r\n";
187
188
0
    const size_t iLeft = find_first_not_of(szWhitespace);
189
0
    const size_t iRight = find_last_not_of(szWhitespace);
190
191
0
    if (iLeft == std::string::npos)
192
0
    {
193
0
        erase();
194
0
        return *this;
195
0
    }
196
197
0
    assign(substr(iLeft, iRight - iLeft + 1));
198
199
0
    return *this;
200
0
}
201
202
/************************************************************************/
203
/*                               Recode()                               */
204
/************************************************************************/
205
206
/** Recode the string */
207
CPLString &CPLString::Recode(const char *pszSrcEncoding,
208
                             const char *pszDstEncoding)
209
210
0
{
211
0
    if (pszSrcEncoding == nullptr)
212
0
        pszSrcEncoding = CPL_ENC_UTF8;
213
0
    if (pszDstEncoding == nullptr)
214
0
        pszDstEncoding = CPL_ENC_UTF8;
215
216
0
    if (strcmp(pszSrcEncoding, pszDstEncoding) == 0)
217
0
        return *this;
218
219
0
    char *pszRecoded = CPLRecode(c_str(), pszSrcEncoding, pszDstEncoding);
220
221
0
    if (pszRecoded == nullptr)
222
0
        return *this;
223
224
0
    assign(pszRecoded);
225
0
    CPLFree(pszRecoded);
226
227
0
    return *this;
228
0
}
229
230
/************************************************************************/
231
/*                               ifind()                                */
232
/************************************************************************/
233
234
/**
235
 * Case insensitive find() alternative.
236
 *
237
 * @param str substring to find.
238
 * @param pos offset in the string at which the search starts.
239
 * @return the position of substring in the string or std::string::npos if not
240
 * found.
241
 */
242
243
size_t CPLString::ifind(const std::string &str, size_t pos) const
244
245
0
{
246
0
    return ifind(str.c_str(), pos);
247
0
}
248
249
/**
250
 * Case insensitive find() alternative.
251
 *
252
 * @param s substring to find.
253
 * @param nPos offset in the string at which the search starts.
254
 * @return the position of the substring in the string or std::string::npos if
255
 * not found.
256
 */
257
258
size_t CPLString::ifind(const char *s, size_t nPos) const
259
260
0
{
261
0
    const char *pszHaystack = c_str();
262
0
    const char chFirst =
263
0
        static_cast<char>(CPLTolower(static_cast<unsigned char>(s[0])));
264
0
    const size_t nTargetLen = strlen(s);
265
266
0
    if (nPos > size())
267
0
        nPos = size();
268
269
0
    pszHaystack += nPos;
270
271
0
    while (*pszHaystack != '\0')
272
0
    {
273
0
        if (chFirst == CPLTolower(static_cast<unsigned char>(*pszHaystack)))
274
0
        {
275
0
            if (EQUALN(pszHaystack, s, nTargetLen))
276
0
                return nPos;
277
0
        }
278
279
0
        nPos++;
280
0
        pszHaystack++;
281
0
    }
282
283
0
    return std::string::npos;
284
0
}
285
286
/************************************************************************/
287
/*                              toupper()                               */
288
/************************************************************************/
289
290
/**
291
 * Convert to upper case in place.
292
 */
293
294
CPLString &CPLString::toupper()
295
296
12.8k
{
297
272k
    for (size_t i = 0; i < size(); i++)
298
259k
        (*this)[i] = static_cast<char>(CPLToupper((*this)[i]));
299
300
12.8k
    return *this;
301
12.8k
}
302
303
/************************************************************************/
304
/*                              tolower()                               */
305
/************************************************************************/
306
307
/**
308
 * Convert to lower case in place.
309
 */
310
311
CPLString &CPLString::tolower()
312
313
0
{
314
0
    for (size_t i = 0; i < size(); i++)
315
0
        (*this)[i] = static_cast<char>(CPLTolower((*this)[i]));
316
317
0
    return *this;
318
0
}
319
320
/************************************************************************/
321
/*                             replaceAll()                             */
322
/************************************************************************/
323
324
/**
325
 * Replace all occurrences of osBefore with osAfter.
326
 */
327
CPLString &CPLString::replaceAll(const std::string &osBefore,
328
                                 const std::string &osAfter)
329
22.0k
{
330
22.0k
    const size_t nBeforeSize = osBefore.size();
331
22.0k
    const size_t nAfterSize = osAfter.size();
332
22.0k
    if (nBeforeSize)
333
22.0k
    {
334
22.0k
        size_t nStartPos = 0;
335
23.7k
        while ((nStartPos = find(osBefore, nStartPos)) != std::string::npos)
336
1.74k
        {
337
1.74k
            replace(nStartPos, nBeforeSize, osAfter);
338
1.74k
            nStartPos += nAfterSize;
339
1.74k
        }
340
22.0k
    }
341
22.0k
    return *this;
342
22.0k
}
343
344
/**
345
 * Replace all occurrences of chBefore with osAfter.
346
 */
347
CPLString &CPLString::replaceAll(char chBefore, const std::string &osAfter)
348
0
{
349
0
    return replaceAll(std::string(&chBefore, 1), osAfter);
350
0
}
351
352
/**
353
 * Replace all occurrences of osBefore with chAfter.
354
 */
355
CPLString &CPLString::replaceAll(const std::string &osBefore, char chAfter)
356
22.0k
{
357
22.0k
    return replaceAll(osBefore, std::string(&chAfter, 1));
358
22.0k
}
359
360
/**
361
 * Replace all occurrences of chBefore with chAfter.
362
 */
363
CPLString &CPLString::replaceAll(char chBefore, char chAfter)
364
0
{
365
0
    return replaceAll(std::string(&chBefore, 1), std::string(&chAfter, 1));
366
0
}
367
368
/************************************************************************/
369
/*                             endsWith()                              */
370
/************************************************************************/
371
372
/**
373
 * Returns whether the string ends with another string
374
 * @param osStr other string.
375
 * @return true if the string ends with osStr.
376
 */
377
bool CPLString::endsWith(const std::string &osStr) const
378
0
{
379
0
    if (size() < osStr.size())
380
0
        return false;
381
0
    return substr(size() - osStr.size()) == osStr;
382
0
}
383
384
/************************************************************************/
385
/*                             URLEncode()                              */
386
/************************************************************************/
387
388
/**
389
 * Return a string that *can* be a valid URL.
390
 *
391
 * Said otherwise if URLEncode() != *this was not a valid URL according to
392
 * https://datatracker.ietf.org/doc/html/rfc3986.html.
393
 *
394
 * This replaces all characters that are not reserved (:/?#[]\@!$&'()*+,;=),
395
 * unreserved (a-z, A-Z, 0-9 and -.-~) or already percent-encoded by their
396
 * percent-encoding.
397
 *
398
 * Note that when composing a URL, and typically query-parameters, it might
399
 * be needed to use CPLEscape(,,CPLES_URL) to also substitute reserved
400
 * characters.
401
 *
402
 * @return a URL encoded string
403
 * @since 3.12
404
 */
405
CPLString CPLString::URLEncode() const
406
0
{
407
    // Helper to check if a substring is a valid percent-encoding
408
0
    auto isPercentEncoded = [](const char *str) -> bool
409
0
    {
410
0
        return str[0] == '%' &&
411
0
               std::isxdigit(static_cast<unsigned char>(str[1])) &&
412
0
               std::isxdigit(static_cast<unsigned char>(str[2]));
413
0
    };
414
415
    // Cf https://datatracker.ietf.org/doc/html/rfc3986.html
416
0
    const char *unreserved =
417
0
        "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~";
418
0
    const char *reserved = ":/?#[]@!$&'()*+,;=";
419
0
    CPLString osEncoded;
420
0
    osEncoded.reserve(size());
421
0
    for (size_t i = 0; i < size(); ++i)
422
0
    {
423
0
        char ch = (*this)[i];
424
        // If already percent-encoded, copy as is
425
0
        if (ch == '%' && i + 2 < size() && isPercentEncoded(c_str() + i))
426
0
        {
427
0
            osEncoded += ch;
428
0
            osEncoded += (*this)[i + 1];
429
0
            osEncoded += (*this)[i + 2];
430
0
            i += 2;
431
0
        }
432
0
        else if (strchr(unreserved, ch) || strchr(reserved, ch))
433
0
        {
434
0
            osEncoded += ch;
435
0
        }
436
0
        else
437
0
        {
438
0
            char buf[4];
439
0
            snprintf(buf, sizeof(buf), "%%%02X",
440
0
                     static_cast<unsigned char>(ch));
441
0
            osEncoded += buf;
442
0
        }
443
0
    }
444
0
    return osEncoded;
445
0
}
446
447
/************************************************************************/
448
/*                         CPLURLGetValue()                             */
449
/************************************************************************/
450
451
/**
452
 * Return the value matching a key from a key=value pair in a URL.
453
 *
454
 * @param pszURL the URL.
455
 * @param pszKey the key to find.
456
 * @return the value of empty string if not found.
457
 */
458
CPLString CPLURLGetValue(const char *pszURL, const char *pszKey)
459
0
{
460
0
    CPLString osKey(pszKey);
461
0
    osKey += "=";
462
0
    size_t nKeyPos = CPLString(pszURL).ifind(osKey);
463
0
    if (nKeyPos != std::string::npos && nKeyPos > 0 &&
464
0
        (pszURL[nKeyPos - 1] == '?' || pszURL[nKeyPos - 1] == '&'))
465
0
    {
466
0
        CPLString osValue(pszURL + nKeyPos + osKey.size());
467
0
        const char *pszValue = osValue.c_str();
468
0
        const char *pszSep = strchr(pszValue, '&');
469
0
        if (pszSep)
470
0
        {
471
0
            osValue.resize(pszSep - pszValue);
472
0
        }
473
0
        return osValue;
474
0
    }
475
0
    return "";
476
0
}
477
478
/************************************************************************/
479
/*                          CPLURLAddKVP()                              */
480
/************************************************************************/
481
482
/**
483
 * Return a new URL with a new key=value pair.
484
 *
485
 * @param pszURL the URL.
486
 * @param pszKey the key to find.
487
 * @param pszValue the value of the key (may be NULL to unset an existing KVP).
488
 * @return the modified URL.
489
 */
490
CPLString CPLURLAddKVP(const char *pszURL, const char *pszKey,
491
                       const char *pszValue)
492
0
{
493
0
    CPLString osURL(strchr(pszURL, '?') == nullptr
494
0
                        ? CPLString(pszURL).append("?")
495
0
                        : pszURL);
496
497
0
    CPLString osKey(pszKey);
498
0
    osKey += "=";
499
0
    size_t nKeyPos = osURL.ifind(osKey);
500
0
    if (nKeyPos != std::string::npos && nKeyPos > 0 &&
501
0
        (osURL[nKeyPos - 1] == '?' || osURL[nKeyPos - 1] == '&'))
502
0
    {
503
0
        CPLString osNewURL(osURL);
504
0
        osNewURL.resize(nKeyPos);
505
0
        if (pszValue)
506
0
        {
507
0
            osNewURL += osKey;
508
0
            osNewURL += pszValue;
509
0
        }
510
0
        const char *pszNext = strchr(osURL.c_str() + nKeyPos, '&');
511
0
        if (pszNext)
512
0
        {
513
0
            if (osNewURL.back() == '&' || osNewURL.back() == '?')
514
0
                osNewURL += pszNext + 1;
515
0
            else
516
0
                osNewURL += pszNext;
517
0
        }
518
0
        return osNewURL;
519
0
    }
520
0
    else
521
0
    {
522
0
        CPLString osNewURL(std::move(osURL));
523
0
        if (pszValue)
524
0
        {
525
0
            if (osNewURL.back() != '&' && osNewURL.back() != '?')
526
0
                osNewURL += '&';
527
0
            osNewURL += osKey;
528
0
            osNewURL += pszValue;
529
0
        }
530
0
        return osNewURL;
531
0
    }
532
0
}
533
534
/************************************************************************/
535
/*                            CPLOPrintf()                              */
536
/************************************************************************/
537
538
/** Return a CPLString with the content of sprintf() */
539
CPLString CPLOPrintf(CPL_FORMAT_STRING(const char *pszFormat), ...)
540
541
0
{
542
0
    va_list args;
543
0
    va_start(args, pszFormat);
544
545
0
    CPLString osTarget;
546
0
    osTarget.vPrintf(pszFormat, args);
547
548
0
    va_end(args);
549
550
0
    return osTarget;
551
0
}
552
553
/************************************************************************/
554
/*                            CPLOvPrintf()                             */
555
/************************************************************************/
556
557
/** Return a CPLString with the content of vsprintf() */
558
CPLString CPLOvPrintf(CPL_FORMAT_STRING(const char *pszFormat), va_list args)
559
560
0
{
561
0
    CPLString osTarget;
562
0
    osTarget.vPrintf(pszFormat, args);
563
0
    return osTarget;
564
0
}
565
566
/************************************************************************/
567
/*                            CPLQuotedSQLIdentifer()                   */
568
/************************************************************************/
569
570
/** Return a CPLString of the SQL quoted identifier */
571
CPLString CPLQuotedSQLIdentifier(const char *pszIdent)
572
573
0
{
574
0
    CPLString osIdent;
575
576
0
    if (pszIdent)
577
0
    {
578
0
        char *pszQuotedIdent = CPLEscapeString(pszIdent, -1, CPLES_SQLI);
579
0
        osIdent.Printf("\"%s\"", pszQuotedIdent);
580
0
        CPLFree(pszQuotedIdent);
581
0
    }
582
583
0
    return osIdent;
584
0
}