/* Copyright 2012 10gen Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * 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 Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . * * As a special exception, the copyright holders give permission to link the * code of portions of this program with the OpenSSL library under certain * conditions as described in each individual source file and distribute * linked combinations including the program with the OpenSSL library. You * must comply with the GNU Affero General Public License in all respects * for all of the code used other than as permitted herein. If you modify * file(s) with this exception, you may extend this exception to your * version of the file(s), but you are not obligated to do so. If you do not * wish to do so, delete this exception statement from your version. If you * delete this exception statement from all source files in the program, * then also delete it in the license file. */ #include "mongo/platform/basic.h" #include "mongo/base/parse_number.h" #include #include #include #include #include #include namespace mongo { /** * Returns the value of the digit "c", with the same conversion behavior as strtol. * * Assumes "c" is an ASCII character or UTF-8 octet. */ static uint8_t _digitValue(char c) { if (c >= '0' && c <= '9') return uint8_t(c - '0'); if (c >= 'a' && c <= 'z') return uint8_t(c - 'a' + 10); if (c >= 'A' && c <= 'Z') return uint8_t(c - 'A' + 10); return 36; // Illegal digit value for all supported bases. } /** * Assuming "stringValue" represents a parseable number, extracts the sign and returns a * substring with any sign characters stripped away. "*isNegative" is set to true if the * number is negative, and false otherwise. */ static inline StringData _extractSign(StringData stringValue, bool* isNegative) { if (stringValue.empty()) { *isNegative = false; return stringValue; } bool foundSignMarker; switch (stringValue[0]) { case '-': foundSignMarker = true; *isNegative = true; break; case '+': foundSignMarker = true; *isNegative = false; break; default: foundSignMarker = false; *isNegative = false; break; } if (foundSignMarker) return stringValue.substr(1); return stringValue; } /** * Assuming "stringValue" represents a parseable number, determines what base to use given * "inputBase". Stores the correct base into "*outputBase". Follows strtol rules. If * "inputBase" is not 0, *outputBase is set to "inputBase". Otherwise, if "stringValue" starts * with "0x" or "0X", sets outputBase to 16, or if it starts with 0, sets outputBase to 8. * * Returns stringValue, unless it sets *outputBase to 16, in which case it will strip off the * "0x" or "0X" prefix, if present. */ static inline StringData _extractBase(StringData stringValue, int inputBase, int* outputBase) { const StringData hexPrefixLower("0x", StringData::LiteralTag()); const StringData hexPrefixUpper("0X", StringData::LiteralTag()); if (inputBase == 0) { if (stringValue.size() > 2 && (stringValue.startsWith(hexPrefixLower) || stringValue.startsWith(hexPrefixUpper))) { *outputBase = 16; return stringValue.substr(2); } if (stringValue.size() > 1 && stringValue[0] == '0') { *outputBase = 8; return stringValue; } *outputBase = 10; return stringValue; } else { *outputBase = inputBase; if (inputBase == 16 && (stringValue.startsWith(hexPrefixLower) || stringValue.startsWith(hexPrefixUpper))) { return stringValue.substr(2); } return stringValue; } } template Status parseNumberFromStringWithBase(StringData stringValue, int base, NumberType* result) { typedef ::std::numeric_limits limits; if (base == 1 || base < 0 || base > 36) return Status(ErrorCodes::BadValue, "Invalid base", 0); bool isNegative = false; StringData str = _extractBase(_extractSign(stringValue, &isNegative), base, &base); if (str.empty()) return Status(ErrorCodes::FailedToParse, "No digits"); NumberType n(0); if (isNegative) { if (limits::is_signed) { for (size_t i = 0; i < str.size(); ++i) { NumberType digitValue = NumberType(_digitValue(str[i])); if (int(digitValue) >= base) { return Status(ErrorCodes::FailedToParse, "Bad digit \"" + str.substr(i, 1).toString() + "\" while parsing " + stringValue.toString()); } // MSVC: warning C4146: unary minus operator applied to unsigned type, result still unsigned // This code is statically known to be dead when NumberType is unsigned, so the warning is not real #pragma warning(push) #pragma warning(disable : 4146) if ((NumberType(limits::min() / base) > n) || ((limits::min() - NumberType(n * base)) > -digitValue)) { #pragma warning(pop) return Status(ErrorCodes::FailedToParse, "Underflow"); } n *= NumberType(base); n -= NumberType(digitValue); } } else { return Status(ErrorCodes::FailedToParse, "Negative value"); } } else { for (size_t i = 0; i < str.size(); ++i) { NumberType digitValue = NumberType(_digitValue(str[i])); if (int(digitValue) >= base) { return Status(ErrorCodes::FailedToParse, "Bad digit \"" + str.substr(i, 1).toString() + "\" while parsing " + stringValue.toString()); } if ((NumberType(limits::max() / base) < n) || (NumberType(limits::max() - n * base) < digitValue)) { return Status(ErrorCodes::FailedToParse, "Overflow"); } n *= NumberType(base); n += NumberType(digitValue); } } *result = n; return Status::OK(); } // Definition of the various supported implementations of parseNumberFromStringWithBase. #define DEFINE_PARSE_NUMBER_FROM_STRING_WITH_BASE(NUMBER_TYPE) \ template Status parseNumberFromStringWithBase(StringData, int, NUMBER_TYPE*); DEFINE_PARSE_NUMBER_FROM_STRING_WITH_BASE(long) DEFINE_PARSE_NUMBER_FROM_STRING_WITH_BASE(long long) DEFINE_PARSE_NUMBER_FROM_STRING_WITH_BASE(unsigned long) DEFINE_PARSE_NUMBER_FROM_STRING_WITH_BASE(unsigned long long) DEFINE_PARSE_NUMBER_FROM_STRING_WITH_BASE(short) DEFINE_PARSE_NUMBER_FROM_STRING_WITH_BASE(unsigned short) DEFINE_PARSE_NUMBER_FROM_STRING_WITH_BASE(int) DEFINE_PARSE_NUMBER_FROM_STRING_WITH_BASE(unsigned int) DEFINE_PARSE_NUMBER_FROM_STRING_WITH_BASE(int8_t); DEFINE_PARSE_NUMBER_FROM_STRING_WITH_BASE(uint8_t); #undef DEFINE_PARSE_NUMBER_FROM_STRING_WITH_BASE #ifdef _WIN32 namespace { /** * Converts ascii c-locale uppercase characters to lower case, leaves other char values * unchanged. */ char toLowerAscii(char c) { if (isascii(c) && isupper(c)) return _tolower(c); return c; } } // namespace #endif // defined(_WIN32) template <> Status parseNumberFromStringWithBase(StringData stringValue, int base, double* result) { if (base != 0) { return Status(ErrorCodes::BadValue, "Must pass 0 as base to parseNumberFromStringWithBase."); } if (stringValue.empty()) return Status(ErrorCodes::FailedToParse, "Empty string"); if (isspace(stringValue[0])) return Status(ErrorCodes::FailedToParse, "Leading whitespace"); std::string str = stringValue.toString(); const char* cStr = str.c_str(); char* endp; errno = 0; double d = strtod(cStr, &endp); int actualErrno = errno; if (endp != stringValue.size() + cStr) { #ifdef _WIN32 // The Windows libc implementation of strtod cannot parse +/-infinity or nan, // so handle that here. std::transform(str.begin(), str.end(), str.begin(), toLowerAscii); if (str == StringData("nan", StringData::LiteralTag())) { *result = std::numeric_limits::quiet_NaN(); return Status::OK(); } else if (str == StringData("+infinity", StringData::LiteralTag()) || str == StringData("infinity", StringData::LiteralTag())) { *result = std::numeric_limits::infinity(); return Status::OK(); } else if (str == StringData("-infinity", StringData::LiteralTag())) { *result = -std::numeric_limits::infinity(); return Status::OK(); } #endif // defined(_WIN32) return Status(ErrorCodes::FailedToParse, "Did not consume whole number."); } if (actualErrno == ERANGE) return Status(ErrorCodes::FailedToParse, "Out of range"); *result = d; return Status::OK(); } } // namespace mongo