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