Branch data Line data Source code
1 : : // Copyright (c) 2009-2010 Satoshi Nakamoto
2 : : // Copyright (c) 2009-present The Bitcoin Core developers
3 : : // Distributed under the MIT software license, see the accompanying
4 : : // file COPYING or http://www.opensource.org/licenses/mit-license.php.
5 : :
6 : : #include <util/strencodings.h>
7 : :
8 : : #include <crypto/hex_base.h>
9 : : #include <span.h>
10 : : #include <util/check.h>
11 : : #include <util/overflow.h>
12 : :
13 : : #include <limits>
14 : : #include <optional>
15 : : #include <sstream>
16 : : #include <string>
17 : : #include <vector>
18 : :
19 : : static const std::string CHARS_ALPHA_NUM = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
20 : :
21 : : static const std::string SAFE_CHARS[] =
22 : : {
23 : : CHARS_ALPHA_NUM + " .,;-_/:?@()", // SAFE_CHARS_DEFAULT
24 : : CHARS_ALPHA_NUM + " .,;-_?@", // SAFE_CHARS_UA_COMMENT
25 : : CHARS_ALPHA_NUM + ".-_", // SAFE_CHARS_FILENAME
26 : : CHARS_ALPHA_NUM + "!*'();:@&=+$,/?#[]-_.~%", // SAFE_CHARS_URI
27 : : };
28 : :
29 : 56332 : std::string SanitizeString(std::string_view str, int rule)
30 : : {
31 : 56332 : std::string result;
32 [ + + ]: 458969 : for (char c : str) {
33 [ + + ]: 402637 : if (SAFE_CHARS[rule].find(c) != std::string::npos) {
34 [ + - ]: 181998 : result.push_back(c);
35 : : }
36 : : }
37 : 56332 : return result;
38 : 0 : }
39 : :
40 : 424227 : bool IsHex(std::string_view str)
41 : : {
42 [ + + ]: 380023161 : for (char c : str) {
43 [ + + ]: 379734654 : if (HexDigit(c) < 0) return false;
44 : : }
45 [ + + + + ]: 288507 : return (str.size() > 0) && (str.size()%2 == 0);
46 : : }
47 : :
48 : : template <typename Byte>
49 : 770942 : std::optional<std::vector<Byte>> TryParseHex(std::string_view str)
50 : : {
51 [ + - ]: 770942 : std::vector<Byte> vch;
52 [ + - ]: 770942 : vch.reserve(str.size() / 2); // two hex characters form a single byte
53 : :
54 : 770942 : auto it = str.begin();
55 : 770942 : while (it != str.end()) {
56 [ + + ]: 190244052 : if (IsSpace(*it)) {
57 : 6354 : ++it;
58 : 6354 : continue;
59 : : }
60 [ + - ]: 190237698 : auto c1 = HexDigit(*(it++));
61 [ + + ]: 190237698 : if (it == str.end()) return std::nullopt;
62 [ + - ]: 190237654 : auto c2 = HexDigit(*(it++));
63 [ + + ]: 190237654 : if (c1 < 0 || c2 < 0) return std::nullopt;
64 [ + - + + ]: 381252440 : vch.push_back(Byte(c1 << 4) | Byte(c2));
65 : : }
66 : 770816 : return vch;
67 : 770942 : }
68 : : template std::optional<std::vector<std::byte>> TryParseHex(std::string_view);
69 : : template std::optional<std::vector<uint8_t>> TryParseHex(std::string_view);
70 : :
71 : 22660 : bool SplitHostPort(std::string_view in, uint16_t& portOut, std::string& hostOut)
72 : : {
73 : 22660 : bool valid = false;
74 [ + + ]: 22660 : size_t colon = in.find_last_of(':');
75 : : // if a : is found, and it either follows a [...], or no other : is in the string, treat it as port separator
76 : 22660 : bool fHaveColon = colon != in.npos;
77 [ + + + + : 22660 : bool fBracketed = fHaveColon && (in[0] == '[' && in[colon - 1] == ']'); // if there is a colon, and in[0]=='[', colon is not 0, so in[colon-1] is safe
+ + ]
78 [ + + + + ]: 22660 : bool fMultiColon{fHaveColon && colon != 0 && (in.find_last_of(':', colon - 1) != in.npos)};
79 [ + + + + : 22660 : if (fHaveColon && (colon == 0 || fBracketed || !fMultiColon)) {
+ + ]
80 [ + + ]: 2418 : if (const auto n{ToIntegral<uint16_t>(in.substr(colon + 1))}) {
81 : 195 : in = in.substr(0, colon);
82 : 195 : portOut = *n;
83 : 195 : valid = (portOut != 0);
84 : : }
85 : : } else {
86 : : valid = true;
87 : : }
88 [ + + + + : 22660 : if (in.size() > 0 && in[0] == '[' && in[in.size() - 1] == ']') {
+ + ]
89 : 110 : hostOut = in.substr(1, in.size() - 2);
90 : : } else {
91 : 22550 : hostOut = in;
92 : : }
93 : :
94 : 22660 : return valid;
95 : : }
96 : :
97 : 38927 : std::string EncodeBase64(std::span<const unsigned char> input)
98 : : {
99 : 38927 : static const char *pbase64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
100 : :
101 [ + - ]: 38927 : std::string str;
102 [ + - ]: 38927 : str.reserve(CeilDiv(input.size(), 3u) * 4);
103 [ + - ]: 33036246 : ConvertBits<8, 6, true>([&](int v) { str += pbase64[v]; }, input.begin(), input.end());
104 [ + - - + : 77788 : while (str.size() % 4) str += '=';
+ + ]
105 : 38927 : return str;
106 : 0 : }
107 : :
108 : 18916 : std::optional<std::vector<unsigned char>> DecodeBase64(std::string_view str)
109 : : {
110 : 18916 : static const int8_t decode64_table[256]{
111 : : -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
112 : : -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
113 : : -1, -1, -1, 62, -1, -1, -1, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1,
114 : : -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
115 : : 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, -1, 26, 27, 28,
116 : : 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48,
117 : : 49, 50, 51, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
118 : : -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
119 : : -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
120 : : -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
121 : : -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
122 : : -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
123 : : -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1
124 : : };
125 : :
126 [ + + ]: 18916 : if (str.size() % 4 != 0) return {};
127 : : /* One or two = characters at the end are permitted. */
128 [ + + + + ]: 18749 : if (str.size() >= 1 && str.back() == '=') str.remove_suffix(1);
129 [ + + + + ]: 18749 : if (str.size() >= 1 && str.back() == '=') str.remove_suffix(1);
130 : :
131 : 18749 : std::vector<unsigned char> ret;
132 [ + - ]: 18749 : ret.reserve((str.size() * 3) / 4);
133 [ + - ]: 18749 : bool valid = ConvertBits<6, 8, false>(
134 : 45560766 : [&](unsigned char c) { ret.push_back(c); },
135 : : str.begin(), str.end(),
136 [ + + ]: 60753188 : [](char c) { return decode64_table[uint8_t(c)]; }
137 : : );
138 [ + + ]: 18749 : if (!valid) return {};
139 : :
140 : 18660 : return ret;
141 : 18749 : }
142 : :
143 : 207269 : std::string EncodeBase32(std::span<const unsigned char> input, bool pad)
144 : : {
145 : 207269 : static const char *pbase32 = "abcdefghijklmnopqrstuvwxyz234567";
146 : :
147 [ + - ]: 207269 : std::string str;
148 [ + - ]: 207269 : str.reserve(CeilDiv(input.size(), 5u) * 8);
149 [ + - ]: 13931099 : ConvertBits<8, 5, true>([&](int v) { str += pbase32[v]; }, input.begin(), input.end());
150 [ + + ]: 207269 : if (pad) {
151 [ + + ]: 78203 : while (str.size() % 8) {
152 [ + - - + ]: 96021 : str += '=';
153 : : }
154 : : }
155 : 207269 : return str;
156 : 0 : }
157 : :
158 : 7029 : std::string EncodeBase32(std::string_view str, bool pad)
159 : : {
160 : 7029 : return EncodeBase32(MakeUCharSpan(str), pad);
161 : : }
162 : :
163 : 28769 : std::optional<std::vector<unsigned char>> DecodeBase32(std::string_view str)
164 : : {
165 : 28769 : static const int8_t decode32_table[256]{
166 : : -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
167 : : -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
168 : : -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 26, 27, 28, 29, 30, 31, -1, -1, -1, -1,
169 : : -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
170 : : 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, -1, 0, 1, 2,
171 : : 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22,
172 : : 23, 24, 25, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
173 : : -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
174 : : -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
175 : : -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
176 : : -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
177 : : -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
178 : : -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1
179 : : };
180 : :
181 [ + + ]: 28769 : if (str.size() % 8 != 0) return {};
182 : : /* 1, 3, 4, or 6 padding '=' suffix characters are permitted. */
183 [ + + + + ]: 10256 : if (str.size() >= 1 && str.back() == '=') str.remove_suffix(1);
184 [ + + + + ]: 10256 : if (str.size() >= 2 && str.substr(str.size() - 2) == "==") str.remove_suffix(2);
185 [ + + + + ]: 10256 : if (str.size() >= 1 && str.back() == '=') str.remove_suffix(1);
186 [ + + + + ]: 10256 : if (str.size() >= 2 && str.substr(str.size() - 2) == "==") str.remove_suffix(2);
187 : :
188 : 10256 : std::vector<unsigned char> ret;
189 [ + - ]: 10256 : ret.reserve((str.size() * 5) / 8);
190 [ + - ]: 10256 : bool valid = ConvertBits<5, 8, false>(
191 : 388027 : [&](unsigned char c) { ret.push_back(c); },
192 : : str.begin(), str.end(),
193 [ + + ]: 628050 : [](char c) { return decode32_table[uint8_t(c)]; }
194 : : );
195 : :
196 [ + + ]: 10256 : if (!valid) return {};
197 : :
198 : 9334 : return ret;
199 : 10256 : }
200 : :
201 : 5464 : std::string FormatParagraph(std::string_view in, size_t width, size_t indent)
202 : : {
203 [ - + ]: 5464 : assert(width >= indent);
204 : 5464 : std::stringstream out;
205 : 5464 : size_t ptr = 0;
206 : 5464 : size_t indented = 0;
207 [ + + ]: 13541 : while (ptr < in.size())
208 : : {
209 [ + + ]: 3702 : size_t lineend = in.find_first_of('\n', ptr);
210 [ + + ]: 3702 : if (lineend == std::string::npos) {
211 : 3238 : lineend = in.size();
212 : : }
213 : 3702 : const size_t linelen = lineend - ptr;
214 : 3702 : const size_t rem_width = width - indented;
215 [ + + ]: 3702 : if (linelen <= rem_width) {
216 [ + - ]: 2179 : out << in.substr(ptr, linelen + 1);
217 : 2179 : ptr = lineend + 1;
218 : 2179 : indented = 0;
219 : : } else {
220 : 1523 : size_t finalspace = in.find_last_of(" \n", ptr + rem_width);
221 [ + + ]: 1523 : if (finalspace == std::string::npos || finalspace < ptr) {
222 : : // No place to break; just include the entire word and move on
223 : 1230 : finalspace = in.find_first_of("\n ", ptr);
224 [ + + ]: 1230 : if (finalspace == std::string::npos) {
225 : : // End of the string, just add it and break
226 [ + - ]: 1089 : out << in.substr(ptr);
227 : : break;
228 : : }
229 : : }
230 [ + - + - ]: 868 : out << in.substr(ptr, finalspace - ptr) << "\n";
231 [ + + ]: 434 : if (in[finalspace] == '\n') {
232 : : indented = 0;
233 [ + + ]: 370 : } else if (indent) {
234 [ + - - + ]: 512 : out << std::string(indent, ' ');
235 : 256 : indented = indent;
236 : : }
237 : 434 : ptr = finalspace + 1;
238 : : }
239 : : }
240 [ + - ]: 10928 : return out.str();
241 : 5464 : }
242 : :
243 : : /** Upper bound for mantissa.
244 : : * 10^18-1 is the largest arbitrary decimal that will fit in a signed 64-bit integer.
245 : : * Larger integers cannot consist of arbitrary combinations of 0-9:
246 : : *
247 : : * 999999999999999999 1^18-1
248 : : * 9223372036854775807 (1<<63)-1 (max int64_t)
249 : : * 9999999999999999999 1^19-1 (would overflow)
250 : : */
251 : : static const int64_t UPPER_BOUND = 1000000000000000000LL - 1LL;
252 : :
253 : : /** Helper function for ParseFixedPoint */
254 : 2205814 : static inline bool ProcessMantissaDigit(char ch, int64_t &mantissa, int &mantissa_tzeros)
255 : : {
256 [ + + ]: 2205814 : if(ch == '0')
257 : 2203271 : ++mantissa_tzeros;
258 : : else {
259 [ + + ]: 507766 : for (int i=0; i<=mantissa_tzeros; ++i) {
260 [ + + ]: 505279 : if (mantissa > (UPPER_BOUND / 10LL))
261 : : return false; /* overflow */
262 : 505223 : mantissa *= 10;
263 : : }
264 : 2487 : mantissa += ch - '0';
265 : 2487 : mantissa_tzeros = 0;
266 : : }
267 : : return true;
268 : : }
269 : :
270 : 5468 : bool ParseFixedPoint(std::string_view val, int decimals, int64_t *amount_out)
271 : : {
272 : 5468 : int64_t mantissa = 0;
273 : 5468 : int64_t exponent = 0;
274 : 5468 : int mantissa_tzeros = 0;
275 : 5468 : bool mantissa_sign = false;
276 : 5468 : bool exponent_sign = false;
277 : 5468 : int ptr = 0;
278 [ + + ]: 5468 : int end = val.size();
279 : 5468 : int point_ofs = 0;
280 : :
281 [ + + + + ]: 5468 : if (ptr < end && val[ptr] == '-') {
282 : : mantissa_sign = true;
283 : : ++ptr;
284 : : }
285 [ + + ]: 5468 : if (ptr < end)
286 : : {
287 [ + + ]: 5433 : if (val[ptr] == '0') {
288 : : /* pass single 0 */
289 : 675 : ++ptr;
290 [ + + + + ]: 4758 : } else if (val[ptr] >= '1' && val[ptr] <= '9') {
291 [ + + + + ]: 1597849 : while (ptr < end && IsDigit(val[ptr])) {
292 [ + + ]: 1597547 : if (!ProcessMantissaDigit(val[ptr], mantissa, mantissa_tzeros))
293 : : return false; /* overflow */
294 : 1597509 : ++ptr;
295 : : }
296 : : } else return false; /* missing expected digit */
297 : : } else return false; /* empty string or loose '-' */
298 [ + + + + ]: 977 : if (ptr < end && val[ptr] == '.')
299 : : {
300 : 740 : ++ptr;
301 [ + + + + ]: 740 : if (ptr < end && IsDigit(val[ptr]))
302 : : {
303 [ + + + + ]: 608960 : while (ptr < end && IsDigit(val[ptr])) {
304 [ + + ]: 608267 : if (!ProcessMantissaDigit(val[ptr], mantissa, mantissa_tzeros))
305 : : return false; /* overflow */
306 : 608249 : ++ptr;
307 : 608249 : ++point_ofs;
308 : : }
309 : : } else return false; /* missing expected digit */
310 : : }
311 [ + + + + : 930 : if (ptr < end && (val[ptr] == 'e' || val[ptr] == 'E'))
+ + ]
312 : : {
313 : 153 : ++ptr;
314 [ + + + + ]: 153 : if (ptr < end && val[ptr] == '+')
315 : 12 : ++ptr;
316 [ + + + + ]: 141 : else if (ptr < end && val[ptr] == '-') {
317 : 26 : exponent_sign = true;
318 : 26 : ++ptr;
319 : : }
320 [ + + + + ]: 153 : if (ptr < end && IsDigit(val[ptr])) {
321 [ + + + + ]: 32758 : while (ptr < end && IsDigit(val[ptr])) {
322 [ + + ]: 32650 : if (exponent > (UPPER_BOUND / 10LL))
323 : : return false; /* overflow */
324 : 32639 : exponent = exponent * 10 + val[ptr] - '0';
325 : 32639 : ++ptr;
326 : : }
327 : : } else return false; /* missing expected digit */
328 : : }
329 [ + + ]: 885 : if (ptr != end)
330 : : return false; /* trailing garbage */
331 : :
332 : : /* finalize exponent */
333 [ + + ]: 782 : if (exponent_sign)
334 : 19 : exponent = -exponent;
335 : 782 : exponent = exponent - point_ofs + mantissa_tzeros;
336 : :
337 : : /* finalize mantissa */
338 [ + + ]: 782 : if (mantissa_sign)
339 : 37 : mantissa = -mantissa;
340 : :
341 : : /* convert to one 64-bit fixed-point value */
342 : 782 : exponent += decimals;
343 [ + + ]: 782 : if (exponent < 0)
344 : : return false; /* cannot represent values smaller than 10^-decimals */
345 [ + + ]: 743 : if (exponent >= 18)
346 : : return false; /* cannot represent values larger than or equal to 10^(18-decimals) */
347 : :
348 [ + + ]: 5468 : for (int i=0; i < exponent; ++i) {
349 [ + + ]: 4795 : if (mantissa > (UPPER_BOUND / 10LL) || mantissa < -(UPPER_BOUND / 10LL))
350 : : return false; /* overflow */
351 : 4758 : mantissa *= 10;
352 : : }
353 [ + - ]: 673 : if (mantissa > UPPER_BOUND || mantissa < -UPPER_BOUND)
354 : : return false; /* overflow */
355 : :
356 [ + - ]: 673 : if (amount_out)
357 : 673 : *amount_out = mantissa;
358 : :
359 : : return true;
360 : : }
361 : :
362 : 24020 : std::string ToLower(std::string_view str)
363 : : {
364 [ + - ]: 24020 : std::string r;
365 [ + - ]: 24020 : r.reserve(str.size());
366 [ + + + - : 44047506 : for (auto ch : str) r += ToLower(ch);
+ + ]
367 : 24020 : return r;
368 : 0 : }
369 : :
370 : 6187 : std::string ToUpper(std::string_view str)
371 : : {
372 [ + - ]: 6187 : std::string r;
373 [ + - ]: 6187 : r.reserve(str.size());
374 [ + + + - : 110525 : for (auto ch : str) r += ToUpper(ch);
+ + ]
375 : 6187 : return r;
376 : 0 : }
377 : :
378 : 1501 : std::string Capitalize(std::string str)
379 : : {
380 [ + + ]: 1501 : if (str.empty()) return str;
381 [ + + ]: 2614 : str[0] = ToUpper(str.front());
382 : 1472 : return str;
383 : : }
384 : :
385 : 1238 : std::optional<uint64_t> ParseByteUnits(std::string_view str, ByteUnit default_multiplier)
386 : : {
387 [ + + ]: 1238 : if (str.empty()) {
388 : 29 : return std::nullopt;
389 : : }
390 : 1209 : auto multiplier = default_multiplier;
391 [ + + + + : 1209 : char unit = str.back();
+ + + +
+ ]
392 [ + + + + : 1209 : switch (unit) {
+ + + +
+ ]
393 : : case 'k':
394 : : multiplier = ByteUnit::k;
395 : : break;
396 : 1 : case 'K':
397 : 1 : multiplier = ByteUnit::K;
398 : 1 : break;
399 : 3 : case 'm':
400 : 3 : multiplier = ByteUnit::m;
401 : 3 : break;
402 : 2 : case 'M':
403 : 2 : multiplier = ByteUnit::M;
404 : 2 : break;
405 : 9 : case 'g':
406 : 9 : multiplier = ByteUnit::g;
407 : 9 : break;
408 : 3 : case 'G':
409 : 3 : multiplier = ByteUnit::G;
410 : 3 : break;
411 : 23 : case 't':
412 : 23 : multiplier = ByteUnit::t;
413 : 23 : break;
414 : 1 : case 'T':
415 : 1 : multiplier = ByteUnit::T;
416 : 1 : break;
417 : 1164 : default:
418 : 1164 : unit = 0;
419 : 1164 : break;
420 : : }
421 : :
422 : 1251 : uint64_t unit_amount = static_cast<uint64_t>(multiplier);
423 : 1209 : auto parsed_num = ToIntegral<uint64_t>(unit ? str.substr(0, str.size() - 1) : str);
424 [ + + + + ]: 1209 : if (!parsed_num || parsed_num > std::numeric_limits<uint64_t>::max() / unit_amount) { // check overflow
425 : 1192 : return std::nullopt;
426 : : }
427 : 17 : return *parsed_num * unit_amount;
428 : : }
429 : :
430 : 31 : bool CaseInsensitiveEqual(std::string_view s1, std::string_view s2)
431 : : {
432 [ + - ]: 31 : if (s1.size() != s2.size()) return false;
433 [ + + ]: 709 : for (size_t i = 0; i < s1.size(); ++i) {
434 [ + + ]: 678 : char c1 = s1[i];
435 [ + + ]: 678 : if (c1 >= 'A' && c1 <= 'Z') c1 -= ('A' - 'a');
436 [ - + ]: 678 : char c2 = s2[i];
437 [ - + ]: 678 : if (c2 >= 'A' && c2 <= 'Z') c2 -= ('A' - 'a');
438 [ + - ]: 678 : if (c1 != c2) return false;
439 : : }
440 : : return true;
441 : : }
|