• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "base/strings/string_util.h"
6 
7 #include <ctype.h>
8 #include <errno.h>
9 #include <math.h>
10 #include <stdarg.h>
11 #include <stdint.h>
12 #include <stdio.h>
13 #include <stdlib.h>
14 #include <string.h>
15 #include <time.h>
16 #include <wchar.h>
17 #include <wctype.h>
18 
19 #include <algorithm>
20 #include <iterator>
21 #include <limits>
22 #include <vector>
23 
24 #include "base/logging.h"
25 #include "base/strings/utf_string_conversion_utils.h"
26 #include "base/strings/utf_string_conversions.h"
27 #include "base/third_party/icu/icu_utf.h"
28 #include "util/build_config.h"
29 
30 namespace base {
31 
32 namespace {
33 
34 // Used by ReplaceStringPlaceholders to track the position in the string of
35 // replaced parameters.
36 struct ReplacementOffset {
ReplacementOffsetbase::__anon7c2692690111::ReplacementOffset37   ReplacementOffset(uintptr_t parameter, size_t offset)
38       : parameter(parameter), offset(offset) {}
39 
40   // Index of the parameter.
41   uintptr_t parameter;
42 
43   // Starting position in the string.
44   size_t offset;
45 };
46 
CompareParameter(const ReplacementOffset & elem1,const ReplacementOffset & elem2)47 static bool CompareParameter(const ReplacementOffset& elem1,
48                              const ReplacementOffset& elem2) {
49   return elem1.parameter < elem2.parameter;
50 }
51 
52 // Assuming that a pointer is the size of a "machine word", then
53 // uintptr_t is an integer type that is also a machine word.
54 typedef uintptr_t MachineWord;
55 const uintptr_t kMachineWordAlignmentMask = sizeof(MachineWord) - 1;
56 
IsAlignedToMachineWord(const void * pointer)57 inline bool IsAlignedToMachineWord(const void* pointer) {
58   return !(reinterpret_cast<MachineWord>(pointer) & kMachineWordAlignmentMask);
59 }
60 
61 template <typename T>
AlignToMachineWord(T * pointer)62 inline T* AlignToMachineWord(T* pointer) {
63   return reinterpret_cast<T*>(reinterpret_cast<MachineWord>(pointer) &
64                               ~kMachineWordAlignmentMask);
65 }
66 
67 template <size_t size, typename CharacterType>
68 struct NonASCIIMask;
69 template <>
70 struct NonASCIIMask<4, char16_t> {
valuebase::__anon7c2692690111::NonASCIIMask71   static inline uint32_t value() { return 0xFF80FF80U; }
72 };
73 template <>
74 struct NonASCIIMask<4, char> {
valuebase::__anon7c2692690111::NonASCIIMask75   static inline uint32_t value() { return 0x80808080U; }
76 };
77 template <>
78 struct NonASCIIMask<8, char16_t> {
valuebase::__anon7c2692690111::NonASCIIMask79   static inline uint64_t value() { return 0xFF80FF80FF80FF80ULL; }
80 };
81 template <>
82 struct NonASCIIMask<8, char> {
valuebase::__anon7c2692690111::NonASCIIMask83   static inline uint64_t value() { return 0x8080808080808080ULL; }
84 };
85 
86 }  // namespace
87 
88 namespace {
89 
90 template <typename StringType>
ToLowerASCIIImpl(std::basic_string_view<typename StringType::value_type> str)91 StringType ToLowerASCIIImpl(
92     std::basic_string_view<typename StringType::value_type> str) {
93   StringType ret;
94   ret.reserve(str.size());
95   for (size_t i = 0; i < str.size(); i++)
96     ret.push_back(ToLowerASCII(str[i]));
97   return ret;
98 }
99 
100 template <typename StringType>
ToUpperASCIIImpl(std::basic_string_view<typename StringType::value_type> str)101 StringType ToUpperASCIIImpl(
102     std::basic_string_view<typename StringType::value_type> str) {
103   StringType ret;
104   ret.reserve(str.size());
105   for (size_t i = 0; i < str.size(); i++)
106     ret.push_back(ToUpperASCII(str[i]));
107   return ret;
108 }
109 
110 }  // namespace
111 
ToLowerASCII(std::string_view str)112 std::string ToLowerASCII(std::string_view str) {
113   return ToLowerASCIIImpl<std::string>(str);
114 }
115 
ToLowerASCII(std::u16string_view str)116 std::u16string ToLowerASCII(std::u16string_view str) {
117   return ToLowerASCIIImpl<std::u16string>(str);
118 }
119 
ToUpperASCII(std::string_view str)120 std::string ToUpperASCII(std::string_view str) {
121   return ToUpperASCIIImpl<std::string>(str);
122 }
123 
ToUpperASCII(std::u16string_view str)124 std::u16string ToUpperASCII(std::u16string_view str) {
125   return ToUpperASCIIImpl<std::u16string>(str);
126 }
127 
128 template <class StringType>
CompareCaseInsensitiveASCIIT(std::basic_string_view<typename StringType::value_type> a,std::basic_string_view<typename StringType::value_type> b)129 int CompareCaseInsensitiveASCIIT(
130     std::basic_string_view<typename StringType::value_type> a,
131     std::basic_string_view<typename StringType::value_type> b) {
132   // Find the first characters that aren't equal and compare them.  If the end
133   // of one of the strings is found before a nonequal character, the lengths
134   // of the strings are compared.
135   size_t i = 0;
136   while (i < a.length() && i < b.length()) {
137     typename StringType::value_type lower_a = ToLowerASCII(a[i]);
138     typename StringType::value_type lower_b = ToLowerASCII(b[i]);
139     if (lower_a < lower_b)
140       return -1;
141     if (lower_a > lower_b)
142       return 1;
143     i++;
144   }
145 
146   // End of one string hit before finding a different character. Expect the
147   // common case to be "strings equal" at this point so check that first.
148   if (a.length() == b.length())
149     return 0;
150 
151   if (a.length() < b.length())
152     return -1;
153   return 1;
154 }
155 
CompareCaseInsensitiveASCII(std::string_view a,std::string_view b)156 int CompareCaseInsensitiveASCII(std::string_view a, std::string_view b) {
157   return CompareCaseInsensitiveASCIIT<std::string>(a, b);
158 }
159 
CompareCaseInsensitiveASCII(std::u16string_view a,std::u16string_view b)160 int CompareCaseInsensitiveASCII(std::u16string_view a, std::u16string_view b) {
161   return CompareCaseInsensitiveASCIIT<std::u16string>(a, b);
162 }
163 
EqualsCaseInsensitiveASCII(std::string_view a,std::string_view b)164 bool EqualsCaseInsensitiveASCII(std::string_view a, std::string_view b) {
165   if (a.length() != b.length())
166     return false;
167   return CompareCaseInsensitiveASCIIT<std::string>(a, b) == 0;
168 }
169 
EqualsCaseInsensitiveASCII(std::u16string_view a,std::u16string_view b)170 bool EqualsCaseInsensitiveASCII(std::u16string_view a, std::u16string_view b) {
171   if (a.length() != b.length())
172     return false;
173   return CompareCaseInsensitiveASCIIT<std::u16string>(a, b) == 0;
174 }
175 
176 template <class StringType>
177 bool ReplaceCharsT(
178     const StringType& input,
179     std::basic_string_view<typename StringType::value_type> find_any_of_these,
180     std::basic_string_view<typename StringType::value_type> replace_with,
181     StringType* output);
182 
ReplaceChars(const std::u16string & input,std::u16string_view replace_chars,const std::u16string & replace_with,std::u16string * output)183 bool ReplaceChars(const std::u16string& input,
184                   std::u16string_view replace_chars,
185                   const std::u16string& replace_with,
186                   std::u16string* output) {
187   return ReplaceCharsT(input, replace_chars, std::u16string_view(replace_with),
188                        output);
189 }
190 
ReplaceChars(const std::string & input,std::string_view replace_chars,const std::string & replace_with,std::string * output)191 bool ReplaceChars(const std::string& input,
192                   std::string_view replace_chars,
193                   const std::string& replace_with,
194                   std::string* output) {
195   return ReplaceCharsT(input, replace_chars, std::string_view(replace_with),
196                        output);
197 }
198 
RemoveChars(const std::u16string & input,std::u16string_view remove_chars,std::u16string * output)199 bool RemoveChars(const std::u16string& input,
200                  std::u16string_view remove_chars,
201                  std::u16string* output) {
202   return ReplaceCharsT(input, remove_chars, std::u16string_view(), output);
203 }
204 
RemoveChars(const std::string & input,std::string_view remove_chars,std::string * output)205 bool RemoveChars(const std::string& input,
206                  std::string_view remove_chars,
207                  std::string* output) {
208   return ReplaceCharsT(input, remove_chars, std::string_view(), output);
209 }
210 
211 template <typename Str>
TrimStringT(const Str & input,std::basic_string_view<typename Str::value_type> trim_chars,TrimPositions positions,Str * output)212 TrimPositions TrimStringT(
213     const Str& input,
214     std::basic_string_view<typename Str::value_type> trim_chars,
215     TrimPositions positions,
216     Str* output) {
217   // Find the edges of leading/trailing whitespace as desired. Need to use
218   // a std::string_view version of input to be able to call find* on it with the
219   // std::string_view version of trim_chars (normally the trim_chars will be a
220   // constant so avoid making a copy).
221   std::basic_string_view<typename Str::value_type> input_piece(input);
222   const size_t last_char = input.length() - 1;
223   const size_t first_good_char = (positions & TRIM_LEADING)
224                                      ? input_piece.find_first_not_of(trim_chars)
225                                      : 0;
226   const size_t last_good_char = (positions & TRIM_TRAILING)
227                                     ? input_piece.find_last_not_of(trim_chars)
228                                     : last_char;
229 
230   // When the string was all trimmed, report that we stripped off characters
231   // from whichever position the caller was interested in. For empty input, we
232   // stripped no characters, but we still need to clear |output|.
233   if (input.empty() || (first_good_char == Str::npos) ||
234       (last_good_char == Str::npos)) {
235     bool input_was_empty = input.empty();  // in case output == &input
236     output->clear();
237     return input_was_empty ? TRIM_NONE : positions;
238   }
239 
240   // Trim.
241   *output = input.substr(first_good_char, last_good_char - first_good_char + 1);
242 
243   // Return where we trimmed from.
244   return static_cast<TrimPositions>(
245       ((first_good_char == 0) ? TRIM_NONE : TRIM_LEADING) |
246       ((last_good_char == last_char) ? TRIM_NONE : TRIM_TRAILING));
247 }
248 
TrimString(const std::u16string & input,std::u16string_view trim_chars,std::u16string * output)249 bool TrimString(const std::u16string& input,
250                 std::u16string_view trim_chars,
251                 std::u16string* output) {
252   return TrimStringT(input, trim_chars, TRIM_ALL, output) != TRIM_NONE;
253 }
254 
TrimString(const std::string & input,std::string_view trim_chars,std::string * output)255 bool TrimString(const std::string& input,
256                 std::string_view trim_chars,
257                 std::string* output) {
258   return TrimStringT(input, trim_chars, TRIM_ALL, output) != TRIM_NONE;
259 }
260 
261 template <typename char_type>
TrimStringPieceT(std::basic_string_view<char_type> input,std::basic_string_view<char_type> trim_chars,TrimPositions positions)262 std::basic_string_view<char_type> TrimStringPieceT(
263     std::basic_string_view<char_type> input,
264     std::basic_string_view<char_type> trim_chars,
265     TrimPositions positions) {
266   size_t begin =
267       (positions & TRIM_LEADING) ? input.find_first_not_of(trim_chars) : 0;
268   if (begin == std::basic_string_view<char_type>::npos)
269     return std::basic_string_view<char_type>();  // All trimmed.
270 
271   size_t end = (positions & TRIM_TRAILING)
272                    ? input.find_last_not_of(trim_chars) + 1
273                    : input.size();
274   return input.substr(begin, end - begin);
275 }
276 
TrimString(std::u16string_view input,std::u16string_view trim_chars,TrimPositions positions)277 std::u16string_view TrimString(std::u16string_view input,
278                                std::u16string_view trim_chars,
279                                TrimPositions positions) {
280   return TrimStringPieceT(input, trim_chars, positions);
281 }
282 
TrimString(std::string_view input,std::string_view trim_chars,TrimPositions positions)283 std::string_view TrimString(std::string_view input,
284                             std::string_view trim_chars,
285                             TrimPositions positions) {
286   return TrimStringPieceT(input, trim_chars, positions);
287 }
288 
TruncateUTF8ToByteSize(const std::string & input,const size_t byte_size,std::string * output)289 void TruncateUTF8ToByteSize(const std::string& input,
290                             const size_t byte_size,
291                             std::string* output) {
292   DCHECK(output);
293   if (byte_size > input.length()) {
294     *output = input;
295     return;
296   }
297   DCHECK_LE(byte_size,
298             static_cast<uint32_t>(std::numeric_limits<int32_t>::max()));
299   // Note: This cast is necessary because CBU8_NEXT uses int32_ts.
300   int32_t truncation_length = static_cast<int32_t>(byte_size);
301   int32_t char_index = truncation_length - 1;
302   const char* data = input.data();
303 
304   // Using CBU8, we will move backwards from the truncation point
305   // to the beginning of the string looking for a valid UTF8
306   // character.  Once a full UTF8 character is found, we will
307   // truncate the string to the end of that character.
308   while (char_index >= 0) {
309     int32_t prev = char_index;
310     base_icu::UChar32 code_point = 0;
311     CBU8_NEXT(data, char_index, truncation_length, code_point);
312     if (!IsValidCharacter(code_point) || !IsValidCodepoint(code_point)) {
313       char_index = prev - 1;
314     } else {
315       break;
316     }
317   }
318 
319   if (char_index >= 0)
320     *output = input.substr(0, char_index);
321   else
322     output->clear();
323 }
324 
TrimWhitespace(const std::u16string & input,TrimPositions positions,std::u16string * output)325 TrimPositions TrimWhitespace(const std::u16string& input,
326                              TrimPositions positions,
327                              std::u16string* output) {
328   return TrimStringT(input, std::u16string_view(kWhitespaceUTF16), positions,
329                      output);
330 }
331 
TrimWhitespace(std::u16string_view input,TrimPositions positions)332 std::u16string_view TrimWhitespace(std::u16string_view input,
333                                    TrimPositions positions) {
334   return TrimStringPieceT(input, std::u16string_view(kWhitespaceUTF16),
335                           positions);
336 }
337 
TrimWhitespaceASCII(const std::string & input,TrimPositions positions,std::string * output)338 TrimPositions TrimWhitespaceASCII(const std::string& input,
339                                   TrimPositions positions,
340                                   std::string* output) {
341   return TrimStringT(input, std::string_view(kWhitespaceASCII), positions,
342                      output);
343 }
344 
TrimWhitespaceASCII(std::string_view input,TrimPositions positions)345 std::string_view TrimWhitespaceASCII(std::string_view input,
346                                      TrimPositions positions) {
347   return TrimStringPieceT(input, std::string_view(kWhitespaceASCII), positions);
348 }
349 
350 template <typename STR>
CollapseWhitespaceT(const STR & text,bool trim_sequences_with_line_breaks)351 STR CollapseWhitespaceT(const STR& text, bool trim_sequences_with_line_breaks) {
352   STR result;
353   result.resize(text.size());
354 
355   // Set flags to pretend we're already in a trimmed whitespace sequence, so we
356   // will trim any leading whitespace.
357   bool in_whitespace = true;
358   bool already_trimmed = true;
359 
360   int chars_written = 0;
361   for (typename STR::const_iterator i(text.begin()); i != text.end(); ++i) {
362     if (IsUnicodeWhitespace(*i)) {
363       if (!in_whitespace) {
364         // Reduce all whitespace sequences to a single space.
365         in_whitespace = true;
366         result[chars_written++] = L' ';
367       }
368       if (trim_sequences_with_line_breaks && !already_trimmed &&
369           ((*i == '\n') || (*i == '\r'))) {
370         // Whitespace sequences containing CR or LF are eliminated entirely.
371         already_trimmed = true;
372         --chars_written;
373       }
374     } else {
375       // Non-whitespace characters are copied straight across.
376       in_whitespace = false;
377       already_trimmed = false;
378       result[chars_written++] = *i;
379     }
380   }
381 
382   if (in_whitespace && !already_trimmed) {
383     // Any trailing whitespace is eliminated.
384     --chars_written;
385   }
386 
387   result.resize(chars_written);
388   return result;
389 }
390 
CollapseWhitespace(const std::u16string & text,bool trim_sequences_with_line_breaks)391 std::u16string CollapseWhitespace(const std::u16string& text,
392                                   bool trim_sequences_with_line_breaks) {
393   return CollapseWhitespaceT(text, trim_sequences_with_line_breaks);
394 }
395 
CollapseWhitespaceASCII(const std::string & text,bool trim_sequences_with_line_breaks)396 std::string CollapseWhitespaceASCII(const std::string& text,
397                                     bool trim_sequences_with_line_breaks) {
398   return CollapseWhitespaceT(text, trim_sequences_with_line_breaks);
399 }
400 
ContainsOnlyChars(std::string_view input,std::string_view characters)401 bool ContainsOnlyChars(std::string_view input, std::string_view characters) {
402   return input.find_first_not_of(characters) == std::string_view::npos;
403 }
404 
ContainsOnlyChars(std::u16string_view input,std::u16string_view characters)405 bool ContainsOnlyChars(std::u16string_view input,
406                        std::u16string_view characters) {
407   return input.find_first_not_of(characters) == std::u16string_view::npos;
408 }
409 
410 template <class Char>
DoIsStringASCII(const Char * characters,size_t length)411 inline bool DoIsStringASCII(const Char* characters, size_t length) {
412   MachineWord all_char_bits = 0;
413   const Char* end = characters + length;
414 
415   // Prologue: align the input.
416   while (!IsAlignedToMachineWord(characters) && characters != end) {
417     all_char_bits |= *characters;
418     ++characters;
419   }
420 
421   // Compare the values of CPU word size.
422   const Char* word_end = AlignToMachineWord(end);
423   const size_t loop_increment = sizeof(MachineWord) / sizeof(Char);
424   while (characters < word_end) {
425     all_char_bits |= *(reinterpret_cast<const MachineWord*>(characters));
426     characters += loop_increment;
427   }
428 
429   // Process the remaining bytes.
430   while (characters != end) {
431     all_char_bits |= *characters;
432     ++characters;
433   }
434 
435   MachineWord non_ascii_bit_mask =
436       NonASCIIMask<sizeof(MachineWord), Char>::value();
437   return !(all_char_bits & non_ascii_bit_mask);
438 }
439 
IsStringASCII(std::string_view str)440 bool IsStringASCII(std::string_view str) {
441   return DoIsStringASCII(str.data(), str.length());
442 }
443 
IsStringASCII(std::u16string_view str)444 bool IsStringASCII(std::u16string_view str) {
445   return DoIsStringASCII(str.data(), str.length());
446 }
447 
IsStringUTF8(std::string_view str)448 bool IsStringUTF8(std::string_view str) {
449   const char* src = str.data();
450   int32_t src_len = static_cast<int32_t>(str.length());
451   int32_t char_index = 0;
452 
453   while (char_index < src_len) {
454     int32_t code_point;
455     CBU8_NEXT(src, char_index, src_len, code_point);
456     if (!IsValidCharacter(code_point))
457       return false;
458   }
459   return true;
460 }
461 
462 // Implementation note: Normally this function will be called with a hardcoded
463 // constant for the lowercase_ascii parameter. Constructing a std::string_view
464 // from a C constant requires running strlen, so the result will be two passes
465 // through the buffers, one to file the length of lowercase_ascii, and one to
466 // compare each letter.
467 //
468 // This function could have taken a const char* to avoid this and only do one
469 // pass through the string. But the strlen is faster than the case-insensitive
470 // compares and lets us early-exit in the case that the strings are different
471 // lengths (will often be the case for non-matches). So whether one approach or
472 // the other will be faster depends on the case.
473 //
474 // The hardcoded strings are typically very short so it doesn't matter, and the
475 // string piece gives additional flexibility for the caller (doesn't have to be
476 // null terminated) so we choose the std::string_view route.
477 template <typename Str>
DoLowerCaseEqualsASCII(std::basic_string_view<typename Str::value_type> str,std::string_view lowercase_ascii)478 static inline bool DoLowerCaseEqualsASCII(
479     std::basic_string_view<typename Str::value_type> str,
480     std::string_view lowercase_ascii) {
481   if (str.size() != lowercase_ascii.size())
482     return false;
483   for (size_t i = 0; i < str.size(); i++) {
484     if (ToLowerASCII(str[i]) != lowercase_ascii[i])
485       return false;
486   }
487   return true;
488 }
489 
LowerCaseEqualsASCII(std::string_view str,std::string_view lowercase_ascii)490 bool LowerCaseEqualsASCII(std::string_view str,
491                           std::string_view lowercase_ascii) {
492   return DoLowerCaseEqualsASCII<std::string>(str, lowercase_ascii);
493 }
494 
LowerCaseEqualsASCII(std::u16string_view str,std::string_view lowercase_ascii)495 bool LowerCaseEqualsASCII(std::u16string_view str,
496                           std::string_view lowercase_ascii) {
497   return DoLowerCaseEqualsASCII<std::u16string>(str, lowercase_ascii);
498 }
499 
EqualsASCII(std::u16string_view str,std::string_view ascii)500 bool EqualsASCII(std::u16string_view str, std::string_view ascii) {
501   if (str.length() != ascii.length())
502     return false;
503   return std::equal(ascii.begin(), ascii.end(), str.begin());
504 }
505 
506 template <typename char_type>
StartsWithT(std::basic_string_view<char_type> str,std::basic_string_view<char_type> search_for,CompareCase case_sensitivity)507 bool StartsWithT(std::basic_string_view<char_type> str,
508                  std::basic_string_view<char_type> search_for,
509                  CompareCase case_sensitivity) {
510   if (search_for.size() > str.size())
511     return false;
512 
513   std::basic_string_view<char_type> source = str.substr(0, search_for.size());
514 
515   switch (case_sensitivity) {
516     case CompareCase::SENSITIVE:
517       return source == search_for;
518 
519     case CompareCase::INSENSITIVE_ASCII:
520       return std::equal(search_for.begin(), search_for.end(), source.begin(),
521                         CaseInsensitiveCompareASCII<char_type>());
522 
523     default:
524       NOTREACHED();
525       return false;
526   }
527 }
528 
StartsWith(std::string_view str,std::string_view search_for,CompareCase case_sensitivity)529 bool StartsWith(std::string_view str,
530                 std::string_view search_for,
531                 CompareCase case_sensitivity) {
532   return StartsWithT(str, search_for, case_sensitivity);
533 }
534 
StartsWith(std::u16string_view str,std::u16string_view search_for,CompareCase case_sensitivity)535 bool StartsWith(std::u16string_view str,
536                 std::u16string_view search_for,
537                 CompareCase case_sensitivity) {
538   return StartsWithT(str, search_for, case_sensitivity);
539 }
540 
541 template <typename Str>
EndsWithT(std::basic_string_view<typename Str::value_type> str,std::basic_string_view<typename Str::value_type> search_for,CompareCase case_sensitivity)542 bool EndsWithT(std::basic_string_view<typename Str::value_type> str,
543                std::basic_string_view<typename Str::value_type> search_for,
544                CompareCase case_sensitivity) {
545   if (search_for.size() > str.size())
546     return false;
547 
548   std::basic_string_view<typename Str::value_type> source =
549       str.substr(str.size() - search_for.size(), search_for.size());
550 
551   switch (case_sensitivity) {
552     case CompareCase::SENSITIVE:
553       return source == search_for;
554 
555     case CompareCase::INSENSITIVE_ASCII:
556       return std::equal(
557           source.begin(), source.end(), search_for.begin(),
558           CaseInsensitiveCompareASCII<typename Str::value_type>());
559 
560     default:
561       NOTREACHED();
562       return false;
563   }
564 }
565 
EndsWith(std::string_view str,std::string_view search_for,CompareCase case_sensitivity)566 bool EndsWith(std::string_view str,
567               std::string_view search_for,
568               CompareCase case_sensitivity) {
569   return EndsWithT<std::string>(str, search_for, case_sensitivity);
570 }
571 
EndsWith(std::u16string_view str,std::u16string_view search_for,CompareCase case_sensitivity)572 bool EndsWith(std::u16string_view str,
573               std::u16string_view search_for,
574               CompareCase case_sensitivity) {
575   return EndsWithT<std::u16string>(str, search_for, case_sensitivity);
576 }
577 
HexDigitToInt(char16_t c)578 char HexDigitToInt(char16_t c) {
579   DCHECK(IsHexDigit(c));
580   if (c >= '0' && c <= '9')
581     return static_cast<char>(c - '0');
582   if (c >= 'A' && c <= 'F')
583     return static_cast<char>(c - 'A' + 10);
584   if (c >= 'a' && c <= 'f')
585     return static_cast<char>(c - 'a' + 10);
586   return 0;
587 }
588 
IsUnicodeWhitespace(char16_t c)589 bool IsUnicodeWhitespace(char16_t c) {
590   // kWhitespaceWide is a NULL-terminated string
591   for (const char16_t* cur = kWhitespaceUTF16; *cur; ++cur) {
592     if (*cur == c)
593       return true;
594   }
595   return false;
596 }
597 
598 static const char* const kByteStringsUnlocalized[] = {" B",  " kB", " MB",
599                                                       " GB", " TB", " PB"};
600 
FormatBytesUnlocalized(int64_t bytes)601 std::u16string FormatBytesUnlocalized(int64_t bytes) {
602   double unit_amount = static_cast<double>(bytes);
603   size_t dimension = 0;
604   const int kKilo = 1024;
605   while (unit_amount >= kKilo &&
606          dimension < std::size(kByteStringsUnlocalized) - 1) {
607     unit_amount /= kKilo;
608     dimension++;
609   }
610 
611   char buf[64];
612   if (bytes != 0 && dimension > 0 && unit_amount < 100) {
613     base::snprintf(buf, std::size(buf), "%.1lf%s", unit_amount,
614                    kByteStringsUnlocalized[dimension]);
615   } else {
616     base::snprintf(buf, std::size(buf), "%.0lf%s", unit_amount,
617                    kByteStringsUnlocalized[dimension]);
618   }
619 
620   return ASCIIToUTF16(buf);
621 }
622 
623 // A Matcher for DoReplaceMatchesAfterOffset() that matches substrings.
624 template <class StringType>
625 struct SubstringMatcher {
626   std::basic_string_view<typename StringType::value_type> find_this;
627 
Findbase::SubstringMatcher628   size_t Find(const StringType& input, size_t pos) {
629     return input.find(find_this.data(), pos, find_this.length());
630   }
MatchSizebase::SubstringMatcher631   size_t MatchSize() { return find_this.length(); }
632 };
633 
634 // A Matcher for DoReplaceMatchesAfterOffset() that matches single characters.
635 template <class StringType>
636 struct CharacterMatcher {
637   std::basic_string_view<typename StringType::value_type> find_any_of_these;
638 
Findbase::CharacterMatcher639   size_t Find(const StringType& input, size_t pos) {
640     return input.find_first_of(find_any_of_these.data(), pos,
641                                find_any_of_these.length());
642   }
MatchSizebase::CharacterMatcher643   constexpr size_t MatchSize() { return 1; }
644 };
645 
646 enum class ReplaceType { REPLACE_ALL, REPLACE_FIRST };
647 
648 // Runs in O(n) time in the length of |str|, and transforms the string without
649 // reallocating when possible. Returns |true| if any matches were found.
650 //
651 // This is parameterized on a |Matcher| traits type, so that it can be the
652 // implementation for both ReplaceChars() and ReplaceSubstringsAfterOffset().
653 template <class StringType, class Matcher>
DoReplaceMatchesAfterOffset(StringType * str,size_t initial_offset,Matcher matcher,std::basic_string_view<typename StringType::value_type> replace_with,ReplaceType replace_type)654 bool DoReplaceMatchesAfterOffset(
655     StringType* str,
656     size_t initial_offset,
657     Matcher matcher,
658     std::basic_string_view<typename StringType::value_type> replace_with,
659     ReplaceType replace_type) {
660   using CharTraits = typename StringType::traits_type;
661 
662   const size_t find_length = matcher.MatchSize();
663   if (!find_length)
664     return false;
665 
666   // If the find string doesn't appear, there's nothing to do.
667   size_t first_match = matcher.Find(*str, initial_offset);
668   if (first_match == StringType::npos)
669     return false;
670 
671   // If we're only replacing one instance, there's no need to do anything
672   // complicated.
673   const size_t replace_length = replace_with.length();
674   if (replace_type == ReplaceType::REPLACE_FIRST) {
675     str->replace(first_match, find_length, replace_with.data(), replace_length);
676     return true;
677   }
678 
679   // If the find and replace strings are the same length, we can simply use
680   // replace() on each instance, and finish the entire operation in O(n) time.
681   if (find_length == replace_length) {
682     auto* buffer = &((*str)[0]);
683     for (size_t offset = first_match; offset != StringType::npos;
684          offset = matcher.Find(*str, offset + replace_length)) {
685       CharTraits::copy(buffer + offset, replace_with.data(), replace_length);
686     }
687     return true;
688   }
689 
690   // Since the find and replace strings aren't the same length, a loop like the
691   // one above would be O(n^2) in the worst case, as replace() will shift the
692   // entire remaining string each time. We need to be more clever to keep things
693   // O(n).
694   //
695   // When the string is being shortened, it's possible to just shift the matches
696   // down in one pass while finding, and truncate the length at the end of the
697   // search.
698   //
699   // If the string is being lengthened, more work is required. The strategy used
700   // here is to make two find() passes through the string. The first pass counts
701   // the number of matches to determine the new size. The second pass will
702   // either construct the new string into a new buffer (if the existing buffer
703   // lacked capacity), or else -- if there is room -- create a region of scratch
704   // space after |first_match| by shifting the tail of the string to a higher
705   // index, and doing in-place moves from the tail to lower indices thereafter.
706   size_t str_length = str->length();
707   size_t expansion = 0;
708   if (replace_length > find_length) {
709     // This operation lengthens the string; determine the new length by counting
710     // matches.
711     const size_t expansion_per_match = (replace_length - find_length);
712     size_t num_matches = 0;
713     for (size_t match = first_match; match != StringType::npos;
714          match = matcher.Find(*str, match + find_length)) {
715       expansion += expansion_per_match;
716       ++num_matches;
717     }
718     const size_t final_length = str_length + expansion;
719 
720     if (str->capacity() < final_length) {
721       // If we'd have to allocate a new buffer to grow the string, build the
722       // result directly into the new allocation via append().
723       StringType src(str->get_allocator());
724       str->swap(src);
725       str->reserve(final_length);
726 
727       size_t pos = 0;
728       for (size_t match = first_match;; match = matcher.Find(src, pos)) {
729         str->append(src, pos, match - pos);
730         str->append(replace_with.data(), replace_length);
731         pos = match + find_length;
732 
733         // A mid-loop test/break enables skipping the final Find() call; the
734         // number of matches is known, so don't search past the last one.
735         if (!--num_matches)
736           break;
737       }
738 
739       // Handle substring after the final match.
740       str->append(src, pos, str_length - pos);
741       return true;
742     }
743 
744     // Prepare for the copy/move loop below -- expand the string to its final
745     // size by shifting the data after the first match to the end of the resized
746     // string.
747     size_t shift_src = first_match + find_length;
748     size_t shift_dst = shift_src + expansion;
749 
750     // Big |expansion| factors (relative to |str_length|) require padding up to
751     // |shift_dst|.
752     if (shift_dst > str_length)
753       str->resize(shift_dst);
754 
755     str->replace(shift_dst, str_length - shift_src, *str, shift_src,
756                  str_length - shift_src);
757     str_length = final_length;
758   }
759 
760   // We can alternate replacement and move operations. This won't overwrite the
761   // unsearched region of the string so long as |write_offset| <= |read_offset|;
762   // that condition is always satisfied because:
763   //
764   //   (a) If the string is being shortened, |expansion| is zero and
765   //       |write_offset| grows slower than |read_offset|.
766   //
767   //   (b) If the string is being lengthened, |write_offset| grows faster than
768   //       |read_offset|, but |expansion| is big enough so that |write_offset|
769   //       will only catch up to |read_offset| at the point of the last match.
770   auto* buffer = &((*str)[0]);
771   size_t write_offset = first_match;
772   size_t read_offset = first_match + expansion;
773   do {
774     if (replace_length) {
775       CharTraits::copy(buffer + write_offset, replace_with.data(),
776                        replace_length);
777       write_offset += replace_length;
778     }
779     read_offset += find_length;
780 
781     // min() clamps StringType::npos (the largest unsigned value) to str_length.
782     size_t match = std::min(matcher.Find(*str, read_offset), str_length);
783 
784     size_t length = match - read_offset;
785     if (length) {
786       CharTraits::move(buffer + write_offset, buffer + read_offset, length);
787       write_offset += length;
788       read_offset += length;
789     }
790   } while (read_offset < str_length);
791 
792   // If we're shortening the string, truncate it now.
793   str->resize(write_offset);
794   return true;
795 }
796 
797 template <class StringType>
ReplaceCharsT(const StringType & input,std::basic_string_view<typename StringType::value_type> find_any_of_these,std::basic_string_view<typename StringType::value_type> replace_with,StringType * output)798 bool ReplaceCharsT(
799     const StringType& input,
800     std::basic_string_view<typename StringType::value_type> find_any_of_these,
801     std::basic_string_view<typename StringType::value_type> replace_with,
802     StringType* output) {
803   // Commonly, this is called with output and input being the same string; in
804   // that case, this assignment is inexpensive.
805   *output = input;
806 
807   return DoReplaceMatchesAfterOffset(
808       output, 0, CharacterMatcher<StringType>{find_any_of_these}, replace_with,
809       ReplaceType::REPLACE_ALL);
810 }
811 
ReplaceFirstSubstringAfterOffset(std::u16string * str,size_t start_offset,std::u16string_view find_this,std::u16string_view replace_with)812 void ReplaceFirstSubstringAfterOffset(std::u16string* str,
813                                       size_t start_offset,
814                                       std::u16string_view find_this,
815                                       std::u16string_view replace_with) {
816   DoReplaceMatchesAfterOffset(str, start_offset,
817                               SubstringMatcher<std::u16string>{find_this},
818                               replace_with, ReplaceType::REPLACE_FIRST);
819 }
820 
ReplaceFirstSubstringAfterOffset(std::string * str,size_t start_offset,std::string_view find_this,std::string_view replace_with)821 void ReplaceFirstSubstringAfterOffset(std::string* str,
822                                       size_t start_offset,
823                                       std::string_view find_this,
824                                       std::string_view replace_with) {
825   DoReplaceMatchesAfterOffset(str, start_offset,
826                               SubstringMatcher<std::string>{find_this},
827                               replace_with, ReplaceType::REPLACE_FIRST);
828 }
829 
ReplaceSubstringsAfterOffset(std::u16string * str,size_t start_offset,std::u16string_view find_this,std::u16string_view replace_with)830 void ReplaceSubstringsAfterOffset(std::u16string* str,
831                                   size_t start_offset,
832                                   std::u16string_view find_this,
833                                   std::u16string_view replace_with) {
834   DoReplaceMatchesAfterOffset(str, start_offset,
835                               SubstringMatcher<std::u16string>{find_this},
836                               replace_with, ReplaceType::REPLACE_ALL);
837 }
838 
ReplaceSubstringsAfterOffset(std::string * str,size_t start_offset,std::string_view find_this,std::string_view replace_with)839 void ReplaceSubstringsAfterOffset(std::string* str,
840                                   size_t start_offset,
841                                   std::string_view find_this,
842                                   std::string_view replace_with) {
843   DoReplaceMatchesAfterOffset(str, start_offset,
844                               SubstringMatcher<std::string>{find_this},
845                               replace_with, ReplaceType::REPLACE_ALL);
846 }
847 
848 template <class string_type>
WriteIntoT(string_type * str,size_t length_with_null)849 inline typename string_type::value_type* WriteIntoT(string_type* str,
850                                                     size_t length_with_null) {
851   DCHECK_GT(length_with_null, 1u);
852   str->reserve(length_with_null);
853   str->resize(length_with_null - 1);
854   return &((*str)[0]);
855 }
856 
WriteInto(std::string * str,size_t length_with_null)857 char* WriteInto(std::string* str, size_t length_with_null) {
858   return WriteIntoT(str, length_with_null);
859 }
860 
WriteInto(std::u16string * str,size_t length_with_null)861 char16_t* WriteInto(std::u16string* str, size_t length_with_null) {
862   return WriteIntoT(str, length_with_null);
863 }
864 
865 #if defined(_MSC_VER) && !defined(__clang__)
866 // Work around VC++ code-gen bug. https://crbug.com/804884
867 #pragma optimize("", off)
868 #endif
869 
870 // Generic version for all JoinString overloads. |list_type| must be a sequence
871 // (std::vector or std::initializer_list) of strings/string_views of any type.
872 template <typename char_type, typename list_type>
JoinStringT(const list_type & parts,std::basic_string_view<char_type> sep)873 static std::basic_string<char_type> JoinStringT(
874     const list_type& parts,
875     std::basic_string_view<char_type> sep) {
876   if (parts.size() == 0)
877     return std::basic_string<char_type>();
878 
879   // Pre-allocate the eventual size of the string. Start with the size of all of
880   // the separators (note that this *assumes* parts.size() > 0).
881   size_t total_size = (parts.size() - 1) * sep.size();
882   for (const auto& part : parts)
883     total_size += part.size();
884   std::basic_string<char_type> result;
885   result.reserve(total_size);
886 
887   auto iter = parts.begin();
888   DCHECK(iter != parts.end());
889   result.append(*iter);
890   ++iter;
891 
892   for (; iter != parts.end(); ++iter) {
893     result.append(sep);
894     result.append(*iter);
895   }
896 
897   // Sanity-check that we pre-allocated correctly.
898   DCHECK_EQ(total_size, result.size());
899 
900   return result;
901 }
902 
JoinString(const std::vector<std::string> & parts,std::string_view separator)903 std::string JoinString(const std::vector<std::string>& parts,
904                        std::string_view separator) {
905   return JoinStringT(parts, separator);
906 }
907 
JoinString(const std::vector<std::u16string> & parts,std::u16string_view separator)908 std::u16string JoinString(const std::vector<std::u16string>& parts,
909                           std::u16string_view separator) {
910   return JoinStringT(parts, separator);
911 }
912 
913 #if defined(_MSC_VER) && !defined(__clang__)
914 // Work around VC++ code-gen bug. https://crbug.com/804884
915 #pragma optimize("", on)
916 #endif
917 
JoinString(const std::vector<std::string_view> & parts,std::string_view separator)918 std::string JoinString(const std::vector<std::string_view>& parts,
919                        std::string_view separator) {
920   return JoinStringT(parts, separator);
921 }
922 
JoinString(const std::vector<std::u16string_view> & parts,std::u16string_view separator)923 std::u16string JoinString(const std::vector<std::u16string_view>& parts,
924                           std::u16string_view separator) {
925   return JoinStringT(parts, separator);
926 }
927 
JoinString(std::initializer_list<std::string_view> parts,std::string_view separator)928 std::string JoinString(std::initializer_list<std::string_view> parts,
929                        std::string_view separator) {
930   return JoinStringT(parts, separator);
931 }
932 
JoinString(std::initializer_list<std::u16string_view> parts,std::u16string_view separator)933 std::u16string JoinString(std::initializer_list<std::u16string_view> parts,
934                           std::u16string_view separator) {
935   return JoinStringT(parts, separator);
936 }
937 
938 template <class FormatStringType, class OutStringType>
DoReplaceStringPlaceholders(const FormatStringType & format_string,const std::vector<OutStringType> & subst,std::vector<size_t> * offsets)939 OutStringType DoReplaceStringPlaceholders(
940     const FormatStringType& format_string,
941     const std::vector<OutStringType>& subst,
942     std::vector<size_t>* offsets) {
943   size_t substitutions = subst.size();
944   DCHECK_LT(substitutions, 10U);
945 
946   size_t sub_length = 0;
947   for (const auto& cur : subst)
948     sub_length += cur.length();
949 
950   OutStringType formatted;
951   formatted.reserve(format_string.length() + sub_length);
952 
953   std::vector<ReplacementOffset> r_offsets;
954   for (auto i = format_string.begin(); i != format_string.end(); ++i) {
955     if ('$' == *i) {
956       if (i + 1 != format_string.end()) {
957         ++i;
958         if ('$' == *i) {
959           while (i != format_string.end() && '$' == *i) {
960             formatted.push_back('$');
961             ++i;
962           }
963           --i;
964         } else {
965           if (*i < '1' || *i > '9') {
966             DLOG(ERROR) << "Invalid placeholder: $" << *i;
967             continue;
968           }
969           uintptr_t index = *i - '1';
970           if (offsets) {
971             ReplacementOffset r_offset(index,
972                                        static_cast<int>(formatted.size()));
973             r_offsets.insert(
974                 std::upper_bound(r_offsets.begin(), r_offsets.end(), r_offset,
975                                  &CompareParameter),
976                 r_offset);
977           }
978           if (index < substitutions)
979             formatted.append(subst.at(index));
980         }
981       }
982     } else {
983       formatted.push_back(*i);
984     }
985   }
986   if (offsets) {
987     for (const auto& cur : r_offsets)
988       offsets->push_back(cur.offset);
989   }
990   return formatted;
991 }
992 
ReplaceStringPlaceholders(const std::u16string & format_string,const std::vector<std::u16string> & subst,std::vector<size_t> * offsets)993 std::u16string ReplaceStringPlaceholders(
994     const std::u16string& format_string,
995     const std::vector<std::u16string>& subst,
996     std::vector<size_t>* offsets) {
997   return DoReplaceStringPlaceholders(format_string, subst, offsets);
998 }
999 
ReplaceStringPlaceholders(std::string_view format_string,const std::vector<std::string> & subst,std::vector<size_t> * offsets)1000 std::string ReplaceStringPlaceholders(std::string_view format_string,
1001                                       const std::vector<std::string>& subst,
1002                                       std::vector<size_t>* offsets) {
1003   return DoReplaceStringPlaceholders(format_string, subst, offsets);
1004 }
1005 
ReplaceStringPlaceholders(const std::u16string & format_string,const std::u16string & a,size_t * offset)1006 std::u16string ReplaceStringPlaceholders(const std::u16string& format_string,
1007                                          const std::u16string& a,
1008                                          size_t* offset) {
1009   std::vector<size_t> offsets;
1010   std::vector<std::u16string> subst;
1011   subst.push_back(a);
1012   std::u16string result =
1013       ReplaceStringPlaceholders(format_string, subst, &offsets);
1014 
1015   DCHECK_EQ(1U, offsets.size());
1016   if (offset)
1017     *offset = offsets[0];
1018   return result;
1019 }
1020 
1021 // The following code is compatible with the OpenBSD lcpy interface.  See:
1022 //   http://www.gratisoft.us/todd/papers/strlcpy.html
1023 //   ftp://ftp.openbsd.org/pub/OpenBSD/src/lib/libc/string/{wcs,str}lcpy.c
1024 
1025 namespace {
1026 
1027 template <typename CHAR>
lcpyT(CHAR * dst,const CHAR * src,size_t dst_size)1028 size_t lcpyT(CHAR* dst, const CHAR* src, size_t dst_size) {
1029   for (size_t i = 0; i < dst_size; ++i) {
1030     if ((dst[i] = src[i]) == 0)  // We hit and copied the terminating NULL.
1031       return i;
1032   }
1033 
1034   // We were left off at dst_size.  We over copied 1 byte.  Null terminate.
1035   if (dst_size != 0)
1036     dst[dst_size - 1] = 0;
1037 
1038   // Count the rest of the |src|, and return it's length in characters.
1039   while (src[dst_size])
1040     ++dst_size;
1041   return dst_size;
1042 }
1043 
1044 }  // namespace
1045 
1046 }  // namespace base
1047