1 // Copyright 2022 The Chromium Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 #include "string_util.h"
16 
17 #include <algorithm>
18 #include <iomanip>
19 #include <sstream>
20 #include <string>
21 
22 #include <openssl/base64.h>
23 #include <openssl/mem.h>
24 
25 BSSL_NAMESPACE_BEGIN
26 namespace string_util {
27 
IsAscii(std::string_view str)28 bool IsAscii(std::string_view str) {
29   for (unsigned char c : str) {
30     if (c > 127) {
31       return false;
32     }
33   }
34   return true;
35 }
36 
IsEqualNoCase(std::string_view str1,std::string_view str2)37 bool IsEqualNoCase(std::string_view str1, std::string_view str2) {
38   return std::equal(str1.begin(), str1.end(), str2.begin(), str2.end(),
39                     [](const unsigned char a, const unsigned char b) {
40                       return OPENSSL_tolower(a) == OPENSSL_tolower(b);
41                     });
42 }
43 
EndsWithNoCase(std::string_view str,std::string_view suffix)44 bool EndsWithNoCase(std::string_view str, std::string_view suffix) {
45   return suffix.size() <= str.size() &&
46          IsEqualNoCase(suffix, str.substr(str.size() - suffix.size()));
47 }
48 
StartsWithNoCase(std::string_view str,std::string_view prefix)49 bool StartsWithNoCase(std::string_view str, std::string_view prefix) {
50   return prefix.size() <= str.size() &&
51          IsEqualNoCase(prefix, str.substr(0, prefix.size()));
52 }
53 
FindAndReplace(std::string_view str,std::string_view find,std::string_view replace)54 std::string FindAndReplace(std::string_view str, std::string_view find,
55                            std::string_view replace) {
56   std::string ret;
57 
58   if (find.empty()) {
59     return std::string(str);
60   }
61   while (!str.empty()) {
62     size_t index = str.find(find);
63     if (index == std::string_view::npos) {
64       ret.append(str);
65       break;
66     }
67     ret.append(str.substr(0, index));
68     ret.append(replace);
69     str = str.substr(index + find.size());
70   }
71   return ret;
72 }
73 
74 // TODO(bbe) get rid of this once we can c++20.
EndsWith(std::string_view str,std::string_view suffix)75 bool EndsWith(std::string_view str, std::string_view suffix) {
76   return suffix.size() <= str.size() &&
77          suffix == str.substr(str.size() - suffix.size());
78 }
79 
80 // TODO(bbe) get rid of this once we can c++20.
StartsWith(std::string_view str,std::string_view prefix)81 bool StartsWith(std::string_view str, std::string_view prefix) {
82   return prefix.size() <= str.size() && prefix == str.substr(0, prefix.size());
83 }
84 
HexEncode(Span<const uint8_t> data)85 std::string HexEncode(Span<const uint8_t> data) {
86   std::ostringstream out;
87   for (uint8_t b : data) {
88     out << std::hex << std::setfill('0') << std::setw(2) << std::uppercase
89         << int{b};
90   }
91   return out.str();
92 }
93 
94 // TODO(bbe) get rid of this once extracted to boringssl. Everything else
95 // in third_party uses std::to_string
NumberToDecimalString(int i)96 std::string NumberToDecimalString(int i) {
97   std::ostringstream out;
98   out << std::dec << i;
99   return out.str();
100 }
101 
SplitString(std::string_view str,char split_char)102 std::vector<std::string_view> SplitString(std::string_view str,
103                                           char split_char) {
104   std::vector<std::string_view> out;
105 
106   if (str.empty()) {
107     return out;
108   }
109 
110   while (true) {
111     // Find end of current token
112     size_t i = str.find(split_char);
113 
114     // Add current token
115     out.push_back(str.substr(0, i));
116 
117     if (i == str.npos) {
118       // That was the last token
119       break;
120     }
121     // Continue to next
122     str = str.substr(i + 1);
123   }
124 
125   return out;
126 }
127 
IsUnicodeWhitespace(char c)128 static bool IsUnicodeWhitespace(char c) {
129   return c == 9 || c == 10 || c == 11 || c == 12 || c == 13 || c == ' ';
130 }
131 
CollapseWhitespaceASCII(std::string_view text,bool trim_sequences_with_line_breaks)132 std::string CollapseWhitespaceASCII(std::string_view text,
133                                     bool trim_sequences_with_line_breaks) {
134   std::string result;
135   result.resize(text.size());
136 
137   // Set flags to pretend we're already in a trimmed whitespace sequence, so we
138   // will trim any leading whitespace.
139   bool in_whitespace = true;
140   bool already_trimmed = true;
141 
142   int chars_written = 0;
143   for (auto i = text.begin(); i != text.end(); ++i) {
144     if (IsUnicodeWhitespace(*i)) {
145       if (!in_whitespace) {
146         // Reduce all whitespace sequences to a single space.
147         in_whitespace = true;
148         result[chars_written++] = L' ';
149       }
150       if (trim_sequences_with_line_breaks && !already_trimmed &&
151           ((*i == '\n') || (*i == '\r'))) {
152         // Whitespace sequences containing CR or LF are eliminated entirely.
153         already_trimmed = true;
154         --chars_written;
155       }
156     } else {
157       // Non-whitespace chracters are copied straight across.
158       in_whitespace = false;
159       already_trimmed = false;
160       result[chars_written++] = *i;
161     }
162   }
163 
164   if (in_whitespace && !already_trimmed) {
165     // Any trailing whitespace is eliminated.
166     --chars_written;
167   }
168 
169   result.resize(chars_written);
170   return result;
171 }
172 
Base64Encode(const std::string_view & input,std::string * output)173 bool Base64Encode(const std::string_view &input, std::string *output) {
174   size_t len;
175   if (!EVP_EncodedLength(&len, input.size())) {
176     return false;
177   }
178   std::vector<char> encoded(len);
179   len = EVP_EncodeBlock(reinterpret_cast<uint8_t *>(encoded.data()),
180                         reinterpret_cast<const uint8_t *>(input.data()),
181                         input.size());
182   if (!len) {
183     return false;
184   }
185   output->assign(encoded.data(), len);
186   return true;
187 }
188 
Base64Decode(const std::string_view & input,std::string * output)189 bool Base64Decode(const std::string_view &input, std::string *output) {
190   size_t len;
191   if (!EVP_DecodedLength(&len, input.size())) {
192     return false;
193   }
194   std::vector<char> decoded(len);
195   if (!EVP_DecodeBase64(reinterpret_cast<uint8_t *>(decoded.data()), &len, len,
196                         reinterpret_cast<const uint8_t *>(input.data()),
197                         input.size())) {
198     return false;
199   }
200   output->assign(decoded.data(), len);
201   return true;
202 }
203 
204 }  // namespace string_util
205 BSSL_NAMESPACE_END
206