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