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