1 /*
2 	OpenLieroX
3 
4 	string utilities
5 
6 	code under LGPL
7 	created 01-05-2007
8 	by Albert Zeyer and Dark Charlie
9 */
10 
11 #ifdef _MSC_VER
12 #pragma warning(disable: 4786)  // WARNING: identifier XXX was truncated to 255 characters in the debug info...
13 #endif
14 
15 #include "LieroX.h" // for tLX
16 
17 #include "StringUtils.h"
18 #include "CFont.h"
19 #include "ConfigHandler.h" // for getting color value from data/frontend/colours.cfg
20 #include "FindFile.h"
21 #include "MathLib.h"
22 #include "StringBuf.h"
23 
24 
25 #include <sstream>
26 #include <iomanip>
27 // HTML parsing library for StripHtmlTags()
28 #include <libxml/xmlmemory.h>
29 #include <libxml/HTMLparser.h>
30 #include <zlib.h>
31 
32 
33 ///////////////////
34 // Trim the leading & ending spaces from a string
TrimSpaces(std::string & szLine)35 void TrimSpaces(std::string& szLine) {
36 	size_t n = 0;
37 	std::string::iterator p;
38 	for(p = szLine.begin(); p != szLine.end(); p++, n++)
39 		if(!isspace((uchar)*p) || isgraph((uchar)*p)) break;
40 	if(n>0) szLine.erase(0,n);
41 
42 	n = 0;
43 	std::string::reverse_iterator p2;
44 	for(p2 = szLine.rbegin(); p2 != szLine.rend(); p2++, n++)
45 		if(!isspace((uchar)*p2) || isgraph((uchar)*p2)) break;
46 	if(n>0) szLine.erase(szLine.size()-n);
47 }
48 
49 
50 ///////////////////
51 // Replace a string in text, returns true, if something was replaced
replace(const std::string & text,const std::string & what,const std::string & with,std::string & result)52 bool replace(const std::string& text, const std::string& what, const std::string& with, std::string& result)
53 {
54 	result = text;
55 	return replace(result, what, with);
56 }
57 
58 ///////////////////
59 // Replace a string in text, returns result, replaces maximally max occurences
replacemax(const std::string & text,const std::string & what,const std::string & with,std::string & result,int max)60 std::string replacemax(const std::string& text, const std::string& what, const std::string& with, std::string& result, int max)
61 {
62 	result = text;
63 
64 	size_t pos = 0;
65 	size_t what_len = what.length();
66 	size_t with_len = with.length();
67 	if((pos = result.find(what, pos)) != std::string::npos) {
68 		result.replace(pos, what_len, with);
69 		pos += with_len;
70 	}
71 
72 	return result;
73 }
74 
replacemax(const std::string & text,const std::string & what,const std::string & with,int max)75 std::string replacemax(const std::string& text, const std::string& what, const std::string& with, int max) {
76 	std::string result;
77 	return replacemax(text, what, with, result, max);
78 }
79 
80 ///////////////////
81 // Replace a string in text, returns result, replaces maximally max occurences
82 // returns true, if at least one replace was made
replace(std::string & text,const std::string & what,const std::string & with)83 bool replace(std::string& text, const std::string& what, const std::string& with) {
84 	// Make sure there is something to replace
85 	if (!what.size())  {
86 		return false;
87 	}
88 
89 	bool one_repl = false;
90 	size_t pos = 0;
91 	while((pos = text.find(what, pos)) != std::string::npos) {
92 		text.replace(pos, what.length(), with);
93 		pos += with.length();
94 		one_repl = true;
95 	}
96 	return one_repl;
97 }
98 
99 // Previous function has uncomfortable API
Replace(const std::string & text,const std::string & what,const std::string & with)100 std::string Replace(const std::string & text, const std::string& what, const std::string& with)
101 {
102 	std::string tmp;
103 	std::string::size_type idx1 = 0, idx2 = 0;
104 
105 	while( ( idx2 = text.find(what, idx1) ) != std::string::npos )
106 	{
107 		tmp.append( text, idx1, idx2 - idx1 );
108 		tmp.append(with);
109 		idx1 = idx2 + what.size();
110 	}
111 	if( tmp == "" )
112 		return text;
113 	tmp.append( text, idx1, text.size() - idx1 );
114 	return tmp;
115 }
116 
117 // chrcasecmp - like strcasecomp, but for a single char
chrcasecmp(const char c1,const char c2)118 int chrcasecmp(const char c1, const char c2)
119 {
120 	return (tolower(c1) == tolower(c2));
121 }
122 
123 //////////////////
124 // Gets the string [beginning of text,searched character)
ReadUntil(const std::string & text,char until_character)125 std::string ReadUntil(const std::string& text, char until_character) {
126 	size_t pos = 0;
127 	for(std::string::const_iterator i = text.begin(); i != text.end(); i++, pos++) {
128 		if(*i == until_character)
129 			return text.substr(0, pos);
130 	}
131 	return text;
132 }
133 
ReadUntil(const std::string & text,std::string::const_iterator & it,char until_character,const std::string & alternative)134 std::string ReadUntil(const std::string& text, std::string::const_iterator& it, char until_character, const std::string& alternative) {
135 	std::string::const_iterator start = it;
136 	for(; it != text.end(); it++) {
137 		if(*it == until_character)
138 			return std::string(start, it);
139 	}
140 	return alternative;
141 }
142 
143 
ReadUntil(FILE * fp,char until_character)144 std::string	ReadUntil(FILE* fp, char until_character) {
145 	char buf[256];
146 	std::string res;
147 	res = "";
148 	size_t buf_pos = 0;
149 	while(true) {
150 		if(fread(&buf[buf_pos],1,1,fp) == 0 || buf[buf_pos] == until_character) {
151 			res.append(buf,buf_pos);
152 			break;
153 		}
154 		buf_pos++;
155 		if(buf_pos >= sizeof(buf)) {
156 			buf_pos = 0;
157 			res.append(buf,sizeof(buf));
158 		}
159 	}
160 
161 	return res;
162 }
163 
PrettyPrint(const std::string & prefix,const std::string & buf,const PrintOutFct & printOutFct,bool firstLineWithPrefix)164 bool PrettyPrint(const std::string& prefix, const std::string& buf, const PrintOutFct& printOutFct, bool firstLineWithPrefix) {
165 	std::string::const_iterator it = buf.begin();
166 	bool firstLine = true;
167 	while(true) {
168 		std::string tmp = ReadUntil(buf, it, '\n', std::string(it, buf.end()));
169 		if(it == buf.end()) {
170 			if(tmp != "") {
171 				printOutFct.print( (!firstLineWithPrefix && firstLine) ? tmp : (prefix + tmp) );
172 				return false;
173 			}
174 			return !firstLine || firstLineWithPrefix;
175 		}
176 		++it;
177 		printOutFct.print( (!firstLineWithPrefix && firstLine) ? (tmp + "\n") : (prefix + tmp + "\n") );
178 		firstLine = false;
179 	}
180 }
181 
182 
HexDump(Iterator<char>::Ref start,const PrintOutFct & printOutFct,const std::set<size_t> & marks,size_t count)183 Iterator<char>::Ref HexDump(Iterator<char>::Ref start, const PrintOutFct& printOutFct, const std::set<size_t>& marks, size_t count) {
184 	std::string tmpLeft;
185 	std::string tmpRight;
186 	unsigned int tmpChars = 0;
187 	static const unsigned int charsInLine = 16;
188 
189 	size_t c = 0;
190 	while(start->isValid() && c < count) {
191 		unsigned char ch = start->get();
192 
193 		tmpLeft += FixedWidthStr_LeftFill(hex(ch), 2, '0');
194 		tmpLeft += marks.count(c) ? "]" : marks.count(c + 1) ? "[" : " ";
195 		if(ch >= 32 && ch <= 126)
196 			tmpRight += ch;
197 		else
198 			tmpRight += '.';
199 
200 		tmpChars++;
201 		if(tmpChars == charsInLine / 2) {
202 			tmpLeft += "  ";
203 			tmpRight += " ";
204 		}
205 		if(tmpChars == charsInLine) {
206 			printOutFct.print( tmpLeft + "| " + tmpRight + "\n" );
207 			tmpChars = 0;
208 			tmpLeft = tmpRight = "";
209 		}
210 
211 		start->next();
212 		c++;
213 	}
214 
215 	tmpLeft += std::string((charsInLine - tmpChars) * 3, ' ');
216 	tmpRight += std::string((charsInLine - tmpChars), ' ');
217 	if(tmpChars < charsInLine / 2) { tmpLeft += "  "; tmpRight += " "; }
218 	printOutFct.print( tmpLeft + "| " + tmpRight + "\n" );
219 
220 	return start;
221 }
222 
223 
224 //////////////////////////
225 // Contains a definition of some common colors, used internally by StrToCol
GetColorByName(const std::string & str,bool & fail)226 Color GetColorByName(const std::string& str, bool& fail)
227 {
228 	fail = false;
229 
230 	struct ColorEntry  {
231 		std::string sName;
232 		Uint8 r, g, b, a;
233 	};
234 #define OP SDL_ALPHA_OPAQUE
235 #define TR SDL_ALPHA_TRANSPARENT
236 
237 	// TODO: more? These are taken from the HTML specification
238 	static const ColorEntry predefined[] = {
239 		{ "white", 255, 255, 255,OP },
240 		{ "black", 0, 0, 0, OP },
241 		{ "red", 255, 0, 0, OP },
242 		{ "green", 0, 127, 0, OP },
243 		{ "blue", 0, 0, 255, OP },
244 		{ "silver", 0xC0, 0xC0, 0xC0, OP },
245 		{ "gray", 127, 127, 127, OP },
246 		{ "grey", 127, 127, 127, OP },
247 		{ "purple", 127, 0, 127, OP },
248 		{ "fuchsia", 255, 0, 255, OP },
249 		{ "pink", 255, 0, 255, OP },
250 		{ "lime", 0, 255, 0, OP },
251 		{ "olive", 127, 127, 0, OP },
252 		{ "yellow", 255, 255, 0, OP },
253 		{ "navy", 0, 0, 127, OP },
254 		{ "teal", 0, 127, 127, OP },
255 		{ "aqua", 0, 255, 255, OP },
256 		{ "gold", 255, 215, 0, OP },
257 		{ "transparent", 0, 0, 0, TR }
258 	};
259 
260 	for (size_t i = 0; i < sizeof(predefined) / sizeof(ColorEntry); ++i)
261 		if (stringcaseequal(str, predefined[i].sName))
262 			return Color(predefined[i].r, predefined[i].g, predefined[i].b, predefined[i].a);
263 
264 	fail = true;
265 
266 	return Color();
267 }
268 
269 /////////////////////
270 // Helper function for HexToCol, accepts only adjusted values
HexToCol_Pure(const std::string & hex,Color & col,bool & fail)271 static void HexToCol_Pure(const std::string& hex, Color& col, bool& fail)
272 {
273 	col.r = MIN(from_string<int>(hex.substr(0,2), std::hex, fail), 255);
274 	col.g = MIN(from_string<int>(hex.substr(2,2), std::hex, fail), 255);
275 	col.b = MIN(from_string<int>(hex.substr(4,2), std::hex, fail), 255);
276 	if (hex.size() >= 8)
277 		col.a = MIN(from_string<int>(hex.substr(6,2), std::hex, fail), 255);
278 	else
279 		col.a = SDL_ALPHA_OPAQUE;
280 }
281 
282 ////////////////////////
283 // Helper function for StrToCol
HexToCol(const std::string & hex,bool & fail)284 static Color HexToCol(const std::string& hex, bool& fail)
285 {
286 	fail = false;
287 	Color res;
288 
289 	// For example FFF for white
290 	if (hex.size() == 3)  {
291 		std::string tmp;
292 		tmp += hex[0]; tmp += hex[0];
293 		tmp += hex[1]; tmp += hex[1];
294 		tmp += hex[2]; tmp += hex[2];
295 
296 		HexToCol_Pure(tmp, res, fail);
297 
298 		return res;
299 	}
300 
301 	// Same as the above but with alpha
302 	if (hex.size() == 4)  {
303 		std::string tmp;
304 		tmp += hex[0]; tmp += hex[0];
305 		tmp += hex[1]; tmp += hex[1];
306 		tmp += hex[2]; tmp += hex[2];
307 		tmp += hex[3]; tmp += hex[3];
308 
309 		HexToCol_Pure(tmp, res, fail);
310 
311 		return res;
312 	}
313 
314 	// For example FFFFFF for white
315 	if (hex.size() == 6)  {
316 		HexToCol_Pure(hex, res, fail);
317 		return res;
318 	}
319 
320 	// Same as the above but with alpha
321 	if (hex.size() >= 8)  {
322 		HexToCol_Pure(hex, res, fail);
323 		return res;
324 	}
325 
326 	fail = true;
327 	return Color();
328 }
329 
ColToHex(Color col)330 std::string ColToHex(Color col) {
331 	std::string buf;
332 	buf += FixedWidthStr_LeftFill(itoa(col.r, 16), 2, '0') +
333 	buf += FixedWidthStr_LeftFill(itoa(col.g, 16), 2, '0') +
334 	buf += FixedWidthStr_LeftFill(itoa(col.b, 16), 2, '0');
335 	if(col.a != SDL_ALPHA_OPAQUE)
336 		buf += FixedWidthStr_LeftFill(itoa(col.a, 16), 2, '0');
337 
338 	return "#" + buf;
339 }
340 
341 
342 //////////////////////
343 // Returns true if the value ends with a percent sign
is_percent(const std::string & str)344 static bool is_percent(const std::string& str)
345 {
346 	if (!str.size())
347 		return false;
348 	return (*(str.rbegin())) == '%';
349 }
350 
351 ///////////////////
352 // Returns a color defined function-like: rgba(0, 0, 0, 0)
ColFromFunc(const std::string & func,bool & fail)353 static Color ColFromFunc(const std::string& func, bool& fail)
354 {
355 	StringBuf tmp(func);
356 
357 	tmp.trimBlank();
358 	tmp.adjustBlank();
359 	std::string func_name = tmp.readUntil('(');
360 	TrimSpaces(func_name);
361 
362 	// Get the params
363 	StringBuf params = tmp.readUntil(')');
364 	std::vector<std::string> tokens = params.splitBy(',');
365 	if (tokens.size() < 3)  {
366 		fail = true;
367 		return Color();
368 	}
369 
370 	// Adjust the tokens
371 	for (std::vector<std::string>::iterator it = tokens.begin(); it != tokens.end(); it++)  {
372 		TrimSpaces(*it);
373 	}
374 
375 	// The param count is >= 3
376 	Color res;
377 	res.r = MIN(255, from_string<int>(tokens[0], fail)); if (is_percent(tokens[0])) res.r = (Uint8)MIN(255.0f, (float)res.r * 2.55f);
378 	res.g = MIN(255, from_string<int>(tokens[1], fail)); if (is_percent(tokens[1])) res.g = (Uint8)MIN(255.0f, (float)res.g * 2.55f);
379 	res.b = MIN(255, from_string<int>(tokens[2], fail)); if (is_percent(tokens[2])) res.b = (Uint8)MIN(255.0f, (float)res.b * 2.55f);
380 	if (tokens.size() >= 4 && stringcaseequal(func_name, "rgba"))  {
381 		res.a = MIN(255, from_string<int>(tokens[3], fail)); if (is_percent(tokens[3])) res.a = (Uint8)MIN(255.0f, (float)res.a * 2.55f);
382 	} else
383 		res.a = SDL_ALPHA_OPAQUE;
384 
385 	return res;
386 }
387 
ColFromSeperatedNums(const std::string & txt,bool & fail)388 static Color ColFromSeperatedNums(const std::string& txt, bool& fail) {
389 	std::vector<std::string> tokens = explode(txt, ",");
390 	if (tokens.size() < 3)  {
391 		fail = true;
392 		return Color();
393 	}
394 
395 	Color res;
396 	res.r = MIN(255, from_string<int>(tokens[0], fail)); if (is_percent(tokens[0])) res.r = (Uint8)MIN(255.0f, (float)res.r * 2.55f);
397 	res.g = MIN(255, from_string<int>(tokens[1], fail)); if (is_percent(tokens[1])) res.g = (Uint8)MIN(255.0f, (float)res.g * 2.55f);
398 	res.b = MIN(255, from_string<int>(tokens[2], fail)); if (is_percent(tokens[2])) res.b = (Uint8)MIN(255.0f, (float)res.b * 2.55f);
399 	if(tokens.size() >= 4) {
400 		res.a = MIN(255, from_string<int>(tokens[3], fail)); if (is_percent(tokens[3])) res.a = (Uint8)MIN(255.0f, (float)res.a * 2.55f);
401 	} else
402 		res.a = SDL_ALPHA_OPAQUE;
403 
404 	return res;
405 }
406 
407 //////////////////
408 // Converts a string to a colour
StrToCol(const std::string & str,bool & fail)409 Color StrToCol(const std::string& str, bool& fail) {
410 	fail = false;
411 
412 	// Create the temp and copy it there
413 	std::string temp = str;
414 	TrimSpaces(temp);
415 
416 	if(temp.size() > 0 && temp[temp.size()-1] == ';') {
417 		temp.erase(temp.size()-1);
418 		TrimSpaces(temp);
419 	}
420 
421 	// Check for a blank string
422 	if (temp.size() == 0)  {
423 		fail = true;
424 		return Color();
425 	}
426 
427 	// Is the # character present?
428 	if (temp[0] == '#')  { // str != "" here
429 		temp.erase(0,1);
430 		stringlwr(temp);
431 		return HexToCol(temp, fail);
432 	}
433 
434 	// Is the function-style present?
435 	if (temp.size() >= 4)
436 		if (stringcaseequal(temp.substr(0, 4), "rgba"))
437 			return ColFromFunc(temp, fail);
438 	if (temp.size() >= 3)
439 		if (stringcaseequal(temp.substr(0, 3), "rgb"))
440 			return ColFromFunc(temp, fail);
441 
442 	// like "r,g,b", e.g. this is used in gamescripts
443 	if(temp.find(",") != std::string::npos)
444 		return ColFromSeperatedNums(temp, fail);
445 
446 	// Check if it's a known predefined color
447 	return GetColorByName(str, fail);
448 }
449 
StrToCol(const std::string & str)450 Color StrToCol(const std::string& str)
451 {
452 	bool fail = false;
453 	return StrToCol(str, fail);
454 }
455 
stringcasecmp(const std::string & s1,const std::string & s2)456 short stringcasecmp(const std::string& s1, const std::string& s2) {
457 	std::string::const_iterator p1, p2;
458 	p1 = s1.begin();
459 	p2 = s2.begin();
460 	short dif;
461 	while(true) {
462 		if(p1 == s1.end()) {
463 			if(p2 == s2.end())
464 				return 0;
465 			// not at end of s2
466 			return -1; // s1 < s2
467 		}
468 		if(p2 == s2.end())
469 			// not at end of s1
470 			return 1; // s1 > s2
471 
472 		dif = (short)(uchar)tolower((uchar)*p1) - (short)(uchar)tolower((uchar)*p2);
473 		if(dif != 0) return dif; // dif > 0  <=>  s1 > s2
474 
475 		p1++; p2++;
476 	}
477 }
478 
stringcaseequal(const std::string & s1,const std::string & s2)479 bool stringcaseequal(const std::string& s1, const std::string& s2) {
480 	if (s1.size() != s2.size()) return false;
481 	return stringcasecmp(s1, s2) == 0;
482 }
483 
subStrEqual(const std::string & s1,const std::string s2,size_t p)484 bool subStrEqual(const std::string& s1, const std::string s2, size_t p) {
485 	if((s1.size() < p || s2.size() < p) && s1.size() != s2.size()) return false;
486 	for(size_t i = 0; i < p && i < s1.size(); i++)
487 		if(s1[i] != s2[i]) return false;
488 	return true;
489 }
490 
subStrCaseEqual(const std::string & s1,const std::string s2,size_t p)491 bool subStrCaseEqual(const std::string& s1, const std::string s2, size_t p) {
492 	if((s1.size() < p || s2.size() < p) && s1.size() != s2.size()) return false;
493 	for(size_t i = 0; i < p && i < s1.size(); i++)
494 		if(tolower(s1[i]) != tolower(s2[i])) return false;
495 	return true;
496 }
497 
maxStartingEqualStr(const std::list<std::string> & strs,bool caseSensitive)498 static size_t maxStartingEqualStr(const std::list<std::string>& strs, bool caseSensitive) {
499 	if(strs.size() == 0) return 0;
500 
501 	size_t l = 0;
502 	while(true) {
503 		int i = 0;
504 		char c = 0;
505 		for(std::list<std::string>::const_iterator it = strs.begin(); it != strs.end(); ++it, ++i) {
506 			if(it->size() <= l) return l;
507 			if(i == 0)
508 				c = (*it)[l];
509 			else {
510 				bool equal = false;
511 				if(caseSensitive) equal = (*it)[l] == c;
512 				else equal = tolower((*it)[l]) == tolower(c);
513 				if(!equal) return l;
514 			}
515 		}
516 
517 		l++;
518 	}
519 }
520 
maxStartingEqualStr(const std::list<std::string> & strs)521 size_t maxStartingEqualStr(const std::list<std::string>& strs) {
522 	return maxStartingEqualStr(strs, true);
523 }
524 
maxStartingCaseEqualStr(const std::list<std::string> & strs)525 size_t maxStartingCaseEqualStr(const std::list<std::string>& strs) {
526 	return maxStartingEqualStr(strs, false);
527 }
528 
529 
explode(const std::string & str,const std::string & delim)530 std::vector<std::string> explode(const std::string& str, const std::string& delim) {
531 	std::vector<std::string> result;
532 
533 	size_t delim_len = delim.size();
534 	std::string rest = str;
535 	size_t pos;
536 	while((pos = rest.find(delim)) != std::string::npos) {
537 		result.push_back(rest.substr(0,pos));
538 		rest.erase(0,pos+delim_len);
539 	}
540 	result.push_back(rest);
541 
542 	return result;
543 }
544 
545 // reads up to maxlen-1 chars from fp
freadstr(std::string & result,size_t maxlen,FILE * fp)546 void freadstr(std::string& result, size_t maxlen, FILE *fp) {
547 	if (!fp) return;
548 
549 	char buf[1024];
550 	size_t ret, c;
551 	result = "";
552 
553 	for(size_t len = 0; len < maxlen; len += sizeof(buf)) {
554 		c = MIN(sizeof(buf), maxlen - len);
555 		ret = fread(buf, 1, c, fp);
556 		if(ret > 0)
557 			result.append(buf, ret);
558 		if(ret < c)
559 			break;
560 	}
561 }
562 
563 
fwrite(const std::string & txt,size_t len,FILE * fp)564 size_t fwrite(const std::string& txt, size_t len, FILE* fp) {
565 	size_t len_of_txt = MIN(txt.size()+1, len-1);
566 	size_t ret = fwrite(txt.c_str(), 1, len_of_txt, fp);
567 	if(ret != len_of_txt)
568 		return ret;
569 	for(; len_of_txt < len; len_of_txt++)
570 		if(fwrite("\0", 1, 1, fp) == 0)
571 			return len_of_txt;
572 	return len;
573 }
574 
575 
findLastPathSep(const std::string & path)576 size_t findLastPathSep(const std::string& path) {
577 	size_t slash = path.rfind('\\');
578 	size_t slash2 = path.rfind('/');
579 	if(slash == std::string::npos)
580 		slash = slash2;
581 	else if(slash2 != std::string::npos)
582 		slash = MAX(slash, slash2);
583 	return slash;
584 }
585 
586 
stringlwr(std::string & txt)587 void stringlwr(std::string& txt) {
588 	for(std::string::iterator i = txt.begin(); i != txt.end(); i++)
589 		*i = tolower((uchar)*i);
590 }
591 
stringtolower(const std::string & txt)592 std::string stringtolower(const std::string& txt)
593 {
594 	std::string res;
595 	for(std::string::const_iterator i = txt.begin(); i != txt.end(); i++)
596 		res += tolower((uchar)*i);
597 	return res;
598 }
599 
600 
strincludes(const std::string & str,const std::string & what)601 bool strincludes(const std::string& str, const std::string& what) {
602 	return str.find(what) != std::string::npos;
603 }
604 
GetFileExtension(const std::string & path)605 std::string GetFileExtension(const std::string& path) {
606 	std::string filename = GetBaseFilename(path);
607 	size_t p = filename.rfind('.');
608 	if(p == std::string::npos) return "";
609 	return filename.substr(p+1);
610 }
611 
GetBaseFilename(const std::string & filename)612 std::string GetBaseFilename(const std::string& filename) {
613 	size_t p = findLastPathSep(filename);
614 	if(p == std::string::npos) return filename;
615 	return filename.substr(p+1);
616 }
617 
GetDirName(const std::string & filename)618 std::string GetDirName(const std::string& filename) {
619 	size_t p = findLastPathSep(filename);
620 	if(p == std::string::npos) return "";
621 	return filename.substr(0, p);
622 }
623 
GetBaseFilenameWithoutExt(const std::string & filename)624 std::string GetBaseFilenameWithoutExt(const std::string& filename) {
625 	std::string f = GetBaseFilename(filename);
626 	size_t p = f.rfind('.');
627 	if(p == std::string::npos) return f;
628 	return f.substr(0,p);
629 }
630 
SplitFilename(const std::string & filename,size_t numPartsFromRight)631 std::list<std::string> SplitFilename(const std::string& filename, size_t numPartsFromRight) {
632 	std::list<std::string> ret;
633 	std::string restFn = filename;
634 	while(ret.size() < numPartsFromRight) {
635 		std::string next = GetBaseFilename(restFn);
636 		ret.push_front(next);
637 		if(next.size() == restFn.size()) break;
638 		restFn.erase(restFn.size() - next.size() - 1);
639 	}
640 	return ret;
641 }
642 
643 
644 
ucfirst(std::string & text)645 void ucfirst(std::string& text)
646 {
647 	if (text == "") return;
648 
649 	text[0] = toupper(text[0]);
650 	bool wasalpha = isalpha((uchar)text[0]) != 0;
651 
652 	for (std::string::iterator it=text.begin()+1;it != text.end();it++)  {
653 		if (isalpha((uchar)*it))  {
654 			if (wasalpha)
655 				*it = tolower((uchar)*it);
656 			else
657 				*it = toupper((uchar)*it);
658 			wasalpha = true;
659 		} else {
660 			wasalpha = false;
661 		}
662 	}
663 
664 
665 }
666 
667 
668 /////////////////////////
669 // Find a substring in a string
670 // WARNING: does NOT support UTF8, use Utf8StringCaseFind instead
stringcasefind(const std::string & text,const std::string & search_for)671 size_t stringcasefind(const std::string& text, const std::string& search_for)
672 {
673 	if (text.size() == 0 || search_for.size() == 0 || search_for.size() > text.size())
674 		return std::string::npos;
675 
676 	std::string::const_iterator it1 = text.begin();
677 	std::string::const_iterator it2 = search_for.begin();
678 
679 	size_t number_of_same = 0;
680 	size_t result = 0;
681 
682 	// Go through the text
683 	while (it1 != text.end())  {
684 		char c1 = (char)tolower((uchar)*it1);
685 		char c2 = (char)tolower((uchar)*it2);
686 
687 		// The two characters are the same
688 		if (c1 == c2)  {
689 			number_of_same++;  // If number of same characters equals to the size of the substring, we've found it!
690 			if (number_of_same == search_for.size())
691 				return result - number_of_same + 1;
692 			it2++;
693 		} else {
694 			number_of_same = 0;
695 			it2 = search_for.begin();
696 		}
697 
698 		result++;
699 		it1++;
700 	}
701 
702 	return std::string::npos; // Not found
703 }
704 
705 /////////////////////////
706 // Find a substring in a string, starts searching from the end of the text
707 // WARNING: does NOT support UTF8
stringcaserfind(const std::string & text,const std::string & search_for)708 size_t stringcaserfind(const std::string& text, const std::string& search_for)
709 {
710 	// HINT: simply the above one with reverse iterators
711 
712 	if (text.size() == 0 || search_for.size() == 0 || search_for.size() > text.size())
713 		return std::string::npos;
714 
715 	std::string::const_reverse_iterator it1 = text.rbegin();
716 	std::string::const_reverse_iterator it2 = search_for.rbegin();
717 
718 	size_t number_of_same = 0;
719 	size_t result = 0;
720 
721 	// Go through the text
722 	while (it1 != text.rend())  {
723 		char c1 = (char)tolower((uchar)*it1);
724 		char c2 = (char)tolower((uchar)*it2);
725 
726 		// The two characters are the same
727 		if (c1 == c2)  {
728 			number_of_same++;  // If number of same characters equals to the size of the substring, we've found it!
729 			if (number_of_same == search_for.size())
730 				return text.size() - result - 1;
731 			it2++;
732 		} else {
733 			number_of_same = 0;
734 			it2 = search_for.rbegin();
735 		}
736 
737 		result++;
738 		it1++;
739 	}
740 
741 	return std::string::npos; // Not found
742 }
743 
744 // StripHTMLTags() copied from http://sugarmaplesoftware.com/25/strip-html-tags/
745 
charactersParsed(void * context,const xmlChar * ch,int len)746 static void charactersParsed(void* context, const xmlChar* ch, int len)
747 /*" Callback function for stringByStrippingHTML. "*/
748 {
749 	std::string* result = (std::string*) context;
750 	*result += std::string( (const char *) ch, len );
751 }
752 
753 /* GCS: custom error function to ignore errors */
xmlErrorHandlerDummy(void *,xmlErrorPtr)754 static void xmlErrorHandlerDummy(void *, xmlErrorPtr)
755 {
756 	/* ignore all errors */
757 }
758 
StripHtmlTags(const std::string & src)759 std::string StripHtmlTags( const std::string & src )
760 /*" Interpretes the receiver as HTML, removes all tags and returns the plain text. "*/
761 {
762 	if (!src.size())
763 		return "";
764 
765 	std::string str;
766 	htmlSAXHandler handler;
767 	memset(&handler, 0, sizeof(handler));
768 	handler.characters = & charactersParsed;
769 
770 	/* GCS: override structuredErrorFunc to mine so I can ignore errors */
771 	xmlSetStructuredErrorFunc(NULL, &xmlErrorHandlerDummy);
772 
773 	std::string tmp = Replace(HtmlEntityUnpairedBrackets(src), "<br>", "\n" );
774 
775 	htmlDocPtr doc = htmlSAXParseDoc( (xmlChar *) tmp.c_str(), "utf-8", &handler, &str );
776 
777 	xmlFree(doc);
778 	xmlResetLastError();
779 
780 	// Remove all "\r" and spaces at the beginning of the line
781 	// TODO: use some faster method, like str.find_first_of(" \n\r\t")
782 	std::string ret;
783 	bool lineStart = true;
784 	for( std::string::size_type f = 0; f < str.size(); f++ )
785 	{
786 		if( lineStart )
787 		{
788 			if( str[f] == ' ' || str[f] == '\t' )
789 				continue;
790 			else lineStart = false;
791 		}
792 		if( str[f] == '\n' )
793 			lineStart = true;
794 		if( str[f] != '\r' )
795 			ret += str[f];
796 	}
797 
798 	return ret;
799 }
800 
801 /////////////////
802 // Get next word from a string
GetNextWord(std::string::const_iterator it,const std::string & str)803 std::string GetNextWord(std::string::const_iterator it, const std::string& str)
804 {
805 	// Check
806 	if (str == "" || it == str.end())
807 		return "";
808 
809 	// Check
810 	if (it == str.end())
811 		return "";
812 
813 	// Get the word
814 	std::string res;
815 	while (it != str.end())  {
816 		if (isspace((uchar)*it))
817 			return res;
818 		res += *it;
819 		it++;
820 	}
821 
822 	return res;
823 }
824 
825 ////////////////////////
826 // Checks for standalone < and > and replaces them with the corresponding entities
HtmlEntityUnpairedBrackets(const std::string & txt)827 std::string HtmlEntityUnpairedBrackets(const std::string &txt)
828 {
829 	// Check
830 	if (!txt.size())
831 		return "";
832 
833 	// Get the positions of unclosed brackets
834 	bool wait_for_close = false;
835 	size_t wait_for_close_pos = 0;
836 	std::list<size_t> unpaired_pos;
837 	size_t curpos = 0;
838 	for (std::string::const_iterator it = txt.begin(); it != txt.end(); it++, curpos++)  {
839 		if (*it == '<')  {
840 			if (wait_for_close)
841 				unpaired_pos.push_back(wait_for_close_pos);
842 			wait_for_close = true;
843 			wait_for_close_pos = curpos;
844 		}
845 
846 		// One character after the < character
847 		if (wait_for_close && curpos == wait_for_close_pos + 1)  {
848 			// Make sure it's a a-z A-Z letter or a slash
849 			if (!((*it >= 'a' && *it <= 'z') || (*it >= 'A' && *it <= 'Z') || *it == '/'))  {
850 				unpaired_pos.push_back(wait_for_close_pos);
851 				wait_for_close = false;
852 				wait_for_close_pos = 0;
853 			}
854 		}
855 
856 		// Closing bracket
857 		if (wait_for_close && *it == '>')  {
858 			wait_for_close = false;
859 			wait_for_close_pos = 0;
860 		}
861 	}
862 
863 	if (wait_for_close)
864 		unpaired_pos.push_back(wait_for_close_pos);
865 
866 	// Replace the unclosed brackets with html entities
867 	std::string result;
868 	size_t startpos = 0;
869 	for (std::list<size_t>::iterator it = unpaired_pos.begin(); it != unpaired_pos.end(); it++)  {
870 		result += txt.substr(startpos, *it - startpos) + "&lt;";
871 		startpos = *it + 1;
872 	}
873 
874 	// Last chunk
875 	if (startpos < txt.size())
876 		result += txt.substr(startpos);
877 
878 	return result;
879 }
880 
881 
882 ////////////////////
883 // Helper function for AutoDetectLinks
GetLink(std::string::const_iterator & it,const std::string::const_iterator & end,size_t & pos)884 static std::string GetLink(std::string::const_iterator& it, const std::string::const_iterator& end, size_t& pos)
885 {
886 /*
887 The URI standard, RFC 2396, <http://www.ietf.org/rfc/rfc2396.txt>
888 
889 ; HTTP
890 
891 httpurl = "http://" hostport [ "/" hpath [ "?" search ]]
892 hpath = hsegment *[ "/" hsegment ]
893 hsegment = *[ uchar | ";" | ":" | "@" | "&" | "=" ]
894 search = *[ uchar | ";" | ":" | "@" | "&" | "=" ]
895 
896 lowalpha = ...
897 hialpha = ...
898 digit = ...
899 
900 alpha = lowalpha | hialpha
901 safe = "$" | "-" | "_" | "." | "+"
902 extra = "!" | "*" | "'" | "(" | ")" | ","
903 national = "{" | "}" | "|" | "\" | "^" | "~" | "[" | "]" | "`"
904 punctuation = "<" | ">" | "#" | "%" | <">
905 
906 reserved = ";" | "/" | "?" | ":" | "@" | "&" | "="
907 hex = digit | "A" | "B" | "C" | "D" | "E" | "F" | "a" | "b" | "c" | "d" | "e" | "f"
908 escape = "%" hex hex
909 
910 unreserved = alpha | digit | safe | extra
911 uchar = unreserved | escape
912 xchar = unreserved | reserved | escape
913 digits = 1*digit
914 */
915 
916 	const std::string valid_url_chars = "/" "%" "?" // reserved
917 										";" ":" "@" "&" "=" // search
918 										"$" "-" "_" "." "+" // safe
919 										"!" "*" "'" "(" ")" "," // extra
920 										"{" "}" "|" "\\" "^" "~" "[" "]" "`" // national
921 										"#" "\"";	// punctuation (part of)
922 
923 	std::string link;
924 	bool was_dot = false;
925 	bool was_ques = false;
926 	for (; it != end; it++, ++pos)  {
927 		// Breaking characters
928 		if (!isalnum((uchar)*it) && valid_url_chars.find(*it) == std::string::npos)  {
929 			if (was_ques)  {
930 				link.resize(link.size() - 1);
931 				it--;
932 				was_ques = false;
933 			}
934 			break;
935 		}
936 
937 		// Multiple question marks
938 		if (*it == '?')  {
939 			if (was_ques)  {
940 				link.resize(link.size() - 1);
941 				it--;
942 				was_ques = false;
943 				break;
944 			}
945 			was_ques = true;
946 		} else
947 			was_ques = false;
948 
949 		// Multiple dots
950 		if (*it == '.')  {
951 			if (was_dot)  {
952 				link.resize(link.size() - 1);
953 				it--;
954 				was_dot = false;
955 				break;
956 			}
957 			was_dot = true;
958 		} else
959 			was_dot = false;
960 
961 		link += *it;
962 	}
963 
964 	if ((was_ques || was_dot) && link.size())  {
965 		link.resize(link.size() - 1);
966 	}
967 
968 	TrimSpaces(link);
969 
970 	return link;
971 }
972 
973 //////////////////////////
974 // Automatically find hyperlinks in the given text and encapsulate them with <a> and </a>
AutoDetectLinks(const std::string & text)975 std::string AutoDetectLinks(const std::string& text)
976 {
977 	static const std::string prefixes[] = { "www.", "http://", "https://", "mailto:", "ftp://" };
978 
979 	std::string result;
980 	size_t pos = 0;
981 	bool in_tag = false;
982 	for (std::string::const_iterator it = text.begin(); it != text.end(); it++, pos++)  {
983 		if (*it == '<')  {
984 			in_tag = true;
985 			result += *it;
986 			continue;
987 		}
988 
989 		if (*it == '>')  {
990 			in_tag = false;
991 			result += *it;
992 			continue;
993 		}
994 
995 		// Do not search inside html tags
996 		if (in_tag)  {
997 			result += *it;
998 			continue;
999 		}
1000 
1001 		for (size_t i = 0; i < sizeof(prefixes)/sizeof(std::string); ++i)  {
1002 			if (text.size() - pos > prefixes[i].size() + 4)  {  // 4 = minimum length of the address, for example a.de
1003 				if (stringcaseequal(text.substr(pos, prefixes[i].size()), prefixes[i]))  {
1004 
1005 					// Get the link
1006 					std::string link = GetLink(it, text.end(), pos);
1007 
1008 					// Add the link
1009 					result += "<a href=\"" + link + "\">" + link + "</a>";
1010 					break;
1011 				}
1012 			}
1013 		}
1014 
1015 		if (it != text.end())
1016 			result += *it;
1017 		else
1018 			break;
1019 	}
1020 
1021 	return result;
1022 }
1023 
EscapeHtmlTags(const std::string & src)1024 std::string EscapeHtmlTags( const std::string & src )
1025 {
1026 	std::string ret ( src );
1027 
1028 	replace( ret, "&", "&amp;" );
1029 	replace( ret, "<", "&lt;" );
1030 	replace( ret, ">", "&gt;" );
1031 	replace( ret, "\"", "&quot;" );
1032 	replace( ret, "\'", "&apos;" );
1033 
1034 	return ret;
1035 }
1036 
Compress(const std::string & in,std::string * out,bool noCompression)1037 bool Compress( const std::string & in, std::string * out, bool noCompression )
1038 {
1039 	*out = ""; //out->clear();
1040 	z_stream strm;
1041 	int ret;
1042 	/* allocate deflate state */
1043 	strm.zalloc = Z_NULL;
1044 	strm.zfree = Z_NULL;
1045 	strm.opaque = Z_NULL;
1046 	int compression = Z_BEST_COMPRESSION;
1047 	if( noCompression )
1048 		compression = Z_NO_COMPRESSION;
1049 	ret = deflateInit(&strm, compression);
1050     if (ret != Z_OK)
1051 		return false;
1052 	strm.avail_in = (uint)in.size(); // TODO: possible overflow on 64-bit systems
1053 	strm.next_in = (Bytef *) in.c_str();
1054 	char buf[16384];
1055 	do{
1056 		strm.avail_out = sizeof(buf);
1057 		strm.next_out = (Bytef *) buf;
1058 		ret = deflate(&strm, Z_FINISH);
1059 		if( ret != Z_OK && ret != Z_STREAM_END )
1060 		{
1061 			//errors << "Compress() error" << endl;
1062 			deflateEnd(&strm);
1063 			return false;
1064 		};
1065 		out->append( buf, sizeof(buf) - strm.avail_out );
1066 	} while( ret != Z_STREAM_END );
1067 
1068 	deflateEnd(&strm);
1069 	return true;
1070 }
1071 
Decompress(const std::string & in,std::string * out)1072 bool Decompress( const std::string & in, std::string * out )
1073 {
1074 	*out = ""; //out->clear();
1075 	z_stream strm;
1076 	int ret;
1077 	/* allocate inflate state */
1078 	strm.zalloc = Z_NULL;
1079 	strm.zfree = Z_NULL;
1080 	strm.opaque = Z_NULL;
1081 	strm.avail_in = 0;
1082 	strm.next_in = Z_NULL;
1083 	ret = inflateInit(&strm);
1084 	if (ret != Z_OK)
1085 		return false;
1086 
1087 	strm.avail_in = (uint)in.size();  // TODO: possible overflow on 64-bit systems
1088 	strm.next_in = (Bytef *) in.c_str();
1089 	char buf[16384];
1090 	do{
1091 		strm.avail_out = sizeof(buf);
1092 		strm.next_out = (Bytef *) buf;
1093 		ret = inflate(&strm, Z_NO_FLUSH);
1094 		if( ret != Z_OK && ret != Z_STREAM_END )
1095 		{
1096 			//errors << "Decompress() error" << endl;
1097 			inflateEnd(&strm);
1098 			return false;
1099 		};
1100 		out->append( buf, sizeof(buf) - strm.avail_out );
1101 	} while( ret != Z_STREAM_END );
1102 
1103 	inflateEnd(&strm);
1104 	return true;
1105 }
1106 
StringChecksum(const std::string & data)1107 size_t StringChecksum( const std::string & data )
1108 {
1109 	return adler32(0L, (const Bytef *)data.c_str(), (uint)data.size());
1110 }
1111 
FileChecksum(const std::string & path,size_t * _checksum,size_t * _filesize)1112 bool FileChecksum( const std::string & path, size_t * _checksum, size_t * _filesize )
1113 {
1114 	FILE * ff = OpenGameFile( path, "rb" );
1115 	if( ff == NULL )
1116 		return false;
1117 	char buf[16384];
1118 	uLong checksum = adler32(0L, Z_NULL, 0);
1119 	size_t size = 0;
1120 
1121 	while( ! feof( ff ) )
1122 	{
1123 		size_t read = fread( buf, 1, sizeof(buf), ff );
1124 		checksum = adler32( checksum, (const Bytef *)buf, read);
1125 		size += read;
1126 	};
1127 	fclose( ff );
1128 
1129 	if( _checksum )
1130 		*_checksum = checksum;
1131 	if( _filesize )
1132 		*_filesize = size;
1133 	return true;
1134 }
1135 
1136 // Oops, wget is under GPL, so replaced Base64Encode() with version from CURL which is under LGPL
1137 /* ---- Base64 Encoding/Decoding Table --- */
1138 static const char table64[]=
1139   "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
1140 
decodeQuantum(const std::string & src,std::string::size_type pos)1141 static std::string decodeQuantum ( const std::string &src, std::string::size_type pos )
1142 {
1143 	unsigned int x = 0;
1144 	std::string dest ( 3, '\0' );
1145 
1146 	for ( int i = 0; i < 4 && src.size() > i + pos ; i++ ) {
1147 		const char* found = strchr ( table64, src[i+pos] );
1148 		if ( found != NULL )
1149 			x = ( x << 6 ) + ( unsigned int ) ( found - table64 );
1150 		else if ( src[i+pos] == '=' )
1151 			x = ( x << 6 );
1152 	}
1153 
1154 	dest[2] = ( char ) ( ( unsigned char ) ( x & 255 ) );
1155 	x >>= 8;
1156 	dest[1] = ( char ) ( ( unsigned char ) ( x & 255 ) );
1157 	x >>= 8;
1158 	dest[0] = ( char ) ( ( unsigned char ) ( x & 255 ) );
1159 	return dest;
1160 }
1161 
Base64Decode(const std::string & src)1162 std::string Base64Decode(const std::string &src)
1163 {
1164   size_t length = 0;
1165   int equalsTerm = 0;
1166   int i;
1167   int numQuantums;
1168   std::string::size_type srcPos = 0;
1169 
1170   while( src.size() > length && src[length] != '=' )
1171     length++;
1172   /* A maximum of two = padding characters is allowed */
1173   if(src[length] == '=') {
1174     equalsTerm++;
1175     if(src[length+equalsTerm] == '=')
1176       equalsTerm++;
1177   }
1178   numQuantums = (length + equalsTerm) / 4;
1179 
1180   /* Don't allocate a buffer if the decoded length is 0 */
1181   if(numQuantums <= 0)
1182     return "";
1183 
1184   std::string newstr;
1185 
1186   /* Decode all but the last quantum (which may not decode to a
1187   multiple of 3 bytes) */
1188   for(i = 0; i < numQuantums - 1; i++) {
1189     newstr += decodeQuantum(src.c_str(), srcPos);
1190     srcPos += 4;
1191   }
1192 
1193   std::string lastQuantum = decodeQuantum(src, srcPos);
1194   for(i = 0; i < 3 - equalsTerm; i++)
1195     newstr += lastQuantum[i];
1196 
1197   return newstr;
1198 }
1199 
Base64Encode(const std::string & indata)1200 std::string Base64Encode(const std::string &indata)
1201 {
1202   unsigned char ibuf[3];
1203   unsigned char obuf[4];
1204   int i;
1205   int inputparts;
1206   std::string::size_type inPos = 0;
1207 
1208   std::string output;
1209 
1210   while(indata.length() > inPos) {
1211     for (i = inputparts = 0; i < 3; i++) {
1212       if(indata.length() > inPos) {
1213         inputparts++;
1214         ibuf[i] = (unsigned char)indata[inPos];
1215         inPos++;
1216       }
1217       else
1218         ibuf[i] = 0;
1219     }
1220 
1221     // TODO: ugly, substitute with constants
1222     obuf[0] = (unsigned char)  ((ibuf[0] & 0xFC) >> 2);
1223     obuf[1] = (unsigned char) (((ibuf[0] & 0x03) << 4) | \
1224                                ((ibuf[1] & 0xF0) >> 4));
1225     obuf[2] = (unsigned char) (((ibuf[1] & 0x0F) << 2) | \
1226                                ((ibuf[2] & 0xC0) >> 6));
1227     obuf[3] = (unsigned char)   (ibuf[2] & 0x3F);
1228 
1229     switch(inputparts) {
1230     case 1: /* only one byte read */
1231       output += table64[obuf[0]];
1232       output += table64[obuf[1]];
1233       output += "==";
1234       break;
1235     case 2: /* two bytes read */
1236       output += table64[obuf[0]];
1237       output += table64[obuf[1]];
1238       output += table64[obuf[2]];
1239       output += "=";
1240       break;
1241     default:
1242       output += table64[obuf[0]];
1243       output += table64[obuf[1]];
1244       output += table64[obuf[2]];
1245       output += table64[obuf[3]];
1246       break;
1247     }
1248   }
1249   return output;
1250 }
1251 
1252 // Substitute space with + and all non-alphanum symbols with %XX
UrlEncode(const std::string & data)1253 std::string UrlEncode(const std::string &data)
1254 {
1255 	std::string ret;
1256 	for( size_t f=0; f<data.size(); f++ )
1257 	{
1258 		char c = data[f];
1259 		if( isalnum(c) || c == '.' || c == '-' || c == '_' )
1260 			ret += c;
1261 		else
1262 		{
1263 			std::ostringstream os;
1264 			// unsigned(c) will produce numbers like 0xFFFFFF80 for value -128, so I'm using unsigned((unsigned char)c)
1265 			os << "%" << std::hex << std::setw(2) << std::setfill('0') << unsigned((unsigned char)c);
1266 			ret += os.str();
1267 		};
1268 	};
1269 	return ret;
1270 };
1271 
strSeemsLikeChatCommand(const std::string & str)1272 bool strSeemsLikeChatCommand(const std::string& str) {
1273 	if(str.size() == 0) return false;
1274 	if(str[0] == '/') {
1275 		if(str.size() == 1) return true;
1276 		if(str[1] == '/') return false;
1277 		return true;
1278 	}
1279 	return false;
1280 }
1281 
1282 
1283 
from_string(const std::string & s,bool & fail)1284 template<> VectorD2<int> from_string< VectorD2<int> >(const std::string& s, bool& fail) {
1285 	std::string tmp = s;
1286 	TrimSpaces(tmp);
1287 	if(tmp.size() > 2 && tmp[0] == '(' && tmp[tmp.size()-1] == ')')
1288 		tmp = tmp.substr(1, tmp.size() - 2);
1289 	size_t f = tmp.find(',');
1290 	if(f == std::string::npos) { fail = true; return VectorD2<int>(); }
1291 	VectorD2<int> v;
1292 	fail = false;
1293 	v.x = from_string<int>(tmp.substr(0, f), fail);
1294 	if(fail) return VectorD2<int>();
1295 	v.y = from_string<int>(tmp.substr(f + 1), fail);
1296 	if(fail) return VectorD2<int>();
1297 	return v;
1298 }
1299