Coverage Report

Created: 2026-05-16 08:20

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
98.3M
{
44
98.3M
    va_list args;
45
46
98.3M
    va_start(args, pszFormat);
47
98.3M
    vPrintf(pszFormat, args);
48
98.3M
    va_end(args);
49
50
98.3M
    return *this;
51
98.3M
}
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
132M
{
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
132M
    va_list wrk_args;
83
84
132M
#ifdef va_copy
85
132M
    va_copy(wrk_args, args);
86
#else
87
    wrk_args = args;
88
#endif
89
90
132M
    char szModestBuffer[500] = {};
91
132M
    szModestBuffer[0] = '\0';
92
132M
    int nPR = CPLvsnprintf(szModestBuffer, sizeof(szModestBuffer), pszFormat,
93
132M
                           wrk_args);
94
132M
    if (nPR == -1 || nPR >= static_cast<int>(sizeof(szModestBuffer)) - 1)
95
2.20M
    {
96
2.20M
        int nWorkBufferSize = 2000;
97
2.20M
        char *pszWorkBuffer = static_cast<char *>(CPLMalloc(nWorkBufferSize));
98
99
2.20M
#ifdef va_copy
100
2.20M
        va_end(wrk_args);
101
2.20M
        va_copy(wrk_args, args);
102
#else
103
        wrk_args = args;
104
#endif
105
2.25M
        while ((nPR = CPLvsnprintf(pszWorkBuffer, nWorkBufferSize, pszFormat,
106
2.25M
                                   wrk_args)) >= nWorkBufferSize - 1 ||
107
2.20M
               nPR == -1)
108
54.0k
        {
109
54.0k
            nWorkBufferSize *= 4;
110
54.0k
            pszWorkBuffer =
111
54.0k
                static_cast<char *>(CPLRealloc(pszWorkBuffer, nWorkBufferSize));
112
54.0k
#ifdef va_copy
113
54.0k
            va_end(wrk_args);
114
54.0k
            va_copy(wrk_args, args);
115
#else
116
            wrk_args = args;
117
#endif
118
54.0k
        }
119
2.20M
        *this = pszWorkBuffer;
120
2.20M
        CPLFree(pszWorkBuffer);
121
2.20M
    }
122
130M
    else
123
130M
    {
124
130M
        *this = szModestBuffer;
125
130M
    }
126
132M
#ifdef va_copy
127
132M
    va_end(wrk_args);
128
132M
#endif
129
130
132M
#endif /* !defined(HAVE_VSNPRINTF) */
131
132
132M
    return *this;
133
132M
}
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
23.2k
{
156
23.2k
    if (pszFormat == nullptr)
157
22.8k
        pszFormat = "%g";
158
159
    // presumably long enough for any number.
160
23.2k
    const size_t buf_size = 512;
161
23.2k
    char szWork[buf_size] = {};
162
163
23.2k
    CPLsnprintf(szWork, buf_size, pszFormat, dfValue);
164
165
23.2k
    *this += szWork;
166
167
23.2k
    return *this;
168
23.2k
}
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
74.8M
{
186
74.8M
    constexpr char szWhitespace[] = " \t\r\n";
187
188
74.8M
    const size_t iLeft = find_first_not_of(szWhitespace);
189
74.8M
    const size_t iRight = find_last_not_of(szWhitespace);
190
191
74.8M
    if (iLeft == std::string::npos)
192
10.6M
    {
193
10.6M
        erase();
194
10.6M
        return *this;
195
10.6M
    }
196
197
64.2M
    assign(substr(iLeft, iRight - iLeft + 1));
198
199
64.2M
    return *this;
200
74.8M
}
201
202
/************************************************************************/
203
/*                               Recode()                               */
204
/************************************************************************/
205
206
/** Recode the string */
207
CPLString &CPLString::Recode(const char *pszSrcEncoding,
208
                             const char *pszDstEncoding)
209
210
52.5M
{
211
52.5M
    if (pszSrcEncoding == nullptr)
212
0
        pszSrcEncoding = CPL_ENC_UTF8;
213
52.5M
    if (pszDstEncoding == nullptr)
214
0
        pszDstEncoding = CPL_ENC_UTF8;
215
216
52.5M
    if (strcmp(pszSrcEncoding, pszDstEncoding) == 0)
217
0
        return *this;
218
219
52.5M
    char *pszRecoded = CPLRecode(c_str(), pszSrcEncoding, pszDstEncoding);
220
221
52.5M
    if (pszRecoded == nullptr)
222
0
        return *this;
223
224
52.5M
    assign(pszRecoded);
225
52.5M
    CPLFree(pszRecoded);
226
227
52.5M
    return *this;
228
52.5M
}
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
1.67M
{
246
1.67M
    return ifind(str.c_str(), pos);
247
1.67M
}
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
40.8M
{
261
40.8M
    const char *pszHaystack = c_str();
262
40.8M
    const char chFirst =
263
40.8M
        static_cast<char>(CPLTolower(static_cast<unsigned char>(s[0])));
264
40.8M
    const size_t nTargetLen = strlen(s);
265
266
40.8M
    if (nPos > size())
267
0
        nPos = size();
268
269
40.8M
    pszHaystack += nPos;
270
271
463M
    while (*pszHaystack != '\0')
272
423M
    {
273
423M
        if (chFirst == CPLTolower(static_cast<unsigned char>(*pszHaystack)))
274
13.9M
        {
275
13.9M
            if (EQUALN(pszHaystack, s, nTargetLen))
276
770k
                return nPos;
277
13.9M
        }
278
279
422M
        nPos++;
280
422M
        pszHaystack++;
281
422M
    }
282
283
40.1M
    return std::string::npos;
284
40.8M
}
285
286
/************************************************************************/
287
/*                              toupper()                               */
288
/************************************************************************/
289
290
/**
291
 * Convert to upper case in place.
292
 */
293
294
CPLString &CPLString::toupper()
295
296
8.06M
{
297
135M
    for (size_t i = 0; i < size(); i++)
298
127M
        (*this)[i] = static_cast<char>(CPLToupper((*this)[i]));
299
300
8.06M
    return *this;
301
8.06M
}
302
303
/************************************************************************/
304
/*                              tolower()                               */
305
/************************************************************************/
306
307
/**
308
 * Convert to lower case in place.
309
 */
310
311
CPLString &CPLString::tolower()
312
313
717k
{
314
23.0M
    for (size_t i = 0; i < size(); i++)
315
22.3M
        (*this)[i] = static_cast<char>(CPLTolower((*this)[i]));
316
317
717k
    return *this;
318
717k
}
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
33.9M
{
330
33.9M
    const size_t nBeforeSize = osBefore.size();
331
33.9M
    const size_t nAfterSize = osAfter.size();
332
33.9M
    if (nBeforeSize)
333
33.9M
    {
334
33.9M
        size_t nStartPos = 0;
335
37.1M
        while ((nStartPos = find(osBefore, nStartPos)) != std::string::npos)
336
3.18M
        {
337
3.18M
            replace(nStartPos, nBeforeSize, osAfter);
338
3.18M
            nStartPos += nAfterSize;
339
3.18M
        }
340
33.9M
    }
341
33.9M
    return *this;
342
33.9M
}
343
344
/**
345
 * Replace all occurrences of chBefore with osAfter.
346
 */
347
CPLString &CPLString::replaceAll(char chBefore, const std::string &osAfter)
348
12.6k
{
349
12.6k
    return replaceAll(std::string(&chBefore, 1), osAfter);
350
12.6k
}
351
352
/**
353
 * Replace all occurrences of osBefore with chAfter.
354
 */
355
CPLString &CPLString::replaceAll(const std::string &osBefore, char chAfter)
356
33.1M
{
357
33.1M
    return replaceAll(osBefore, std::string(&chAfter, 1));
358
33.1M
}
359
360
/**
361
 * Replace all occurrences of chBefore with chAfter.
362
 */
363
CPLString &CPLString::replaceAll(char chBefore, char chAfter)
364
660k
{
365
660k
    return replaceAll(std::string(&chBefore, 1), std::string(&chAfter, 1));
366
660k
}
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
9.29k
{
379
9.29k
    if (size() < osStr.size())
380
51
        return false;
381
9.24k
    return substr(size() - osStr.size()) == osStr;
382
9.29k
}
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
2.40k
{
407
    // Helper to check if a substring is a valid percent-encoding
408
2.40k
    auto isPercentEncoded = [](const char *str) -> bool
409
2.40k
    {
410
488
        return str[0] == '%' &&
411
488
               std::isxdigit(static_cast<unsigned char>(str[1])) &&
412
318
               std::isxdigit(static_cast<unsigned char>(str[2]));
413
488
    };
414
415
    // Cf https://datatracker.ietf.org/doc/html/rfc3986.html
416
2.40k
    const char *unreserved =
417
2.40k
        "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~";
418
2.40k
    const char *reserved = ":/?#[]@!$&'()*+,;=";
419
2.40k
    CPLString osEncoded;
420
2.40k
    osEncoded.reserve(size());
421
1.19M
    for (size_t i = 0; i < size(); ++i)
422
1.19M
    {
423
1.19M
        char ch = (*this)[i];
424
        // If already percent-encoded, copy as is
425
1.19M
        if (ch == '%' && i + 2 < size() && isPercentEncoded(c_str() + i))
426
302
        {
427
302
            osEncoded += ch;
428
302
            osEncoded += (*this)[i + 1];
429
302
            osEncoded += (*this)[i + 2];
430
302
            i += 2;
431
302
        }
432
1.19M
        else if (strchr(unreserved, ch) || strchr(reserved, ch))
433
896k
        {
434
896k
            osEncoded += ch;
435
896k
        }
436
295k
        else
437
295k
        {
438
295k
            char buf[4];
439
295k
            snprintf(buf, sizeof(buf), "%%%02X",
440
295k
                     static_cast<unsigned char>(ch));
441
295k
            osEncoded += buf;
442
295k
        }
443
1.19M
    }
444
2.40k
    return osEncoded;
445
2.40k
}
446
447
/************************************************************************/
448
/*                        SQLQuotedIdentifier()                         */
449
/************************************************************************/
450
451
/** Returns a string between double quotes and with all double quotes
452
 * inside the string are escaped by being doubled.
453
 *
454
 * Aimed at being used for SQL identifiers (table names, column names, etc.).
455
 *
456
 * @since 3.13
457
 */
458
CPLString CPLString::SQLQuotedIdentifier() const
459
0
{
460
0
    CPLString ret("\"");
461
0
    CPLString tmp(*this);
462
0
    tmp.replaceAll('"', "\"\"");
463
0
    ret += tmp;
464
0
    ret += '"';
465
0
    return ret;
466
0
}
467
468
/************************************************************************/
469
/*                          SQLQuotedLiteral()                          */
470
/************************************************************************/
471
472
/** Returns a string between single quotes and with all single quotes
473
 * inside the string are escaped by being doubled.
474
 *
475
 * Aimed at being used for SQL literal strings.
476
 *
477
 * @since 3.13
478
 */
479
CPLString CPLString::SQLQuotedLiteral() const
480
0
{
481
0
    CPLString ret("'");
482
0
    CPLString tmp(*this);
483
0
    tmp.replaceAll('\'', "''");
484
0
    ret += tmp;
485
0
    ret += '\'';
486
0
    return ret;
487
0
}
488
489
/************************************************************************/
490
/*                           CPLURLGetValue()                           */
491
/************************************************************************/
492
493
/**
494
 * Return the value matching a key from a key=value pair in a URL.
495
 *
496
 * @param pszURL the URL.
497
 * @param pszKey the key to find.
498
 * @return the value of empty string if not found.
499
 */
500
CPLString CPLURLGetValue(const char *pszURL, const char *pszKey)
501
657k
{
502
657k
    CPLString osKey(pszKey);
503
657k
    osKey += "=";
504
657k
    size_t nKeyPos = CPLString(pszURL).ifind(osKey);
505
657k
    if (nKeyPos != std::string::npos && nKeyPos > 0 &&
506
35.3k
        (pszURL[nKeyPos - 1] == '?' || pszURL[nKeyPos - 1] == '&'))
507
34.9k
    {
508
34.9k
        CPLString osValue(pszURL + nKeyPos + osKey.size());
509
34.9k
        const char *pszValue = osValue.c_str();
510
34.9k
        const char *pszSep = strchr(pszValue, '&');
511
34.9k
        if (pszSep)
512
12.5k
        {
513
12.5k
            osValue.resize(pszSep - pszValue);
514
12.5k
        }
515
34.9k
        return osValue;
516
34.9k
    }
517
622k
    return "";
518
657k
}
519
520
/************************************************************************/
521
/*                            CPLURLAddKVP()                            */
522
/************************************************************************/
523
524
/**
525
 * Return a new URL with a new key=value pair.
526
 *
527
 * @param pszURL the URL.
528
 * @param pszKey the key to find.
529
 * @param pszValue the value of the key (may be NULL to unset an existing KVP).
530
 * @return the modified URL.
531
 */
532
CPLString CPLURLAddKVP(const char *pszURL, const char *pszKey,
533
                       const char *pszValue)
534
1.01M
{
535
1.01M
    CPLString osURL(strchr(pszURL, '?') == nullptr
536
1.01M
                        ? CPLString(pszURL).append("?")
537
1.01M
                        : pszURL);
538
539
1.01M
    CPLString osKey(pszKey);
540
1.01M
    osKey += "=";
541
1.01M
    size_t nKeyPos = osURL.ifind(osKey);
542
1.01M
    if (nKeyPos != std::string::npos && nKeyPos > 0 &&
543
78.5k
        (osURL[nKeyPos - 1] == '?' || osURL[nKeyPos - 1] == '&'))
544
18.4k
    {
545
18.4k
        CPLString osNewURL(osURL);
546
18.4k
        osNewURL.resize(nKeyPos);
547
18.4k
        if (pszValue)
548
427
        {
549
427
            osNewURL += osKey;
550
427
            osNewURL += pszValue;
551
427
        }
552
18.4k
        const char *pszNext = strchr(osURL.c_str() + nKeyPos, '&');
553
18.4k
        if (pszNext)
554
7.32k
        {
555
7.32k
            if (osNewURL.back() == '&' || osNewURL.back() == '?')
556
6.94k
                osNewURL += pszNext + 1;
557
378
            else
558
378
                osNewURL += pszNext;
559
7.32k
        }
560
18.4k
        return osNewURL;
561
18.4k
    }
562
996k
    else
563
996k
    {
564
996k
        CPLString osNewURL(std::move(osURL));
565
996k
        if (pszValue)
566
204k
        {
567
204k
            if (osNewURL.back() != '&' && osNewURL.back() != '?')
568
157k
                osNewURL += '&';
569
204k
            osNewURL += osKey;
570
204k
            osNewURL += pszValue;
571
204k
        }
572
996k
        return osNewURL;
573
996k
    }
574
1.01M
}
575
576
/************************************************************************/
577
/*                             CPLOPrintf()                             */
578
/************************************************************************/
579
580
/** Return a CPLString with the content of sprintf() */
581
CPLString CPLOPrintf(CPL_FORMAT_STRING(const char *pszFormat), ...)
582
583
330k
{
584
330k
    va_list args;
585
330k
    va_start(args, pszFormat);
586
587
330k
    CPLString osTarget;
588
330k
    osTarget.vPrintf(pszFormat, args);
589
590
330k
    va_end(args);
591
592
330k
    return osTarget;
593
330k
}
594
595
/************************************************************************/
596
/*                            CPLOvPrintf()                             */
597
/************************************************************************/
598
599
/** Return a CPLString with the content of vsprintf() */
600
CPLString CPLOvPrintf(CPL_FORMAT_STRING(const char *pszFormat), va_list args)
601
602
0
{
603
0
    CPLString osTarget;
604
0
    osTarget.vPrintf(pszFormat, args);
605
0
    return osTarget;
606
0
}
607
608
/************************************************************************/
609
/*                       CPLQuotedSQLIdentifer()                        */
610
/************************************************************************/
611
612
/** Return a CPLString of the SQL quoted identifier */
613
CPLString CPLQuotedSQLIdentifier(const char *pszIdent)
614
615
0
{
616
0
    CPLString osIdent;
617
618
0
    if (pszIdent)
619
0
    {
620
0
        char *pszQuotedIdent = CPLEscapeString(pszIdent, -1, CPLES_SQLI);
621
0
        osIdent.Printf("\"%s\"", pszQuotedIdent);
622
0
        CPLFree(pszQuotedIdent);
623
0
    }
624
625
0
    return osIdent;
626
0
}