xref: /aosp_15_r20/external/cronet/base/strings/string_util_impl_helpers.h (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1 // Copyright 2020 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #ifndef BASE_STRINGS_STRING_UTIL_IMPL_HELPERS_H_
6 #define BASE_STRINGS_STRING_UTIL_IMPL_HELPERS_H_
7 
8 #include <algorithm>
9 #include <optional>
10 #include <string_view>
11 
12 #include "base/check.h"
13 #include "base/check_op.h"
14 #include "base/logging.h"
15 #include "base/notreached.h"
16 #include "base/ranges/algorithm.h"
17 #include "base/strings/string_piece.h"
18 #include "base/third_party/icu/icu_utf.h"
19 
20 namespace base::internal {
21 
22 // Used by ReplaceStringPlaceholders to track the position in the string of
23 // replaced parameters.
24 struct ReplacementOffset {
ReplacementOffsetReplacementOffset25   ReplacementOffset(uintptr_t parameter, size_t offset)
26       : parameter(parameter), offset(offset) {}
27 
28   // Index of the parameter.
29   size_t parameter;
30 
31   // Starting position in the string.
32   size_t offset;
33 };
34 
CompareParameter(const ReplacementOffset & elem1,const ReplacementOffset & elem2)35 static bool CompareParameter(const ReplacementOffset& elem1,
36                              const ReplacementOffset& elem2) {
37   return elem1.parameter < elem2.parameter;
38 }
39 
40 // Assuming that a pointer is the size of a "machine word", then
41 // uintptr_t is an integer type that is also a machine word.
42 using MachineWord = uintptr_t;
43 
IsMachineWordAligned(const void * pointer)44 inline bool IsMachineWordAligned(const void* pointer) {
45   return !(reinterpret_cast<MachineWord>(pointer) & (sizeof(MachineWord) - 1));
46 }
47 
48 template <typename T, typename CharT = typename T::value_type>
ToLowerASCIIImpl(T str)49 std::basic_string<CharT> ToLowerASCIIImpl(T str) {
50   std::basic_string<CharT> ret;
51   ret.reserve(str.size());
52   for (size_t i = 0; i < str.size(); i++)
53     ret.push_back(ToLowerASCII(str[i]));
54   return ret;
55 }
56 
57 template <typename T, typename CharT = typename T::value_type>
ToUpperASCIIImpl(T str)58 std::basic_string<CharT> ToUpperASCIIImpl(T str) {
59   std::basic_string<CharT> ret;
60   ret.reserve(str.size());
61   for (size_t i = 0; i < str.size(); i++)
62     ret.push_back(ToUpperASCII(str[i]));
63   return ret;
64 }
65 
66 template <typename T, typename CharT = typename T::value_type>
TrimStringT(T input,T trim_chars,TrimPositions positions,std::basic_string<CharT> * output)67 TrimPositions TrimStringT(T input,
68                           T trim_chars,
69                           TrimPositions positions,
70                           std::basic_string<CharT>* output) {
71   // Find the edges of leading/trailing whitespace as desired. Need to use
72   // a StringPiece version of input to be able to call find* on it with the
73   // StringPiece version of trim_chars (normally the trim_chars will be a
74   // constant so avoid making a copy).
75   const size_t last_char = input.length() - 1;
76   const size_t first_good_char =
77       (positions & TRIM_LEADING) ? input.find_first_not_of(trim_chars) : 0;
78   const size_t last_good_char = (positions & TRIM_TRAILING)
79                                     ? input.find_last_not_of(trim_chars)
80                                     : last_char;
81 
82   // When the string was all trimmed, report that we stripped off characters
83   // from whichever position the caller was interested in. For empty input, we
84   // stripped no characters, but we still need to clear |output|.
85   if (input.empty() || first_good_char == std::basic_string<CharT>::npos ||
86       last_good_char == std::basic_string<CharT>::npos) {
87     bool input_was_empty = input.empty();  // in case output == &input
88     output->clear();
89     return input_was_empty ? TRIM_NONE : positions;
90   }
91 
92   // Trim.
93   output->assign(input.data() + first_good_char,
94                  last_good_char - first_good_char + 1);
95 
96   // Return where we trimmed from.
97   return static_cast<TrimPositions>(
98       (first_good_char == 0 ? TRIM_NONE : TRIM_LEADING) |
99       (last_good_char == last_char ? TRIM_NONE : TRIM_TRAILING));
100 }
101 
102 template <typename T, typename CharT = typename T::value_type>
TrimStringPieceT(T input,T trim_chars,TrimPositions positions)103 T TrimStringPieceT(T input, T trim_chars, TrimPositions positions) {
104   size_t begin =
105       (positions & TRIM_LEADING) ? input.find_first_not_of(trim_chars) : 0;
106   size_t end = (positions & TRIM_TRAILING)
107                    ? input.find_last_not_of(trim_chars) + 1
108                    : input.size();
109   return input.substr(std::min(begin, input.size()), end - begin);
110 }
111 
112 template <typename T, typename CharT = typename T::value_type>
CollapseWhitespaceT(T text,bool trim_sequences_with_line_breaks)113 std::basic_string<CharT> CollapseWhitespaceT(
114     T text,
115     bool trim_sequences_with_line_breaks) {
116   std::basic_string<CharT> result;
117   result.resize(text.size());
118 
119   // Set flags to pretend we're already in a trimmed whitespace sequence, so we
120   // will trim any leading whitespace.
121   bool in_whitespace = true;
122   bool already_trimmed = true;
123 
124   size_t chars_written = 0;
125   for (auto c : text) {
126     if (IsWhitespace(c)) {
127       if (!in_whitespace) {
128         // Reduce all whitespace sequences to a single space.
129         in_whitespace = true;
130         result[chars_written++] = L' ';
131       }
132       if (trim_sequences_with_line_breaks && !already_trimmed &&
133           ((c == '\n') || (c == '\r'))) {
134         // Whitespace sequences containing CR or LF are eliminated entirely.
135         already_trimmed = true;
136         --chars_written;
137       }
138     } else {
139       // Non-whitespace characters are copied straight across.
140       in_whitespace = false;
141       already_trimmed = false;
142       result[chars_written++] = c;
143     }
144   }
145 
146   if (in_whitespace && !already_trimmed) {
147     // Any trailing whitespace is eliminated.
148     --chars_written;
149   }
150 
151   result.resize(chars_written);
152   return result;
153 }
154 
155 template <class Char>
DoIsStringASCII(const Char * characters,size_t length)156 bool DoIsStringASCII(const Char* characters, size_t length) {
157   // Bitmasks to detect non ASCII characters for character sizes of 8, 16 and 32
158   // bits.
159   constexpr MachineWord NonASCIIMasks[] = {
160       0, MachineWord(0x8080808080808080ULL), MachineWord(0xFF80FF80FF80FF80ULL),
161       0, MachineWord(0xFFFFFF80FFFFFF80ULL),
162   };
163 
164   if (!length)
165     return true;
166   constexpr MachineWord non_ascii_bit_mask = NonASCIIMasks[sizeof(Char)];
167   static_assert(non_ascii_bit_mask, "Error: Invalid Mask");
168   MachineWord all_char_bits = 0;
169   const Char* end = characters + length;
170 
171   // Prologue: align the input.
172   while (!IsMachineWordAligned(characters) && characters < end)
173     all_char_bits |= static_cast<MachineWord>(*characters++);
174   if (all_char_bits & non_ascii_bit_mask)
175     return false;
176 
177   // Compare the values of CPU word size.
178   constexpr size_t chars_per_word = sizeof(MachineWord) / sizeof(Char);
179   constexpr int batch_count = 16;
180   while (characters <= end - batch_count * chars_per_word) {
181     all_char_bits = 0;
182     for (int i = 0; i < batch_count; ++i) {
183       all_char_bits |= *(reinterpret_cast<const MachineWord*>(characters));
184       characters += chars_per_word;
185     }
186     if (all_char_bits & non_ascii_bit_mask)
187       return false;
188   }
189 
190   // Process the remaining words.
191   all_char_bits = 0;
192   while (characters <= end - chars_per_word) {
193     all_char_bits |= *(reinterpret_cast<const MachineWord*>(characters));
194     characters += chars_per_word;
195   }
196 
197   // Process the remaining bytes.
198   while (characters < end)
199     all_char_bits |= static_cast<MachineWord>(*characters++);
200 
201   return !(all_char_bits & non_ascii_bit_mask);
202 }
203 
204 template <bool (*Validator)(base_icu::UChar32)>
DoIsStringUTF8(StringPiece str)205 inline bool DoIsStringUTF8(StringPiece str) {
206   const uint8_t* src = reinterpret_cast<const uint8_t*>(str.data());
207   size_t src_len = str.length();
208   size_t char_index = 0;
209 
210   while (char_index < src_len) {
211     base_icu::UChar32 code_point;
212     CBU8_NEXT(src, char_index, src_len, code_point);
213     if (!Validator(code_point))
214       return false;
215   }
216   return true;
217 }
218 
219 template <typename T, typename CharT = typename T::value_type>
StartsWithT(T str,T search_for,CompareCase case_sensitivity)220 bool StartsWithT(T str, T search_for, CompareCase case_sensitivity) {
221   if (search_for.size() > str.size())
222     return false;
223 
224   std::basic_string_view<CharT> source = str.substr(0, search_for.size());
225 
226   switch (case_sensitivity) {
227     case CompareCase::SENSITIVE:
228       return source == search_for;
229 
230     case CompareCase::INSENSITIVE_ASCII:
231       return std::equal(search_for.begin(), search_for.end(), source.begin(),
232                         CaseInsensitiveCompareASCII<CharT>());
233   }
234 }
235 
236 template <typename T, typename CharT = typename T::value_type>
EndsWithT(T str,T search_for,CompareCase case_sensitivity)237 bool EndsWithT(T str, T search_for, CompareCase case_sensitivity) {
238   if (search_for.size() > str.size())
239     return false;
240 
241   std::basic_string_view<CharT> source =
242       str.substr(str.size() - search_for.size(), search_for.size());
243 
244   switch (case_sensitivity) {
245     case CompareCase::SENSITIVE:
246       return source == search_for;
247 
248     case CompareCase::INSENSITIVE_ASCII:
249       return std::equal(source.begin(), source.end(), search_for.begin(),
250                         CaseInsensitiveCompareASCII<CharT>());
251   }
252 }
253 
254 // A Matcher for DoReplaceMatchesAfterOffset() that matches substrings.
255 template <class CharT>
256 struct SubstringMatcher {
257   std::basic_string_view<CharT> find_this;
258 
FindSubstringMatcher259   size_t Find(const std::basic_string<CharT>& input, size_t pos) {
260     return input.find(find_this.data(), pos, find_this.length());
261   }
MatchSizeSubstringMatcher262   size_t MatchSize() { return find_this.length(); }
263 };
264 
265 // Type deduction helper for SubstringMatcher.
266 template <typename T, typename CharT = typename T::value_type>
MakeSubstringMatcher(T find_this)267 auto MakeSubstringMatcher(T find_this) {
268   return SubstringMatcher<CharT>{find_this};
269 }
270 
271 // A Matcher for DoReplaceMatchesAfterOffset() that matches single characters.
272 template <class CharT>
273 struct CharacterMatcher {
274   std::basic_string_view<CharT> find_any_of_these;
275 
FindCharacterMatcher276   size_t Find(const std::basic_string<CharT>& input, size_t pos) {
277     return input.find_first_of(find_any_of_these.data(), pos,
278                                find_any_of_these.length());
279   }
MatchSizeCharacterMatcher280   constexpr size_t MatchSize() { return 1; }
281 };
282 
283 // Type deduction helper for CharacterMatcher.
284 template <typename T, typename CharT = typename T::value_type>
MakeCharacterMatcher(T find_any_of_these)285 auto MakeCharacterMatcher(T find_any_of_these) {
286   return CharacterMatcher<CharT>{find_any_of_these};
287 }
288 
289 enum class ReplaceType { REPLACE_ALL, REPLACE_FIRST };
290 
291 // Runs in O(n) time in the length of |str|, and transforms the string without
292 // reallocating when possible. Returns |true| if any matches were found.
293 //
294 // This is parameterized on a |Matcher| traits type, so that it can be the
295 // implementation for both ReplaceChars() and ReplaceSubstringsAfterOffset().
296 template <typename Matcher, typename T, typename CharT = typename T::value_type>
DoReplaceMatchesAfterOffset(std::basic_string<CharT> * str,size_t initial_offset,Matcher matcher,T replace_with,ReplaceType replace_type)297 bool DoReplaceMatchesAfterOffset(std::basic_string<CharT>* str,
298                                  size_t initial_offset,
299                                  Matcher matcher,
300                                  T replace_with,
301                                  ReplaceType replace_type) {
302   using CharTraits = std::char_traits<CharT>;
303 
304   const size_t find_length = matcher.MatchSize();
305   if (!find_length)
306     return false;
307 
308   // If the find string doesn't appear, there's nothing to do.
309   size_t first_match = matcher.Find(*str, initial_offset);
310   if (first_match == std::basic_string<CharT>::npos)
311     return false;
312 
313   // If we're only replacing one instance, there's no need to do anything
314   // complicated.
315   const size_t replace_length = replace_with.length();
316   if (replace_type == ReplaceType::REPLACE_FIRST) {
317     str->replace(first_match, find_length, replace_with.data(), replace_length);
318     return true;
319   }
320 
321   // If the find and replace strings are the same length, we can simply use
322   // replace() on each instance, and finish the entire operation in O(n) time.
323   if (find_length == replace_length) {
324     auto* buffer = &((*str)[0]);
325     for (size_t offset = first_match; offset != std::basic_string<CharT>::npos;
326          offset = matcher.Find(*str, offset + replace_length)) {
327       CharTraits::copy(buffer + offset, replace_with.data(), replace_length);
328     }
329     return true;
330   }
331 
332   // Since the find and replace strings aren't the same length, a loop like the
333   // one above would be O(n^2) in the worst case, as replace() will shift the
334   // entire remaining string each time. We need to be more clever to keep things
335   // O(n).
336   //
337   // When the string is being shortened, it's possible to just shift the matches
338   // down in one pass while finding, and truncate the length at the end of the
339   // search.
340   //
341   // If the string is being lengthened, more work is required. The strategy used
342   // here is to make two find() passes through the string. The first pass counts
343   // the number of matches to determine the new size. The second pass will
344   // either construct the new string into a new buffer (if the existing buffer
345   // lacked capacity), or else -- if there is room -- create a region of scratch
346   // space after |first_match| by shifting the tail of the string to a higher
347   // index, and doing in-place moves from the tail to lower indices thereafter.
348   size_t str_length = str->length();
349   size_t expansion = 0;
350   if (replace_length > find_length) {
351     // This operation lengthens the string; determine the new length by counting
352     // matches.
353     const size_t expansion_per_match = (replace_length - find_length);
354     size_t num_matches = 0;
355     for (size_t match = first_match; match != std::basic_string<CharT>::npos;
356          match = matcher.Find(*str, match + find_length)) {
357       expansion += expansion_per_match;
358       ++num_matches;
359     }
360     const size_t final_length = str_length + expansion;
361 
362     if (str->capacity() < final_length) {
363       // If we'd have to allocate a new buffer to grow the string, build the
364       // result directly into the new allocation via append().
365       std::basic_string<CharT> src(str->get_allocator());
366       str->swap(src);
367       str->reserve(final_length);
368 
369       size_t pos = 0;
370       for (size_t match = first_match;; match = matcher.Find(src, pos)) {
371         str->append(src, pos, match - pos);
372         str->append(replace_with.data(), replace_length);
373         pos = match + find_length;
374 
375         // A mid-loop test/break enables skipping the final Find() call; the
376         // number of matches is known, so don't search past the last one.
377         if (!--num_matches)
378           break;
379       }
380 
381       // Handle substring after the final match.
382       str->append(src, pos, str_length - pos);
383       return true;
384     }
385 
386     // Prepare for the copy/move loop below -- expand the string to its final
387     // size by shifting the data after the first match to the end of the resized
388     // string.
389     size_t shift_src = first_match + find_length;
390     size_t shift_dst = shift_src + expansion;
391 
392     // Big |expansion| factors (relative to |str_length|) require padding up to
393     // |shift_dst|.
394     if (shift_dst > str_length)
395       str->resize(shift_dst);
396 
397     str->replace(shift_dst, str_length - shift_src, *str, shift_src,
398                  str_length - shift_src);
399     str_length = final_length;
400   }
401 
402   // We can alternate replacement and move operations. This won't overwrite the
403   // unsearched region of the string so long as |write_offset| <= |read_offset|;
404   // that condition is always satisfied because:
405   //
406   //   (a) If the string is being shortened, |expansion| is zero and
407   //       |write_offset| grows slower than |read_offset|.
408   //
409   //   (b) If the string is being lengthened, |write_offset| grows faster than
410   //       |read_offset|, but |expansion| is big enough so that |write_offset|
411   //       will only catch up to |read_offset| at the point of the last match.
412   auto* buffer = &((*str)[0]);
413   size_t write_offset = first_match;
414   size_t read_offset = first_match + expansion;
415   do {
416     if (replace_length) {
417       CharTraits::copy(buffer + write_offset, replace_with.data(),
418                        replace_length);
419       write_offset += replace_length;
420     }
421     read_offset += find_length;
422 
423     // min() clamps std::basic_string<CharT>::npos (the largest unsigned value)
424     // to str_length.
425     size_t match = std::min(matcher.Find(*str, read_offset), str_length);
426 
427     size_t length = match - read_offset;
428     if (length) {
429       CharTraits::move(buffer + write_offset, buffer + read_offset, length);
430       write_offset += length;
431       read_offset += length;
432     }
433   } while (read_offset < str_length);
434 
435   // If we're shortening the string, truncate it now.
436   str->resize(write_offset);
437   return true;
438 }
439 
440 template <typename T, typename CharT = typename T::value_type>
ReplaceCharsT(T input,T find_any_of_these,T replace_with,std::basic_string<CharT> * output)441 bool ReplaceCharsT(T input,
442                    T find_any_of_these,
443                    T replace_with,
444                    std::basic_string<CharT>* output) {
445   // Commonly, this is called with output and input being the same string; in
446   // that case, skip the copy.
447   if (input.data() != output->data() || input.size() != output->size())
448     output->assign(input.data(), input.size());
449 
450   return DoReplaceMatchesAfterOffset(output, 0,
451                                      MakeCharacterMatcher(find_any_of_these),
452                                      replace_with, ReplaceType::REPLACE_ALL);
453 }
454 
455 template <class string_type>
WriteIntoT(string_type * str,size_t length_with_null)456 inline typename string_type::value_type* WriteIntoT(string_type* str,
457                                                     size_t length_with_null) {
458   DCHECK_GE(length_with_null, 1u);
459   str->reserve(length_with_null);
460   str->resize(length_with_null - 1);
461   return &((*str)[0]);
462 }
463 
464 // Generic version for all JoinString overloads. |list_type| must be a sequence
465 // (base::span or std::initializer_list) of strings/StringPieces (std::string,
466 // std::u16string, StringPiece or StringPiece16). |CharT| is either char or
467 // char16_t.
468 template <typename list_type,
469           typename T,
470           typename CharT = typename T::value_type>
JoinStringT(list_type parts,T sep)471 static std::basic_string<CharT> JoinStringT(list_type parts, T sep) {
472   if (std::empty(parts))
473     return std::basic_string<CharT>();
474 
475   // Pre-allocate the eventual size of the string. Start with the size of all of
476   // the separators (note that this *assumes* parts.size() > 0).
477   size_t total_size = (parts.size() - 1) * sep.size();
478   for (const auto& part : parts)
479     total_size += part.size();
480   std::basic_string<CharT> result;
481   result.reserve(total_size);
482 
483   auto iter = parts.begin();
484   CHECK(iter != parts.end(), base::NotFatalUntil::M125);
485   result.append(*iter);
486   ++iter;
487 
488   for (; iter != parts.end(); ++iter) {
489     result.append(sep);
490     result.append(*iter);
491   }
492 
493   // Sanity-check that we pre-allocated correctly.
494   DCHECK_EQ(total_size, result.size());
495 
496   return result;
497 }
498 
499 // Replaces placeholders in `format_string` with values from `subst`.
500 // * `placeholder_prefix`: Allows using a specific character as the placeholder
501 // prefix. `base::ReplaceStringPlaceholders` uses '$'.
502 // * `should_escape_multiple_placeholder_prefixes`:
503 //   * If this parameter is `true`, which is the case with
504 //   `base::ReplaceStringPlaceholders`, `placeholder_prefix` characters are
505 //   replaced by that number less one. Eg $$->$, $$$->$$, etc.
506 //   * If this parameter is `false`, each literal `placeholder_prefix` character
507 //   in `format_string` is escaped with another `placeholder_prefix`. For
508 //   instance, with `%` as the `placeholder_prefix`: %%->%, %%%%->%%, etc.
509 // * `is_strict_mode`:
510 //   * If this parameter is `true`, error handling is stricter. The function
511 //   returns `std::nullopt` if:
512 //     * a placeholder %N is encountered where N > substitutions.size().
513 //     * a literal `%` is not escaped with a `%`.
514 template <typename T, typename CharT = typename T::value_type>
DoReplaceStringPlaceholders(T format_string,const std::vector<std::basic_string<CharT>> & subst,const CharT placeholder_prefix,const bool should_escape_multiple_placeholder_prefixes,const bool is_strict_mode,std::vector<size_t> * offsets)515 std::optional<std::basic_string<CharT>> DoReplaceStringPlaceholders(
516     T format_string,
517     const std::vector<std::basic_string<CharT>>& subst,
518     const CharT placeholder_prefix,
519     const bool should_escape_multiple_placeholder_prefixes,
520     const bool is_strict_mode,
521     std::vector<size_t>* offsets) {
522   size_t substitutions = subst.size();
523   DCHECK_LT(substitutions, 10U);
524 
525   size_t sub_length = 0;
526   for (const auto& cur : subst) {
527     sub_length += cur.length();
528   }
529 
530   std::basic_string<CharT> formatted;
531   formatted.reserve(format_string.length() + sub_length);
532 
533   std::vector<ReplacementOffset> r_offsets;
534   for (auto i = format_string.begin(); i != format_string.end(); ++i) {
535     if (placeholder_prefix == *i) {
536       if (i + 1 != format_string.end()) {
537         ++i;
538         if (placeholder_prefix == *i) {
539           do {
540             formatted.push_back(placeholder_prefix);
541             ++i;
542           } while (should_escape_multiple_placeholder_prefixes &&
543                    i != format_string.end() && placeholder_prefix == *i);
544           --i;
545         } else {
546           if (*i < '1' || *i > '9') {
547             if (is_strict_mode) {
548               DLOG(ERROR) << "Invalid placeholder after placeholder prefix: "
549                           << std::basic_string<CharT>(1, placeholder_prefix)
550                           << std::basic_string<CharT>(1, *i);
551               return std::nullopt;
552             }
553 
554             continue;
555           }
556           const size_t index = static_cast<size_t>(*i - '1');
557           if (offsets) {
558             ReplacementOffset r_offset(index, formatted.size());
559             r_offsets.insert(
560                 ranges::upper_bound(r_offsets, r_offset, &CompareParameter),
561                 r_offset);
562           }
563           if (index < substitutions) {
564             formatted.append(subst.at(index));
565           } else if (is_strict_mode) {
566             DLOG(ERROR) << "index out of range: " << index << ": "
567                         << substitutions;
568             return std::nullopt;
569           }
570         }
571       } else if (is_strict_mode) {
572         DLOG(ERROR) << "unexpected placeholder prefix at end of string";
573         return std::nullopt;
574       }
575     } else {
576       formatted.push_back(*i);
577     }
578   }
579   if (offsets) {
580     for (const auto& cur : r_offsets) {
581       offsets->push_back(cur.offset);
582     }
583   }
584   return formatted;
585 }
586 
587 // The following code is compatible with the OpenBSD lcpy interface.  See:
588 //   http://www.gratisoft.us/todd/papers/strlcpy.html
589 //   ftp://ftp.openbsd.org/pub/OpenBSD/src/lib/libc/string/{wcs,str}lcpy.c
590 
591 template <typename CHAR>
lcpyT(CHAR * dst,const CHAR * src,size_t dst_size)592 size_t lcpyT(CHAR* dst, const CHAR* src, size_t dst_size) {
593   for (size_t i = 0; i < dst_size; ++i) {
594     if ((dst[i] = src[i]) == 0)  // We hit and copied the terminating NULL.
595       return i;
596   }
597 
598   // We were left off at dst_size.  We over copied 1 byte.  Null terminate.
599   if (dst_size != 0)
600     dst[dst_size - 1] = 0;
601 
602   // Count the rest of the |src|, and return it's length in characters.
603   while (src[dst_size])
604     ++dst_size;
605   return dst_size;
606 }
607 
608 }  // namespace base::internal
609 
610 #endif  // BASE_STRINGS_STRING_UTIL_IMPL_HELPERS_H_
611