// Copyright 2013 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "base/strings/string_util.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "base/basictypes.h" #include "base/logging.h" #include "base/memory/singleton.h" #include "base/strings/string_split.h" #include "base/strings/utf_string_conversion_utils.h" #include "base/strings/utf_string_conversions.h" #include "base/third_party/icu/icu_utf.h" #include "build/build_config.h" // Remove when this entire file is in the base namespace. using base::char16; using base::string16; namespace { // Force the singleton used by EmptyString[16] to be a unique type. This // prevents other code that might accidentally use Singleton from // getting our internal one. struct EmptyStrings { EmptyStrings() {} const std::string s; const string16 s16; static EmptyStrings* GetInstance() { return Singleton::get(); } }; // Used by ReplaceStringPlaceholders to track the position in the string of // replaced parameters. struct ReplacementOffset { ReplacementOffset(uintptr_t parameter, size_t offset) : parameter(parameter), offset(offset) {} // Index of the parameter. uintptr_t parameter; // Starting position in the string. size_t offset; }; static bool CompareParameter(const ReplacementOffset& elem1, const ReplacementOffset& elem2) { return elem1.parameter < elem2.parameter; } // Assuming that a pointer is the size of a "machine word", then // uintptr_t is an integer type that is also a machine word. typedef uintptr_t MachineWord; const uintptr_t kMachineWordAlignmentMask = sizeof(MachineWord) - 1; inline bool IsAlignedToMachineWord(const void* pointer) { return !(reinterpret_cast(pointer) & kMachineWordAlignmentMask); } template inline T* AlignToMachineWord(T* pointer) { return reinterpret_cast(reinterpret_cast(pointer) & ~kMachineWordAlignmentMask); } template struct NonASCIIMask; template<> struct NonASCIIMask<4, base::char16> { static inline uint32_t value() { return 0xFF80FF80U; } }; template<> struct NonASCIIMask<4, char> { static inline uint32_t value() { return 0x80808080U; } }; template<> struct NonASCIIMask<8, base::char16> { static inline uint64_t value() { return 0xFF80FF80FF80FF80ULL; } }; template<> struct NonASCIIMask<8, char> { static inline uint64_t value() { return 0x8080808080808080ULL; } }; #if defined(WCHAR_T_IS_UTF32) template<> struct NonASCIIMask<4, wchar_t> { static inline uint32_t value() { return 0xFFFFFF80U; } }; template<> struct NonASCIIMask<8, wchar_t> { static inline uint64_t value() { return 0xFFFFFF80FFFFFF80ULL; } }; #endif // WCHAR_T_IS_UTF32 } // namespace namespace base { bool IsWprintfFormatPortable(const wchar_t* format) { for (const wchar_t* position = format; *position != '\0'; ++position) { if (*position == '%') { bool in_specification = true; bool modifier_l = false; while (in_specification) { // Eat up characters until reaching a known specifier. if (*++position == '\0') { // The format string ended in the middle of a specification. Call // it portable because no unportable specifications were found. The // string is equally broken on all platforms. return true; } if (*position == 'l') { // 'l' is the only thing that can save the 's' and 'c' specifiers. modifier_l = true; } else if (((*position == 's' || *position == 'c') && !modifier_l) || *position == 'S' || *position == 'C' || *position == 'F' || *position == 'D' || *position == 'O' || *position == 'U') { // Not portable. return false; } if (wcschr(L"diouxXeEfgGaAcspn%", *position)) { // Portable, keep scanning the rest of the format string. in_specification = false; } } } } return true; } const std::string& EmptyString() { return EmptyStrings::GetInstance()->s; } const string16& EmptyString16() { return EmptyStrings::GetInstance()->s16; } template bool ReplaceCharsT(const STR& input, const STR& replace_chars, const STR& replace_with, STR* output) { bool removed = false; size_t replace_length = replace_with.length(); *output = input; size_t found = output->find_first_of(replace_chars); while (found != STR::npos) { removed = true; output->replace(found, 1, replace_with); found = output->find_first_of(replace_chars, found + replace_length); } return removed; } bool ReplaceChars(const string16& input, const base::StringPiece16& replace_chars, const string16& replace_with, string16* output) { return ReplaceCharsT(input, replace_chars.as_string(), replace_with, output); } bool ReplaceChars(const std::string& input, const base::StringPiece& replace_chars, const std::string& replace_with, std::string* output) { return ReplaceCharsT(input, replace_chars.as_string(), replace_with, output); } bool RemoveChars(const string16& input, const base::StringPiece16& remove_chars, string16* output) { return ReplaceChars(input, remove_chars.as_string(), string16(), output); } bool RemoveChars(const std::string& input, const base::StringPiece& remove_chars, std::string* output) { return ReplaceChars(input, remove_chars.as_string(), std::string(), output); } template TrimPositions TrimStringT(const Str& input, BasicStringPiece trim_chars, TrimPositions positions, Str* output) { // Find the edges of leading/trailing whitespace as desired. Need to use // a StringPiece version of input to be able to call find* on it with the // StringPiece version of trim_chars (normally the trim_chars will be a // constant so avoid making a copy). BasicStringPiece input_piece(input); const size_t last_char = input.length() - 1; const size_t first_good_char = (positions & TRIM_LEADING) ? input_piece.find_first_not_of(trim_chars) : 0; const size_t last_good_char = (positions & TRIM_TRAILING) ? input_piece.find_last_not_of(trim_chars) : last_char; // When the string was all trimmed, report that we stripped off characters // from whichever position the caller was interested in. For empty input, we // stripped no characters, but we still need to clear |output|. if (input.empty() || (first_good_char == Str::npos) || (last_good_char == Str::npos)) { bool input_was_empty = input.empty(); // in case output == &input output->clear(); return input_was_empty ? TRIM_NONE : positions; } // Trim. *output = input.substr(first_good_char, last_good_char - first_good_char + 1); // Return where we trimmed from. return static_cast( ((first_good_char == 0) ? TRIM_NONE : TRIM_LEADING) | ((last_good_char == last_char) ? TRIM_NONE : TRIM_TRAILING)); } bool TrimString(const string16& input, base::StringPiece16 trim_chars, string16* output) { return TrimStringT(input, trim_chars, TRIM_ALL, output) != TRIM_NONE; } bool TrimString(const std::string& input, base::StringPiece trim_chars, std::string* output) { return TrimStringT(input, trim_chars, TRIM_ALL, output) != TRIM_NONE; } template BasicStringPiece TrimStringPieceT(BasicStringPiece input, BasicStringPiece trim_chars, TrimPositions positions) { size_t begin = (positions & TRIM_LEADING) ? input.find_first_not_of(trim_chars) : 0; size_t end = (positions & TRIM_TRAILING) ? input.find_last_not_of(trim_chars) + 1 : input.size(); return input.substr(begin, end - begin); } StringPiece16 TrimString(StringPiece16 input, const base::StringPiece16& trim_chars, TrimPositions positions) { return TrimStringPieceT(input, trim_chars, positions); } StringPiece TrimString(StringPiece input, const base::StringPiece& trim_chars, TrimPositions positions) { return TrimStringPieceT(input, trim_chars, positions); } void TruncateUTF8ToByteSize(const std::string& input, const size_t byte_size, std::string* output) { DCHECK(output); if (byte_size > input.length()) { *output = input; return; } DCHECK_LE(byte_size, static_cast(kint32max)); // Note: This cast is necessary because CBU8_NEXT uses int32s. int32 truncation_length = static_cast(byte_size); int32 char_index = truncation_length - 1; const char* data = input.data(); // Using CBU8, we will move backwards from the truncation point // to the beginning of the string looking for a valid UTF8 // character. Once a full UTF8 character is found, we will // truncate the string to the end of that character. while (char_index >= 0) { int32 prev = char_index; base_icu::UChar32 code_point = 0; CBU8_NEXT(data, char_index, truncation_length, code_point); if (!IsValidCharacter(code_point) || !IsValidCodepoint(code_point)) { char_index = prev - 1; } else { break; } } if (char_index >= 0 ) *output = input.substr(0, char_index); else output->clear(); } TrimPositions TrimWhitespace(const string16& input, TrimPositions positions, string16* output) { return TrimStringT(input, StringPiece16(kWhitespaceUTF16), positions, output); } StringPiece16 TrimWhitespaceASCII(StringPiece16 input, TrimPositions positions) { return TrimStringPieceT(input, StringPiece16(kWhitespaceUTF16), positions); } TrimPositions TrimWhitespaceASCII(const std::string& input, TrimPositions positions, std::string* output) { return TrimStringT(input, StringPiece(kWhitespaceASCII), positions, output); } StringPiece TrimWhitespaceASCII(StringPiece input, TrimPositions positions) { return TrimStringPieceT(input, StringPiece(kWhitespaceASCII), positions); } // This function is only for backward-compatibility. // To be removed when all callers are updated. TrimPositions TrimWhitespace(const std::string& input, TrimPositions positions, std::string* output) { return TrimWhitespaceASCII(input, positions, output); } template STR CollapseWhitespaceT(const STR& text, bool trim_sequences_with_line_breaks) { STR result; result.resize(text.size()); // Set flags to pretend we're already in a trimmed whitespace sequence, so we // will trim any leading whitespace. bool in_whitespace = true; bool already_trimmed = true; int chars_written = 0; for (typename STR::const_iterator i(text.begin()); i != text.end(); ++i) { if (IsUnicodeWhitespace(*i)) { if (!in_whitespace) { // Reduce all whitespace sequences to a single space. in_whitespace = true; result[chars_written++] = L' '; } if (trim_sequences_with_line_breaks && !already_trimmed && ((*i == '\n') || (*i == '\r'))) { // Whitespace sequences containing CR or LF are eliminated entirely. already_trimmed = true; --chars_written; } } else { // Non-whitespace chracters are copied straight across. in_whitespace = false; already_trimmed = false; result[chars_written++] = *i; } } if (in_whitespace && !already_trimmed) { // Any trailing whitespace is eliminated. --chars_written; } result.resize(chars_written); return result; } string16 CollapseWhitespace(const string16& text, bool trim_sequences_with_line_breaks) { return CollapseWhitespaceT(text, trim_sequences_with_line_breaks); } std::string CollapseWhitespaceASCII(const std::string& text, bool trim_sequences_with_line_breaks) { return CollapseWhitespaceT(text, trim_sequences_with_line_breaks); } bool ContainsOnlyChars(const StringPiece& input, const StringPiece& characters) { return input.find_first_not_of(characters) == StringPiece::npos; } bool ContainsOnlyChars(const StringPiece16& input, const StringPiece16& characters) { return input.find_first_not_of(characters) == StringPiece16::npos; } template inline bool DoIsStringASCII(const Char* characters, size_t length) { MachineWord all_char_bits = 0; const Char* end = characters + length; // Prologue: align the input. while (!IsAlignedToMachineWord(characters) && characters != end) { all_char_bits |= *characters; ++characters; } // Compare the values of CPU word size. const Char* word_end = AlignToMachineWord(end); const size_t loop_increment = sizeof(MachineWord) / sizeof(Char); while (characters < word_end) { all_char_bits |= *(reinterpret_cast(characters)); characters += loop_increment; } // Process the remaining bytes. while (characters != end) { all_char_bits |= *characters; ++characters; } MachineWord non_ascii_bit_mask = NonASCIIMask::value(); return !(all_char_bits & non_ascii_bit_mask); } bool IsStringASCII(const StringPiece& str) { return DoIsStringASCII(str.data(), str.length()); } bool IsStringASCII(const StringPiece16& str) { return DoIsStringASCII(str.data(), str.length()); } bool IsStringASCII(const string16& str) { return DoIsStringASCII(str.data(), str.length()); } #if defined(WCHAR_T_IS_UTF32) bool IsStringASCII(const std::wstring& str) { return DoIsStringASCII(str.data(), str.length()); } #endif bool IsStringUTF8(const StringPiece& str) { const char *src = str.data(); int32 src_len = static_cast(str.length()); int32 char_index = 0; while (char_index < src_len) { int32 code_point; CBU8_NEXT(src, char_index, src_len, code_point); if (!IsValidCharacter(code_point)) return false; } return true; } template static inline bool DoLowerCaseEqualsASCII(Iter a_begin, Iter a_end, const char* b) { for (Iter it = a_begin; it != a_end; ++it, ++b) { if (!*b || ToLowerASCII(*it) != *b) return false; } return *b == 0; } // Front-ends for LowerCaseEqualsASCII. bool LowerCaseEqualsASCII(const std::string& a, const char* b) { return DoLowerCaseEqualsASCII(a.begin(), a.end(), b); } bool LowerCaseEqualsASCII(const string16& a, const char* b) { return DoLowerCaseEqualsASCII(a.begin(), a.end(), b); } bool LowerCaseEqualsASCII(std::string::const_iterator a_begin, std::string::const_iterator a_end, const char* b) { return DoLowerCaseEqualsASCII(a_begin, a_end, b); } bool LowerCaseEqualsASCII(string16::const_iterator a_begin, string16::const_iterator a_end, const char* b) { return DoLowerCaseEqualsASCII(a_begin, a_end, b); } bool LowerCaseEqualsASCII(const char* a_begin, const char* a_end, const char* b) { return DoLowerCaseEqualsASCII(a_begin, a_end, b); } bool LowerCaseEqualsASCII(const char* a_begin, const char* a_end, const char* b_begin, const char* b_end) { while (a_begin != a_end && b_begin != b_end && ToLowerASCII(*a_begin) == *b_begin) { a_begin++; b_begin++; } return a_begin == a_end && b_begin == b_end; } bool LowerCaseEqualsASCII(const char16* a_begin, const char16* a_end, const char* b) { return DoLowerCaseEqualsASCII(a_begin, a_end, b); } bool EqualsASCII(const string16& a, const StringPiece& b) { if (a.length() != b.length()) return false; return std::equal(b.begin(), b.end(), a.begin()); } template bool StartsWithT(BasicStringPiece str, BasicStringPiece search_for, CompareCase case_sensitivity) { if (search_for.size() > str.size()) return false; BasicStringPiece source = str.substr(0, search_for.size()); switch (case_sensitivity) { case CompareCase::SENSITIVE: return source == search_for; case CompareCase::INSENSITIVE_ASCII: return std::equal( search_for.begin(), search_for.end(), source.begin(), base::CaseInsensitiveCompareASCII()); default: NOTREACHED(); return false; } } bool StartsWith(StringPiece str, StringPiece search_for, CompareCase case_sensitivity) { return StartsWithT(str, search_for, case_sensitivity); } bool StartsWith(StringPiece16 str, StringPiece16 search_for, CompareCase case_sensitivity) { return StartsWithT(str, search_for, case_sensitivity); } bool StartsWith(const string16& str, const string16& search, bool case_sensitive) { if (!case_sensitive) { // This function was originally written using the current locale functions // for case-insensitive comparisons. Emulate this behavior until callers // can be converted either to use the case-insensitive ASCII one (most // callers) or ICU functions in base_i18n. if (search.size() > str.size()) return false; return std::equal(search.begin(), search.end(), str.begin(), CaseInsensitiveCompare()); } return StartsWith(StringPiece16(str), StringPiece16(search), CompareCase::SENSITIVE); } template bool EndsWithT(BasicStringPiece str, BasicStringPiece search_for, CompareCase case_sensitivity) { if (search_for.size() > str.size()) return false; BasicStringPiece source = str.substr(str.size() - search_for.size(), search_for.size()); switch (case_sensitivity) { case CompareCase::SENSITIVE: return source == search_for; case CompareCase::INSENSITIVE_ASCII: return std::equal( source.begin(), source.end(), search_for.begin(), base::CaseInsensitiveCompareASCII()); default: NOTREACHED(); return false; } } bool EndsWith(StringPiece str, StringPiece search_for, CompareCase case_sensitivity) { return EndsWithT(str, search_for, case_sensitivity); } bool EndsWith(StringPiece16 str, StringPiece16 search_for, CompareCase case_sensitivity) { return EndsWithT(str, search_for, case_sensitivity); } bool EndsWith(const string16& str, const string16& search, bool case_sensitive) { if (!case_sensitive) { // This function was originally written using the current locale functions // for case-insensitive comparisons. Emulate this behavior until callers // can be converted either to use the case-insensitive ASCII one (most // callers) or ICU functions in base_i18n. if (search.size() > str.size()) return false; return std::equal(search.begin(), search.end(), str.begin() + (str.size() - search.size()), CaseInsensitiveCompare()); } return EndsWith(StringPiece16(str), StringPiece16(search), CompareCase::SENSITIVE); } char HexDigitToInt(wchar_t c) { DCHECK(IsHexDigit(c)); if (c >= '0' && c <= '9') return static_cast(c - '0'); if (c >= 'A' && c <= 'F') return static_cast(c - 'A' + 10); if (c >= 'a' && c <= 'f') return static_cast(c - 'a' + 10); return 0; } static const char* const kByteStringsUnlocalized[] = { " B", " kB", " MB", " GB", " TB", " PB" }; string16 FormatBytesUnlocalized(int64 bytes) { double unit_amount = static_cast(bytes); size_t dimension = 0; const int kKilo = 1024; while (unit_amount >= kKilo && dimension < arraysize(kByteStringsUnlocalized) - 1) { unit_amount /= kKilo; dimension++; } char buf[64]; if (bytes != 0 && dimension > 0 && unit_amount < 100) { base::snprintf(buf, arraysize(buf), "%.1lf%s", unit_amount, kByteStringsUnlocalized[dimension]); } else { base::snprintf(buf, arraysize(buf), "%.0lf%s", unit_amount, kByteStringsUnlocalized[dimension]); } return ASCIIToUTF16(buf); } // Runs in O(n) time in the length of |str|. template void DoReplaceSubstringsAfterOffset(StringType* str, size_t offset, BasicStringPiece find_this, BasicStringPiece replace_with, bool replace_all) { DCHECK(!find_this.empty()); // If the find string doesn't appear, there's nothing to do. offset = str->find(find_this.data(), offset, find_this.size()); if (offset == StringType::npos) return; // If we're only replacing one instance, there's no need to do anything // complicated. size_t find_length = find_this.length(); if (!replace_all) { str->replace(offset, find_length, replace_with.data(), replace_with.size()); return; } // If the find and replace strings are the same length, we can simply use // replace() on each instance, and finish the entire operation in O(n) time. size_t replace_length = replace_with.length(); if (find_length == replace_length) { do { str->replace(offset, find_length, replace_with.data(), replace_with.size()); offset = str->find(find_this.data(), offset + replace_length, find_this.size()); } while (offset != StringType::npos); return; } // Since the find and replace strings aren't the same length, a loop like the // one above would be O(n^2) in the worst case, as replace() will shift the // entire remaining string each time. We need to be more clever to keep // things O(n). // // If we're shortening the string, we can alternate replacements with shifting // forward the intervening characters using memmove(). size_t str_length = str->length(); if (find_length > replace_length) { size_t write_offset = offset; do { if (replace_length) { str->replace(write_offset, replace_length, replace_with.data(), replace_with.size()); write_offset += replace_length; } size_t read_offset = offset + find_length; offset = std::min( str->find(find_this.data(), read_offset, find_this.size()), str_length); size_t length = offset - read_offset; if (length) { memmove(&(*str)[write_offset], &(*str)[read_offset], length * sizeof(typename StringType::value_type)); write_offset += length; } } while (offset < str_length); str->resize(write_offset); return; } // We're lengthening the string. We can use alternating replacements and // memmove() calls like above, but we need to precalculate the final string // length and then expand from back-to-front to avoid overwriting the string // as we're reading it, needing to shift, or having to copy to a second string // temporarily. size_t first_match = offset; // First, calculate the final length and resize the string. size_t final_length = str_length; size_t expansion = replace_length - find_length; size_t current_match; do { final_length += expansion; // Minor optimization: save this offset into |current_match|, so that on // exit from the loop, |current_match| will point at the last instance of // the find string, and we won't need to find() it again immediately. current_match = offset; offset = str->find(find_this.data(), offset + find_length, find_this.size()); } while (offset != StringType::npos); str->resize(final_length); // Now do the replacement loop, working backwards through the string. for (size_t prev_match = str_length, write_offset = final_length; ; current_match = str->rfind(find_this.data(), current_match - 1, find_this.size())) { size_t read_offset = current_match + find_length; size_t length = prev_match - read_offset; if (length) { write_offset -= length; memmove(&(*str)[write_offset], &(*str)[read_offset], length * sizeof(typename StringType::value_type)); } write_offset -= replace_length; str->replace(write_offset, replace_length, replace_with.data(), replace_with.size()); if (current_match == first_match) return; prev_match = current_match; } } void ReplaceFirstSubstringAfterOffset(string16* str, size_t start_offset, StringPiece16 find_this, StringPiece16 replace_with) { DoReplaceSubstringsAfterOffset( str, start_offset, find_this, replace_with, false); // Replace first. } void ReplaceFirstSubstringAfterOffset(std::string* str, size_t start_offset, StringPiece find_this, StringPiece replace_with) { DoReplaceSubstringsAfterOffset( str, start_offset, find_this, replace_with, false); // Replace first. } void ReplaceSubstringsAfterOffset(string16* str, size_t start_offset, StringPiece16 find_this, StringPiece16 replace_with) { DoReplaceSubstringsAfterOffset( str, start_offset, find_this, replace_with, true); // Replace all. } void ReplaceSubstringsAfterOffset(std::string* str, size_t start_offset, StringPiece find_this, StringPiece replace_with) { DoReplaceSubstringsAfterOffset( str, start_offset, find_this, replace_with, true); // Replace all. } template inline typename string_type::value_type* WriteIntoT(string_type* str, size_t length_with_null) { DCHECK_GT(length_with_null, 1u); str->reserve(length_with_null); str->resize(length_with_null - 1); return &((*str)[0]); } char* WriteInto(std::string* str, size_t length_with_null) { return WriteIntoT(str, length_with_null); } char16* WriteInto(base::string16* str, size_t length_with_null) { return WriteIntoT(str, length_with_null); } } // namespace base template static STR JoinStringT(const std::vector& parts, const STR& sep) { if (parts.empty()) return STR(); STR result(parts[0]); typename std::vector::const_iterator iter = parts.begin(); ++iter; for (; iter != parts.end(); ++iter) { result += sep; result += *iter; } return result; } std::string JoinString(const std::vector& parts, char sep) { return JoinStringT(parts, std::string(1, sep)); } string16 JoinString(const std::vector& parts, char16 sep) { return JoinStringT(parts, string16(1, sep)); } std::string JoinString(const std::vector& parts, const std::string& separator) { return JoinStringT(parts, separator); } string16 JoinString(const std::vector& parts, const string16& separator) { return JoinStringT(parts, separator); } template OutStringType DoReplaceStringPlaceholders(const FormatStringType& format_string, const std::vector& subst, std::vector* offsets) { size_t substitutions = subst.size(); size_t sub_length = 0; for (typename std::vector::const_iterator iter = subst.begin(); iter != subst.end(); ++iter) { sub_length += iter->length(); } OutStringType formatted; formatted.reserve(format_string.length() + sub_length); std::vector r_offsets; for (typename FormatStringType::const_iterator i = format_string.begin(); i != format_string.end(); ++i) { if ('$' == *i) { if (i + 1 != format_string.end()) { ++i; DCHECK('$' == *i || '1' <= *i) << "Invalid placeholder: " << *i; if ('$' == *i) { while (i != format_string.end() && '$' == *i) { formatted.push_back('$'); ++i; } --i; } else { uintptr_t index = 0; while (i != format_string.end() && '0' <= *i && *i <= '9') { index *= 10; index += *i - '0'; ++i; } --i; index -= 1; if (offsets) { ReplacementOffset r_offset(index, static_cast(formatted.size())); r_offsets.insert(std::lower_bound(r_offsets.begin(), r_offsets.end(), r_offset, &CompareParameter), r_offset); } if (index < substitutions) formatted.append(subst.at(index)); } } } else { formatted.push_back(*i); } } if (offsets) { for (std::vector::const_iterator i = r_offsets.begin(); i != r_offsets.end(); ++i) { offsets->push_back(i->offset); } } return formatted; } string16 ReplaceStringPlaceholders(const string16& format_string, const std::vector& subst, std::vector* offsets) { return DoReplaceStringPlaceholders(format_string, subst, offsets); } std::string ReplaceStringPlaceholders(const base::StringPiece& format_string, const std::vector& subst, std::vector* offsets) { return DoReplaceStringPlaceholders(format_string, subst, offsets); } string16 ReplaceStringPlaceholders(const string16& format_string, const string16& a, size_t* offset) { std::vector offsets; std::vector subst; subst.push_back(a); string16 result = ReplaceStringPlaceholders(format_string, subst, &offsets); DCHECK_EQ(1U, offsets.size()); if (offset) *offset = offsets[0]; return result; } // The following code is compatible with the OpenBSD lcpy interface. See: // http://www.gratisoft.us/todd/papers/strlcpy.html // ftp://ftp.openbsd.org/pub/OpenBSD/src/lib/libc/string/{wcs,str}lcpy.c namespace { template size_t lcpyT(CHAR* dst, const CHAR* src, size_t dst_size) { for (size_t i = 0; i < dst_size; ++i) { if ((dst[i] = src[i]) == 0) // We hit and copied the terminating NULL. return i; } // We were left off at dst_size. We over copied 1 byte. Null terminate. if (dst_size != 0) dst[dst_size - 1] = 0; // Count the rest of the |src|, and return it's length in characters. while (src[dst_size]) ++dst_size; return dst_size; } } // namespace size_t base::strlcpy(char* dst, const char* src, size_t dst_size) { return lcpyT(dst, src, dst_size); } size_t base::wcslcpy(wchar_t* dst, const wchar_t* src, size_t dst_size) { return lcpyT(dst, src, dst_size); }