core/logic/stringutil.cpp
author Nicholas Hastings <skamonkey@gmail.com>
Wed Jul 03 23:27:04 2013 -0400 (2013-07-03)
changeset 3930 bf78b97106e5
parent 3929 0bc0ffdb2eba
child 3931 576fa42454dc
permissions -rw-r--r--
Fix posix builds.
dvander@3002
     1
/**
dvander@3002
     2
 * vim: set ts=4 sw=4 tw=99 et :
dvander@3002
     3
 * =============================================================================
dvander@3002
     4
 * SourceMod
dvander@3002
     5
 * Copyright (C) 2004-2009 AlliedModders LLC.  All rights reserved.
dvander@3002
     6
 * =============================================================================
dvander@3002
     7
 *
dvander@3002
     8
 * This program is free software; you can redistribute it and/or modify it under
dvander@3002
     9
 * the terms of the GNU General Public License, version 3.0, as published by the
dvander@3002
    10
 * Free Software Foundation.
dvander@3002
    11
 * 
dvander@3002
    12
 * This program is distributed in the hope that it will be useful, but WITHOUT
dvander@3002
    13
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
dvander@3002
    14
 * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
dvander@3002
    15
 * details.
dvander@3002
    16
 *
dvander@3002
    17
 * You should have received a copy of the GNU General Public License along with
dvander@3002
    18
 * this program.  If not, see <http://www.gnu.org/licenses/>.
dvander@3002
    19
 *
dvander@3002
    20
 * As a special exception, AlliedModders LLC gives you permission to link the
dvander@3002
    21
 * code of this program (as well as its derivative works) to "Half-Life 2," the
dvander@3002
    22
 * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
dvander@3002
    23
 * by the Valve Corporation.  You must obey the GNU General Public License in
dvander@3002
    24
 * all respects for all other code used.  Additionally, AlliedModders LLC grants
dvander@3002
    25
 * this exception to all derivative works.  AlliedModders LLC defines further
dvander@3002
    26
 * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
dvander@3002
    27
 * or <http://www.sourcemod.net/license.php>.
dvander@3002
    28
 *
dvander@3002
    29
 * Version: $Id$
dvander@3002
    30
 */
dvander@3002
    31
dvander@3002
    32
#include <string.h>
dvander@3010
    33
#include <stdio.h>
dvander@3002
    34
#include <ctype.h>
dvander@3007
    35
#include <sm_platform.h>
dvander@3002
    36
#include "stringutil.h"
dvander@3002
    37
dvander@3002
    38
const char *stristr(const char *str, const char *substr)
dvander@3002
    39
{
dvander@3002
    40
	if (!*substr)
dvander@3002
    41
	{
dvander@3002
    42
		return ((char *)str);
dvander@3002
    43
	}
dvander@3002
    44
dvander@3002
    45
	char *needle = (char *)substr;
dvander@3002
    46
	char *prevloc = (char *)str;
dvander@3002
    47
	char *haystack = (char *)str;
dvander@3002
    48
dvander@3002
    49
	while (*haystack)
dvander@3002
    50
	{
dvander@3002
    51
		if (tolower(*haystack) == tolower(*needle))
dvander@3002
    52
		{
dvander@3002
    53
			haystack++;
dvander@3002
    54
			if (!*++needle)
dvander@3002
    55
			{
dvander@3002
    56
				return prevloc;
dvander@3002
    57
			}
dvander@3002
    58
		}
dvander@3002
    59
		else
dvander@3002
    60
		{
dvander@3002
    61
			haystack = ++prevloc;
dvander@3002
    62
			needle = (char *)substr;
dvander@3002
    63
		}
dvander@3002
    64
	}
dvander@3002
    65
dvander@3002
    66
	return NULL;
dvander@3002
    67
}
dvander@3002
    68
dvander@3004
    69
unsigned int strncopy(char *dest, const char *src, size_t count)
dvander@3004
    70
{
dvander@3004
    71
	if (!count)
dvander@3004
    72
	{
dvander@3004
    73
		return 0;
dvander@3004
    74
	}
dvander@3002
    75
dvander@3004
    76
	char *start = dest;
dvander@3004
    77
	while ((*src) && (--count))
dvander@3004
    78
	{
dvander@3004
    79
		*dest++ = *src++;
dvander@3004
    80
	}
dvander@3004
    81
	*dest = '\0';
dvander@3004
    82
dvander@3004
    83
	return (dest - start);
dvander@3004
    84
}
dvander@3004
    85
dvander@3005
    86
unsigned int UTIL_ReplaceAll(char *subject, size_t maxlength, const char *search, const char *replace, bool caseSensitive)
dvander@3005
    87
{
dvander@3005
    88
	size_t searchLen = strlen(search);
dvander@3005
    89
	size_t replaceLen = strlen(replace);
dvander@3004
    90
dvander@3005
    91
	char *ptr = subject;
dvander@3005
    92
	unsigned int total = 0;
dvander@3005
    93
	while ((ptr = UTIL_ReplaceEx(ptr, maxlength, search, searchLen, replace, replaceLen, caseSensitive)) != NULL)
dvander@3005
    94
	{
dvander@3005
    95
		total++;
dvander@3005
    96
		if (*ptr == '\0')
dvander@3005
    97
		{
dvander@3005
    98
			break;
dvander@3005
    99
		}
dvander@3005
   100
	}
dvander@3005
   101
dvander@3005
   102
	return total;
dvander@3005
   103
}
dvander@3005
   104
dvander@3005
   105
/**
dvander@3005
   106
 * NOTE: Do not edit this for the love of god unless you have 
dvander@3005
   107
 * read the test cases and understand the code behind each one.
dvander@3005
   108
 * While I don't guarantee there aren't mistakes, I do guarantee
dvander@3005
   109
 * that plugins will end up relying on tiny idiosyncrasies of this
dvander@3005
   110
 * function, just like they did with AMX Mod X.
dvander@3005
   111
 *
dvander@3005
   112
 * There are explicitly more cases than the AMX Mod X version because
dvander@3005
   113
 * we're not doing a blind copy.  Each case is specifically optimized
dvander@3005
   114
 * for what needs to be done.  Even better, we don't have to error on
dvander@3005
   115
 * bad buffer sizes.  Instead, this function will smartly cut off the 
dvander@3005
   116
 * string in a way that pushes old data out.
dvander@3005
   117
 */
dvander@3005
   118
char *UTIL_ReplaceEx(char *subject, size_t maxLen, const char *search, size_t searchLen, const char *replace, size_t replaceLen, bool caseSensitive)
dvander@3005
   119
{
dvander@3005
   120
	char *ptr = subject;
dvander@3005
   121
	size_t browsed = 0;
dvander@3005
   122
	size_t textLen = strlen(subject);
dvander@3005
   123
dvander@3005
   124
	/* It's not possible to search or replace */
dvander@3005
   125
	if (searchLen > textLen)
dvander@3005
   126
	{
dvander@3005
   127
		return NULL;
dvander@3005
   128
	}
dvander@3005
   129
dvander@3005
   130
	/* Handle the case of one byte replacement.
dvander@3005
   131
	 * It's only valid in one case.
dvander@3005
   132
	 */
dvander@3005
   133
	if (maxLen == 1)
dvander@3005
   134
	{
dvander@3005
   135
		/* If the search matches and the replace length is 0, 
dvander@3005
   136
		 * we can just terminate the string and be done.
dvander@3005
   137
		 */
dvander@3005
   138
		if ((caseSensitive ? strcmp(subject, search) : strcasecmp(subject, search)) == 0 && replaceLen == 0)
dvander@3005
   139
		{
dvander@3005
   140
			*subject = '\0';
dvander@3005
   141
			return subject;
dvander@3005
   142
		}
dvander@3005
   143
		else
dvander@3005
   144
		{
dvander@3005
   145
			return NULL;
dvander@3005
   146
		}
dvander@3005
   147
	}
dvander@3005
   148
dvander@3005
   149
	/* Subtract one off the maxlength so we can include the null terminator */
dvander@3005
   150
	maxLen--;
dvander@3005
   151
dvander@3005
   152
	while (*ptr != '\0' && (browsed <= textLen - searchLen))
dvander@3005
   153
	{
dvander@3005
   154
		/* See if we get a comparison */
dvander@3005
   155
		if ((caseSensitive ? strncmp(ptr, search, searchLen) : strncasecmp(ptr, search, searchLen)) == 0)
dvander@3005
   156
		{
dvander@3005
   157
			if (replaceLen > searchLen)
dvander@3005
   158
			{
dvander@3005
   159
				/* First, see if we have enough space to do this operation */
dvander@3005
   160
				if (maxLen - textLen < replaceLen - searchLen)
dvander@3005
   161
				{
dvander@3005
   162
					/* First, see if the replacement length goes out of bounds. */
dvander@3005
   163
					if (browsed + replaceLen >= maxLen)
dvander@3005
   164
					{
dvander@3005
   165
						/* EXAMPLE CASE:
dvander@3005
   166
						 * Subject: AABBBCCC
dvander@3005
   167
						 * Buffer : 12 bytes
dvander@3005
   168
						 * Search : BBB
dvander@3005
   169
						 * Replace: DDDDDDDDDD
dvander@3005
   170
						 * OUTPUT : AADDDDDDDDD
dvander@3005
   171
						 * POSITION:           ^
dvander@3005
   172
						 */
dvander@3005
   173
						/* If it does, we'll just bound the length and do a strcpy. */
dvander@3005
   174
						replaceLen = maxLen - browsed;
dvander@3005
   175
						/* Note, we add one to the final result for the null terminator */
dvander@3005
   176
						strncopy(ptr, replace, replaceLen+1);
dvander@3005
   177
					}
dvander@3005
   178
					else
dvander@3005
   179
					{
dvander@3005
   180
						/* EXAMPLE CASE:
dvander@3005
   181
						 * Subject: AABBBCCC
dvander@3005
   182
						 * Buffer : 12 bytes
dvander@3005
   183
						 * Search : BBB
dvander@3005
   184
						 * Replace: DDDDDDD
dvander@3005
   185
						 * OUTPUT : AADDDDDDDCC
dvander@3005
   186
						 * POSITION:         ^
dvander@3005
   187
						 */
dvander@3005
   188
						/* We're going to have some bytes left over... */
dvander@3005
   189
						size_t origBytesToCopy = (textLen - (browsed + searchLen)) + 1;
dvander@3005
   190
						size_t realBytesToCopy = (maxLen - (browsed + replaceLen)) + 1;
dvander@3005
   191
						char *moveFrom = ptr + searchLen + (origBytesToCopy - realBytesToCopy);
dvander@3005
   192
						char *moveTo = ptr + replaceLen;
dvander@3005
   193
dvander@3005
   194
						/* First, move our old data out of the way. */
dvander@3005
   195
						memmove(moveTo, moveFrom, realBytesToCopy);
dvander@3005
   196
dvander@3005
   197
						/* Now, do our replacement. */
dvander@3005
   198
						memcpy(ptr, replace, replaceLen);
dvander@3005
   199
					}
dvander@3005
   200
				}
dvander@3005
   201
				else
dvander@3005
   202
				{
dvander@3005
   203
					/* EXAMPLE CASE:
dvander@3005
   204
					 * Subject: AABBBCCC
dvander@3005
   205
					 * Buffer : 12 bytes
dvander@3005
   206
					 * Search : BBB
dvander@3005
   207
					 * Replace: DDDD
dvander@3005
   208
					 * OUTPUT : AADDDDCCC
dvander@3005
   209
					 * POSITION:      ^
dvander@3005
   210
					 */
dvander@3005
   211
					/* Yes, we have enough space.  Do a normal move operation. */
dvander@3005
   212
					char *moveFrom = ptr + searchLen;
dvander@3005
   213
					char *moveTo = ptr + replaceLen;
dvander@3005
   214
dvander@3005
   215
					/* First move our old data out of the way. */
dvander@3005
   216
					size_t bytesToCopy = (textLen - (browsed + searchLen)) + 1;
dvander@3005
   217
					memmove(moveTo, moveFrom, bytesToCopy);
dvander@3005
   218
dvander@3005
   219
					/* Now do our replacement. */
dvander@3005
   220
					memcpy(ptr, replace, replaceLen);
dvander@3005
   221
				}
dvander@3005
   222
			}
dvander@3005
   223
			else if (replaceLen < searchLen)
dvander@3005
   224
			{
dvander@3005
   225
				/* EXAMPLE CASE:
dvander@3005
   226
				 * Subject: AABBBCCC
dvander@3005
   227
				 * Buffer : 12 bytes
dvander@3005
   228
				 * Search : BBB
dvander@3005
   229
				 * Replace: D
dvander@3005
   230
				 * OUTPUT : AADCCC
dvander@3005
   231
				 * POSITION:   ^
dvander@3005
   232
				 */
dvander@3005
   233
				/* If the replacement does not grow the string length, we do not
dvander@3005
   234
				 * need to do any fancy checking at all.  Yay!
dvander@3005
   235
				 */
dvander@3005
   236
				char *moveFrom = ptr + searchLen;		/* Start after the search pointer */
dvander@3005
   237
				char *moveTo = ptr + replaceLen;		/* Copy to where the replacement ends */
dvander@3005
   238
dvander@3005
   239
				/* Copy our replacement in, if any */
dvander@3005
   240
				if (replaceLen)
dvander@3005
   241
				{
dvander@3005
   242
					memcpy(ptr, replace, replaceLen);
dvander@3005
   243
				}
dvander@3005
   244
dvander@3005
   245
				/* Figure out how many bytes to move down, including null terminator */
dvander@3005
   246
				size_t bytesToCopy = (textLen - (browsed + searchLen)) + 1;
dvander@3005
   247
dvander@3005
   248
				/* Move the rest of the string down */
dvander@3005
   249
				memmove(moveTo, moveFrom, bytesToCopy);
dvander@3005
   250
			}
dvander@3005
   251
			else
dvander@3005
   252
			{
dvander@3005
   253
				/* EXAMPLE CASE:
dvander@3005
   254
				 * Subject: AABBBCCC
dvander@3005
   255
				 * Buffer : 12 bytes
dvander@3005
   256
				 * Search : BBB
dvander@3005
   257
				 * Replace: DDD
dvander@3005
   258
				 * OUTPUT : AADDDCCC
dvander@3005
   259
				 * POSITION:     ^
dvander@3005
   260
				 */
dvander@3005
   261
				/* We don't have to move anything around, just do a straight copy */
dvander@3005
   262
				memcpy(ptr, replace, replaceLen);
dvander@3005
   263
			}
dvander@3005
   264
dvander@3005
   265
			return ptr + replaceLen;
dvander@3005
   266
		}
dvander@3005
   267
		ptr++;
dvander@3005
   268
		browsed++;
dvander@3005
   269
	}
dvander@3005
   270
dvander@3005
   271
	return NULL;
dvander@3005
   272
}
dvander@3005
   273
dvander@3010
   274
size_t UTIL_DecodeHexString(unsigned char *buffer, size_t maxlength, const char *hexstr)
dvander@3010
   275
{
dvander@3010
   276
	size_t written = 0;
dvander@3010
   277
	size_t length = strlen(hexstr);
dvander@3010
   278
dvander@3010
   279
	for (size_t i = 0; i < length; i++)
dvander@3010
   280
	{
dvander@3010
   281
		if (written >= maxlength)
dvander@3010
   282
			break;
dvander@3010
   283
		buffer[written++] = hexstr[i];
dvander@3010
   284
		if (hexstr[i] == '\\' && hexstr[i + 1] == 'x')
dvander@3010
   285
		{
dvander@3010
   286
			if (i + 3 >= length)
dvander@3010
   287
				continue;
dvander@3010
   288
			/* Get the hex part. */
dvander@3010
   289
			char s_byte[3];
dvander@3010
   290
			int r_byte;
dvander@3010
   291
			s_byte[0] = hexstr[i + 2];
dvander@3010
   292
			s_byte[1] = hexstr[i + 3];
dvander@3010
   293
			s_byte[2] = '\0';
dvander@3010
   294
			/* Read it as an integer */
dvander@3010
   295
			sscanf(s_byte, "%x", &r_byte);
dvander@3010
   296
			/* Save the value */
dvander@3010
   297
			buffer[written - 1] = r_byte;
dvander@3010
   298
			/* Adjust index */
dvander@3010
   299
			i += 3;
dvander@3010
   300
		}
dvander@3010
   301
	}
dvander@3010
   302
dvander@3010
   303
	return written;
dvander@3010
   304
}
dvander@3010
   305
voidedweasel@3929
   306
#define PATHSEPARATOR(c) ((c) == '\\' || (c) == '/')
voidedweasel@3929
   307
voidedweasel@3929
   308
void UTIL_StripExtension(const char *in, char *out, int outSize)
voidedweasel@3929
   309
{
voidedweasel@3929
   310
	// Find the last dot. If it's followed by a dot or a slash, then it's part of a 
voidedweasel@3929
   311
	// directory specifier like ../../somedir/./blah.
voidedweasel@3929
   312
voidedweasel@3929
   313
	// scan backward for '.'
voidedweasel@3929
   314
	int end = strlen(in) - 1;
voidedweasel@3929
   315
	while (end > 0 && in[end] != '.' && !PATHSEPARATOR(in[end]))
voidedweasel@3929
   316
	{
voidedweasel@3929
   317
		--end;
voidedweasel@3929
   318
	}
voidedweasel@3929
   319
voidedweasel@3929
   320
	if (end > 0 && !PATHSEPARATOR(in[end]) && end < outSize)
voidedweasel@3929
   321
	{
skamonkey@3930
   322
		int nChars = MIN(end, outSize-1);
voidedweasel@3929
   323
		if (out != in)
voidedweasel@3929
   324
		{
voidedweasel@3929
   325
			memcpy(out, in, nChars);
voidedweasel@3929
   326
		}
voidedweasel@3929
   327
		out[nChars] = 0;
voidedweasel@3929
   328
	}
voidedweasel@3929
   329
	else
voidedweasel@3929
   330
	{
voidedweasel@3929
   331
		// nothing found
voidedweasel@3929
   332
		if (out != in)
voidedweasel@3929
   333
		{
voidedweasel@3929
   334
			strncopy(out, in, outSize);
voidedweasel@3929
   335
		}
voidedweasel@3929
   336
	}
voidedweasel@3929
   337
}