/** * vim: set ts=4 : * ============================================================================= * SourceMod * Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved. * ============================================================================= * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU General Public License, version 3.0, as published by the * Free Software Foundation. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License along with * this program. If not, see . * * As a special exception, AlliedModders LLC gives you permission to link the * code of this program (as well as its derivative works) to "Half-Life 2," the * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software * by the Valve Corporation. You must obey the GNU General Public License in * all respects for all other code used. Additionally, AlliedModders LLC grants * this exception to all derivative works. AlliedModders LLC defines further * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), * or . * * Version: $Id$ */ #include #include #include #include "sm_stringutil.h" #include "Logger.h" #include "PluginSys.h" #include "Translator.h" #include "PlayerManager.h" #define LADJUST 0x00000004 /* left adjustment */ #define ZEROPAD 0x00000080 /* zero (as opposed to blank) pad */ #define UPPERDIGITS 0x00000200 /* make alpha digits uppercase */ #define to_digit(c) ((c) - '0') #define is_digit(c) ((unsigned)to_digit(c) <= 9) #define CHECK_ARGS(x) \ if ((arg+x) > args) { \ pCtx->ThrowNativeErrorEx(SP_ERROR_PARAM, "String formatted incorrectly - parameter %d (total %d)", arg, args); \ return 0; \ } inline void ReorderTranslationParams(const Translation *pTrans, cell_t *params) { cell_t new_params[MAX_TRANSLATE_PARAMS]; for (unsigned int i = 0; i < pTrans->fmt_count; i++) { new_params[i] = params[pTrans->fmt_order[i]]; } memcpy(params, new_params, pTrans->fmt_count * sizeof(cell_t)); } size_t Translate(char *buffer, size_t maxlen, IPluginContext *pCtx, const char *key, cell_t target, const cell_t *params, int *arg, bool *error) { unsigned int langid; *error = false; Translation pTrans; CPlugin *pl = (CPlugin *)g_PluginSys.FindPluginByContext(pCtx->GetContext()); unsigned int max_params = 0; IPhraseCollection *pPhrases; pPhrases = pl->GetPhrases(); try_serverlang: if (target == SOURCEMOD_SERVER_LANGUAGE) { langid = g_Translator.GetServerLanguage(); } else if ((target >= 1) && (target <= g_Players.GetMaxClients())) { langid = g_Translator.GetClientLanguage(target); } else { pCtx->ThrowNativeErrorEx(SP_ERROR_PARAM, "Translation failed: invalid client index %d", target); goto error_out; } if (pPhrases->FindTranslation(key, langid, &pTrans) != Trans_Okay) { if (target != SOURCEMOD_SERVER_LANGUAGE && langid != g_Translator.GetServerLanguage()) { target = SOURCEMOD_SERVER_LANGUAGE; goto try_serverlang; } else if (langid != SOURCEMOD_LANGUAGE_ENGLISH) { if (pPhrases->FindTranslation(key, SOURCEMOD_LANGUAGE_ENGLISH, &pTrans) != Trans_Okay) { pCtx->ThrowNativeErrorEx(SP_ERROR_PARAM, "Language phrase \"%s\" not found", key); goto error_out; } } else { pCtx->ThrowNativeErrorEx(SP_ERROR_PARAM, "Language phrase \"%s\" not found", key); goto error_out; } } max_params = pTrans.fmt_count; if (max_params) { cell_t new_params[MAX_TRANSLATE_PARAMS]; /* Check if we're going to over the limit */ if ((*arg) + (max_params - 1) > (size_t)params[0]) { pCtx->ThrowNativeErrorEx(SP_ERROR_PARAMS_MAX, "Translation string formatted incorrectly - missing at least %d parameters", ((*arg + (max_params - 1)) - params[0]) ); goto error_out; } /* If we need to re-order the parameters, do so with a temporary array. * Otherwise, we could run into trouble with continual formats, a la ShowActivity(). */ memcpy(new_params, params, sizeof(cell_t) * (params[0] + 1)); ReorderTranslationParams(&pTrans, &new_params[*arg]); return atcprintf(buffer, maxlen, pTrans.szPhrase, pCtx, new_params, arg); } else { return atcprintf(buffer, maxlen, pTrans.szPhrase, pCtx, params, arg); } error_out: *error = true; return 0; } void AddString(char **buf_p, size_t &maxlen, const char *string, int width, int prec) { int size = 0; char *buf; static char nlstr[] = {'(','n','u','l','l',')','\0'}; buf = *buf_p; if (string == NULL) { string = nlstr; prec = -1; } if (prec >= 0) { for (size = 0; size < prec; size++) { if (string[size] == '\0') { break; } } } else { while (string[size++]); size--; } if (size > (int)maxlen) { size = maxlen; } maxlen -= size; width -= size; while (size--) { *buf++ = *string++; } while ((width-- > 0) && maxlen) { *buf++ = ' '; maxlen--; } *buf_p = buf; } void AddFloat(char **buf_p, size_t &maxlen, double fval, int width, int prec, int flags) { int digits; // non-fraction part digits double tmp; // temporary char *buf = *buf_p; // output buffer pointer int val; // temporary int sign = 0; // 0: positive, 1: negative int fieldlength; // for padding int significant_digits = 0; // number of significant digits written const int MAX_SIGNIFICANT_DIGITS = 16; // default precision if (prec < 0) { prec = 6; } // get the sign if (fval < 0) { fval = -fval; sign = 1; } // compute whole-part digits count digits = (int)log10(fval) + 1; // Only print 0.something if 0 < fval < 1 if (digits < 1) { digits = 1; } // compute the field length fieldlength = digits + prec + ((prec > 0) ? 1 : 0) + sign; // minus sign BEFORE left padding if padding with zeros if (sign && maxlen && (flags & ZEROPAD)) { *buf++ = '-'; maxlen--; } // right justify if required if ((flags & LADJUST) == 0) { while ((fieldlength < width) && maxlen) { *buf++ = (flags & ZEROPAD) ? '0' : ' '; width--; maxlen--; } } // minus sign AFTER left padding if padding with spaces if (sign && maxlen && !(flags & ZEROPAD)) { *buf++ = '-'; maxlen--; } // write the whole part tmp = pow(10.0, digits-1); while ((digits--) && maxlen) { if (++significant_digits > MAX_SIGNIFICANT_DIGITS) { *buf++ = '0'; } else { val = (int)(fval / tmp); *buf++ = '0' + val; fval -= val * tmp; tmp *= 0.1; } maxlen--; } // write the fraction part if (maxlen) { *buf++ = '.'; maxlen--; } tmp = pow(10.0, prec); fval *= tmp; while (prec-- && maxlen) { if (++significant_digits > MAX_SIGNIFICANT_DIGITS) { *buf++ = '0'; } else { tmp *= 0.1; val = (int)(fval / tmp); *buf++ = '0' + val; fval -= val * tmp; } maxlen--; } // left justify if required if (flags & LADJUST) { while ((fieldlength < width) && maxlen) { // right-padding only with spaces, ZEROPAD is ignored *buf++ = ' '; width--; maxlen--; } } // update parent's buffer pointer *buf_p = buf; } void AddBinary(char **buf_p, size_t &maxlen, unsigned int val, int width, int flags) { char text[32]; int digits; char *buf; digits = 0; do { if (val & 1) { text[digits++] = '1'; } else { text[digits++] = '0'; } val >>= 1; } while (val); buf = *buf_p; if (!(flags & LADJUST)) { while (digits < width && maxlen) { *buf++ = (flags & ZEROPAD) ? '0' : ' '; width--; maxlen--; } } while (digits-- && maxlen) { *buf++ = text[digits]; width--; maxlen--; } if (flags & LADJUST) { while (width-- && maxlen) { *buf++ = (flags & ZEROPAD) ? '0' : ' '; maxlen--; } } *buf_p = buf; } void AddUInt(char **buf_p, size_t &maxlen, unsigned int val, int width, int flags) { char text[32]; int digits; char *buf; digits = 0; do { text[digits++] = '0' + val % 10; val /= 10; } while (val); buf = *buf_p; if (!(flags & LADJUST)) { while (digits < width && maxlen) { *buf++ = (flags & ZEROPAD) ? '0' : ' '; width--; maxlen--; } } while (digits-- && maxlen) { *buf++ = text[digits]; width--; maxlen--; } if (flags & LADJUST) { while (width-- && maxlen) { *buf++ = (flags & ZEROPAD) ? '0' : ' '; maxlen--; } } *buf_p = buf; } void AddInt(char **buf_p, size_t &maxlen, int val, int width, int flags) { char text[32]; int digits; int signedVal; char *buf; unsigned int unsignedVal; digits = 0; signedVal = val; if (val < 0) { /* we want the unsigned version */ unsignedVal = abs(val); } else { unsignedVal = val; } do { text[digits++] = '0' + unsignedVal % 10; unsignedVal /= 10; } while (unsignedVal); if (signedVal < 0) { text[digits++] = '-'; } buf = *buf_p; if (!(flags & LADJUST)) { while ((digits < width) && maxlen) { *buf++ = (flags & ZEROPAD) ? '0' : ' '; width--; maxlen--; } } while (digits-- && maxlen) { *buf++ = text[digits]; width--; maxlen--; } if (flags & LADJUST) { while (width-- && maxlen) { *buf++ = (flags & ZEROPAD) ? '0' : ' '; maxlen--; } } *buf_p = buf; } void AddHex(char **buf_p, size_t &maxlen, unsigned int val, int width, int flags) { char text[32]; int digits; char *buf; char digit; int hexadjust; if (flags & UPPERDIGITS) { hexadjust = 'A' - '9' - 1; } else { hexadjust = 'a' - '9' - 1; } digits = 0; do { digit = ('0' + val % 16); if (digit > '9') { digit += hexadjust; } text[digits++] = digit; val /= 16; } while(val); buf = *buf_p; if (!(flags & LADJUST)) { while (digits < width && maxlen) { *buf++ = (flags & ZEROPAD) ? '0' : ' '; width--; maxlen--; } } while (digits-- && maxlen) { *buf++ = text[digits]; width--; maxlen--; } if (flags & LADJUST) { while (width-- && maxlen) { *buf++ = (flags & ZEROPAD) ? '0' : ' '; maxlen--; } } *buf_p = buf; } bool gnprintf(char *buffer, size_t maxlen, const char *format, IPhraseCollection *pPhrases, void **params, unsigned int numparams, unsigned int &curparam, size_t *pOutLength, const char **pFailPhrase) { if (!buffer || !maxlen) { if (pOutLength != NULL) { *pOutLength = 0; } return true; } if (numparams > MAX_TRANSLATE_PARAMS) { if (pFailPhrase != NULL) { *pFailPhrase = NULL; } return false; } int arg = 0; char *buf_p; char ch; int flags; int width; int prec; int n; char sign; const char *fmt; size_t llen = maxlen - 1; buf_p = buffer; fmt = format; while (true) { // run through the format string until we hit a '%' or '\0' for (ch = *fmt; llen && ((ch = *fmt) != '\0') && (ch != '%'); fmt++) { *buf_p++ = ch; llen--; } if ((ch == '\0') || (llen <= 0)) { goto done; } // skip over the '%' fmt++; // reset formatting state flags = 0; width = 0; prec = -1; sign = '\0'; rflag: ch = *fmt++; reswitch: switch(ch) { case '-': { flags |= LADJUST; goto rflag; } case '.': { n = 0; while(is_digit((ch = *fmt++))) { n = 10 * n + (ch - '0'); } prec = (n < 0) ? -1 : n; goto reswitch; } case '0': { flags |= ZEROPAD; goto rflag; } case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': { n = 0; do { n = 10 * n + (ch - '0'); ch = *fmt++; } while(is_digit(ch)); width = n; goto reswitch; } case 'c': { if (!llen) { goto done; } if (curparam >= numparams) { if (pFailPhrase != NULL) { *pFailPhrase = NULL; } return false; } char *c = (char *)params[curparam]; curparam++; *buf_p++ = *c; llen--; arg++; break; } case 'b': { if (curparam >= numparams) { if (pFailPhrase != NULL) { *pFailPhrase = NULL; } return false; } int *value = (int *)params[curparam]; curparam++; AddBinary(&buf_p, llen, *value, width, flags); arg++; break; } case 'd': case 'i': { if (curparam >= numparams) { if (pFailPhrase != NULL) { *pFailPhrase = NULL; } return false; } int *value = (int *)params[curparam]; curparam++; AddInt(&buf_p, llen, *value, width, flags); arg++; break; } case 'u': { if (curparam >= numparams) { if (pFailPhrase != NULL) { *pFailPhrase = NULL; } return false; } unsigned int *value = (unsigned int *)params[curparam]; curparam++; AddUInt(&buf_p, llen, *value, width, flags); arg++; break; } case 'f': { if (curparam >= numparams) { if (pFailPhrase != NULL) { *pFailPhrase = NULL; } return false; } float *value = (float *)params[curparam]; curparam++; AddFloat(&buf_p, llen, *value, width, prec, flags); arg++; break; } case 's': { if (curparam >= numparams) { if (pFailPhrase != NULL) { *pFailPhrase = NULL; } return false; } const char *str = (const char *)params[curparam]; curparam++; AddString(&buf_p, llen, str, width, prec); arg++; break; } case 'T': case 't': { int target; const char *key; size_t out_length; Translation trans; unsigned int lang_id; if (curparam >= numparams) { if (pFailPhrase != NULL) { *pFailPhrase = NULL; } return false; } key = (const char *)(params[curparam]); curparam++; if (ch == 'T') { if (curparam >= numparams) { if (pFailPhrase != NULL) { *pFailPhrase = NULL; } return false; } target = *((int *)(params[curparam])); curparam++; } else { target = g_Translator.GetGlobalTarget(); } try_again: if (target == SOURCEMOD_SERVER_LANGUAGE) { lang_id = g_Translator.GetServerLanguage(); } else if (target >= 1 && target <= g_Players.GetMaxClients()) { lang_id = g_Translator.GetClientLanguage(target); } else { lang_id = g_Translator.GetServerLanguage(); } if (pPhrases == NULL) { if (pFailPhrase != NULL) { *pFailPhrase = key; } return false; } if (pPhrases->FindTranslation(key, lang_id, &trans) != Trans_Okay) { if (target != SOURCEMOD_SERVER_LANGUAGE && lang_id != g_Translator.GetServerLanguage()) { target = SOURCEMOD_SERVER_LANGUAGE; goto try_again; } else if (lang_id != SOURCEMOD_LANGUAGE_ENGLISH) { if (pPhrases->FindTranslation(key, SOURCEMOD_LANGUAGE_ENGLISH, &trans) != Trans_Okay) { if (pFailPhrase != NULL) { *pFailPhrase = key; } return false; } } else { if (pFailPhrase != NULL) { *pFailPhrase = key; } return false; } } if (trans.fmt_count) { unsigned int i; void *new_params[MAX_TRANSLATE_PARAMS]; if (curparam + trans.fmt_count > numparams) { if (pFailPhrase != NULL) { *pFailPhrase = NULL; } return false; } /* Copy the array and re-order the stack */ memcpy(new_params, params, sizeof(void *) * numparams); for (i = 0; i < trans.fmt_count; i++) { new_params[i] = const_cast(params[curparam + trans.fmt_order[i]]); } if (!gnprintf(buf_p, llen, trans.szPhrase, pPhrases, new_params, numparams, curparam, &out_length, pFailPhrase)) { return false; } } else { if (!gnprintf(buf_p, llen, trans.szPhrase, pPhrases, params, numparams, curparam, &out_length, pFailPhrase)) { return false; } } buf_p += out_length; llen -= out_length; break; } case 'X': { if (curparam >= numparams) { if (pFailPhrase != NULL) { *pFailPhrase = NULL; } return false; } unsigned int *value = (unsigned int *)params[curparam]; curparam++; flags |= UPPERDIGITS; AddHex(&buf_p, llen, *value, width, flags); arg++; break; } case 'x': { if (curparam >= numparams) { if (pFailPhrase != NULL) { *pFailPhrase = NULL; } return false; } unsigned int *value = (unsigned int *)params[curparam]; curparam++; AddHex(&buf_p, llen, *value, width, flags); arg++; break; } case '%': { if (!llen) { goto done; } *buf_p++ = ch; llen--; break; } case '\0': { if (!llen) { goto done; } *buf_p++ = '%'; llen--; goto done; } default: { if (!llen) { goto done; } *buf_p++ = ch; llen--; break; } } } done: *buf_p = '\0'; if (pOutLength != NULL) { *pOutLength = (maxlen - llen - 1); } return true; } size_t atcprintf(char *buffer, size_t maxlen, const char *format, IPluginContext *pCtx, const cell_t *params, int *param) { if (!buffer || !maxlen) { return 0; } int arg; int args = params[0]; char *buf_p; char ch; int flags; int width; int prec; int n; char sign; const char *fmt; size_t llen = maxlen - 1; buf_p = buffer; arg = *param; fmt = format; while (true) { // run through the format string until we hit a '%' or '\0' for (ch = *fmt; llen && ((ch = *fmt) != '\0') && (ch != '%'); fmt++) { *buf_p++ = ch; llen--; } if ((ch == '\0') || (llen <= 0)) { goto done; } // skip over the '%' fmt++; // reset formatting state flags = 0; width = 0; prec = -1; sign = '\0'; rflag: ch = *fmt++; reswitch: switch(ch) { case '-': { flags |= LADJUST; goto rflag; } case '.': { n = 0; while(is_digit((ch = *fmt++))) { n = 10 * n + (ch - '0'); } prec = (n < 0) ? -1 : n; goto reswitch; } case '0': { flags |= ZEROPAD; goto rflag; } case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': { n = 0; do { n = 10 * n + (ch - '0'); ch = *fmt++; } while(is_digit(ch)); width = n; goto reswitch; } case 'c': { CHECK_ARGS(0); if (!llen) { goto done; } char *c; pCtx->LocalToString(params[arg], &c); *buf_p++ = *c; llen--; arg++; break; } case 'b': { CHECK_ARGS(0); cell_t *value; pCtx->LocalToPhysAddr(params[arg], &value); AddBinary(&buf_p, llen, *value, width, flags); arg++; break; } case 'd': case 'i': { CHECK_ARGS(0); cell_t *value; pCtx->LocalToPhysAddr(params[arg], &value); AddInt(&buf_p, llen, static_cast(*value), width, flags); arg++; break; } case 'u': { CHECK_ARGS(0); cell_t *value; pCtx->LocalToPhysAddr(params[arg], &value); AddUInt(&buf_p, llen, static_cast(*value), width, flags); arg++; break; } case 'f': { CHECK_ARGS(0); cell_t *value; pCtx->LocalToPhysAddr(params[arg], &value); AddFloat(&buf_p, llen, sp_ctof(*value), width, prec, flags); arg++; break; } case 'L': { CHECK_ARGS(0); cell_t *value; pCtx->LocalToPhysAddr(params[arg], &value); char buffer[255]; if (*value) { CPlayer *player = g_Players.GetPlayerByIndex(*value); if (!player || !player->IsConnected()) { return pCtx->ThrowNativeError("Client index %d is invalid", *value); } const char *auth = player->GetAuthString(); if (!auth || auth[0] == '\0') { auth = "STEAM_ID_PENDING"; } int userid = engine->GetPlayerUserId(player->GetEdict()); UTIL_Format(buffer, sizeof(buffer), "%s<%d><%s><>", player->GetName(), userid, auth); } else { UTIL_Format(buffer, sizeof(buffer), "Console<0>"); } AddString(&buf_p, llen, buffer, width, prec); arg++; break; } case 'N': { CHECK_ARGS(0); cell_t *value; pCtx->LocalToPhysAddr(params[arg], &value); const char *name = "Console"; if (*value) { CPlayer *player = g_Players.GetPlayerByIndex(*value); if (!player || !player->IsConnected()) { return pCtx->ThrowNativeError("Client index %d is invalid", *value); } name = player->GetName(); } AddString(&buf_p, llen, name, width, prec); arg++; break; } case 's': { CHECK_ARGS(0); char *str; int err; if ((err=pCtx->LocalToString(params[arg], &str)) != SP_ERROR_NONE) { pCtx->ThrowNativeErrorEx(err, "Could not deference string"); return 0; } AddString(&buf_p, llen, str, width, prec); arg++; break; } case 'T': { CHECK_ARGS(1); char *key; bool error; size_t res; cell_t *target; pCtx->LocalToString(params[arg++], &key); pCtx->LocalToPhysAddr(params[arg++], &target); res = Translate(buf_p, llen, pCtx, key, *target, params, &arg, &error); if (error) { return 0; } buf_p += res; llen -= res; break; } case 't': { CHECK_ARGS(0); char *key; bool error; size_t res; cell_t target = g_SourceMod.GetGlobalTarget(); pCtx->LocalToString(params[arg++], &key); res = Translate(buf_p, llen, pCtx, key, target, params, &arg, &error); if (error) { return 0; } buf_p += res; llen -= res; break; } case 'X': { CHECK_ARGS(0); cell_t *value; pCtx->LocalToPhysAddr(params[arg], &value); flags |= UPPERDIGITS; AddHex(&buf_p, llen, static_cast(*value), width, flags); arg++; break; } case 'x': { CHECK_ARGS(0); cell_t *value; pCtx->LocalToPhysAddr(params[arg], &value); AddHex(&buf_p, llen, static_cast(*value), width, flags); arg++; break; } case '%': { if (!llen) { goto done; } *buf_p++ = ch; llen--; break; } case '\0': { if (!llen) { goto done; } *buf_p++ = '%'; llen--; goto done; } default: { if (!llen) { goto done; } *buf_p++ = ch; llen--; break; } } } done: *buf_p = '\0'; *param = arg; return (maxlen - llen - 1); } const char *stristr(const char *str, const char *substr) { if (!*substr) { return ((char *)str); } char *needle = (char *)substr; char *prevloc = (char *)str; char *haystack = (char *)str; while (*haystack) { if (tolower(*haystack) == tolower(*needle)) { haystack++; if (!*++needle) { return prevloc; } } else { haystack = ++prevloc; needle = (char *)substr; } } return NULL; } unsigned int strncopy(char *dest, const char *src, size_t count) { if (!count) { return 0; } char *start = dest; while ((*src) && (--count)) { *dest++ = *src++; } *dest = '\0'; return (dest - start); } size_t UTIL_Format(char *buffer, size_t maxlength, const char *fmt, ...) { va_list ap; va_start(ap, fmt); size_t len = vsnprintf(buffer, maxlength, fmt, ap); va_end(ap); if (len >= maxlength) { buffer[maxlength - 1] = '\0'; return (maxlength - 1); } else { return len; } } size_t UTIL_FormatArgs(char *buffer, size_t maxlength, const char *fmt, va_list ap) { size_t len = vsnprintf(buffer, maxlength, fmt, ap); if (len >= maxlength) { buffer[maxlength - 1] = '\0'; return (maxlength - 1); } else { return len; } } char *sm_strdup(const char *str) { char *ptr = new char[strlen(str)+1]; strcpy(ptr, str); return ptr; } unsigned int UTIL_ReplaceAll(char *subject, size_t maxlength, const char *search, const char *replace) { size_t searchLen = strlen(search); size_t replaceLen = strlen(replace); char *ptr = subject; unsigned int total = 0; while ((ptr = UTIL_ReplaceEx(ptr, maxlength, search, searchLen, replace, replaceLen)) != NULL) { total++; if (*ptr == '\0') { break; } } return total; } /** * NOTE: Do not edit this for the love of god unless you have * read the test cases and understand the code behind each one. * While I don't guarantee there aren't mistakes, I do guarantee * that plugins will end up relying on tiny idiosyncrasies of this * function, just like they did with AMX Mod X. * * There are explicitly more cases than the AMX Mod X version because * we're not doing a blind copy. Each case is specifically optimized * for what needs to be done. Even better, we don't have to error on * bad buffer sizes. Instead, this function will smartly cut off the * string in a way that pushes old data out. */ char *UTIL_ReplaceEx(char *subject, size_t maxLen, const char *search, size_t searchLen, const char *replace, size_t replaceLen) { char *ptr = subject; size_t browsed = 0; size_t textLen = strlen(subject); /* It's not possible to search or replace */ if (searchLen > textLen) { return NULL; } /* Handle the case of one byte replacement. * It's only valid in one case. */ if (maxLen == 1) { /* If the search matches and the replace length is 0, * we can just terminate the string and be done. */ if (strcmp(subject, search) == 0 && replaceLen == 0) { *subject = '\0'; return subject; } else { return NULL; } } /* Subtract one off the maxlength so we can include the null terminator */ maxLen--; while (*ptr != '\0' && (browsed <= textLen - searchLen)) { /* See if we get a comparison */ if (strncmp(ptr, search, searchLen) == 0) { if (replaceLen > searchLen) { /* First, see if we have enough space to do this operation */ if (maxLen - textLen < replaceLen - searchLen) { /* First, see if the replacement length goes out of bounds. */ if (browsed + replaceLen >= maxLen) { /* EXAMPLE CASE: * Subject: AABBBCCC * Buffer : 12 bytes * Search : BBB * Replace: DDDDDDDDDD * OUTPUT : AADDDDDDDDD * POSITION: ^ */ /* If it does, we'll just bound the length and do a strcpy. */ replaceLen = maxLen - browsed; /* Note, we add one to the final result for the null terminator */ strncopy(ptr, replace, replaceLen+1); } else { /* EXAMPLE CASE: * Subject: AABBBCCC * Buffer : 12 bytes * Search : BBB * Replace: DDDDDDD * OUTPUT : AADDDDDDDCC * POSITION: ^ */ /* We're going to have some bytes left over... */ size_t origBytesToCopy = (textLen - (browsed + searchLen)) + 1; size_t realBytesToCopy = (maxLen - (browsed + replaceLen)) + 1; char *moveFrom = ptr + searchLen + (origBytesToCopy - realBytesToCopy); char *moveTo = ptr + replaceLen; /* First, move our old data out of the way. */ memmove(moveTo, moveFrom, realBytesToCopy); /* Now, do our replacement. */ memcpy(ptr, replace, replaceLen); } } else { /* EXAMPLE CASE: * Subject: AABBBCCC * Buffer : 12 bytes * Search : BBB * Replace: DDDD * OUTPUT : AADDDDCCC * POSITION: ^ */ /* Yes, we have enough space. Do a normal move operation. */ char *moveFrom = ptr + searchLen; char *moveTo = ptr + replaceLen; /* First move our old data out of the way. */ size_t bytesToCopy = (textLen - (browsed + searchLen)) + 1; memmove(moveTo, moveFrom, bytesToCopy); /* Now do our replacement. */ memcpy(ptr, replace, replaceLen); } } else if (replaceLen < searchLen) { /* EXAMPLE CASE: * Subject: AABBBCCC * Buffer : 12 bytes * Search : BBB * Replace: D * OUTPUT : AADCCC * POSOTION: ^ */ /* If the replacement does not grow the string length, we do not * need to do any fancy checking at all. Yay! */ char *moveFrom = ptr + searchLen; /* Start after the search pointer */ char *moveTo = ptr + replaceLen; /* Copy to where the replacement ends */ /* Copy our replacement in, if any */ if (replaceLen) { memcpy(ptr, replace, replaceLen); } /* Figure out how many bytes to move down, including null terminator */ size_t bytesToCopy = (textLen - (browsed + searchLen)) + 1; /* Move the rest of the string down */ memmove(moveTo, moveFrom, bytesToCopy); } else { /* EXAMPLE CASE: * Subject: AABBBCCC * Buffer : 12 bytes * Search : BBB * Replace: DDD * OUTPUT : AADDDCCC * POSITION: ^ */ /* We don't have to move anything around, just do a straight copy */ memcpy(ptr, replace, replaceLen); } return ptr + replaceLen; } ptr++; browsed++; } return NULL; } char *UTIL_TrimWhitespace(char *str, size_t &len) { char *end = str + len - 1; if (!len) { return str; } /* Iterate backwards through string until we reach first non-whitespace char */ while (end >= str && textparsers->IsWhitespace(end)) { end--; len--; } /* Replace first whitespace char (at the end) with null terminator. * If there is none, we're just replacing the null terminator. */ *(end + 1) = '\0'; while (*str != '\0' && textparsers->IsWhitespace(str)) { str++; len--; } return str; }