Wed, 27 Jan 2021 12:38:00 +0200
added protocol5 branch's string reading function which seems to be more reliable
/* Copyright 2014 - 2016 Teemu Piippo All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include <cstring> #include "main.h" #include "mystring.h" #include "md5.h" BEGIN_ZFC_NAMESPACE /*! * \brief Compares this string with another. * \param other The string to compare with. * \returns -1 if this string is lexicographically less than \c other, * 0 if they are equal, or * 1 if this string is lexicographically greater than \c other. */ int String::compare (const String& other) const { return m_string.compare (other.stdString()); } /*! * \brief Removes all instances of an unwanted character from this string. * \param unwanted Character to remove. */ void String::strip (char unwanted) { for (int pos = 0; (pos = find (unwanted)) != -1;) removeAt (pos--); } /*! * \brief Removes all instances of multiple characters from this string. * \param unwanted Characters to remove. */ void String::strip (const List<char>& unwanted) { for (char character : unwanted) strip(character); } /*! * \returns an upper-case version of this string. */ String String::toUpperCase() const { String result (m_string); for (char &ch : result) { if (islower(ch)) ch -= 'a' - 'A'; } return result; } /*! * \returns a lower-case version of this string. */ String String::toLowerCase() const { String result (m_string); for (char &ch : result) { if (isupper(ch)) ch += 'a' - 'A'; } return result; } /*! * \brief Splits this string using the provided delimeter. * \param delimeter Delimeter to use for splitting. * \returns a string list containing the split strings. */ StringList String::split (char delimeter) const { String delimeterString; delimeterString += delimeter; return split (delimeterString); } /*! * \brief Splits this string using the provided delimeter. * \param delimeter Delimeter to use for splitting. * \returns a string list containing the split strings. */ StringList String::split (const String& delimeter) const { StringList result; int a = 0; int b; // Find all separators and store the text left to them. while ((b = find (delimeter, a)) != -1) { String sub = mid (a, b); if (sub.length() > 0) result << sub; a = b + delimeter.length(); } // Add the string at the right of the last separator if (a < (int) length()) result.append (mid (a, length())); return result; } /*! * \brief Replaces all instances of \c text with \c replacement. * \param text Text to replace away. * \param replacement Text to replace \c text with. */ void String::replace (const char* text, const char* replacement) { int position; while ((position = find (text)) != -1) m_string = m_string.replace (position, strlen (text), replacement); } /*! * \param character Character to count. * \returns the amount of \c character found in the string. */ int String::count (char character) const { int result = 0; for (char ch : *this) { if (ch == character) result++; } return result; } /*! * \param a Starting index of the range. * \param b Ending index of the range. * \returns a sub-string containing all characters from \c a to \c b, not including the character at \c b. */ String String::mid (int rangeBegin, int rangeEnd) const { modifyIndex(rangeBegin); modifyIndex(rangeEnd); rangeBegin = max(rangeBegin, 0); rangeEnd = min(rangeEnd, length()); if (rangeEnd <= rangeBegin) return ""; else return m_string.substr(rangeBegin, rangeEnd - rangeBegin); } /*! * \param length Amount of characters to return. * \returns the \c length right-most characters of the string. */ String String::right(int length) const { if (length >= this->length()) return *this; else return String(chars() + this->length() - length); } /*! * \brief Finds the first instance of a sub-string. * \param subString Sub-string to search within this string. * \param startingPosition Position to start looking for the sub-string from. * \returns the position the first instance of sub-string found, or -1 if not found. */ int String::find (const char* subString, int startingPosition) const { int position = m_string.find (subString, startingPosition); if (position == int (std::string::npos)) return -1; else return position; } /*! * \brief Finds the first instance of a character. * \param character Character to search within this string. * \param startingPosition Position to start looking for the character from. * \returns the position of the first instance of the provided character found, or -1 if not found. */ int String::find (char character, int startingPosition) const { int position = m_string.find (character, startingPosition); if (position == int (std::string::npos)) return -1; else return position; } /*! * \brief Finds the last instance of a sub-string. * \param subString Sub-string to search within this string. * \param startingPosition Position to start looking for the sub-string from. * \returns the position the last instance of sub-string found, or -1 if not found. */ int String::findLast (const char* subString, int startingPosition) const { modifyIndex(startingPosition); for (; startingPosition > 0; startingPosition--) { if (strncmp (chars() + startingPosition, subString, strlen (subString)) == 0) return startingPosition; } return -1; } /*! * \brief Converts this string to an integer. * \param ok An pointer to a boolean to store whether or not the conversion was successful. * If \c ok is \c NULL, the success state is not stored. * \param base The base to interpret this string with. * \returns the resulting integer. */ long String::toInt (bool* ok, int base) const { errno = 0; char* endPointer; long result = strtol (chars(), &endPointer, base); if (ok != nullptr) *ok = (errno == 0 and *endPointer == '\0'); return result; } /*! * \brief Converts this string to a floating-point number. * \param ok An pointer to a boolean to store whether or not the conversion was successful. * If \c ok is \c NULL, the success state is not stored. * \returns the resulting floating-point number. */ float String::toFloat (bool* ok) const { return static_cast<float>(toDouble(ok)); } /*! * \brief Converts this string to a double-precision floating-point number. * \param ok An pointer to a boolean to store whether or not the conversion was successful. * If \c ok is \c NULL, the success state is not stored. * \returns the resulting floating-point number. */ double String::toDouble (bool* ok) const { errno = 0; char* endptr; double i = strtod (chars(), &endptr); if (ok != nullptr) *ok = (errno == 0 and *endptr == '\0'); return i; } /*! * \brief Catenates this string with another string. * \param text String to catenate to the end of this string. * \returns the resulting string. */ String String::operator+ (const String& text) const { String newString = *this; newString.append (text); return newString; } /*! * \brief Catenates this string with another string. * \param text String to catenate to the end of this string. * \returns the resulting string. */ String String::operator+ (const char* text) const { String newString = *this; newString.append (text); return newString; } /*! * \returns whether or not this string represents a number. */ bool String::isNumeric() const { char* endPointer; strtol (chars(), &endPointer, 10); return (endPointer != nullptr) and (*endPointer != '\0'); } /*! * \param other Sub-string to find from the end of this string. * \return whether or not this string ends with the provided sub-string. */ bool String::endsWith (const String& other) const { if (length() < other.length()) { return false; } else { const int offset = length() - other.length(); return strncmp (chars() + offset, other.chars(), other.length()) == 0; } } /*! * \param other Sub-string to find from the beginning of this string. * \returns whether or not this string begins with the provided sub-string. */ bool String::startsWith (const String& other) const { if (length() < other.length()) return false; else return strncmp (chars(), other.chars(), other.length()) == 0; } /*! * \brief Formats this string using \c printf -like syntax. * \param formatString Template string to use with formatting. * \param ... Variadic arguments to use with formatting. */ void __cdecl String::sprintf (const char* formatString, ...) { va_list args; va_start (args, formatString); this->vsprintf (formatString, args); va_end (args); } /*! * \brief Formats this string using \c vsnprintf, using the provided arguments. * \param formatString Template string to use with formatting. * \param args Variadic arguments to use with formatting. */ void String::vsprintf (const char* formatString, va_list args) { // Copy the argument list so that we have something to provide to vsnprintf in case we have to call it again. va_list argsCopy; va_copy(argsCopy, args); // First, attempt to format using a fixed-size buffer. static char buffer[1024]; size_t length = vsnprintf(buffer, sizeof buffer, formatString, args); if (length < sizeof buffer) { // vsnprintf succeeded in fitting the formatted string into the buffer, so we're done. m_string = buffer; } else { // vsnprintf needs more space, so we have to allocate a new buffer and try again. Vector<char> newBuffer(length + 1); vsnprintf(newBuffer.data(), length + 1, formatString, argsCopy); m_string = newBuffer; } } /*! * \brief Joins the elements of this string list into one longer string. * \param delimeter The delimeter to place between the element strings. * \returns the catenated string. */ String StringList::join (const String& delimeter) { String result; for (const String &item : container()) { if (result.isEmpty() == false) result += delimeter; result += item; } return result; } /*! * \brief Tries to match this string against a mask pattern. In the pattern, '?' refers to one character, and '*' to * any number of characters. * \param pattern The masking pattern to use for matching. * \returns whether or not this string matches the provided pattern. */ bool String::maskAgainst (const String& pattern) const { // Elevate to uppercase for case-insensitive matching String pattern_upper = pattern.toUpperCase(); String this_upper = toUpperCase(); const char* maskstring = pattern_upper.chars(); const char* mptr = &maskstring[0]; for (const char* sptr = this_upper.chars(); *sptr != '\0'; sptr++) { if (*mptr == '?') { if (*(sptr + 1) == '\0') { // ? demands that there's a character here and there wasn't. // Therefore, mask matching fails return false; } } else if (*mptr == '*') { char end = *(++mptr); // If '*' is the final character of the message, all of the remaining // string matches against the '*'. We don't need to bother checking // the string any further. if (end == '\0') return true; // Skip to the end character while (*sptr != end and *sptr != '\0') sptr++; // String ended while the mask still had stuff if (*sptr == '\0') return false; } else if (*sptr != *mptr) return false; mptr++; } return true; } /*! * \brief Converts a short integer into a string. * \param value The value to convert. * \returns the resulting string. */ String String::fromNumber (short int value) { char buffer[32]; ::sprintf (buffer, "%d", value); return String (buffer); } /*! * \brief Converts an integer into a string. * \param value The value to convert. * \returns the resulting string. */ String String::fromNumber (int value) { char buffer[32]; ::sprintf (buffer, "%d", value); return String (buffer); } /*! * \brief Converts a long integer into a string. * \param value The value to convert. * \returns the resulting string. */ String String::fromNumber (long int value) { char buffer[32]; ::sprintf (buffer, "%ld", value); return String (buffer); } /*! * \brief Converts an unsigned short integer into a string. * \param value The value to convert. * \returns the resulting string. */ String String::fromNumber (unsigned short int value) { char buffer[32]; ::sprintf (buffer, "%u", value); return String (buffer); } /*! * \brief Converts an unsigned integer into a string. * \param value The value to convert. * \returns the resulting string. */ String String::fromNumber (unsigned int value) { char buffer[32]; ::sprintf (buffer, "%u", value); return String (buffer); } /*! * \brief Converts an unsigned long integer into a string. * \param value The value to convert. * \returns the resulting string. */ String String::fromNumber (unsigned long int value) { char buffer[32]; ::sprintf (buffer, "%lu", value); return String (buffer); } /*! * \brief Converts a double-precision floating point number into a string, using the "%f" format specifier. * \param value The value to convert. * \returns the resulting string. */ String String::fromNumber (double value) { char buffer[64]; ::sprintf (buffer, "%f", value); return String (buffer); } /*! * \brief Constructs a string from a vector of bytes. The bytes do not have to be null-terminated. * \param bytes Bytes to use for construction * \returns the resulting string. */ String String::fromBytes(const ByteArray& bytes) { return String(reinterpret_cast<const Vector<char>&>(bytes)); } /*! * \returns the MD5-checksum of this string. */ String String::md5() const { char checksum[33]; CalculateMD5 (reinterpret_cast<const unsigned char*> (chars()), length(), checksum); checksum[sizeof checksum - 1] = '\0'; return String (checksum); } /*! * \brief Removes leading and trailing whitespace from this string. Alternatively a custom filter can be used to strip * something else than whitespace. * \param filter The filtering function to use. */ void String::normalize (int (*filter)(int)) { int a = 0; int b = length() - 1; while ((*filter) (m_string[a]) and a != b) ++a; while ((*filter) (m_string[b]) and a != b) --b; if (a == b) m_string = ""; else if (a != 0 or b != length() - 1) m_string = m_string.substr (a, b - a + 1); } /*! * \returns a version of this string without leading or trailing whitespace. Alternatively a custom filter can be used * to strip something else than whitespace. */ String String::normalized (int (*filter)(int)) const { String result = *this; result.normalize(filter); return result; } END_ZFC_NAMESPACE