// $Id: string2.cc,v 1.12.2.2 2000/03/10 04:11:58 greear Exp $ // $Revision: 1.12.2.2 $ $Author: greear $ $Date: 2000/03/10 04:11:58 $ // //ScryMUD Server Code //Copyright (C) 1998 Ben Greear // //This program is free software; you can redistribute it and/or //modify it under the terms of the GNU General Public License //as published by the Free Software Foundation; either version 2 //of the License, or (at your option) any later version. // //This program is distributed in the hope that it will be useful, //but WITHOUT ANY WARRANTY; without even the implied warranty of //MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //GNU General Public License for more details. // //You should have received a copy of the GNU General Public License //along with this program; if not, write to the Free Software //Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. // // To contact the Author, Ben Greear: greear@cyberhighway.net, (preferred) // greearb@agcs.com // ///********************** string2.cc ***************************/// /* This is a fairly poweful string class, complete with socket IO based on TCP/IP protocol (read() write()) */ #include "string2.h" #include <stdarg.h> #include <errno.h> #include <signal.h> #include <string.h> extern int core_dump(const char* msg); //misc2.cc /* class functions */ LogStream* String::logfile = NULL; int String::string_cnt = 0; int String::total_bytes = 0; // return a value that should be pretty unique. // This is basically lame, do some research if you want a real // hash function!! --BLG unsigned int String::hash() { unsigned int retval = 0; for (int i = 0; i<cur_len; i++) { retval += (int)(string[i]); } retval += cur_len; return retval; } int String::Assert(int boolean_val, const char* msg) const { if (!boolean_val) { core_dump(msg); } return TRUE; } void String::setLogFile(LogStream* dafile) { logfile = dafile; } LogStream* String::getLogFile() { return logfile; } String String::sterilizeForHtml() const { String retval(Strlen() + 20); char ch; for (int i = 0; i<cur_len; i++) { ch = string[i]; if (ch == '<') { retval += "<"; } else if (ch == '>') { retval += ">"; } else if (ch == '&') { retval += "&"; } else { retval += ch; } }//for return retval; }//sterilizeForHtml void String::Strip() { int i; if (cur_len == 0) return; for (i = 0; i<cur_len; i++) { if (!isspace(string[i])) break; } if (i == cur_len) { Clear(); return; } int end; for (end = cur_len - 1; end >= i; end--) { if (!isspace(string[end])) break; }//for if (end <= i) { Clear(); return; } char buf[cur_len + 1]; memcpy(buf, string + i, (end - i) + 1); buf[(end - i) + 1] = 0; *this = buf; }//Strip //drop a few off the end of the string void String::dropFromEnd(int num_chars) { if (cur_len > num_chars) { string[cur_len - num_chars] = 0; cur_len -= num_chars; } else { *this = ""; } } /** Starts inserting in the string[i+1] position. */ void String::insertAfter(int i, const char* str) { if (!str || (i < 0)) return; if (i > cur_len) { this->Append(str); return; } ensureCapacity(cur_len + strlen(str)); String tmp(string + i+1); strcpy(string + i+1, str); cur_len = strlen(string); this->Append(tmp); }//insertAfter void String::ensureCapacity(int max_length) { //LOGFILE << "In ensure Capacity... max_length: " // << max_length << " max_len: " << max_len // << endl << flush; if (max_len < max_length) { char* tmp = string; string = new char[max_length + max_len + 1]; total_bytes += max_length; //keep our static statistics max_len += max_length; //cur_len remains the same here strcpy(string, tmp); delete[] tmp; }//if //LOGFILE << "In ensure Capacity... done" << endl // << flush; }//ensureCapacity //grabs everything untill that character is found. It removes all that // from the string, so successive calls iterate through it... // Returned pointer is to a String allocated off of the Heap..ie delete it // eventually if that makes sense. String* String::getUntil(char dlm) { String* retval = new String(cur_len); int i; int found_dlm = FALSE; char ch; for (i = 0; i<cur_len; i++) { ch = *(string + i); if (ch == dlm) { found_dlm = TRUE; break; } else retval->Append(ch); }//for //now, move the string over if ((!found_dlm) || (i == cur_len)) { Clear(); } else { String tmp((char*)(string + i + 1)); *this = tmp; } if (retval->Strlen() > 0) return retval; else { delete retval; return NULL; } }//getUntil // After is supposed to be a boolean telling us to put it before // or after... void String::parse_show(short after) { String tmp(cur_len + 100); //doubt more that 100 newlines int i; char chr; for (i = 0; i<cur_len; i++) { if ((chr = string[i]) == '\n') { if (after) tmp.Append("\n\r"); else tmp.Append("\r\n"); continue; }//if tmp.Append(chr); }//for *this = tmp; }//parse_show /* these inserts don't work right */ void String::InsertBefore(char a, int posn) { ensureCapacity(cur_len + 1); int j = posn; char tmp = a; char tmp2; while (TRUE) { tmp2 = string[j]; string[j] = tmp; if (!tmp) { break; }//if tmp = tmp2; j++; }//while cur_len++; }//InsertBefore void String::InsertAfter(char a, int posn) { ensureCapacity(cur_len + 1); int j = posn + 1; char tmp = a; char tmp2; while (TRUE) { tmp2 = string[j]; string[j] = tmp; if (!tmp) { break; }//if tmp = tmp2; j++; }//while cur_len++; }//InsertAfter String::String() { //default constructor //log("In default const.\n"); string_cnt++; string = new char[2]; string[0] = '\0'; cur_len = 0; max_len = 1; total_bytes += 2; } // constructor String::String(const char* S) { //log("In char* const, here is S: "); string_cnt++; if (S) { int i = strlen(S); string = new char[i + 1]; cur_len = i; max_len = i; strcpy(string, S); }//if else { string = new char[2]; string[0] = '\0'; cur_len = 0; max_len = 1; }//else total_bytes += (max_len + 1); } // constructor String::String(const String& S) { //log("In String const, here is source:"); string_cnt++; string = new char[S.cur_len + 1]; strcpy(string, S.string); cur_len = S.cur_len; max_len = S.cur_len; total_bytes += (max_len + 1); } // constructor String::String(const int my_len) { int m_len = my_len; string_cnt++; if (m_len > 100000) { LOGFILE << "WARNING: making string of size 100000" << endl; m_len = 100000; } if (m_len > 1) { string = new char[m_len + 1]; cur_len = 0; max_len = m_len; string[0] = '\0'; }//if else { string = new char[2]; cur_len = 0; max_len = 1; string[0] = '\0'; }//else total_bytes += (max_len + 1); } // constructor String::~String() { string_cnt--; total_bytes -= (max_len + 1); delete[] string; string = NULL; } //destructor void String::Report() const { char buf[strlen(string) + 100]; sprintf(buf, "cur_len: %i, max_len: %i, string: %s", cur_len, max_len, string); LOGFILE << "Report of String -:" << buf << ":-" << endl; }//Report String String::Look_Command(short is_first = 0) const { String temp(100); int i = 0; char a; //LOGFILE << "In look_command -:" << *this << ":-" << endl; if (max_len == 0) { //trivial case... return temp; }//if a = string[i]; while ((isspace(a)) || (a == '.')) { i++; a = string[i]; }//while int in_quotes = FALSE; if (a == '\'') { if (is_first) { temp = '\''; return temp; } if (string[i+1] == '\'') { temp.Append(a); i += 2; a = string[i]; }//if else { in_quotes = TRUE; i++; a = string[i]; }//else }//if while (TRUE) { if (!a || a == '\n') break; //done if (in_quotes) { if (a == '\'') { if (string[i+1] == '\'') { //escaped the quote temp.Append(a); }//if else { break; //all done }//else }//if }//if else { if (isspace(a) || (a == '.')) { //then all done break; }//if else { temp.Append(a); }//else }//else i++; a = string[i]; }//while return temp; }//look_command String String::Get_Command(short& eos, short& term_by_period, short is_first = 0) { String temp(100); int i = 0, k = 0; eos = term_by_period = FALSE; char a; String buf(300); //LOGFILE << "In Get_command -:" << *this << ":-" << endl; a = string[i]; while ((isspace(a)) || (a == '.')) { i++; a = string[i]; }//while if (a) { //wasn't all spaces //LOGFILE << "a -:" << a << ":- i:" << i << endl; if (is_first && (a == '\'')) { temp = '\''; i++; //move to the next one } else { if ((a == '\'') && (string[i+1] != '\'')) { // in quotes //LOGFILE << "In quotes." << endl; i++; a = string[i]; while (a && (a != '\n')) { if (a == '\'') { if (string[i+1] == '\'') { //then escape it. temp += a; i += 2; }//if else { break; } }//if else { temp += a; i++; }//else a = string[i]; }//while if (a == '\'') { //closing quote i++; //move past it }//if }//if else { if ((a == '\'') && (string[i+1] == '\'')) { temp.Append(a); i += 2; a = string[i]; }//if while (!(isspace(a) || a == '.' || !a)) { temp += a; i++; a = string[i]; }//while if (a == '.') term_by_period = TRUE; }//else }//else }//if else { string[0] = '\0'; //null it out, was empty.. I think :) cur_len = 0; eos = TRUE; return temp; //it was only spaces i spose }//else /* This gives a very detailed look at the string in question..for debugin for (int ss = 0; string[ss]; ss++) { buf.Append(ss); buf += "\t-=|"; buf += string[ss]; buf += "|=-\n"; }//for log(buf); buf = "Here is i: "; buf.Append(i); log(buf); buf = "Here is cur_len: "; buf.Append(cur_len); log(buf); */ if (string[i] == '\n') { //log("String[i] was a newline.\n"); eos = TRUE; i++; //to get past newline char }//if k = 0; while (i < cur_len) { string[k] = string[i]; i++; k++; }//while string[k] = '\0'; cur_len = k; /* more debugging stuff log("After adjustment...\n"); buf = '\0'; for (int ss = 0; string[ss]; ss++) { buf.Append(ss); buf += "\t-=|"; buf += string[ss]; buf += "|=-\n"; }//for log(buf); */ //log("Here is string returned by get_command:"); //log(temp); return temp; }//get_command String String::Get_Rest(short destruct = TRUE) { String temp(300); int i = 0, k = 0; char a; //log("In get_rest\n"); if (string[0] == ' ') i++; //junk leading space while (((a = string[i]) != '\n') && (a)) { temp += a; i++; }//while if (a == '\n') i++; //advance past newline if (destruct) { while (i < cur_len) { string[k] = string[i]; i++; k++; }//while string[k] = '\0'; cur_len = k; }//if should destruct return temp; }//get_rest /* This function is book keeping for when you change the string with the [] operator. This is a bit messy, but I need the power, and hopefully, this here will allow some security as well, if used correctly. */ void String::Refigure_strlen() { cur_len = strlen(string); if (cur_len > max_len) { LOGFILE.log(ERROR, "ERROR: cur_len is > max_len, end of string was\n"); //log("over-written. Good luck, you'll need it!\n"); cur_len = max_len; string[cur_len] = '\0'; //try to salvage the situation.. }//if }//Refigure_strlen int String::Contains(const char ch) const { int retval = 0; for (int i = 0; i<cur_len; i++) { if (string[i] == ch) { retval++; } }//for return retval; } /** Reads untill it finds the delim, and then reads untill * it finds another. Escape the delim with a \ (backslash). * This is not too efficient, as it reads one character at * a time, btw. */ int String::readToken(char delim, ifstream& dafile, int include_delim) { char ch; int is_escaped = FALSE; Vclear(); // Get to the beginning while (dafile) { dafile.get(ch); if (ch == '\\') { if (is_escaped) { is_escaped = FALSE; } else { is_escaped = TRUE; } } else if (ch == delim) { if (is_escaped) { is_escaped = FALSE; } else { break; } } else { is_escaped = FALSE; } }//while if (!dafile) return -1; if (include_delim) *this += delim; while (dafile) { dafile.get(ch); if (ch == '\\') { if (is_escaped) { is_escaped = FALSE; *this += ch; } else { is_escaped = TRUE; } } else if (ch == delim) { if (is_escaped) { is_escaped = FALSE; *this += ch; } else { break; } } else { // Only escape the delimiter at this time. if (is_escaped) { *this += '\\'; } *this += ch; is_escaped = FALSE; } }//while if (!dafile) return -1; if (include_delim) *this += delim; return 0; }//readToken void String::Termed_Read(ifstream& da_file) { //reads lines untill it finds // a line containing only "~" char buf[182]; short test; *this = ""; if (!da_file) { LOGFILE.log(WRN, "WARNING: String::Termed_Read, da_file is null\n"); return; } da_file.getline(buf, 180); if (LOGFILE.ofLevel(DB)) { LOGFILE << "String::Termed_Read, buf -:" << buf << ":-" << endl; } test = strcmp(buf, "~"); while (test) { if (!da_file) { LOGFILE.log(WRN, "WARNING: String::Termed_Read, in while da_file null\n"); break; }//if (*this) += buf; da_file.getline(buf, 180); if (LOGFILE.ofLevel(DB)) { LOGFILE << "String::Termed_Read, in while buf -:" << buf << ":-" << endl; } if (strcmp(buf, "~") != 0) { (*this) += "\n"; }//while else { test = FALSE; }//else }//while }//Termed_Read void String::Tolower() { for (int i = 0; i < cur_len; i++) { string[i] = tolower(string[i]); }//for }//Tolower void String::toUpper() { for (int i = 0; i < cur_len; i++) { string[i] = toupper(string[i]); }//for }//Tolower int String::Strlen() const { return cur_len; }//Strlen int String::Write(const int desc, const int max_to_write) { int sofar = 0, this_round; String buf; if (desc == -1) { //cleans up an error message or two. return -1; }//if Assert((max_to_write <= cur_len), "ERROR: trying to write more than exists...cya!\n"); do { //Sprintf(buf, "In Write, desc: %i\n", desc); //log(buf); this_round = write(desc, (string + sofar), max_to_write - sofar); if (this_round < 0) { //some error happened perror("String.Write() err"); LOGFILE.log(ERROR, "ERROR: String.Write() err.\n"); string[0] = '\0'; cur_len = 0; return -1; }//if else if (this_round == 0) { string[0] = '\0'; cur_len = 0; return sofar; }//if else if ((sofar += this_round) >= max_to_write) { if (max_to_write == cur_len) { //wrote it all string[0] = '\0'; cur_len = 0; } else { // These can overlap, the +1 is for the null terminator. memmove(string, string + sofar, (cur_len - sofar + 1)); cur_len = strlen(string); }//else return sofar; }//if } while (TRUE); }//Write (to a descriptor) int String::Read(const int desc, const int max_to_read) { int begin = 0; int sofar = 0; int this_round = 0; int i, j; String tmp(100); //Sprintf(tmp, "begin: %i, sofar: %i, input now: %S", // begin, sofar, this); //log(tmp); ensureCapacity(max_to_read); begin = Strlen(); //log("Beginning of Read do, here is input so far:"); //log(*this); do { this_round = read(desc, (string + sofar + begin), max_to_read - (begin + sofar) - 1); if (this_round > 0) { sofar += this_round; }//if else { if (this_round < 0) { //some error happened if (errno != EAGAIN) { //== EWOULDBLOCK //perror("String.Read() err, not EAGAIn"); string[sofar] = '\0'; cur_len = sofar; //log("ERROR: String::Read exitted on error.\n"); //log(*this); return -1; }//if else //this means it read all in buffer break; }//if else { //log("ERROR: EOF encountered on socket read.\n"); string[sofar] = '\0'; cur_len = sofar; return -1; }//else }//else } while (TRUE); //*(string + begin + sofar -1) != '\n'); //newline *(string + begin + sofar) = 0; cur_len = begin + sofar; /* This is for debugging tmp = '\0'; for (int ss = 0; string[ss]; ss++) { tmp.Append(ss); tmp += "\t-=|"; tmp += string[ss]; tmp += "|=-\n"; }//for log(tmp); */ /* get rid of DOS's carriage return */ j = begin; char a; for (i = begin; (a = string[i]); i++) { if (a == 8) { //deal with backspace if (j > begin) j--; }//if else if (a == ';') { if (string[i+1] == ';') { //should escape it string[j] = ';'; j++; i++; //move past second semicolon }//if else { string[j] = '\n'; j++; }//else }//if should deal with semicolon else if (a != 13) { //delete DOS's CR string[j] = a; j++; }//if }//for string[j] = 0; cur_len = j; char chr; for (i = begin; ((chr = string[i]) != '\n'); i++) { if (!chr) { return 0; //do nothing..has no newline in it }//if }//for return 1; }//Read (from a descriptor) void String::purge() { total_bytes -= (max_len + 1); delete[] string; string = new char[2]; cur_len = 0; max_len = 1; total_bytes += (max_len + 1); string[0] = '\0'; }//Clear void String::Clear() { *this = "\0"; }//Clear void String::Cap() { for (int i = 0; i<cur_len; i++) { if (isalpha(string[i])) { string[i] = toupper(string[i]); return; }//if }//for }//Cap void String::Getline(istream& stream, int max_len) { char buf[max_len + 1]; stream.getline(buf, max_len); *this = buf; }//Getline void String::Append(const char* source) { if (!source) return; int k = strlen(source); ensureCapacity(cur_len + k); /* string + cur_len suggested by Nevyn. Good idea, should * make it faster. --Ben */ strcat(string + cur_len, source); cur_len += k; }//Append void String::Append(const int i) { char source[25]; sprintf(source, "%i", i); this->Append(source); }//Append void String::appendHex(const long i) { char source[25]; sprintf(source, "%lx", i); this->Append(source); }//Append void String::Append(const char c) { char buf[2]; sprintf(buf, "%c", c); this->Append(buf); }//Append void String::Append(const String& S) { ensureCapacity(cur_len + S.cur_len); strcat(string, S.string); cur_len += S.cur_len; }//Append void String::Prepend(const int i) { char source[25]; sprintf(source, "%i", i); this->Prepend(source); }//Prepend void String::Prepend(const long i) { char source[25]; sprintf(source, "%d", (int)(i)); this->Prepend(source); }//Prepend void String::Prepend(const char* source) { String tmp(*this); //keep a copy handy *this = source; this->Append(tmp); }//Prepend void String::Prepend(const String& source) { this->Prepend(source.string); }//Prepend ///********************* OPERATORS ******************************/// String::operator const char*() const { return string; }//to char* operator overload String& String::operator+= (const String &S) { this->Append(S); return *this; }//+= operator String& String::operator+= (const char s) { this->Append(s); return *this; }//+= operator String& String::operator+= (const char* S) { this->Append(S); return *this; }//+= operator String& String::operator+= (const int i) { this->Append(i); return *this; } String& String::operator+= (const long l) { this->Append((int)l); //bah, same thing in a real computer return *this; } String& String::operator= (const int i) { Clear(); this->Append(i); return *this; } String& String::operator= (const long l) { Clear(); Append((int)l); //same thing in a real computer, for now at least return *this; } String& String::operator= (const char c) { Clear(); Append(c); return *this; } String& String::operator= (const String &S) { ensureCapacity(S.cur_len); strcpy(string, S.string); cur_len = S.cur_len; return *this; }//assignment operator String& String::operator= (const char* S) { if (S == NULL) { Clear(); return *this; }//if int k = strlen(S); ensureCapacity(k); strcpy(string, S); cur_len = k; return *this; }//assignment operator char String::charAt(int i) const { Assert(((i >= 0) && (i <= max_len)), "ERROR: Index out of range.\n"); return string[i]; } void String::setCharAt(int idx, const char val) { Assert(((idx >= 0) && (idx < cur_len)), "ERROR: Index out of range.\n"); Assert(val, "ERROR: setCharAt: trying to set a NULL value.\n"); string[idx] = val; } char String::operator[] (const unsigned int i) const { Assert(((int)(i) < max_len), "ERROR: index to high, String::operator[]\n"); return string[i]; }//[] operator String String::operator+ (const char* S) const { String tmp(*this); tmp.Append(S); return tmp; }//assignment operator String String::operator+ (const String& S) const { String tmp(*this); tmp.Append(S); return tmp; }//assignment operator /* equality operators, case insensitive */ int String::operator!= (const char* S) const { return strcasecmp(string, S); }//assignment operator int String::operator== (const char* S) const { return (!(strcasecmp(string, S))); }//assignment operator int String::operator< (const char* S) const { return (strcasecmp(string, S) < 0); }//assignment operator int String::operator> (const char* S) const { return (strcasecmp(string, S) > 0); }//assignment operator int String::operator> (const String& S) const { return (strcasecmp(string, S.string) > 0); }//assignment operator int String::operator< (const String& S) const { return (strcasecmp(string, S.string) < 0); }//assignment operator int String::operator<= (const char* S) const { return (strcasecmp(string, S) <= 0); }//assignment operator int String::operator>= (const char* S) const { return (strcasecmp(string, S) >= 0); }//assignment operator /* friend functions */ void Sprintf(String& targ, const char* string, ... ) { va_list ap; va_start(ap, string); vSprintf(targ, string, ap); va_end(ap); return; } void vSprintf(String& targ, const char* string, va_list ap) { int i = 0; int num = 0; String buf(100); int s_len; char* tmp_chr_str; String* tmp_str; targ = "\0"; //initialize targ for (i = 0; string[i]; ) { if (string[i] == '%') { i++; if (string[i] != '%') { switch (string[i]) { case 'i': /* int */ targ.Append(va_arg(ap, int)); break; case 'I': /* ignore */ (void)va_arg(ap, void*); // Do nothing with it!! break; case 'd': /* long, turn it into an int */ targ.Append(va_arg(ap, int)); break; case 's': /* char * */ if ((tmp_chr_str = va_arg(ap, char *))) { targ.Append(tmp_chr_str); }//if break; case 'p': /* pointer */ targ.appendHex(va_arg(ap, long)); break; case 'S': /* String */ if ((tmp_str = va_arg(ap, String *))) { targ.Append(*tmp_str); }//if break; case 'P': /* pad w/spaces */ i++; //looking at first number now if (isdigit(string[i+1])) { //two digits? if (isdigit(string[i])) { //it should be num = 10 * ((int)(string[i]) - 48); num += (int)(string[i+1]) - 48; }//if else { LOGFILE.log(ERROR, "ERROR: Sprintf, case 'P', first not an int\n"); } }//if two digits else { LOGFILE.log(ERROR, "ERROR: Sprintf, case 'P', second not an int\n"); } i += 2; //now pointing to next valid letter s_len = targ.Strlen(); while (s_len < num) { targ.Append(" "); //pad another space s_len++; }//while i--; //slight hack break; default: targ.Assert(FALSE, "ERROR: default called in Sprintf.\n"); }//switch i++; }//if else { // i++; targ.Append("%"); }//else }//if else { targ.Append(string[i]); i++; }//else }//for }//vSprintf /* WARNING: this will fail if input is > 99 chars. */ istream& operator>> (istream& stream, String& str) { char buf[100]; stream >> buf; str = buf; return stream; }//write_out operator ostream& operator<< (ostream& stream, const String& str) { stream << str.string; return stream; }//write_out operator