mirror of
https://github.com/ioquake/jedi-academy.git
synced 2024-11-22 20:31:26 +00:00
61687fff0c
CVE-2011-2764 The FS_CheckFilenameIsNotExecutable function in qcommon/files.c in the ioQuake3 engine 1.36 and earlier, as used in World of Padman, Smokin' Guns, OpenArena, Tremulous, and ioUrbanTerror, does not properly determine dangerous file extensions, which allows remote attackers to execute arbitrary code via a crafted third-party addon that creates a Trojan horse DLL file. CVE-2011-3012 The ioQuake3 engine, as used in World of Padman 1.2 and earlier, Tremulous 1.1.0, and ioUrbanTerror 2007-12-20, does not check for dangerous file extensions before writing to the quake3 directory, which allows remote attackers to execute arbitrary code via a crafted third-party addon that creates a Trojan horse DLL file, a different vulnerability than CVE-2011-2764. bugzilla #3695 from Tim Angus in ioquake3 svn 1405 git 2c0861c1cea44861c5ceba2dc39e601d6bc3f0af * (bug 3695) Not allowing to write file with lib extention (.dll/.so/...) (TsT <tst2006@gmail.com>) from Tim Angus in ioquake3 svn 1499 git 48d8c8876b6ec035b0bb85f4d3c47c9210c3ca30 * s/FS_FilenameIsExecutable/FS_CheckFilenameIsNotExecutable/g * Fix potential buffer under run in FS_CheckFilenameIsNotExecutable from Thilo Schulz in ioquake3 svn 2098 git c4f739b8d03ca203435744c4a96e3561863ccdfe Fix extension name comparison for DLL files from Zack Middleton in ioquake3 git 6c88bf8aeee3c1e5449682f874f91e86cb393ef4 Rename FS_CheckFilenameIsNotExecutable to ..NotImmutable from Harley Laue in ioquake3 git 1b2a6abed996b43eb108486abbda449b3d16e019 Rename FS_CheckFilenameIsNotImmutable to ..IsMutable
1477 lines
24 KiB
C
1477 lines
24 KiB
C
// Copyright (C) 1999-2000 Id Software, Inc.
|
|
//
|
|
// q_shared.c -- stateless support routines that are included in each code dll
|
|
#include "q_shared.h"
|
|
|
|
/*
|
|
-------------------------
|
|
GetIDForString
|
|
-------------------------
|
|
*/
|
|
|
|
|
|
int GetIDForString ( stringID_table_t *table, const char *string )
|
|
{
|
|
int index = 0;
|
|
|
|
while ( ( table[index].name != NULL ) &&
|
|
( table[index].name[0] != 0 ) )
|
|
{
|
|
if ( !Q_stricmp( table[index].name, string ) )
|
|
return table[index].id;
|
|
|
|
index++;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
-------------------------
|
|
GetStringForID
|
|
-------------------------
|
|
*/
|
|
|
|
const char *GetStringForID( stringID_table_t *table, int id )
|
|
{
|
|
int index = 0;
|
|
|
|
while ( ( table[index].name != NULL ) &&
|
|
( table[index].name[0] != 0 ) )
|
|
{
|
|
if ( table[index].id == id )
|
|
return table[index].name;
|
|
|
|
index++;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
int Com_Clampi( int min, int max, int value )
|
|
{
|
|
if ( value < min )
|
|
{
|
|
return min;
|
|
}
|
|
if ( value > max )
|
|
{
|
|
return max;
|
|
}
|
|
return value;
|
|
}
|
|
|
|
float Com_Clamp( float min, float max, float value ) {
|
|
if ( value < min ) {
|
|
return min;
|
|
}
|
|
if ( value > max ) {
|
|
return max;
|
|
}
|
|
return value;
|
|
}
|
|
|
|
|
|
/*
|
|
============
|
|
COM_SkipPath
|
|
============
|
|
*/
|
|
char *COM_SkipPath (char *pathname)
|
|
{
|
|
char *last;
|
|
|
|
last = pathname;
|
|
while (*pathname)
|
|
{
|
|
if (*pathname=='/')
|
|
last = pathname+1;
|
|
pathname++;
|
|
}
|
|
return last;
|
|
}
|
|
|
|
/*
|
|
============
|
|
COM_GetExtension
|
|
============
|
|
*/
|
|
const char *COM_GetExtension( const char *name )
|
|
{
|
|
const char *dot = strrchr(name, '.'), *slash;
|
|
if (dot && (!(slash = strrchr(name, '/')) || slash < dot))
|
|
return dot + 1;
|
|
else
|
|
return "";
|
|
}
|
|
|
|
|
|
/*
|
|
============
|
|
COM_StripExtension
|
|
============
|
|
*/
|
|
void COM_StripExtension( const char *in, char *out, int destsize ) {
|
|
const char *dot = strrchr(in, '.'), *slash;
|
|
|
|
if (dot && (!(slash = strrchr(in, '/')) || slash < dot))
|
|
Q_strncpyz(out, in, (destsize < dot-in+1 ? destsize : dot-in+1));
|
|
else
|
|
Q_strncpyz(out, in, destsize);
|
|
}
|
|
|
|
/*
|
|
============
|
|
COM_CompareExtension
|
|
|
|
string compare the end of the strings and return qtrue if strings match
|
|
============
|
|
*/
|
|
qboolean COM_CompareExtension(const char *in, const char *ext)
|
|
{
|
|
int inlen, extlen;
|
|
|
|
inlen = strlen(in);
|
|
extlen = strlen(ext);
|
|
|
|
if(extlen <= inlen)
|
|
{
|
|
in += inlen - extlen;
|
|
|
|
if(!Q_stricmp(in, ext))
|
|
return qtrue;
|
|
}
|
|
|
|
return qfalse;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
COM_DefaultExtension
|
|
==================
|
|
*/
|
|
void COM_DefaultExtension (char *path, int maxSize, const char *extension ) {
|
|
char oldPath[MAX_QPATH];
|
|
char *src;
|
|
|
|
//
|
|
// if path doesn't have a .EXT, append extension
|
|
// (extension should include the .)
|
|
//
|
|
src = path + strlen(path) - 1;
|
|
|
|
while (*src != '/' && src != path) {
|
|
if ( *src == '.' ) {
|
|
return; // it has an extension
|
|
}
|
|
src--;
|
|
}
|
|
|
|
Q_strncpyz( oldPath, path, sizeof( oldPath ) );
|
|
Com_sprintf( path, maxSize, "%s%s", oldPath, extension );
|
|
}
|
|
|
|
/*
|
|
============================================================================
|
|
|
|
BYTE ORDER FUNCTIONS
|
|
|
|
============================================================================
|
|
*/
|
|
/*
|
|
// can't just use function pointers, or dll linkage can
|
|
// mess up when qcommon is included in multiple places
|
|
static short (*_BigShort) (short l);
|
|
static short (*_LittleShort) (short l);
|
|
static int (*_BigLong) (int l);
|
|
static int (*_LittleLong) (int l);
|
|
static qint64 (*_BigLong64) (qint64 l);
|
|
static qint64 (*_LittleLong64) (qint64 l);
|
|
static float (*_BigFloat) (const float *l);
|
|
static float (*_LittleFloat) (const float *l);
|
|
|
|
short BigShort(short l){return _BigShort(l);}
|
|
short LittleShort(short l) {return _LittleShort(l);}
|
|
int BigLong (int l) {return _BigLong(l);}
|
|
int LittleLong (int l) {return _LittleLong(l);}
|
|
qint64 BigLong64 (qint64 l) {return _BigLong64(l);}
|
|
qint64 LittleLong64 (qint64 l) {return _LittleLong64(l);}
|
|
float BigFloat (const float *l) {return _BigFloat(l);}
|
|
float LittleFloat (const float *l) {return _LittleFloat(l);}
|
|
*/
|
|
|
|
short ShortSwap (short l)
|
|
{
|
|
byte b1,b2;
|
|
|
|
b1 = l&255;
|
|
b2 = (l>>8)&255;
|
|
|
|
return (b1<<8) + b2;
|
|
}
|
|
|
|
short ShortNoSwap (short l)
|
|
{
|
|
return l;
|
|
}
|
|
|
|
int LongSwap (int l)
|
|
{
|
|
byte b1,b2,b3,b4;
|
|
|
|
b1 = l&255;
|
|
b2 = (l>>8)&255;
|
|
b3 = (l>>16)&255;
|
|
b4 = (l>>24)&255;
|
|
|
|
return ((int)b1<<24) + ((int)b2<<16) + ((int)b3<<8) + b4;
|
|
}
|
|
|
|
int LongNoSwap (int l)
|
|
{
|
|
return l;
|
|
}
|
|
|
|
qint64 Long64Swap (qint64 ll)
|
|
{
|
|
qint64 result;
|
|
|
|
result.b0 = ll.b7;
|
|
result.b1 = ll.b6;
|
|
result.b2 = ll.b5;
|
|
result.b3 = ll.b4;
|
|
result.b4 = ll.b3;
|
|
result.b5 = ll.b2;
|
|
result.b6 = ll.b1;
|
|
result.b7 = ll.b0;
|
|
|
|
return result;
|
|
}
|
|
|
|
qint64 Long64NoSwap (qint64 ll)
|
|
{
|
|
return ll;
|
|
}
|
|
|
|
typedef union {
|
|
float f;
|
|
unsigned int i;
|
|
} _FloatByteUnion;
|
|
|
|
float FloatSwap (const float *f) {
|
|
const _FloatByteUnion *in;
|
|
_FloatByteUnion out;
|
|
|
|
in = (_FloatByteUnion *)f;
|
|
out.i = LongSwap(in->i);
|
|
|
|
return out.f;
|
|
}
|
|
|
|
float FloatNoSwap (const float *f)
|
|
{
|
|
return *f;
|
|
}
|
|
|
|
/*
|
|
================
|
|
Swap_Init
|
|
================
|
|
*/
|
|
/*
|
|
void Swap_Init (void)
|
|
{
|
|
byte swaptest[2] = {1,0};
|
|
|
|
// set the byte swapping variables in a portable manner
|
|
if ( *(short *)swaptest == 1)
|
|
{
|
|
_BigShort = ShortSwap;
|
|
_LittleShort = ShortNoSwap;
|
|
_BigLong = LongSwap;
|
|
_LittleLong = LongNoSwap;
|
|
_BigLong64 = Long64Swap;
|
|
_LittleLong64 = Long64NoSwap;
|
|
_BigFloat = FloatSwap;
|
|
_LittleFloat = FloatNoSwap;
|
|
}
|
|
else
|
|
{
|
|
_BigShort = ShortNoSwap;
|
|
_LittleShort = ShortSwap;
|
|
_BigLong = LongNoSwap;
|
|
_LittleLong = LongSwap;
|
|
_BigLong64 = Long64NoSwap;
|
|
_LittleLong64 = Long64Swap;
|
|
_BigFloat = FloatNoSwap;
|
|
_LittleFloat = FloatSwap;
|
|
}
|
|
|
|
}
|
|
*/
|
|
|
|
/*
|
|
============================================================================
|
|
|
|
PARSING
|
|
|
|
============================================================================
|
|
*/
|
|
|
|
static char com_token[MAX_TOKEN_CHARS];
|
|
static char com_parsename[MAX_TOKEN_CHARS];
|
|
static int com_lines;
|
|
|
|
void COM_BeginParseSession( const char *name )
|
|
{
|
|
com_lines = 0;
|
|
Com_sprintf(com_parsename, sizeof(com_parsename), "%s", name);
|
|
}
|
|
|
|
int COM_GetCurrentParseLine( void )
|
|
{
|
|
return com_lines;
|
|
}
|
|
|
|
char *COM_Parse( const char **data_p )
|
|
{
|
|
return COM_ParseExt( data_p, qtrue );
|
|
}
|
|
|
|
void COM_ParseError( char *format, ... )
|
|
{
|
|
va_list argptr;
|
|
static char string[4096];
|
|
|
|
va_start (argptr, format);
|
|
Q_vsnprintf (string, sizeof(string), format, argptr);
|
|
va_end (argptr);
|
|
|
|
Com_Printf("ERROR: %s, line %d: %s\n", com_parsename, com_lines, string);
|
|
}
|
|
|
|
void COM_ParseWarning( char *format, ... )
|
|
{
|
|
va_list argptr;
|
|
static char string[4096];
|
|
|
|
va_start (argptr, format);
|
|
Q_vsnprintf (string, sizeof(string), format, argptr);
|
|
va_end (argptr);
|
|
|
|
Com_Printf("WARNING: %s, line %d: %s\n", com_parsename, com_lines, string);
|
|
}
|
|
|
|
/*
|
|
==============
|
|
COM_Parse
|
|
|
|
Parse a token out of a string
|
|
Will never return NULL, just empty strings
|
|
|
|
If "allowLineBreaks" is qtrue then an empty
|
|
string will be returned if the next token is
|
|
a newline.
|
|
==============
|
|
*/
|
|
const char *SkipWhitespace( const char *data, qboolean *hasNewLines ) {
|
|
int c;
|
|
|
|
while( (c = *data) <= ' ') {
|
|
if( !c ) {
|
|
return NULL;
|
|
}
|
|
if( c == '\n' ) {
|
|
com_lines++;
|
|
*hasNewLines = qtrue;
|
|
}
|
|
data++;
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
int COM_Compress( char *data_p ) {
|
|
char *in, *out;
|
|
int c;
|
|
qboolean newline = qfalse, whitespace = qfalse;
|
|
|
|
in = out = data_p;
|
|
if (in) {
|
|
while ((c = *in) != 0) {
|
|
// skip double slash comments
|
|
if ( c == '/' && in[1] == '/' ) {
|
|
while (*in && *in != '\n') {
|
|
in++;
|
|
}
|
|
// skip /* */ comments
|
|
} else if ( c == '/' && in[1] == '*' ) {
|
|
while ( *in && ( *in != '*' || in[1] != '/' ) )
|
|
in++;
|
|
if ( *in )
|
|
in += 2;
|
|
// record when we hit a newline
|
|
} else if ( c == '\n' || c == '\r' ) {
|
|
newline = qtrue;
|
|
in++;
|
|
// record when we hit whitespace
|
|
} else if ( c == ' ' || c == '\t') {
|
|
whitespace = qtrue;
|
|
in++;
|
|
// an actual token
|
|
} else {
|
|
// if we have a pending newline, emit it (and it counts as whitespace)
|
|
if (newline) {
|
|
*out++ = '\n';
|
|
newline = qfalse;
|
|
whitespace = qfalse;
|
|
} if (whitespace) {
|
|
*out++ = ' ';
|
|
whitespace = qfalse;
|
|
}
|
|
|
|
// copy quoted strings unmolested
|
|
if (c == '"') {
|
|
*out++ = c;
|
|
in++;
|
|
while (1) {
|
|
c = *in;
|
|
if (c && c != '"') {
|
|
*out++ = c;
|
|
in++;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
if (c == '"') {
|
|
*out++ = c;
|
|
in++;
|
|
}
|
|
} else {
|
|
*out = c;
|
|
out++;
|
|
in++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
*out = 0;
|
|
return out - data_p;
|
|
}
|
|
|
|
char *COM_ParseExt( const char **data_p, qboolean allowLineBreaks )
|
|
{
|
|
int c = 0, len;
|
|
qboolean hasNewLines = qfalse;
|
|
const char *data;
|
|
|
|
data = *data_p;
|
|
len = 0;
|
|
com_token[0] = 0;
|
|
|
|
// make sure incoming data is valid
|
|
if ( !data )
|
|
{
|
|
*data_p = NULL;
|
|
return com_token;
|
|
}
|
|
|
|
while ( 1 )
|
|
{
|
|
// skip whitespace
|
|
data = SkipWhitespace( data, &hasNewLines );
|
|
if ( !data )
|
|
{
|
|
*data_p = NULL;
|
|
return com_token;
|
|
}
|
|
if ( hasNewLines && !allowLineBreaks )
|
|
{
|
|
*data_p = data;
|
|
return com_token;
|
|
}
|
|
|
|
c = *data;
|
|
|
|
// skip double slash comments
|
|
if ( c == '/' && data[1] == '/' )
|
|
{
|
|
data += 2;
|
|
while (*data && *data != '\n') {
|
|
data++;
|
|
}
|
|
}
|
|
// skip /* */ comments
|
|
else if ( c=='/' && data[1] == '*' )
|
|
{
|
|
data += 2;
|
|
while ( *data && ( *data != '*' || data[1] != '/' ) )
|
|
{
|
|
data++;
|
|
}
|
|
if ( *data )
|
|
{
|
|
data += 2;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
// handle quoted strings
|
|
if (c == '\"')
|
|
{
|
|
data++;
|
|
while (1)
|
|
{
|
|
c = *data++;
|
|
if (c=='\"' || !c)
|
|
{
|
|
com_token[len] = 0;
|
|
*data_p = ( char * ) data;
|
|
return com_token;
|
|
}
|
|
if (len < MAX_TOKEN_CHARS - 1)
|
|
{
|
|
com_token[len] = c;
|
|
len++;
|
|
}
|
|
}
|
|
}
|
|
|
|
// parse a regular word
|
|
do
|
|
{
|
|
if (len < MAX_TOKEN_CHARS - 1)
|
|
{
|
|
com_token[len] = c;
|
|
len++;
|
|
}
|
|
data++;
|
|
c = *data;
|
|
if ( c == '\n' )
|
|
com_lines++;
|
|
} while (c>32);
|
|
|
|
com_token[len] = 0;
|
|
|
|
*data_p = ( char * ) data;
|
|
return com_token;
|
|
}
|
|
|
|
|
|
#if 0
|
|
// no longer used
|
|
/*
|
|
===============
|
|
COM_ParseInfos
|
|
===============
|
|
*/
|
|
int COM_ParseInfos( char *buf, int max, char infos[][MAX_INFO_STRING] ) {
|
|
char *token;
|
|
int count;
|
|
char key[MAX_TOKEN_CHARS];
|
|
|
|
count = 0;
|
|
|
|
while ( 1 ) {
|
|
token = COM_Parse( &buf );
|
|
if ( !token[0] ) {
|
|
break;
|
|
}
|
|
if ( strcmp( token, "{" ) ) {
|
|
Com_Printf( "Missing { in info file\n" );
|
|
break;
|
|
}
|
|
|
|
if ( count == max ) {
|
|
Com_Printf( "Max infos exceeded\n" );
|
|
break;
|
|
}
|
|
|
|
infos[count][0] = 0;
|
|
while ( 1 ) {
|
|
token = COM_ParseExt( &buf, qtrue );
|
|
if ( !token[0] ) {
|
|
Com_Printf( "Unexpected end of info file\n" );
|
|
break;
|
|
}
|
|
if ( !strcmp( token, "}" ) ) {
|
|
break;
|
|
}
|
|
Q_strncpyz( key, token, sizeof( key ) );
|
|
|
|
token = COM_ParseExt( &buf, qfalse );
|
|
if ( !token[0] ) {
|
|
strcpy( token, "<NULL>" );
|
|
}
|
|
Info_SetValueForKey( infos[count], key, token );
|
|
}
|
|
count++;
|
|
}
|
|
|
|
return count;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
===============
|
|
COM_ParseString
|
|
===============
|
|
*/
|
|
qboolean COM_ParseString( const char **data, const char **s )
|
|
{
|
|
// *s = COM_ParseExt( data, qtrue );
|
|
*s = COM_ParseExt( data, qfalse );
|
|
if ( s[0] == 0 )
|
|
{
|
|
Com_Printf("unexpected EOF\n");
|
|
return qtrue;
|
|
}
|
|
return qfalse;
|
|
}
|
|
|
|
/*
|
|
===============
|
|
COM_ParseInt
|
|
===============
|
|
*/
|
|
qboolean COM_ParseInt( const char **data, int *i )
|
|
{
|
|
const char *token;
|
|
|
|
token = COM_ParseExt( data, qfalse );
|
|
if ( token[0] == 0 )
|
|
{
|
|
Com_Printf( "unexpected EOF\n" );
|
|
return qtrue;
|
|
}
|
|
|
|
*i = atoi( token );
|
|
return qfalse;
|
|
}
|
|
|
|
/*
|
|
===============
|
|
COM_ParseFloat
|
|
===============
|
|
*/
|
|
qboolean COM_ParseFloat( const char **data, float *f )
|
|
{
|
|
const char *token;
|
|
|
|
token = COM_ParseExt( data, qfalse );
|
|
if ( token[0] == 0 )
|
|
{
|
|
Com_Printf( "unexpected EOF\n" );
|
|
return qtrue;
|
|
}
|
|
|
|
*f = atof( token );
|
|
return qfalse;
|
|
}
|
|
|
|
/*
|
|
===============
|
|
COM_ParseVec4
|
|
===============
|
|
*/
|
|
qboolean COM_ParseVec4( const char **buffer, vec4_t *c)
|
|
{
|
|
int i;
|
|
float f;
|
|
|
|
for (i = 0; i < 4; i++)
|
|
{
|
|
if (COM_ParseFloat(buffer, &f))
|
|
{
|
|
return qtrue;
|
|
}
|
|
(*c)[i] = f;
|
|
}
|
|
return qfalse;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
COM_MatchToken
|
|
==================
|
|
*/
|
|
void COM_MatchToken( const char **buf_p, char *match ) {
|
|
char *token;
|
|
|
|
token = COM_Parse( buf_p );
|
|
if ( strcmp( token, match ) ) {
|
|
Com_Error( ERR_DROP, "MatchToken: %s != %s", token, match );
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
=================
|
|
SkipBracedSection
|
|
|
|
The next token should be an open brace.
|
|
Skips until a matching close brace is found.
|
|
Internal brace depths are properly skipped.
|
|
=================
|
|
*/
|
|
void SkipBracedSection (const char **program) {
|
|
char *token;
|
|
int depth;
|
|
|
|
depth = 0;
|
|
do {
|
|
token = COM_ParseExt( program, qtrue );
|
|
if( token[1] == 0 ) {
|
|
if( token[0] == '{' ) {
|
|
depth++;
|
|
}
|
|
else if( token[0] == '}' ) {
|
|
depth--;
|
|
}
|
|
}
|
|
} while( depth && *program );
|
|
}
|
|
|
|
/*
|
|
=================
|
|
SkipRestOfLine
|
|
=================
|
|
*/
|
|
void SkipRestOfLine ( const char **data ) {
|
|
const char *p;
|
|
int c;
|
|
|
|
p = *data;
|
|
while ( (c = *p++) != 0 ) {
|
|
if ( c == '\n' ) {
|
|
com_lines++;
|
|
break;
|
|
}
|
|
}
|
|
|
|
*data = p;
|
|
}
|
|
|
|
|
|
void Parse1DMatrix (const char **buf_p, int x, float *m) {
|
|
char *token;
|
|
int i;
|
|
|
|
COM_MatchToken( buf_p, "(" );
|
|
|
|
for (i = 0 ; i < x ; i++) {
|
|
token = COM_Parse(buf_p);
|
|
m[i] = atof(token);
|
|
}
|
|
|
|
COM_MatchToken( buf_p, ")" );
|
|
}
|
|
|
|
void Parse2DMatrix (const char **buf_p, int y, int x, float *m) {
|
|
int i;
|
|
|
|
COM_MatchToken( buf_p, "(" );
|
|
|
|
for (i = 0 ; i < y ; i++) {
|
|
Parse1DMatrix (buf_p, x, m + i * x);
|
|
}
|
|
|
|
COM_MatchToken( buf_p, ")" );
|
|
}
|
|
|
|
void Parse3DMatrix (const char **buf_p, int z, int y, int x, float *m) {
|
|
int i;
|
|
|
|
COM_MatchToken( buf_p, "(" );
|
|
|
|
for (i = 0 ; i < z ; i++) {
|
|
Parse2DMatrix (buf_p, y, x, m + i * x*y);
|
|
}
|
|
|
|
COM_MatchToken( buf_p, ")" );
|
|
}
|
|
|
|
/*
|
|
===================
|
|
Com_HexStrToInt
|
|
===================
|
|
*/
|
|
int Com_HexStrToInt( const char *str )
|
|
{
|
|
if ( !str || !str[ 0 ] )
|
|
return -1;
|
|
|
|
// check for hex code
|
|
if( str[ 0 ] == '0' && str[ 1 ] == 'x' )
|
|
{
|
|
int i, n = 0;
|
|
|
|
for( i = 2; i < strlen( str ); i++ )
|
|
{
|
|
char digit;
|
|
|
|
n *= 16;
|
|
|
|
digit = tolower( str[ i ] );
|
|
|
|
if( digit >= '0' && digit <= '9' )
|
|
digit -= '0';
|
|
else if( digit >= 'a' && digit <= 'f' )
|
|
digit = digit - 'a' + 10;
|
|
else
|
|
return -1;
|
|
|
|
n += digit;
|
|
}
|
|
|
|
return n;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
============================================================================
|
|
|
|
LIBRARY REPLACEMENT FUNCTIONS
|
|
|
|
============================================================================
|
|
*/
|
|
|
|
int Q_isprint( int c )
|
|
{
|
|
if ( c >= 0x20 && c <= 0x7E )
|
|
return ( 1 );
|
|
return ( 0 );
|
|
}
|
|
|
|
int Q_islower( int c )
|
|
{
|
|
if (c >= 'a' && c <= 'z')
|
|
return ( 1 );
|
|
return ( 0 );
|
|
}
|
|
|
|
int Q_isupper( int c )
|
|
{
|
|
if (c >= 'A' && c <= 'Z')
|
|
return ( 1 );
|
|
return ( 0 );
|
|
}
|
|
|
|
int Q_isalpha( int c )
|
|
{
|
|
if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'))
|
|
return ( 1 );
|
|
return ( 0 );
|
|
}
|
|
|
|
char* Q_strrchr( const char* string, int c )
|
|
{
|
|
char cc = c;
|
|
char *s;
|
|
char *sp=(char *)0;
|
|
|
|
s = (char*)string;
|
|
|
|
while (*s)
|
|
{
|
|
if (*s == cc)
|
|
sp = s;
|
|
s++;
|
|
}
|
|
if (cc == 0)
|
|
sp = s;
|
|
|
|
return sp;
|
|
}
|
|
|
|
#ifdef _MSC_VER
|
|
/*
|
|
=============
|
|
Q_vsnprintf
|
|
|
|
Special wrapper function for Microsoft's broken _vsnprintf() function.
|
|
MinGW comes with its own snprintf() which is not broken.
|
|
=============
|
|
*/
|
|
|
|
int Q_vsnprintf(char *str, size_t size, const char *format, va_list ap)
|
|
{
|
|
int retval;
|
|
|
|
retval = _vsnprintf(str, size, format, ap);
|
|
|
|
if(retval < 0 || retval == size)
|
|
{
|
|
// Microsoft doesn't adhere to the C99 standard of vsnprintf,
|
|
// which states that the return value must be the number of
|
|
// bytes written if the output string had sufficient length.
|
|
//
|
|
// Obviously we cannot determine that value from Microsoft's
|
|
// implementation, so we have no choice but to return size.
|
|
|
|
str[size - 1] = '\0';
|
|
return size;
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
=============
|
|
Q_strncpyz
|
|
|
|
Safe strncpy that ensures a trailing zero
|
|
=============
|
|
*/
|
|
void Q_strncpyz( char *dest, const char *src, int destsize ) {
|
|
// bk001129 - also NULL dest
|
|
if ( !dest ) {
|
|
Com_Error( ERR_FATAL, "Q_strncpyz: NULL dest" );
|
|
}
|
|
if ( !src ) {
|
|
Com_Error( ERR_FATAL, "Q_strncpyz: NULL src" );
|
|
}
|
|
if ( destsize < 1 ) {
|
|
Com_Error(ERR_FATAL,"Q_strncpyz: destsize < 1" );
|
|
}
|
|
|
|
strncpy( dest, src, destsize-1 );
|
|
dest[destsize-1] = 0;
|
|
}
|
|
|
|
int Q_stricmpn (const char *s1, const char *s2, int n) {
|
|
int c1, c2;
|
|
|
|
// bk001129 - moved in 1.17 fix not in id codebase
|
|
if ( s1 == NULL ) {
|
|
if ( s2 == NULL )
|
|
return 0;
|
|
else
|
|
return -1;
|
|
}
|
|
else if ( s2==NULL )
|
|
return 1;
|
|
|
|
|
|
|
|
do {
|
|
c1 = *s1++;
|
|
c2 = *s2++;
|
|
|
|
if (!n--) {
|
|
return 0; // strings are equal until end point
|
|
}
|
|
|
|
if (c1 != c2) {
|
|
if (c1 >= 'a' && c1 <= 'z') {
|
|
c1 -= ('a' - 'A');
|
|
}
|
|
if (c2 >= 'a' && c2 <= 'z') {
|
|
c2 -= ('a' - 'A');
|
|
}
|
|
if (c1 != c2) {
|
|
return c1 < c2 ? -1 : 1;
|
|
}
|
|
}
|
|
} while (c1);
|
|
|
|
return 0; // strings are equal
|
|
}
|
|
|
|
int Q_strncmp (const char *s1, const char *s2, int n) {
|
|
int c1, c2;
|
|
|
|
do {
|
|
c1 = *s1++;
|
|
c2 = *s2++;
|
|
|
|
if (!n--) {
|
|
return 0; // strings are equal until end point
|
|
}
|
|
|
|
if (c1 != c2) {
|
|
return c1 < c2 ? -1 : 1;
|
|
}
|
|
} while (c1);
|
|
|
|
return 0; // strings are equal
|
|
}
|
|
|
|
int Q_stricmp (const char *s1, const char *s2) {
|
|
return (s1 && s2) ? Q_stricmpn (s1, s2, 99999) : -1;
|
|
}
|
|
|
|
|
|
char *Q_strlwr( char *s1 ) {
|
|
char *s;
|
|
|
|
s = s1;
|
|
while ( *s ) {
|
|
*s = tolower(*s);
|
|
s++;
|
|
}
|
|
return s1;
|
|
}
|
|
|
|
char *Q_strupr( char *s1 ) {
|
|
char *s;
|
|
|
|
s = s1;
|
|
while ( *s ) {
|
|
*s = toupper(*s);
|
|
s++;
|
|
}
|
|
return s1;
|
|
}
|
|
|
|
|
|
// never goes past bounds or leaves without a terminating 0
|
|
void Q_strcat( char *dest, int size, const char *src ) {
|
|
int l1;
|
|
|
|
l1 = strlen( dest );
|
|
if ( l1 >= size ) {
|
|
Com_Error( ERR_FATAL, "Q_strcat: already overflowed" );
|
|
}
|
|
Q_strncpyz( dest + l1, src, size - l1 );
|
|
}
|
|
|
|
|
|
int Q_PrintStrlen( const char *string ) {
|
|
int len;
|
|
const char *p;
|
|
|
|
if( !string ) {
|
|
return 0;
|
|
}
|
|
|
|
len = 0;
|
|
p = string;
|
|
while( *p ) {
|
|
if( Q_IsColorString( p ) ) {
|
|
p += 2;
|
|
continue;
|
|
}
|
|
p++;
|
|
len++;
|
|
}
|
|
|
|
return len;
|
|
}
|
|
|
|
|
|
char *Q_CleanStr( char *string ) {
|
|
char* d;
|
|
char* s;
|
|
int c;
|
|
|
|
s = string;
|
|
d = string;
|
|
while ((c = *s) != 0 ) {
|
|
if ( Q_IsColorString( s ) ) {
|
|
s++;
|
|
}
|
|
else if ( c >= 0x20 && c <= 0x7E ) {
|
|
*d++ = c;
|
|
}
|
|
s++;
|
|
}
|
|
*d = '\0';
|
|
|
|
return string;
|
|
}
|
|
|
|
|
|
void QDECL Com_sprintf( char *dest, int size, const char *fmt, ...) {
|
|
int len;
|
|
va_list argptr;
|
|
char bigbuffer[32000]; // big, but small enough to fit in PPC stack
|
|
|
|
va_start (argptr,fmt);
|
|
len = Q_vsnprintf (bigbuffer,sizeof(bigbuffer),fmt,argptr);
|
|
va_end (argptr);
|
|
if ( len >= sizeof( bigbuffer ) ) {
|
|
Com_Error( ERR_FATAL, "Com_sprintf: overflowed bigbuffer" );
|
|
}
|
|
if (len >= size) {
|
|
Com_Printf ("Com_sprintf: overflow of %i in %i\n", len, size);
|
|
#ifdef _DEBUG
|
|
__asm {
|
|
int 3;
|
|
}
|
|
#endif
|
|
}
|
|
Q_strncpyz (dest, bigbuffer, size );
|
|
}
|
|
|
|
|
|
/*
|
|
============
|
|
va
|
|
|
|
does a varargs printf into a temp buffer, so I don't need to have
|
|
varargs versions of all text functions.
|
|
FIXME: make this buffer size safe someday
|
|
============
|
|
*/
|
|
char * QDECL va( const char *format, ... ) {
|
|
va_list argptr;
|
|
static char string[2][32000]; // in case va is called by nested functions
|
|
static int index = 0;
|
|
char *buf;
|
|
|
|
buf = string[index & 1];
|
|
index++;
|
|
|
|
va_start (argptr, format);
|
|
Q_vsnprintf (buf,sizeof(*string),format,argptr);
|
|
va_end (argptr);
|
|
|
|
return buf;
|
|
}
|
|
|
|
|
|
/*
|
|
=====================================================================
|
|
|
|
INFO STRINGS
|
|
|
|
=====================================================================
|
|
*/
|
|
|
|
/*
|
|
===============
|
|
Info_ValueForKey
|
|
|
|
Searches the string for the given
|
|
key and returns the associated value, or an empty string.
|
|
FIXME: overflow check?
|
|
===============
|
|
*/
|
|
char *Info_ValueForKey( const char *s, const char *key ) {
|
|
char pkey[BIG_INFO_KEY];
|
|
static char value[2][BIG_INFO_VALUE]; // use two buffers so compares
|
|
// work without stomping on each other
|
|
static int valueindex = 0;
|
|
char *o;
|
|
|
|
if ( !s || !key ) {
|
|
return "";
|
|
}
|
|
|
|
if ( strlen( s ) >= BIG_INFO_STRING ) {
|
|
Com_Error( ERR_DROP, "Info_ValueForKey: oversize infostring" );
|
|
}
|
|
|
|
valueindex ^= 1;
|
|
if (*s == '\\')
|
|
s++;
|
|
while (1)
|
|
{
|
|
o = pkey;
|
|
while (*s != '\\')
|
|
{
|
|
if (!*s)
|
|
return "";
|
|
*o++ = *s++;
|
|
}
|
|
*o = 0;
|
|
s++;
|
|
|
|
o = value[valueindex];
|
|
|
|
while (*s != '\\' && *s)
|
|
{
|
|
*o++ = *s++;
|
|
}
|
|
*o = 0;
|
|
|
|
if (!Q_stricmp (key, pkey) )
|
|
return value[valueindex];
|
|
|
|
if (!*s)
|
|
break;
|
|
s++;
|
|
}
|
|
|
|
return "";
|
|
}
|
|
|
|
|
|
/*
|
|
===================
|
|
Info_NextPair
|
|
|
|
Used to itterate through all the key/value pairs in an info string
|
|
===================
|
|
*/
|
|
void Info_NextPair( const char **head, char *key, char *value ) {
|
|
char *o;
|
|
const char *s;
|
|
|
|
s = *head;
|
|
|
|
if ( *s == '\\' ) {
|
|
s++;
|
|
}
|
|
key[0] = 0;
|
|
value[0] = 0;
|
|
|
|
o = key;
|
|
while ( *s != '\\' ) {
|
|
if ( !*s ) {
|
|
*o = 0;
|
|
*head = s;
|
|
return;
|
|
}
|
|
*o++ = *s++;
|
|
}
|
|
*o = 0;
|
|
s++;
|
|
|
|
o = value;
|
|
while ( *s != '\\' && *s ) {
|
|
*o++ = *s++;
|
|
}
|
|
*o = 0;
|
|
|
|
*head = s;
|
|
}
|
|
|
|
|
|
/*
|
|
===================
|
|
Info_RemoveKey
|
|
===================
|
|
*/
|
|
void Info_RemoveKey( char *s, const char *key ) {
|
|
char *start;
|
|
char pkey[MAX_INFO_KEY];
|
|
char value[MAX_INFO_VALUE];
|
|
char *o;
|
|
|
|
if ( strlen( s ) >= MAX_INFO_STRING ) {
|
|
Com_Error( ERR_DROP, "Info_RemoveKey: oversize infostring" );
|
|
}
|
|
|
|
if (strchr (key, '\\')) {
|
|
return;
|
|
}
|
|
|
|
while (1)
|
|
{
|
|
start = s;
|
|
if (*s == '\\')
|
|
s++;
|
|
o = pkey;
|
|
while (*s != '\\')
|
|
{
|
|
if (!*s)
|
|
return;
|
|
*o++ = *s++;
|
|
}
|
|
*o = 0;
|
|
s++;
|
|
|
|
o = value;
|
|
while (*s != '\\' && *s)
|
|
{
|
|
if (!*s)
|
|
return;
|
|
*o++ = *s++;
|
|
}
|
|
*o = 0;
|
|
|
|
if (!strcmp (key, pkey) )
|
|
{
|
|
strcpy (start, s); // remove this part
|
|
return;
|
|
}
|
|
|
|
if (!*s)
|
|
return;
|
|
}
|
|
|
|
}
|
|
|
|
/*
|
|
===================
|
|
Info_RemoveKey_Big
|
|
===================
|
|
*/
|
|
void Info_RemoveKey_Big( char *s, const char *key ) {
|
|
char *start;
|
|
char pkey[BIG_INFO_KEY];
|
|
char value[BIG_INFO_VALUE];
|
|
char *o;
|
|
|
|
if ( strlen( s ) >= BIG_INFO_STRING ) {
|
|
Com_Error( ERR_DROP, "Info_RemoveKey_Big: oversize infostring" );
|
|
}
|
|
|
|
if (strchr (key, '\\')) {
|
|
return;
|
|
}
|
|
|
|
while (1)
|
|
{
|
|
start = s;
|
|
if (*s == '\\')
|
|
s++;
|
|
o = pkey;
|
|
while (*s != '\\')
|
|
{
|
|
if (!*s)
|
|
return;
|
|
*o++ = *s++;
|
|
}
|
|
*o = 0;
|
|
s++;
|
|
|
|
o = value;
|
|
while (*s != '\\' && *s)
|
|
{
|
|
if (!*s)
|
|
return;
|
|
*o++ = *s++;
|
|
}
|
|
*o = 0;
|
|
|
|
if (!strcmp (key, pkey) )
|
|
{
|
|
strcpy (start, s); // remove this part
|
|
return;
|
|
}
|
|
|
|
if (!*s)
|
|
return;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
==================
|
|
Info_Validate
|
|
|
|
Some characters are illegal in info strings because they
|
|
can mess up the server's parsing
|
|
==================
|
|
*/
|
|
qboolean Info_Validate( const char *s ) {
|
|
if ( strchr( s, '\"' ) ) {
|
|
return qfalse;
|
|
}
|
|
if ( strchr( s, ';' ) ) {
|
|
return qfalse;
|
|
}
|
|
return qtrue;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
Info_SetValueForKey
|
|
|
|
Changes or adds a key/value pair
|
|
==================
|
|
*/
|
|
void Info_SetValueForKey( char *s, const char *key, const char *value ) {
|
|
char newi[MAX_INFO_STRING];
|
|
|
|
if ( strlen( s ) >= MAX_INFO_STRING ) {
|
|
Com_Error( ERR_DROP, "Info_SetValueForKey: oversize infostring" );
|
|
}
|
|
|
|
if (strchr (key, '\\') || strchr (value, '\\'))
|
|
{
|
|
Com_Printf ("Can't use keys or values with a \\\n");
|
|
return;
|
|
}
|
|
|
|
if (strchr (key, ';') || strchr (value, ';'))
|
|
{
|
|
Com_Printf ("Can't use keys or values with a semicolon\n");
|
|
return;
|
|
}
|
|
|
|
if (strchr (key, '\"') || strchr (value, '\"'))
|
|
{
|
|
Com_Printf ("Can't use keys or values with a \"\n");
|
|
return;
|
|
}
|
|
|
|
Info_RemoveKey (s, key);
|
|
if (!value || !strlen(value))
|
|
return;
|
|
|
|
Com_sprintf (newi, sizeof(newi), "\\%s\\%s", key, value);
|
|
|
|
if (strlen(newi) + strlen(s) >= MAX_INFO_STRING)
|
|
{
|
|
Com_Printf ("Info string length exceeded\n");
|
|
return;
|
|
}
|
|
|
|
strcat (newi, s);
|
|
strcpy (s, newi);
|
|
}
|
|
|
|
/*
|
|
==================
|
|
Info_SetValueForKey_Big
|
|
|
|
Changes or adds a key/value pair
|
|
==================
|
|
*/
|
|
void Info_SetValueForKey_Big( char *s, const char *key, const char *value ) {
|
|
char newi[BIG_INFO_STRING];
|
|
|
|
if ( strlen( s ) >= BIG_INFO_STRING ) {
|
|
Com_Error( ERR_DROP, "Info_SetValueForKey: oversize infostring" );
|
|
}
|
|
|
|
if (strchr (key, '\\') || strchr (value, '\\'))
|
|
{
|
|
Com_Printf ("Can't use keys or values with a \\\n");
|
|
return;
|
|
}
|
|
|
|
if (strchr (key, ';') || strchr (value, ';'))
|
|
{
|
|
Com_Printf ("Can't use keys or values with a semicolon\n");
|
|
return;
|
|
}
|
|
|
|
if (strchr (key, '\"') || strchr (value, '\"'))
|
|
{
|
|
Com_Printf ("Can't use keys or values with a \"\n");
|
|
return;
|
|
}
|
|
|
|
Info_RemoveKey_Big (s, key);
|
|
if (!value || !strlen(value))
|
|
return;
|
|
|
|
Com_sprintf (newi, sizeof(newi), "\\%s\\%s", key, value);
|
|
|
|
if (strlen(newi) + strlen(s) >= BIG_INFO_STRING)
|
|
{
|
|
Com_Printf ("BIG Info string length exceeded\n");
|
|
return;
|
|
}
|
|
|
|
strcat (s, newi);
|
|
}
|
|
|
|
//====================================================================
|
|
|
|
|