EVOLUTION-MANAGER
Edit File: cpl_string.cpp
/********************************************************************** * * Name: cpl_string.cpp * Project: CPL - Common Portability Library * Purpose: String and Stringlist manipulation functions. * Author: Daniel Morissette, danmo@videotron.ca * ********************************************************************** * Copyright (c) 1998, Daniel Morissette * Copyright (c) 2008-2013, Even Rouault <even dot rouault at mines-paris dot org> * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. ********************************************************************** * * Independent Security Audit 2003/04/04 Andrey Kiselev: * Completed audit of this module. All functions may be used without buffer * overflows and stack corruptions with any kind of input data strings with * except of CPLSPrintf() and CSLAppendPrintf() (see note below). * * Security Audit 2003/03/28 warmerda: * Completed security audit. I believe that this module may be safely used * to parse tokenize arbitrary input strings, assemble arbitrary sets of * names values into string lists, unescape and escape text even if provided * by a potentially hostile source. * * CPLSPrintf() and CSLAppendPrintf() may not be safely invoked on * arbitrary length inputs since it has a fixed size output buffer on system * without vsnprintf(). * **********************************************************************/ #undef WARN_STANDARD_PRINTF #include "cpl_port.h" #include "cpl_string.h" #include <cctype> #include <climits> #include <cstdlib> #include <cstring> #include "cpl_config.h" #include "cpl_multiproc.h" #include "cpl_vsi.h" #if !defined(va_copy) && defined(__va_copy) #define va_copy __va_copy #endif CPL_CVSID("$Id: cpl_string.cpp d9b1be9bfecd6c68b3443bb6c9e3f6cb81b42770 2019-04-01 18:21:14 +0200 Raul Marin $") /*===================================================================== StringList manipulation functions. =====================================================================*/ /********************************************************************** * CSLAddString() **********************************************************************/ /** Append a string to a StringList and return a pointer to the modified * StringList. * * If the input StringList is NULL, then a new StringList is created. * Note that CSLAddString performance when building a list is in O(n^2) * which can cause noticeable slow down when n > 10000. */ char **CSLAddString( char **papszStrList, const char *pszNewString ) { char** papszRet = CSLAddStringMayFail(papszStrList, pszNewString); if( papszRet == nullptr && pszNewString != nullptr ) abort(); return papszRet; } /** Same as CSLAddString() but may return NULL in case of (memory) failure */ char **CSLAddStringMayFail( char **papszStrList, const char *pszNewString ) { if( pszNewString == nullptr ) return papszStrList; // Nothing to do! char* pszDup = VSI_STRDUP_VERBOSE(pszNewString); if( pszDup == nullptr ) return nullptr; // Allocate room for the new string. char** papszStrListNew = nullptr; int nItems = 0; if( papszStrList == nullptr ) papszStrListNew = static_cast<char**>( VSI_CALLOC_VERBOSE( 2, sizeof(char*) ) ); else { nItems = CSLCount(papszStrList); papszStrListNew = static_cast<char**>( VSI_REALLOC_VERBOSE( papszStrList, (nItems+2)*sizeof(char*) ) ); } if( papszStrListNew == nullptr ) { VSIFree(pszDup); return nullptr; } // Copy the string in the list. papszStrListNew[nItems] = pszDup; papszStrListNew[nItems+1] = nullptr; return papszStrListNew; } /************************************************************************/ /* CSLCount() */ /************************************************************************/ /** * Return number of items in a string list. * * Returns the number of items in a string list, not counting the * terminating NULL. Passing in NULL is safe, and will result in a count * of zero. * * Lists are counted by iterating through them so long lists will * take more time than short lists. Care should be taken to avoid using * CSLCount() as an end condition for loops as it will result in O(n^2) * behavior. * * @param papszStrList the string list to count. * * @return the number of entries. */ int CSLCount( CSLConstList papszStrList ) { if( !papszStrList ) return 0; int nItems = 0; while( *papszStrList != nullptr ) { ++nItems; ++papszStrList; } return nItems; } /************************************************************************/ /* CSLGetField() */ /************************************************************************/ /** * Fetches the indicated field, being careful not to crash if the field * doesn't exist within this string list. * * The returned pointer should not be freed, and doesn't necessarily last long. */ const char *CSLGetField( CSLConstList papszStrList, int iField ) { if( papszStrList == nullptr || iField < 0 ) return( "" ); for( int i = 0; i < iField+1; i++ ) { if( papszStrList[i] == nullptr ) return ""; } return( papszStrList[iField] ); } /************************************************************************/ /* CSLDestroy() */ /************************************************************************/ /** * Free string list. * * Frees the passed string list (null terminated array of strings). * It is safe to pass NULL. * * @param papszStrList the list to free. */ void CPL_STDCALL CSLDestroy( char **papszStrList ) { if( !papszStrList ) return; for( char **papszPtr = papszStrList; *papszPtr != nullptr; ++papszPtr ) { CPLFree(*papszPtr); } CPLFree(papszStrList); } /************************************************************************/ /* CSLDuplicate() */ /************************************************************************/ /** * Clone a string list. * * Efficiently allocates a copy of a string list. The returned list is * owned by the caller and should be freed with CSLDestroy(). * * @param papszStrList the input string list. * * @return newly allocated copy. */ char **CSLDuplicate( CSLConstList papszStrList ) { const int nLines = CSLCount(papszStrList); if( nLines == 0 ) return nullptr; CSLConstList papszSrc = papszStrList; char **papszNewList = static_cast<char **>( CPLMalloc( (nLines + 1) * sizeof(char*) ) ); char **papszDst = papszNewList; for( ; *papszSrc != nullptr; ++papszSrc, ++papszDst) { *papszDst = CPLStrdup(*papszSrc); } *papszDst = nullptr; return papszNewList; } /************************************************************************/ /* CSLMerge */ /************************************************************************/ /** * \brief Merge two lists. * * The two lists are merged, ensuring that if any keys appear in both * that the value from the second (papszOverride) list take precedence. * * @param papszOrig the original list, being modified. * @param papszOverride the list of items being merged in. This list * is unaltered and remains owned by the caller. * * @return updated list. */ char **CSLMerge( char **papszOrig, CSLConstList papszOverride ) { if( papszOrig == nullptr && papszOverride != nullptr ) return CSLDuplicate( papszOverride ); if( papszOverride == nullptr ) return papszOrig; for( int i = 0; papszOverride[i] != nullptr; ++i ) { char *pszKey = nullptr; const char *pszValue = CPLParseNameValue( papszOverride[i], &pszKey ); papszOrig = CSLSetNameValue( papszOrig, pszKey, pszValue ); CPLFree( pszKey ); } return papszOrig; } /************************************************************************/ /* CSLLoad2() */ /************************************************************************/ /** * Load a text file into a string list. * * The VSI*L API is used, so VSIFOpenL() supported objects that aren't * physical files can also be accessed. Files are returned as a string list, * with one item in the string list per line. End of line markers are * stripped (by CPLReadLineL()). * * If reading the file fails a CPLError() will be issued and NULL returned. * * @param pszFname the name of the file to read. * @param nMaxLines maximum number of lines to read before stopping, or -1 for * no limit. * @param nMaxCols maximum number of characters in a line before stopping, or -1 * for no limit. * @param papszOptions NULL-terminated array of options. Unused for now. * * @return a string list with the files lines, now owned by caller. To be freed * with CSLDestroy() * * @since GDAL 1.7.0 */ char **CSLLoad2( const char *pszFname, int nMaxLines, int nMaxCols, CSLConstList papszOptions ) { VSILFILE *fp = VSIFOpenL(pszFname, "rb"); if( !fp ) { if( CPLFetchBool( papszOptions, "EMIT_ERROR_IF_CANNOT_OPEN_FILE", true ) ) { // Unable to open file. CPLError( CE_Failure, CPLE_OpenFailed, "CSLLoad2(\"%s\") failed: unable to open file.", pszFname ); } return nullptr; } char **papszStrList=nullptr; int nLines = 0; int nAllocatedLines = 0; CPLErrorReset(); while( !VSIFEofL(fp) && (nMaxLines == -1 || nLines < nMaxLines) ) { const char *pszLine = CPLReadLine2L(fp, nMaxCols, papszOptions); if( pszLine == nullptr ) break; if( nLines + 1 >= nAllocatedLines ) { nAllocatedLines = 16 + nAllocatedLines * 2; char** papszStrListNew = static_cast<char**>( VSIRealloc( papszStrList, nAllocatedLines * sizeof(char*) ) ); if( papszStrListNew == nullptr ) { CPL_IGNORE_RET_VAL( VSIFCloseL(fp) ); CPLReadLineL( nullptr ); CPLError( CE_Failure, CPLE_OutOfMemory, "CSLLoad2(\"%s\") " "failed: not enough memory to allocate lines.", pszFname ); return papszStrList; } papszStrList = papszStrListNew; } papszStrList[nLines] = CPLStrdup(pszLine); papszStrList[nLines + 1] = nullptr; ++nLines; } CPL_IGNORE_RET_VAL( VSIFCloseL(fp) ); // Free the internal thread local line buffer. CPLReadLineL( nullptr ); return papszStrList; } /************************************************************************/ /* CSLLoad() */ /************************************************************************/ /** * Load a text file into a string list. * * The VSI*L API is used, so VSIFOpenL() supported objects that aren't * physical files can also be accessed. Files are returned as a string list, * with one item in the string list per line. End of line markers are * stripped (by CPLReadLineL()). * * If reading the file fails a CPLError() will be issued and NULL returned. * * @param pszFname the name of the file to read. * * @return a string list with the files lines, now owned by caller. To be freed * with CSLDestroy() */ char **CSLLoad( const char *pszFname ) { return CSLLoad2(pszFname, -1, -1, nullptr); } /********************************************************************** * CSLSave() **********************************************************************/ /** Write a StringList to a text file. * * Returns the number of lines written, or 0 if the file could not * be written. */ int CSLSave( CSLConstList papszStrList, const char *pszFname ) { if( papszStrList == nullptr ) return 0; VSILFILE *fp = VSIFOpenL( pszFname, "wt" ); if( fp == nullptr ) { // Unable to open file. CPLError( CE_Failure, CPLE_OpenFailed, "CSLSave(\"%s\") failed: unable to open output file.", pszFname ); return 0; } int nLines = 0; while( *papszStrList != nullptr ) { if( VSIFPrintfL( fp, "%s\n", *papszStrList ) < 1 ) { CPLError( CE_Failure, CPLE_FileIO, "CSLSave(\"%s\") failed: unable to write to output file.", pszFname ); break; // A Problem happened... abort. } ++nLines; ++papszStrList; } if( VSIFCloseL(fp) != 0 ) { CPLError( CE_Failure, CPLE_FileIO, "CSLSave(\"%s\") failed: unable to write to output file.", pszFname ); } return nLines; } /********************************************************************** * CSLPrint() **********************************************************************/ /** Print a StringList to fpOut. If fpOut==NULL, then output is sent * to stdout. * * Returns the number of lines printed. */ int CSLPrint( CSLConstList papszStrList, FILE *fpOut ) { if( !papszStrList ) return 0; if( fpOut == nullptr ) fpOut = stdout; int nLines = 0; while( *papszStrList != nullptr ) { if( VSIFPrintf(fpOut, "%s\n", *papszStrList) < 0 ) return nLines; ++nLines; ++papszStrList; } return nLines; } /********************************************************************** * CSLInsertStrings() **********************************************************************/ /** Copies the contents of a StringList inside another StringList * before the specified line. * * nInsertAtLineNo is a 0-based line index before which the new strings * should be inserted. If this value is -1 or is larger than the actual * number of strings in the list then the strings are added at the end * of the source StringList. * * Returns the modified StringList. */ char **CSLInsertStrings( char **papszStrList, int nInsertAtLineNo, CSLConstList papszNewLines ) { if( papszNewLines == nullptr ) return papszStrList; // Nothing to do! const int nToInsert = CSLCount(papszNewLines); if( nToInsert == 0 ) return papszStrList; // Nothing to do! const int nSrcLines = CSLCount(papszStrList); const int nDstLines = nSrcLines + nToInsert; // Allocate room for the new strings. papszStrList = static_cast<char**>( CPLRealloc( papszStrList, (nDstLines+1) * sizeof(char*) ) ); // Make sure the array is NULL-terminated. It may not be if // papszStrList was NULL before Realloc(). papszStrList[nSrcLines] = nullptr; // Make some room in the original list at the specified location. // Note that we also have to move the NULL pointer at the end of // the source StringList. if( nInsertAtLineNo == -1 || nInsertAtLineNo > nSrcLines ) nInsertAtLineNo = nSrcLines; { char **ppszSrc = papszStrList + nSrcLines; char **ppszDst = papszStrList + nDstLines; for( int i = nSrcLines; i >= nInsertAtLineNo; --i ) { *ppszDst = *ppszSrc; --ppszDst; --ppszSrc; } } // Copy the strings to the list. CSLConstList ppszSrc = papszNewLines; char** ppszDst = papszStrList + nInsertAtLineNo; for( ; *ppszSrc != nullptr; ++ppszSrc, ++ppszDst ) { *ppszDst = CPLStrdup(*ppszSrc); } return papszStrList; } /********************************************************************** * CSLInsertString() **********************************************************************/ /** Insert a string at a given line number inside a StringList * * nInsertAtLineNo is a 0-based line index before which the new string * should be inserted. If this value is -1 or is larger than the actual * number of strings in the list then the string is added at the end * of the source StringList. * * Returns the modified StringList. */ char **CSLInsertString( char **papszStrList, int nInsertAtLineNo, const char *pszNewLine ) { char *apszList[2] = { const_cast<char *>(pszNewLine), nullptr }; return CSLInsertStrings(papszStrList, nInsertAtLineNo, apszList); } /********************************************************************** * CSLRemoveStrings() **********************************************************************/ /** Remove strings inside a StringList * * nFirstLineToDelete is the 0-based line index of the first line to * remove. If this value is -1 or is larger than the actual * number of strings in list then the nNumToRemove last strings are * removed. * * If ppapszRetStrings != NULL then the deleted strings won't be * free'd, they will be stored in a new StringList and the pointer to * this new list will be returned in *ppapszRetStrings. * * Returns the modified StringList. */ char **CSLRemoveStrings( char **papszStrList, int nFirstLineToDelete, int nNumToRemove, char ***ppapszRetStrings ) { const int nSrcLines = CSLCount(papszStrList); if( nNumToRemove < 1 || nSrcLines == 0 ) return papszStrList; // Nothing to do! // If operation will result in an empty StringList, don't waste // time here. const int nDstLines = nSrcLines - nNumToRemove; if( nDstLines < 1 ) { CSLDestroy(papszStrList); return nullptr; } // Remove lines from the source StringList. // Either free() each line or store them to a new StringList depending on // the caller's choice. char **ppszDst = papszStrList + nFirstLineToDelete; if( ppapszRetStrings == nullptr ) { // free() all the strings that will be removed. for( int i = 0; i < nNumToRemove; ++i ) { CPLFree(*ppszDst); *ppszDst = nullptr; } } else { // Store the strings to remove in a new StringList. *ppapszRetStrings = static_cast<char **>( CPLCalloc( nNumToRemove+1, sizeof(char*) ) ); for( int i=0; i < nNumToRemove; ++i ) { (*ppapszRetStrings)[i] = *ppszDst; *ppszDst = nullptr; ++ppszDst; } } // Shift down all the lines that follow the lines to remove. if( nFirstLineToDelete == -1 || nFirstLineToDelete > nSrcLines ) nFirstLineToDelete = nDstLines; char **ppszSrc = papszStrList + nFirstLineToDelete + nNumToRemove; ppszDst = papszStrList + nFirstLineToDelete; for( ; *ppszSrc != nullptr; ++ppszSrc, ++ppszDst ) { *ppszDst = *ppszSrc; } // Move the NULL pointer at the end of the StringList. *ppszDst = *ppszSrc; // At this point, we could realloc() papszStrList to a smaller size, but // since this array will likely grow again in further operations on the // StringList we'll leave it as it is. return papszStrList; } /************************************************************************/ /* CSLFindString() */ /************************************************************************/ /** * Find a string within a string list (case insensitive). * * Returns the index of the entry in the string list that contains the * target string. The string in the string list must be a full match for * the target, but the search is case insensitive. * * @param papszList the string list to be searched. * @param pszTarget the string to be searched for. * * @return the index of the string within the list or -1 on failure. */ int CSLFindString( CSLConstList papszList, const char * pszTarget ) { if( papszList == nullptr ) return -1; for( int i = 0; papszList[i] != nullptr; ++i ) { if( EQUAL(papszList[i], pszTarget) ) return i; } return -1; } /************************************************************************/ /* CSLFindStringCaseSensitive() */ /************************************************************************/ /** * Find a string within a string list(case sensitive) * * Returns the index of the entry in the string list that contains the * target string. The string in the string list must be a full match for * the target. * * @param papszList the string list to be searched. * @param pszTarget the string to be searched for. * * @return the index of the string within the list or -1 on failure. * * @since GDAL 2.0 */ int CSLFindStringCaseSensitive( CSLConstList papszList, const char * pszTarget ) { if( papszList == nullptr ) return -1; for( int i = 0; papszList[i] != nullptr; ++i ) { if( strcmp(papszList[i], pszTarget) == 0 ) return i; } return -1; } /************************************************************************/ /* CSLPartialFindString() */ /************************************************************************/ /** * Find a substring within a string list. * * Returns the index of the entry in the string list that contains the * target string as a substring. The search is case sensitive (unlike * CSLFindString()). * * @param papszHaystack the string list to be searched. * @param pszNeedle the substring to be searched for. * * @return the index of the string within the list or -1 on failure. */ int CSLPartialFindString( CSLConstList papszHaystack, const char * pszNeedle ) { if( papszHaystack == nullptr || pszNeedle == nullptr ) return -1; for( int i = 0; papszHaystack[i] != nullptr; ++i ) { if( strstr(papszHaystack[i], pszNeedle) ) return i; } return -1; } /********************************************************************** * CSLTokenizeString() **********************************************************************/ /** Tokenizes a string and returns a StringList with one string for * each token. */ char **CSLTokenizeString( const char *pszString ) { return CSLTokenizeString2( pszString, " ", CSLT_HONOURSTRINGS ); } /************************************************************************/ /* CSLTokenizeStringComplex() */ /************************************************************************/ /** Obsolete tokenizing api. Use CSLTokenizeString2() */ char ** CSLTokenizeStringComplex( const char * pszString, const char * pszDelimiters, int bHonourStrings, int bAllowEmptyTokens ) { int nFlags = 0; if( bHonourStrings ) nFlags |= CSLT_HONOURSTRINGS; if( bAllowEmptyTokens ) nFlags |= CSLT_ALLOWEMPTYTOKENS; return CSLTokenizeString2( pszString, pszDelimiters, nFlags ); } /************************************************************************/ /* CSLTokenizeString2() */ /************************************************************************/ /** * Tokenize a string. * * This function will split a string into tokens based on specified' * delimiter(s) with a variety of options. The returned result is a * string list that should be freed with CSLDestroy() when no longer * needed. * * The available parsing options are: * * - CSLT_ALLOWEMPTYTOKENS: allow the return of empty tokens when two * delimiters in a row occur with no other text between them. If not set, * empty tokens will be discarded; * - CSLT_STRIPLEADSPACES: strip leading space characters from the token (as * reported by isspace()); * - CSLT_STRIPENDSPACES: strip ending space characters from the token (as * reported by isspace()); * - CSLT_HONOURSTRINGS: double quotes can be used to hold values that should * not be broken into multiple tokens; * - CSLT_PRESERVEQUOTES: string quotes are carried into the tokens when this * is set, otherwise they are removed; * - CSLT_PRESERVEESCAPES: if set backslash escapes (for backslash itself, * and for literal double quotes) will be preserved in the tokens, otherwise * the backslashes will be removed in processing. * * \b Example: * * Parse a string into tokens based on various white space (space, newline, * tab) and then print out results and cleanup. Quotes may be used to hold * white space in tokens. \code char **papszTokens = CSLTokenizeString2( pszCommand, " \t\n", CSLT_HONOURSTRINGS | CSLT_ALLOWEMPTYTOKENS ); for( int i = 0; papszTokens != NULL && papszTokens[i] != NULL; ++i ) printf( "arg %d: '%s'", papszTokens[i] ); // ok CSLDestroy( papszTokens ); \endcode * @param pszString the string to be split into tokens. * @param pszDelimiters one or more characters to be used as token delimiters. * @param nCSLTFlags an ORing of one or more of the CSLT_ flag values. * * @return a string list of tokens owned by the caller. */ char ** CSLTokenizeString2( const char * pszString, const char * pszDelimiters, int nCSLTFlags ) { if( pszString == nullptr ) return static_cast<char **>( CPLCalloc(sizeof(char *), 1)); CPLStringList oRetList; const bool bHonourStrings = (nCSLTFlags & CSLT_HONOURSTRINGS) != 0; const bool bAllowEmptyTokens = (nCSLTFlags & CSLT_ALLOWEMPTYTOKENS) != 0; const bool bStripLeadSpaces = (nCSLTFlags & CSLT_STRIPLEADSPACES) != 0; const bool bStripEndSpaces = (nCSLTFlags & CSLT_STRIPENDSPACES) != 0; char *pszToken = static_cast<char *>(CPLCalloc(10, 1)); int nTokenMax = 10; while( *pszString != '\0' ) { bool bInString = false; bool bStartString = true; int nTokenLen = 0; // Try to find the next delimiter, marking end of token. for( ; *pszString != '\0'; ++pszString ) { // Extend token buffer if we are running close to its end. if( nTokenLen >= nTokenMax-3 ) { nTokenMax = nTokenMax * 2 + 10; pszToken = static_cast<char *>( CPLRealloc( pszToken, nTokenMax )); } // End if this is a delimiter skip it and break. if( !bInString && strchr(pszDelimiters, *pszString) != nullptr ) { ++pszString; break; } // If this is a quote, and we are honouring constant // strings, then process the constant strings, with out delim // but don't copy over the quotes. if( bHonourStrings && *pszString == '"' ) { if( nCSLTFlags & CSLT_PRESERVEQUOTES ) { pszToken[nTokenLen] = *pszString; ++nTokenLen; } bInString = !bInString; continue; } /* * Within string constants we allow for escaped quotes, but in * processing them we will unescape the quotes and \\ sequence * reduces to \ */ if( bInString && pszString[0] == '\\' ) { if( pszString[1] == '"' || pszString[1] == '\\' ) { if( nCSLTFlags & CSLT_PRESERVEESCAPES ) { pszToken[nTokenLen] = *pszString; ++nTokenLen; } ++pszString; } } // Strip spaces at the token start if requested. if( !bInString && bStripLeadSpaces && bStartString && isspace(static_cast<unsigned char>( *pszString )) ) continue; bStartString = false; pszToken[nTokenLen] = *pszString; ++nTokenLen; } // Strip spaces at the token end if requested. if( !bInString && bStripEndSpaces ) { while( nTokenLen && isspace( static_cast<unsigned char>( pszToken[nTokenLen - 1]) ) ) nTokenLen--; } pszToken[nTokenLen] = '\0'; // Add the token. if( pszToken[0] != '\0' || bAllowEmptyTokens ) oRetList.AddString( pszToken ); } /* * If the last token was empty, then we need to capture * it now, as the loop would skip it. */ if( *pszString == '\0' && bAllowEmptyTokens && oRetList.Count() > 0 && strchr(pszDelimiters, *(pszString-1)) != nullptr ) { oRetList.AddString( "" ); } CPLFree( pszToken ); if( oRetList.List() == nullptr ) { // Prefer to return empty lists as a pointer to // a null pointer since some client code might depend on this. oRetList.Assign(static_cast<char **>(CPLCalloc(sizeof(char*), 1))); } return oRetList.StealList(); } /********************************************************************** * CPLSPrintf() * * NOTE: This function should move to cpl_conv.cpp. **********************************************************************/ // For now, assume that a 8000 chars buffer will be enough. constexpr int CPLSPrintf_BUF_SIZE = 8000; constexpr int CPLSPrintf_BUF_Count = 10; /** CPLSPrintf() that works with 10 static buffer. * * It returns a ref. to a static buffer that should not be freed and * is valid only until the next call to CPLSPrintf(). */ const char *CPLSPrintf( CPL_FORMAT_STRING(const char *fmt), ... ) { va_list args; /* -------------------------------------------------------------------- */ /* Get the thread local buffer ring data. */ /* -------------------------------------------------------------------- */ char *pachBufRingInfo = static_cast<char *>( CPLGetTLS( CTLS_CPLSPRINTF ) ); if( pachBufRingInfo == nullptr ) { pachBufRingInfo = static_cast<char *>( CPLCalloc( 1, sizeof(int) + CPLSPrintf_BUF_Count*CPLSPrintf_BUF_SIZE)); CPLSetTLS( CTLS_CPLSPRINTF, pachBufRingInfo, TRUE ); } /* -------------------------------------------------------------------- */ /* Work out which string in the "ring" we want to use this */ /* time. */ /* -------------------------------------------------------------------- */ int *pnBufIndex = reinterpret_cast<int *>( pachBufRingInfo ); const size_t nOffset = sizeof(int) + *pnBufIndex * CPLSPrintf_BUF_SIZE; char *pachBuffer = pachBufRingInfo + nOffset; *pnBufIndex = (*pnBufIndex + 1) % CPLSPrintf_BUF_Count; /* -------------------------------------------------------------------- */ /* Format the result. */ /* -------------------------------------------------------------------- */ va_start(args, fmt); const int ret = CPLvsnprintf(pachBuffer, CPLSPrintf_BUF_SIZE-1, fmt, args); if( ret < 0 || ret >= CPLSPrintf_BUF_SIZE-1 ) { CPLError( CE_Failure, CPLE_AppDefined, "CPLSPrintf() called with too " "big string. Output will be truncated !" ); } va_end(args); return pachBuffer; } /********************************************************************** * CSLAppendPrintf() **********************************************************************/ /** Use CPLSPrintf() to append a new line at the end of a StringList. * Returns the modified StringList. */ char **CSLAppendPrintf( char **papszStrList, CPL_FORMAT_STRING(const char *fmt), ... ) { va_list args; va_start( args, fmt ); CPLString osWork; osWork.vPrintf( fmt, args ); va_end( args ); return CSLAddString(papszStrList, osWork); } /************************************************************************/ /* CPLVASPrintf() */ /************************************************************************/ /** This is intended to serve as an easy to use C callable vasprintf() * alternative. Used in the GeoJSON library for instance */ int CPLVASPrintf( char **buf, CPL_FORMAT_STRING(const char *fmt), va_list ap ) { CPLString osWork; osWork.vPrintf( fmt, ap ); if( buf ) *buf = CPLStrdup(osWork.c_str()); return static_cast<int>(osWork.size()); } /************************************************************************/ /* CPLvsnprintf_get_end_of_formatting() */ /************************************************************************/ static const char* CPLvsnprintf_get_end_of_formatting( const char* fmt ) { char ch = '\0'; // Flag. for( ; (ch = *fmt) != '\0'; ++fmt ) { if( ch == '\'' ) continue; // Bad idea as this is locale specific. if( ch == '-' || ch == '+' || ch == ' ' || ch == '#' || ch == '0' ) continue; break; } // Field width. for( ; (ch = *fmt) != '\0'; ++fmt ) { if( ch == '$' ) return nullptr; // Do not support this. if( *fmt >= '0' && *fmt <= '9' ) continue; break; } // Precision. if( ch == '.' ) { ++fmt; for( ; (ch = *fmt) != '\0'; ++fmt ) { if( ch == '$' ) return nullptr; // Do not support this. if( *fmt >= '0' && *fmt <= '9' ) continue; break; } } // Length modifier. for( ; (ch = *fmt) != '\0'; ++fmt ) { if( ch == 'h' || ch == 'l' || ch == 'j' || ch == 'z' || ch == 't' || ch == 'L' ) continue; else if( ch == 'I' && fmt[1] == '6' && fmt[2] == '4' ) fmt += 2; else return fmt; } return nullptr; } /************************************************************************/ /* CPLvsnprintf() */ /************************************************************************/ #define call_native_snprintf(type) \ local_ret = snprintf(str + offset_out, size - offset_out, localfmt, \ va_arg(wrk_args, type)) /** vsnprintf() wrapper that is not sensitive to LC_NUMERIC settings. * * This function has the same contract as standard vsnprintf(), except that * formatting of floating-point numbers will use decimal point, whatever the * current locale is set. * * @param str output buffer * @param size size of the output buffer (including space for terminating nul) * @param fmt formatting string * @param args arguments * @return the number of characters (excluding terminating nul) that would be * written if size is big enough. Or potentially -1 with Microsoft C runtime * for Visual Studio < 2015. * @since GDAL 2.0 */ int CPLvsnprintf( char *str, size_t size, CPL_FORMAT_STRING(const char* fmt), va_list args ) { if( size == 0 ) return vsnprintf(str, size, fmt, args); va_list wrk_args; #ifdef va_copy va_copy( wrk_args, args ); #else wrk_args = args; #endif const char* fmt_ori = fmt; size_t offset_out = 0; char ch = '\0'; bool bFormatUnknown = false; for( ; (ch = *fmt) != '\0'; ++fmt ) { if( ch == '%' ) { const char* ptrend = CPLvsnprintf_get_end_of_formatting(fmt+1); if( ptrend == nullptr || ptrend - fmt >= 20 ) { bFormatUnknown = true; break; } char end = *ptrend; char end_m1 = ptrend[-1]; char localfmt[22] = {}; memcpy(localfmt, fmt, ptrend - fmt + 1); localfmt[ptrend-fmt+1] = '\0'; int local_ret = 0; if( end == '%' ) { if( offset_out == size-1 ) break; local_ret = 1; str[offset_out] = '%'; } else if( end == 'd' || end == 'i' || end == 'c' ) { if( end_m1 == 'h' ) call_native_snprintf(int); else if( end_m1 == 'l' && ptrend[-2] != 'l' ) call_native_snprintf(long); else if( end_m1 == 'l' && ptrend[-2] == 'l' ) call_native_snprintf(GIntBig); else if( end_m1 == '4' && ptrend[-2] == '6' && ptrend[-3] == 'I' ) // Microsoft I64 modifier. call_native_snprintf(GIntBig); else if( end_m1 == 'z') call_native_snprintf(size_t); else if( (end_m1 >= 'a' && end_m1 <= 'z') || (end_m1 >= 'A' && end_m1 <= 'Z') ) { bFormatUnknown = true; break; } else call_native_snprintf(int); } else if( end == 'o' || end == 'u' || end == 'x' || end == 'X' ) { if( end_m1 == 'h' ) call_native_snprintf(unsigned int); else if( end_m1 == 'l' && ptrend[-2] != 'l' ) call_native_snprintf(unsigned long); else if( end_m1 == 'l' && ptrend[-2] == 'l' ) call_native_snprintf(GUIntBig); else if( end_m1 == '4' && ptrend[-2] == '6' && ptrend[-3] == 'I' ) // Microsoft I64 modifier. call_native_snprintf(GUIntBig); else if( end_m1 == 'z') call_native_snprintf(size_t); else if( (end_m1 >= 'a' && end_m1 <= 'z') || (end_m1 >= 'A' && end_m1 <= 'Z') ) { bFormatUnknown = true; break; } else call_native_snprintf(unsigned int); } else if( end == 'e' || end == 'E' || end == 'f' || end == 'F' || end == 'g' || end == 'G' || end == 'a' || end == 'A' ) { if( end_m1 == 'L' ) call_native_snprintf(long double); else call_native_snprintf(double); // MSVC vsnprintf() returns -1. if( local_ret < 0 || offset_out + local_ret >= size ) break; for( int j = 0; j < local_ret; ++j ) { if( str[offset_out + j] == ',' ) { str[offset_out + j] = '.'; break; } } } else if( end == 's' ) { const char* pszPtr = va_arg(wrk_args, const char*); CPLAssert(pszPtr); local_ret = snprintf(str + offset_out, size - offset_out, localfmt, pszPtr); } else if( end == 'p' ) { call_native_snprintf(void*); } else { bFormatUnknown = true; break; } // MSVC vsnprintf() returns -1. if( local_ret < 0 || offset_out + local_ret >= size ) break; offset_out += local_ret; fmt = ptrend; } else { if( offset_out == size-1 ) break; str[offset_out++] = *fmt; } } if( ch == '\0' && offset_out < size ) str[offset_out] = '\0'; else { if( bFormatUnknown ) { CPLDebug( "CPL", "CPLvsnprintf() called with unsupported " "formatting string: %s", fmt_ori); } #ifdef va_copy va_end( wrk_args ); va_copy( wrk_args, args ); #else wrk_args = args; #endif #if defined(HAVE_VSNPRINTF) offset_out = vsnprintf(str, size, fmt_ori, wrk_args); #else offset_out = vsprintf(str, fmt_ori, wrk_args); #endif } #ifdef va_copy va_end( wrk_args ); #endif return static_cast<int>( offset_out ); } /************************************************************************/ /* CPLsnprintf() */ /************************************************************************/ #if !defined(ALIAS_CPLSNPRINTF_AS_SNPRINTF) #if defined(__clang__) && __clang_major__ == 3 && __clang_minor__ <= 2 #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunknown-pragmas" #pragma clang diagnostic ignored "-Wdocumentation" #endif /** snprintf() wrapper that is not sensitive to LC_NUMERIC settings. * * This function has the same contract as standard snprintf(), except that * formatting of floating-point numbers will use decimal point, whatever the * current locale is set. * * @param str output buffer * @param size size of the output buffer (including space for terminating nul) * @param fmt formatting string * @param ... arguments * @return the number of characters (excluding terminating nul) that would be * written if size is big enough. Or potentially -1 with Microsoft C runtime * for Visual Studio < 2015. * @since GDAL 2.0 */ int CPLsnprintf( char *str, size_t size, CPL_FORMAT_STRING(const char* fmt), ... ) { va_list args; va_start( args, fmt ); const int ret = CPLvsnprintf( str, size, fmt, args ); va_end( args ); return ret; } #endif // !defined(ALIAS_CPLSNPRINTF_AS_SNPRINTF) /************************************************************************/ /* CPLsprintf() */ /************************************************************************/ /** sprintf() wrapper that is not sensitive to LC_NUMERIC settings. * * This function has the same contract as standard sprintf(), except that * formatting of floating-point numbers will use decimal point, whatever the * current locale is set. * * @param str output buffer (must be large enough to hold the result) * @param fmt formatting string * @param ... arguments * @return the number of characters (excluding terminating nul) written in ` * output buffer. * @since GDAL 2.0 */ int CPLsprintf( char *str, CPL_FORMAT_STRING(const char* fmt), ... ) { va_list args; va_start( args, fmt ); const int ret = CPLvsnprintf( str, INT_MAX, fmt, args ); va_end( args ); return ret; } /************************************************************************/ /* CPLprintf() */ /************************************************************************/ /** printf() wrapper that is not sensitive to LC_NUMERIC settings. * * This function has the same contract as standard printf(), except that * formatting of floating-point numbers will use decimal point, whatever the * current locale is set. * * @param fmt formatting string * @param ... arguments * @return the number of characters (excluding terminating nul) written in * output buffer. * @since GDAL 2.0 */ int CPLprintf( CPL_FORMAT_STRING(const char* fmt), ... ) { va_list wrk_args, args; va_start( args, fmt ); #ifdef va_copy va_copy( wrk_args, args ); #else wrk_args = args; #endif char szBuffer[4096] = {}; // Quiet coverity by staring off nul terminated. int ret = CPLvsnprintf( szBuffer, sizeof(szBuffer), fmt, wrk_args ); #ifdef va_copy va_end( wrk_args ); #endif if( ret < int(sizeof(szBuffer))-1 ) ret = printf("%s", szBuffer); /*ok*/ else { #ifdef va_copy va_copy( wrk_args, args ); #else wrk_args = args; #endif ret = vfprintf(stdout, fmt, wrk_args); #ifdef va_copy va_end( wrk_args ); #endif } va_end( args ); return ret; } /************************************************************************/ /* CPLsscanf() */ /************************************************************************/ /** \brief sscanf() wrapper that is not sensitive to LC_NUMERIC settings. * * This function has the same contract as standard sscanf(), except that * formatting of floating-point numbers will use decimal point, whatever the * current locale is set. * * CAUTION: only works with a very limited number of formatting strings, * consisting only of "%lf" and regular characters. * * @param str input string * @param fmt formatting string * @param ... arguments * @return the number of matched patterns; * @since GDAL 2.0 */ int CPLsscanf( const char* str, CPL_SCANF_FORMAT_STRING(const char* fmt), ... ) { bool error = false; int ret = 0; const char* fmt_ori = fmt; va_list args; va_start( args, fmt ); for( ; *fmt != '\0' && *str != '\0'; ++fmt ) { if( *fmt == '%' ) { if( fmt[1] == 'l' && fmt[2] == 'f' ) { fmt += 2; char* end; *(va_arg(args, double*)) = CPLStrtod(str, &end); if( end > str ) { ++ret; str = end; } else break; } else { error = true; break; } } else if( isspace(*fmt) ) { while( *str != '\0' && isspace(*str) ) ++str; } else if( *str != *fmt ) break; else ++str; } va_end( args ); if( error ) { CPLError(CE_Failure, CPLE_NotSupported, "Format %s not supported by CPLsscanf()", fmt_ori); } return ret; } #if defined(__clang__) && __clang_major__ == 3 && __clang_minor__ <= 2 #pragma clang diagnostic pop #endif /************************************************************************/ /* CPLTestBool() */ /************************************************************************/ /** * Test what boolean value contained in the string. * * If pszValue is "NO", "FALSE", "OFF" or "0" will be returned false. * Otherwise, true will be returned. * * @param pszValue the string should be tested. * * @return true or false. */ bool CPLTestBool( const char *pszValue ) { return !(EQUAL(pszValue, "NO") || EQUAL(pszValue, "FALSE") || EQUAL(pszValue, "OFF") || EQUAL(pszValue, "0")); } /************************************************************************/ /* CSLTestBoolean() */ /************************************************************************/ /** * Test what boolean value contained in the string. * * If pszValue is "NO", "FALSE", "OFF" or "0" will be returned FALSE. * Otherwise, TRUE will be returned. * * Deprecated. Removed in GDAL 3.x. * * Use CPLTestBoolean() for C and CPLTestBool() for C++. * * @param pszValue the string should be tested. * * @return TRUE or FALSE. */ int CSLTestBoolean( const char *pszValue ) { return CPLTestBool( pszValue ) ? TRUE : FALSE; } /************************************************************************/ /* CPLTestBoolean() */ /************************************************************************/ /** * Test what boolean value contained in the string. * * If pszValue is "NO", "FALSE", "OFF" or "0" will be returned FALSE. * Otherwise, TRUE will be returned. * * Use this only in C code. In C++, prefer CPLTestBool(). * * @param pszValue the string should be tested. * * @return TRUE or FALSE. */ int CPLTestBoolean( const char *pszValue ) { return CPLTestBool( pszValue ) ? TRUE : FALSE; } /********************************************************************** * CPLFetchBool() **********************************************************************/ /** Check for boolean key value. * * In a StringList of "Name=Value" pairs, look to see if there is a key * with the given name, and if it can be interpreted as being TRUE. If * the key appears without any "=Value" portion it will be considered true. * If the value is NO, FALSE or 0 it will be considered FALSE otherwise * if the key appears in the list it will be considered TRUE. If the key * doesn't appear at all, the indicated default value will be returned. * * @param papszStrList the string list to search. * @param pszKey the key value to look for (case insensitive). * @param bDefault the value to return if the key isn't found at all. * * @return true or false */ bool CPLFetchBool( CSLConstList papszStrList, const char *pszKey, bool bDefault ) { if( CSLFindString( papszStrList, pszKey ) != -1 ) return true; const char * const pszValue = CSLFetchNameValue( papszStrList, pszKey ); if( pszValue == nullptr ) return bDefault; return CPLTestBool( pszValue ); } /********************************************************************** * CSLFetchBoolean() **********************************************************************/ /** DEPRECATED. Check for boolean key value. * * In a StringList of "Name=Value" pairs, look to see if there is a key * with the given name, and if it can be interpreted as being TRUE. If * the key appears without any "=Value" portion it will be considered true. * If the value is NO, FALSE or 0 it will be considered FALSE otherwise * if the key appears in the list it will be considered TRUE. If the key * doesn't appear at all, the indicated default value will be returned. * * @param papszStrList the string list to search. * @param pszKey the key value to look for (case insensitive). * @param bDefault the value to return if the key isn't found at all. * * @return TRUE or FALSE */ int CSLFetchBoolean( CSLConstList papszStrList, const char *pszKey, int bDefault ) { return CPLFetchBool( papszStrList, pszKey, CPL_TO_BOOL(bDefault) ); } /************************************************************************/ /* CSLFetchNameValueDefaulted() */ /************************************************************************/ /** Same as CSLFetchNameValue() but return pszDefault in case of no match */ const char *CSLFetchNameValueDef( CSLConstList papszStrList, const char *pszName, const char *pszDefault ) { const char *pszResult = CSLFetchNameValue( papszStrList, pszName ); if( pszResult != nullptr ) return pszResult; return pszDefault; } /********************************************************************** * CSLFetchNameValue() **********************************************************************/ /** In a StringList of "Name=Value" pairs, look for the * first value associated with the specified name. The search is not * case sensitive. * ("Name:Value" pairs are also supported for backward compatibility * with older stuff.) * * Returns a reference to the value in the StringList that the caller * should not attempt to free. * * Returns NULL if the name is not found. */ const char *CSLFetchNameValue( CSLConstList papszStrList, const char *pszName ) { if( papszStrList == nullptr || pszName == nullptr ) return nullptr; const size_t nLen = strlen(pszName); while( *papszStrList != nullptr ) { if( EQUALN(*papszStrList, pszName, nLen) && ( (*papszStrList)[nLen] == '=' || (*papszStrList)[nLen] == ':' ) ) { return (*papszStrList) + nLen + 1; } ++papszStrList; } return nullptr; } /************************************************************************/ /* CSLFindName() */ /************************************************************************/ /** * Find StringList entry with given key name. * * @param papszStrList the string list to search. * @param pszName the key value to look for (case insensitive). * * @return -1 on failure or the list index of the first occurrence * matching the given key. */ int CSLFindName( CSLConstList papszStrList, const char *pszName ) { if( papszStrList == nullptr || pszName == nullptr ) return -1; const size_t nLen = strlen(pszName); int iIndex = 0; while( *papszStrList != nullptr ) { if( EQUALN(*papszStrList, pszName, nLen) && ( (*papszStrList)[nLen] == '=' || (*papszStrList)[nLen] == ':' ) ) { return iIndex; } ++iIndex; ++papszStrList; } return -1; } /********************************************************************** * CPLParseNameValue() **********************************************************************/ /** * Parse NAME=VALUE string into name and value components. * * Note that if ppszKey is non-NULL, the key (or name) portion will be * allocated using CPLMalloc(), and returned in that pointer. It is the * applications responsibility to free this string, but the application should * not modify or free the returned value portion. * * This function also support "NAME:VALUE" strings and will strip white * space from around the delimiter when forming name and value strings. * * Eventually CSLFetchNameValue() and friends may be modified to use * CPLParseNameValue(). * * @param pszNameValue string in "NAME=VALUE" format. * @param ppszKey optional pointer though which to return the name * portion. * * @return the value portion (pointing into original string). */ const char *CPLParseNameValue( const char *pszNameValue, char **ppszKey ) { for( int i = 0; pszNameValue[i] != '\0'; ++i ) { if( pszNameValue[i] == '=' || pszNameValue[i] == ':' ) { const char *pszValue = pszNameValue + i + 1; while( *pszValue == ' ' || *pszValue == '\t' ) ++pszValue; if( ppszKey != nullptr ) { *ppszKey = static_cast<char *>(CPLMalloc(i + 1)); memcpy( *ppszKey, pszNameValue, i ); (*ppszKey)[i] = '\0'; while( i > 0 && ( (*ppszKey)[i-1] == ' ' || (*ppszKey)[i-1] == '\t') ) { (*ppszKey)[i-1] = '\0'; i--; } } return pszValue; } } return nullptr; } /********************************************************************** * CSLFetchNameValueMultiple() **********************************************************************/ /** In a StringList of "Name=Value" pairs, look for all the * values with the specified name. The search is not case * sensitive. * ("Name:Value" pairs are also supported for backward compatibility * with older stuff.) * * Returns StringList with one entry for each occurrence of the * specified name. The StringList should eventually be destroyed * by calling CSLDestroy(). * * Returns NULL if the name is not found. */ char **CSLFetchNameValueMultiple( CSLConstList papszStrList, const char *pszName ) { if( papszStrList == nullptr || pszName == nullptr ) return nullptr; const size_t nLen = strlen(pszName); char **papszValues = nullptr; while( *papszStrList != nullptr ) { if( EQUALN(*papszStrList, pszName, nLen) && ( (*papszStrList)[nLen] == '=' || (*papszStrList)[nLen] == ':' ) ) { papszValues = CSLAddString(papszValues, (*papszStrList)+nLen+1); } ++papszStrList; } return papszValues; } /********************************************************************** * CSLAddNameValue() **********************************************************************/ /** Add a new entry to a StringList of "Name=Value" pairs, * ("Name:Value" pairs are also supported for backward compatibility * with older stuff.) * * This function does not check if a "Name=Value" pair already exists * for that name and can generate multiple entries for the same name. * Use CSLSetNameValue() if you want each name to have only one value. * * Returns the modified StringList. */ char **CSLAddNameValue( char **papszStrList, const char *pszName, const char *pszValue ) { if( pszName == nullptr || pszValue==nullptr ) return papszStrList; const size_t nLen = strlen(pszName)+strlen(pszValue)+2; char *pszLine = static_cast<char *>(CPLMalloc(nLen) ); snprintf( pszLine, nLen, "%s=%s", pszName, pszValue ); papszStrList = CSLAddString( papszStrList, pszLine ); CPLFree( pszLine ); return papszStrList; } /************************************************************************/ /* CSLSetNameValue() */ /************************************************************************/ /** * Assign value to name in StringList. * * Set the value for a given name in a StringList of "Name=Value" pairs * ("Name:Value" pairs are also supported for backward compatibility * with older stuff.) * * If there is already a value for that name in the list then the value * is changed, otherwise a new "Name=Value" pair is added. * * @param papszList the original list, the modified version is returned. * @param pszName the name to be assigned a value. This should be a well * formed token (no spaces or very special characters). * @param pszValue the value to assign to the name. This should not contain * any newlines (CR or LF) but is otherwise pretty much unconstrained. If * NULL any corresponding value will be removed. * * @return modified StringList. */ char **CSLSetNameValue( char **papszList, const char *pszName, const char *pszValue ) { if( pszName == nullptr ) return papszList; size_t nLen = strlen(pszName); while( nLen > 0 && pszName[nLen-1] == ' ' ) nLen --; char **papszPtr = papszList; while( papszPtr && *papszPtr != nullptr ) { if( EQUALN(*papszPtr, pszName, nLen) ) { size_t i; for( i = nLen; (*papszPtr)[i] == ' '; ++i ) { } if( (*papszPtr)[i] == '=' || (*papszPtr)[i] == ':' ) { // Found it. // Change the value... make sure to keep the ':' or '='. const char cSep = (*papszPtr)[i]; CPLFree(*papszPtr); // If the value is NULL, remove this entry completely. if( pszValue == nullptr ) { while( papszPtr[1] != nullptr ) { *papszPtr = papszPtr[1]; ++papszPtr; } *papszPtr = nullptr; } // Otherwise replace with new value. else { const size_t nLen2 = strlen(pszName)+strlen(pszValue)+2; *papszPtr = static_cast<char *>(CPLMalloc(nLen2) ); snprintf( *papszPtr, nLen2, "%s%c%s", pszName, cSep, pszValue ); } return papszList; } } ++papszPtr; } if( pszValue == nullptr ) return papszList; // The name does not exist yet. Create a new entry. return CSLAddNameValue(papszList, pszName, pszValue); } /************************************************************************/ /* CSLSetNameValueSeparator() */ /************************************************************************/ /** * Replace the default separator (":" or "=") with the passed separator * in the given name/value list. * * Note that if a separator other than ":" or "=" is used, the resulting * list will not be manipulable by the CSL name/value functions any more. * * The CPLParseNameValue() function is used to break the existing lines, * and it also strips white space from around the existing delimiter, thus * the old separator, and any white space will be replaced by the new * separator. For formatting purposes it may be desirable to include some * white space in the new separator. e.g. ": " or " = ". * * @param papszList the list to update. Component strings may be freed * but the list array will remain at the same location. * * @param pszSeparator the new separator string to insert. */ void CSLSetNameValueSeparator( char ** papszList, const char *pszSeparator ) { const int nLines = CSLCount(papszList); for( int iLine = 0; iLine < nLines; ++iLine ) { char *pszKey = nullptr; const char *pszValue = CPLParseNameValue( papszList[iLine], &pszKey ); if( pszValue == nullptr || pszKey == nullptr ) { CPLFree( pszKey ); continue; } char *pszNewLine = static_cast<char *>( CPLMalloc( strlen(pszValue) + strlen(pszKey) + strlen(pszSeparator) + 1 ) ); strcpy( pszNewLine, pszKey ); strcat( pszNewLine, pszSeparator ); strcat( pszNewLine, pszValue ); CPLFree( papszList[iLine] ); papszList[iLine] = pszNewLine; CPLFree( pszKey ); } } /************************************************************************/ /* CPLEscapeString() */ /************************************************************************/ /** * Apply escaping to string to preserve special characters. * * This function will "escape" a variety of special characters * to make the string suitable to embed within a string constant * or to write within a text stream but in a form that can be * reconstituted to its original form. The escaping will even preserve * zero bytes allowing preservation of raw binary data. * * CPLES_BackslashQuotable(0): This scheme turns a binary string into * a form suitable to be placed within double quotes as a string constant. * The backslash, quote, '\\0' and newline characters are all escaped in * the usual C style. * * CPLES_XML(1): This scheme converts the '<', '>', '"' and '&' characters into * their XML/HTML equivalent (<, >, " and &) making a string safe * to embed as CDATA within an XML element. The '\\0' is not escaped and * should not be included in the input. * * CPLES_URL(2): Everything except alphanumerics and the characters * '$', '-', '_', '.', '+', '!', '*', ''', '(', ')' and ',' (see RFC1738) are * converted to a percent followed by a two digit hex encoding of the character * (leading zero supplied if needed). This is the mechanism used for encoding * values to be passed in URLs. * * CPLES_SQL(3): All single quotes are replaced with two single quotes. * Suitable for use when constructing literal values for SQL commands where * the literal will be enclosed in single quotes. * * CPLES_CSV(4): If the values contains commas, semicolons, tabs, double quotes, * or newlines it placed in double quotes, and double quotes in the value are * doubled. Suitable for use when constructing field values for .csv files. * Note that CPLUnescapeString() currently does not support this format, only * CPLEscapeString(). See cpl_csv.cpp for CSV parsing support. * * CPLES_SQLI(7): All double quotes are replaced with two double quotes. * Suitable for use when constructing identifiers for SQL commands where * the literal will be enclosed in double quotes. * * @param pszInput the string to escape. * @param nLength The number of bytes of data to preserve. If this is -1 * the strlen(pszString) function will be used to compute the length. * @param nScheme the encoding scheme to use. * * @return an escaped, zero terminated string that should be freed with * CPLFree() when no longer needed. */ char *CPLEscapeString( const char *pszInput, int nLength, int nScheme ) { if( nLength == -1 ) nLength = static_cast<int>(strlen(pszInput)); const size_t nSizeAlloc = nLength * 6 + 1; char *pszOutput = static_cast<char *>( CPLMalloc( nSizeAlloc ) ); int iOut = 0; if( nScheme == CPLES_BackslashQuotable ) { for( int iIn = 0; iIn < nLength; iIn++ ) { if( pszInput[iIn] == '\0' ) { pszOutput[iOut++] = '\\'; pszOutput[iOut++] = '0'; } else if( pszInput[iIn] == '\n' ) { pszOutput[iOut++] = '\\'; pszOutput[iOut++] = 'n'; } else if( pszInput[iIn] == '"' ) { pszOutput[iOut++] = '\\'; pszOutput[iOut++] = '\"'; } else if( pszInput[iIn] == '\\' ) { pszOutput[iOut++] = '\\'; pszOutput[iOut++] = '\\'; } else pszOutput[iOut++] = pszInput[iIn]; } pszOutput[iOut++] = '\0'; } else if( nScheme == CPLES_XML || nScheme == CPLES_XML_BUT_QUOTES ) { for( int iIn = 0; iIn < nLength; ++iIn ) { if( pszInput[iIn] == '<' ) { pszOutput[iOut++] = '&'; pszOutput[iOut++] = 'l'; pszOutput[iOut++] = 't'; pszOutput[iOut++] = ';'; } else if( pszInput[iIn] == '>' ) { pszOutput[iOut++] = '&'; pszOutput[iOut++] = 'g'; pszOutput[iOut++] = 't'; pszOutput[iOut++] = ';'; } else if( pszInput[iIn] == '&' ) { pszOutput[iOut++] = '&'; pszOutput[iOut++] = 'a'; pszOutput[iOut++] = 'm'; pszOutput[iOut++] = 'p'; pszOutput[iOut++] = ';'; } else if( pszInput[iIn] == '"' && nScheme != CPLES_XML_BUT_QUOTES ) { pszOutput[iOut++] = '&'; pszOutput[iOut++] = 'q'; pszOutput[iOut++] = 'u'; pszOutput[iOut++] = 'o'; pszOutput[iOut++] = 't'; pszOutput[iOut++] = ';'; } // Python 2 does not display the UTF-8 character corresponding // to the byte-order mark (BOM), so escape it. else if( (reinterpret_cast<const unsigned char*>(pszInput))[iIn] == 0xEF && (reinterpret_cast<const unsigned char*>(pszInput))[iIn+1] == 0xBB && (reinterpret_cast<const unsigned char*>(pszInput))[iIn+2] == 0xBF ) { pszOutput[iOut++] = '&'; pszOutput[iOut++] = '#'; pszOutput[iOut++] = 'x'; pszOutput[iOut++] = 'F'; pszOutput[iOut++] = 'E'; pszOutput[iOut++] = 'F'; pszOutput[iOut++] = 'F'; pszOutput[iOut++] = ';'; iIn += 2; } else if( (reinterpret_cast<const unsigned char*>(pszInput))[iIn] < 0x20 && pszInput[iIn] != 0x9 && pszInput[iIn] != 0xA && pszInput[iIn] != 0xD ) { // These control characters are unrepresentable in XML format, // so we just drop them. #4117 } else { pszOutput[iOut++] = pszInput[iIn]; } } pszOutput[iOut++] = '\0'; } else if( nScheme == CPLES_URL ) // Untested at implementation. { for( int iIn = 0; iIn < nLength; ++iIn ) { if( (pszInput[iIn] >= 'a' && pszInput[iIn] <= 'z') || (pszInput[iIn] >= 'A' && pszInput[iIn] <= 'Z') || (pszInput[iIn] >= '0' && pszInput[iIn] <= '9') || pszInput[iIn] == '$' || pszInput[iIn] == '-' || pszInput[iIn] == '_' || pszInput[iIn] == '.' || pszInput[iIn] == '+' || pszInput[iIn] == '!' || pszInput[iIn] == '*' || pszInput[iIn] == '\'' || pszInput[iIn] == '(' || pszInput[iIn] == ')' || pszInput[iIn] == '"' || pszInput[iIn] == ',' ) { pszOutput[iOut++] = pszInput[iIn]; } else { snprintf( pszOutput+iOut, nSizeAlloc - iOut, "%%%02X", static_cast<unsigned char>( pszInput[iIn] ) ); iOut += 3; } } pszOutput[iOut++] = '\0'; } else if( nScheme == CPLES_SQL || nScheme == CPLES_SQLI ) { char szQuote = nScheme == CPLES_SQL ? '\'' : '\"'; for( int iIn = 0; iIn < nLength; ++iIn ) { if( pszInput[iIn] == szQuote ) { pszOutput[iOut++] = szQuote; pszOutput[iOut++] = szQuote; } else { pszOutput[iOut++] = pszInput[iIn]; } } pszOutput[iOut++] = '\0'; } else if( nScheme == CPLES_CSV || nScheme == CPLES_CSV_FORCE_QUOTING ) { if( nScheme == CPLES_CSV && static_cast<int>(strcspn( pszInput, "\",;\t\n\r" )) == nLength ) { memcpy( pszOutput, pszInput, nLength + 1 ); iOut = nLength + 1; } else { pszOutput[iOut++] = '\"'; for( int iIn = 0; iIn < nLength; ++iIn ) { if( pszInput[iIn] == '\"' ) { pszOutput[iOut++] = '\"'; pszOutput[iOut++] = '\"'; } else pszOutput[iOut++] = pszInput[iIn]; } pszOutput[iOut++] = '\"'; pszOutput[iOut++] = '\0'; } } else { pszOutput[iOut++] = '\0'; CPLError( CE_Failure, CPLE_AppDefined, "Undefined escaping scheme (%d) in CPLEscapeString()", nScheme ); } if( iOut == nLength + 1 ) return pszOutput; return static_cast<char*>(CPLRealloc( pszOutput, iOut)); } /************************************************************************/ /* CPLUnescapeString() */ /************************************************************************/ /** * Unescape a string. * * This function does the opposite of CPLEscapeString(). Given a string * with special values escaped according to some scheme, it will return a * new copy of the string returned to its original form. * * @param pszInput the input string. This is a zero terminated string. * @param pnLength location to return the length of the unescaped string, * which may in some cases include embedded '\\0' characters. * @param nScheme the escaped scheme to undo (see CPLEscapeString() for a * list). Does not yet support CSV. * * @return a copy of the unescaped string that should be freed by the * application using CPLFree() when no longer needed. */ CPL_NOSANITIZE_UNSIGNED_INT_OVERFLOW char *CPLUnescapeString( const char *pszInput, int *pnLength, int nScheme ) { int iOut = 0; // TODO: Why times 4? char *pszOutput = static_cast<char *>(CPLMalloc(4 * strlen(pszInput) + 1)); pszOutput[0] = '\0'; if( nScheme == CPLES_BackslashQuotable ) { for( int iIn = 0; pszInput[iIn] != '\0'; ++iIn ) { if( pszInput[iIn] == '\\' ) { ++iIn; if( pszInput[iIn] == '\0' ) break; if( pszInput[iIn] == 'n' ) pszOutput[iOut++] = '\n'; else if( pszInput[iIn] == '0' ) pszOutput[iOut++] = '\0'; else pszOutput[iOut++] = pszInput[iIn]; } else { pszOutput[iOut++] = pszInput[iIn]; } } } else if( nScheme == CPLES_XML || nScheme == CPLES_XML_BUT_QUOTES ) { char ch = '\0'; for( int iIn = 0; (ch = pszInput[iIn]) != '\0'; ++iIn ) { if( ch != '&' ) { pszOutput[iOut++] = ch; } else if( STARTS_WITH_CI(pszInput+iIn, "<") ) { pszOutput[iOut++] = '<'; iIn += 3; } else if( STARTS_WITH_CI(pszInput+iIn, ">") ) { pszOutput[iOut++] = '>'; iIn += 3; } else if( STARTS_WITH_CI(pszInput+iIn, "&") ) { pszOutput[iOut++] = '&'; iIn += 4; } else if( STARTS_WITH_CI(pszInput+iIn, "'") ) { pszOutput[iOut++] = '\''; iIn += 5; } else if( STARTS_WITH_CI(pszInput+iIn, """) ) { pszOutput[iOut++] = '"'; iIn += 5; } else if( STARTS_WITH_CI(pszInput+iIn, "&#x") ) { wchar_t anVal[2] = {0 , 0}; iIn += 3; unsigned int nVal = 0; while( true ) { ch = pszInput[iIn++]; if( ch >= 'a' && ch <= 'f' ) nVal = nVal * 16U + static_cast<unsigned int>(ch - 'a' + 10); else if( ch >= 'A' && ch <= 'F' ) nVal = nVal * 16U + static_cast<unsigned int>(ch - 'A' + 10); else if( ch >= '0' && ch <= '9' ) nVal = nVal * 16U + static_cast<unsigned int>(ch - '0'); else break; } anVal[0] = static_cast<wchar_t>(nVal); if( ch != ';' ) break; iIn--; char * pszUTF8 = CPLRecodeFromWChar( anVal, "WCHAR_T", CPL_ENC_UTF8); int nLen = static_cast<int>(strlen(pszUTF8)); memcpy(pszOutput + iOut, pszUTF8, nLen); CPLFree(pszUTF8); iOut += nLen; } else if( STARTS_WITH_CI(pszInput+iIn, "&#") ) { wchar_t anVal[2] = { 0, 0 }; iIn += 2; unsigned int nVal = 0; while( true ) { ch = pszInput[iIn++]; if( ch >= '0' && ch <= '9' ) nVal = nVal * 10U + static_cast<unsigned int>(ch - '0'); else break; } anVal[0] = static_cast<wchar_t>(nVal); if( ch != ';' ) break; iIn--; char *pszUTF8 = CPLRecodeFromWChar( anVal, "WCHAR_T", CPL_ENC_UTF8); const int nLen = static_cast<int>(strlen(pszUTF8)); memcpy(pszOutput + iOut, pszUTF8, nLen); CPLFree(pszUTF8); iOut += nLen; } else { // Illegal escape sequence. CPLDebug( "CPL", "Error unescaping CPLES_XML text, '&' character " "followed by unhandled escape sequence." ); break; } } } else if( nScheme == CPLES_URL ) { for( int iIn = 0; pszInput[iIn] != '\0'; ++iIn ) { if( pszInput[iIn] == '%' && pszInput[iIn+1] != '\0' && pszInput[iIn+2] != '\0' ) { int nHexChar = 0; if( pszInput[iIn+1] >= 'A' && pszInput[iIn+1] <= 'F' ) nHexChar += 16 * (pszInput[iIn+1] - 'A' + 10); else if( pszInput[iIn+1] >= 'a' && pszInput[iIn+1] <= 'f' ) nHexChar += 16 * (pszInput[iIn+1] - 'a' + 10); else if( pszInput[iIn+1] >= '0' && pszInput[iIn+1] <= '9' ) nHexChar += 16 * (pszInput[iIn+1] - '0'); else CPLDebug( "CPL", "Error unescaping CPLES_URL text, percent not " "followed by two hex digits." ); if( pszInput[iIn+2] >= 'A' && pszInput[iIn+2] <= 'F' ) nHexChar += pszInput[iIn+2] - 'A' + 10; else if( pszInput[iIn+2] >= 'a' && pszInput[iIn+2] <= 'f' ) nHexChar += pszInput[iIn+2] - 'a' + 10; else if( pszInput[iIn+2] >= '0' && pszInput[iIn+2] <= '9' ) nHexChar += pszInput[iIn+2] - '0'; else CPLDebug( "CPL", "Error unescaping CPLES_URL text, percent not " "followed by two hex digits." ); pszOutput[iOut++] = static_cast<char>( nHexChar ); iIn += 2; } else if( pszInput[iIn] == '+' ) { pszOutput[iOut++] = ' '; } else { pszOutput[iOut++] = pszInput[iIn]; } } } else if( nScheme == CPLES_SQL || nScheme == CPLES_SQLI ) { char szQuote = nScheme == CPLES_SQL ? '\'' : '\"'; for( int iIn = 0; pszInput[iIn] != '\0'; ++iIn ) { if( pszInput[iIn] == szQuote && pszInput[iIn+1] == szQuote ) { ++iIn; pszOutput[iOut++] = pszInput[iIn]; } else { pszOutput[iOut++] = pszInput[iIn]; } } } else if( nScheme == CPLES_CSV ) { CPLError( CE_Fatal, CPLE_NotSupported, "CSV Unescaping not yet implemented."); } else { CPLError( CE_Fatal, CPLE_NotSupported, "Unknown escaping style."); } pszOutput[iOut] = '\0'; if( pnLength != nullptr ) *pnLength = iOut; return pszOutput; } /************************************************************************/ /* CPLBinaryToHex() */ /************************************************************************/ /** * Binary to hexadecimal translation. * * @param nBytes number of bytes of binary data in pabyData. * @param pabyData array of data bytes to translate. * * @return hexadecimal translation, zero terminated. Free with CPLFree(). */ char *CPLBinaryToHex( int nBytes, const GByte *pabyData ) { char *pszHex = static_cast<char *>(CPLMalloc(nBytes * 2 + 1)); pszHex[nBytes*2] = '\0'; constexpr char achHex[] = "0123456789ABCDEF"; for( int i = 0; i < nBytes; ++i ) { const int nLow = pabyData[i] & 0x0f; const int nHigh = (pabyData[i] & 0xf0) >> 4; pszHex[i*2] = achHex[nHigh]; pszHex[i*2+1] = achHex[nLow]; } return pszHex; } /************************************************************************/ /* CPLHexToBinary() */ /************************************************************************/ constexpr unsigned char hex2char[256] = { // Not Hex characters. 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 0-9 0,1,2,3,4,5,6,7,8,9,0,0,0,0,0,0, // A-F 0,10,11,12,13,14,15,0,0,0,0,0,0,0,0,0, // Not Hex characters. 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // a-f 0,10,11,12,13,14,15,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // Not Hex characters (upper 128 characters). 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }; /** * Hexadecimal to binary translation * * @param pszHex the input hex encoded string. * @param pnBytes the returned count of decoded bytes placed here. * * @return returns binary buffer of data - free with CPLFree(). */ GByte *CPLHexToBinary( const char *pszHex, int *pnBytes ) { const GByte* pabyHex = reinterpret_cast<const GByte*>(pszHex); const size_t nHexLen = strlen(pszHex); GByte *pabyWKB = static_cast<GByte *>( CPLMalloc(nHexLen / 2 + 2) ); for( size_t i = 0; i < nHexLen/2; ++i ) { const unsigned char h1 = hex2char[pabyHex[2*i]]; const unsigned char h2 = hex2char[pabyHex[2*i+1]]; // First character is high bits, second is low bits. pabyWKB[i] = static_cast<GByte>( (h1 << 4) | h2 ); } pabyWKB[nHexLen/2] = 0; *pnBytes = static_cast<int>(nHexLen/2); return pabyWKB; } /************************************************************************/ /* CPLGetValueType() */ /************************************************************************/ /** * Detect the type of the value contained in a string, whether it is * a real, an integer or a string * Leading and trailing spaces are skipped in the analysis. * * Note: in the context of this function, integer must be understood in a * broad sense. It does not mean that the value can fit into a 32 bit integer * for example. It might be larger. * * @param pszValue the string to analyze * * @return returns the type of the value contained in the string. */ CPLValueType CPLGetValueType( const char* pszValue ) { // Doubles : "+25.e+3", "-25.e-3", "25.e3", "25e3", " 25e3 " // Not doubles: "25e 3", "25e.3", "-2-5e3", "2-5e3", "25.25.3", "-3d", "d1" // "XXeYYYYYYYYYYYYYYYYYYY" that evaluates to infinity if( pszValue == nullptr ) return CPL_VALUE_STRING; const char* pszValueInit = pszValue; // Skip leading spaces. while( isspace(static_cast<unsigned char>( *pszValue )) ) ++pszValue; if( *pszValue == '\0' ) return CPL_VALUE_STRING; // Skip leading + or -. if( *pszValue == '+' || *pszValue == '-' ) ++pszValue; bool bFoundDot = false; bool bFoundExponent = false; bool bIsLastCharExponent = false; bool bIsReal = false; const char* pszAfterExponent = nullptr; bool bFoundMantissa = false; for( ; *pszValue != '\0'; ++pszValue ) { if( isdigit(static_cast<unsigned char>( *pszValue )) ) { bIsLastCharExponent = false; bFoundMantissa = true; } else if( isspace(static_cast<unsigned char>( *pszValue )) ) { const char* pszTmp = pszValue; while( isspace(static_cast<unsigned char>( *pszTmp )) ) ++pszTmp; if( *pszTmp == 0 ) break; else return CPL_VALUE_STRING; } else if( *pszValue == '-' || *pszValue == '+' ) { if( bIsLastCharExponent ) { // Do nothing. } else { return CPL_VALUE_STRING; } bIsLastCharExponent = false; } else if( *pszValue == '.') { bIsReal = true; if( !bFoundDot && !bIsLastCharExponent ) bFoundDot = true; else return CPL_VALUE_STRING; bIsLastCharExponent = false; } else if( *pszValue == 'D' || *pszValue == 'd' || *pszValue == 'E' || *pszValue == 'e' ) { if( !bFoundMantissa ) return CPL_VALUE_STRING; if( !(pszValue[1] == '+' || pszValue[1] == '-' || isdigit(pszValue[1])) ) return CPL_VALUE_STRING; bIsReal = true; if( !bFoundExponent ) bFoundExponent = true; else return CPL_VALUE_STRING; pszAfterExponent = pszValue + 1; bIsLastCharExponent = true; } else { return CPL_VALUE_STRING; } } if( bIsReal && pszAfterExponent && strlen(pszAfterExponent) > 3 ) { // cppcheck-suppress unreadVariable const double dfVal = CPLAtof(pszValueInit); if( CPLIsInf(dfVal) ) return CPL_VALUE_STRING; } return bIsReal ? CPL_VALUE_REAL : CPL_VALUE_INTEGER; } /************************************************************************/ /* CPLStrlcpy() */ /************************************************************************/ /** * Copy source string to a destination buffer. * * This function ensures that the destination buffer is always NUL terminated * (provided that its length is at least 1). * * This function is designed to be a safer, more consistent, and less error * prone replacement for strncpy. Its contract is identical to libbsd's strlcpy. * * Truncation can be detected by testing if the return value of CPLStrlcpy * is greater or equal to nDestSize. \verbatim char szDest[5] = {}; if( CPLStrlcpy(szDest, "abcde", sizeof(szDest)) >= sizeof(szDest) ) fprintf(stderr, "truncation occurred !\n"); \endverbatim * @param pszDest destination buffer * @param pszSrc source string. Must be NUL terminated * @param nDestSize size of destination buffer (including space for the NUL * terminator character) * * @return the length of the source string (=strlen(pszSrc)) * * @since GDAL 1.7.0 */ size_t CPLStrlcpy( char* pszDest, const char* pszSrc, size_t nDestSize ) { if( nDestSize == 0 ) return strlen(pszSrc); char* pszDestIter = pszDest; const char* pszSrcIter = pszSrc; --nDestSize; while( nDestSize != 0 && *pszSrcIter != '\0' ) { *pszDestIter = *pszSrcIter; ++pszDestIter; ++pszSrcIter; --nDestSize; } *pszDestIter = '\0'; return pszSrcIter - pszSrc + strlen(pszSrcIter); } /************************************************************************/ /* CPLStrlcat() */ /************************************************************************/ /** * Appends a source string to a destination buffer. * * This function ensures that the destination buffer is always NUL terminated * (provided that its length is at least 1 and that there is at least one byte * free in pszDest, that is to say strlen(pszDest_before) < nDestSize) * * This function is designed to be a safer, more consistent, and less error * prone replacement for strncat. Its contract is identical to libbsd's strlcat. * * Truncation can be detected by testing if the return value of CPLStrlcat * is greater or equal to nDestSize. \verbatim char szDest[5] = {}; CPLStrlcpy(szDest, "ab", sizeof(szDest)); if( CPLStrlcat(szDest, "cde", sizeof(szDest)) >= sizeof(szDest) ) fprintf(stderr, "truncation occurred !\n"); \endverbatim * @param pszDest destination buffer. Must be NUL terminated before * running CPLStrlcat * @param pszSrc source string. Must be NUL terminated * @param nDestSize size of destination buffer (including space for the * NUL terminator character) * * @return the theoretical length of the destination string after concatenation * (=strlen(pszDest_before) + strlen(pszSrc)). * If strlen(pszDest_before) >= nDestSize, then it returns * nDestSize + strlen(pszSrc) * * @since GDAL 1.7.0 */ size_t CPLStrlcat( char* pszDest, const char* pszSrc, size_t nDestSize ) { char* pszDestIter = pszDest; while( nDestSize != 0 && *pszDestIter != '\0' ) { ++pszDestIter; --nDestSize; } return pszDestIter - pszDest + CPLStrlcpy(pszDestIter, pszSrc, nDestSize); } /************************************************************************/ /* CPLStrnlen() */ /************************************************************************/ /** * Returns the length of a NUL terminated string by reading at most * the specified number of bytes. * * The CPLStrnlen() function returns min(strlen(pszStr), nMaxLen). * Only the first nMaxLen bytes of the string will be read. Useful to * test if a string contains at least nMaxLen characters without reading * the full string up to the NUL terminating character. * * @param pszStr a NUL terminated string * @param nMaxLen maximum number of bytes to read in pszStr * * @return strlen(pszStr) if the length is lesser than nMaxLen, otherwise * nMaxLen if the NUL character has not been found in the first nMaxLen bytes. * * @since GDAL 1.7.0 */ size_t CPLStrnlen ( const char *pszStr, size_t nMaxLen ) { size_t nLen = 0; while( nLen < nMaxLen && *pszStr != '\0' ) { ++nLen; ++pszStr; } return nLen; } /************************************************************************/ /* CSLParseCommandLine() */ /************************************************************************/ /** * Tokenize command line arguments in a list of strings. * * @param pszCommandLine command line * * @return NULL terminated list of strings to free with CSLDestroy() * * @since GDAL 2.1 */ char ** CSLParseCommandLine(const char* pszCommandLine) { return CSLTokenizeString(pszCommandLine); }