Coverage Report

Created: 2025-12-31 06:48

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
22.2k
{
44
22.2k
    va_list args;
45
46
22.2k
    va_start(args, pszFormat);
47
22.2k
    vPrintf(pszFormat, args);
48
22.2k
    va_end(args);
49
50
22.2k
    return *this;
51
22.2k
}
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
22.2k
{
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
22.2k
    va_list wrk_args;
83
84
22.2k
#ifdef va_copy
85
22.2k
    va_copy(wrk_args, args);
86
#else
87
    wrk_args = args;
88
#endif
89
90
22.2k
    char szModestBuffer[500] = {};
91
22.2k
    szModestBuffer[0] = '\0';
92
22.2k
    int nPR = CPLvsnprintf(szModestBuffer, sizeof(szModestBuffer), pszFormat,
93
22.2k
                           wrk_args);
94
22.2k
    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
22.2k
    else
123
22.2k
    {
124
22.2k
        *this = szModestBuffer;
125
22.2k
    }
126
22.2k
#ifdef va_copy
127
22.2k
    va_end(wrk_args);
128
22.2k
#endif
129
130
22.2k
#endif /* !defined(HAVE_VSNPRINTF) */
131
132
22.2k
    return *this;
133
22.2k
}
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
8.92k
{
297
306k
    for (size_t i = 0; i < size(); i++)
298
297k
        (*this)[i] = static_cast<char>(CPLToupper((*this)[i]));
299
300
8.92k
    return *this;
301
8.92k
}
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.9k
{
330
22.9k
    const size_t nBeforeSize = osBefore.size();
331
22.9k
    const size_t nAfterSize = osAfter.size();
332
22.9k
    if (nBeforeSize)
333
22.9k
    {
334
22.9k
        size_t nStartPos = 0;
335
24.3k
        while ((nStartPos = find(osBefore, nStartPos)) != std::string::npos)
336
1.40k
        {
337
1.40k
            replace(nStartPos, nBeforeSize, osAfter);
338
1.40k
            nStartPos += nAfterSize;
339
1.40k
        }
340
22.9k
    }
341
22.9k
    return *this;
342
22.9k
}
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.9k
{
357
22.9k
    return replaceAll(osBefore, std::string(&chAfter, 1));
358
22.9k
}
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
/*                          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
0
{
502
0
    CPLString osKey(pszKey);
503
0
    osKey += "=";
504
0
    size_t nKeyPos = CPLString(pszURL).ifind(osKey);
505
0
    if (nKeyPos != std::string::npos && nKeyPos > 0 &&
506
0
        (pszURL[nKeyPos - 1] == '?' || pszURL[nKeyPos - 1] == '&'))
507
0
    {
508
0
        CPLString osValue(pszURL + nKeyPos + osKey.size());
509
0
        const char *pszValue = osValue.c_str();
510
0
        const char *pszSep = strchr(pszValue, '&');
511
0
        if (pszSep)
512
0
        {
513
0
            osValue.resize(pszSep - pszValue);
514
0
        }
515
0
        return osValue;
516
0
    }
517
0
    return "";
518
0
}
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
0
{
535
0
    CPLString osURL(strchr(pszURL, '?') == nullptr
536
0
                        ? CPLString(pszURL).append("?")
537
0
                        : pszURL);
538
539
0
    CPLString osKey(pszKey);
540
0
    osKey += "=";
541
0
    size_t nKeyPos = osURL.ifind(osKey);
542
0
    if (nKeyPos != std::string::npos && nKeyPos > 0 &&
543
0
        (osURL[nKeyPos - 1] == '?' || osURL[nKeyPos - 1] == '&'))
544
0
    {
545
0
        CPLString osNewURL(osURL);
546
0
        osNewURL.resize(nKeyPos);
547
0
        if (pszValue)
548
0
        {
549
0
            osNewURL += osKey;
550
0
            osNewURL += pszValue;
551
0
        }
552
0
        const char *pszNext = strchr(osURL.c_str() + nKeyPos, '&');
553
0
        if (pszNext)
554
0
        {
555
0
            if (osNewURL.back() == '&' || osNewURL.back() == '?')
556
0
                osNewURL += pszNext + 1;
557
0
            else
558
0
                osNewURL += pszNext;
559
0
        }
560
0
        return osNewURL;
561
0
    }
562
0
    else
563
0
    {
564
0
        CPLString osNewURL(std::move(osURL));
565
0
        if (pszValue)
566
0
        {
567
0
            if (osNewURL.back() != '&' && osNewURL.back() != '?')
568
0
                osNewURL += '&';
569
0
            osNewURL += osKey;
570
0
            osNewURL += pszValue;
571
0
        }
572
0
        return osNewURL;
573
0
    }
574
0
}
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
0
{
584
0
    va_list args;
585
0
    va_start(args, pszFormat);
586
587
0
    CPLString osTarget;
588
0
    osTarget.vPrintf(pszFormat, args);
589
590
0
    va_end(args);
591
592
0
    return osTarget;
593
0
}
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
}