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