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