Line data Source code
1 : // Copyright (c) 2019-2023 Made to Order Software Corp. All Rights Reserved
2 : //
3 : // https://snapwebsites.org/project/safepasswords
4 : // contact@m2osw.com
5 : //
6 : // This program is free software: you can redistribute it and/or modify
7 : // it under the terms of the GNU General Public License as published by
8 : // the Free Software Foundation, either version 3 of the License, or
9 : // (at your option) any later version.
10 : //
11 : // This program is distributed in the hope that it will be useful,
12 : // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 : // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 : // GNU General Public License for more details.
15 : //
16 : // You should have received a copy of the GNU General Public License
17 : // along with this program. If not, see <https://www.gnu.org/licenses/>.
18 :
19 : // self
20 : //
21 : #include "catch_main.h"
22 :
23 :
24 : // safepasswords
25 : //
26 : #include <safepasswords/string.h>
27 :
28 : #include <safepasswords/exception.h>
29 :
30 :
31 : // snapdev
32 : //
33 : #include <snapdev/not_used.h>
34 :
35 :
36 : // libutf8
37 : //
38 : #include <libutf8/libutf8.h>
39 :
40 :
41 : // C++
42 : //
43 : #include <set>
44 :
45 :
46 : // C
47 : //
48 : #include <malloc.h>
49 :
50 :
51 : // last include
52 : //
53 : #include <snapdev/poison.h>
54 :
55 :
56 :
57 : namespace safepasswords
58 : {
59 : namespace detail
60 : {
61 :
62 : // for test purposes, we can capture the free() call, ignore otherwise
63 : typedef void (*free_callback_t)(void * ptr);
64 : void set_free_callback(free_callback_t callback);
65 :
66 : } // namespace detail
67 : } // namespace safepasswords
68 :
69 :
70 : namespace
71 : {
72 :
73 :
74 16 : bool is_zero(char const * ptr, std::size_t size)
75 : {
76 225 : for(std::size_t idx(0); idx < size; ++idx)
77 : {
78 209 : if(ptr[idx] != '\0')
79 : {
80 0 : std::cerr << "--- bad character at " << idx
81 0 : << ": '0x" << std::hex << static_cast<int>(ptr[idx] & 0xFF)
82 0 : << std::dec
83 0 : << "' (ptr: " << static_cast<void const *>(ptr) << ")"
84 0 : << "\n";
85 0 : return false;
86 : }
87 : }
88 :
89 16 : return true;
90 : }
91 :
92 :
93 0 : void free_callback(void * ptr)
94 : {
95 0 : std::size_t const size(malloc_usable_size(ptr));
96 0 : is_zero(static_cast<char const *>(ptr), size);
97 0 : ::free(ptr);
98 0 : }
99 :
100 :
101 : }
102 : // no name namespace
103 :
104 :
105 :
106 10 : CATCH_TEST_CASE("string", "[string]")
107 : {
108 10 : safepasswords::detail::set_free_callback(free_callback);
109 :
110 10 : CATCH_START_SECTION("string: verify constructor (empty)")
111 : {
112 1 : safepasswords::string empty;
113 1 : CATCH_REQUIRE(empty.empty());
114 1 : CATCH_REQUIRE(empty.length() == 0);
115 1 : CATCH_REQUIRE(empty.data() == nullptr);
116 1 : }
117 10 : CATCH_END_SECTION()
118 :
119 10 : CATCH_START_SECTION("string: verify constructor (char const *)")
120 : {
121 1 : safepasswords::string simple("password1");
122 1 : CATCH_REQUIRE_FALSE(simple.empty());
123 1 : CATCH_REQUIRE(simple.length() == 9);
124 1 : char const * ptr(simple.data());
125 1 : CATCH_REQUIRE(ptr != nullptr);
126 1 : CATCH_REQUIRE(memcmp(ptr, "password1", 9) == 0);
127 1 : }
128 10 : CATCH_END_SECTION()
129 :
130 10 : CATCH_START_SECTION("string: verify constructor (char const * + length)")
131 : {
132 1 : safepasswords::string full("password1-and-length", 13);
133 1 : CATCH_REQUIRE_FALSE(full.empty());
134 1 : CATCH_REQUIRE(full.length() == 13);
135 1 : char const * ptr(full.data());
136 1 : CATCH_REQUIRE(ptr != nullptr);
137 1 : CATCH_REQUIRE(memcmp(ptr, "password1-and", 13) == 0);
138 1 : }
139 10 : CATCH_END_SECTION()
140 :
141 10 : CATCH_START_SECTION("string: verify to_std_string()")
142 : {
143 1 : char const * secrets[] = {
144 : "pwd1",
145 : "top-secret",
146 : "hidden",
147 : "invisible",
148 : };
149 5 : for(std::size_t idx(0); idx < std::size(secrets); ++idx)
150 : {
151 4 : std::size_t const len(strlen(secrets[idx]));
152 4 : safepasswords::string simple(secrets[idx]);
153 4 : CATCH_REQUIRE_FALSE(simple.empty());
154 4 : CATCH_REQUIRE(simple.length() == len);
155 4 : CATCH_REQUIRE(simple.to_std_string() == secrets[idx]);
156 4 : char const * ptr(simple.data());
157 4 : CATCH_REQUIRE(ptr != nullptr);
158 4 : CATCH_REQUIRE(memcmp(ptr, secrets[idx], len) == 0);
159 4 : }
160 : }
161 10 : CATCH_END_SECTION()
162 :
163 10 : CATCH_START_SECTION("string: verify clear()")
164 : {
165 1 : char const * secrets[] = {
166 : "clear",
167 : "this",
168 : "secret",
169 : "now",
170 : };
171 5 : for(std::size_t idx(0); idx < std::size(secrets); ++idx)
172 : {
173 4 : std::size_t const len(strlen(secrets[idx]));
174 4 : safepasswords::string p(secrets[idx]);
175 4 : CATCH_REQUIRE_FALSE(p.empty());
176 4 : CATCH_REQUIRE(p.length() == len);
177 4 : CATCH_REQUIRE(p.to_std_string() == secrets[idx]);
178 4 : char const * ptr(p.data());
179 4 : CATCH_REQUIRE(ptr != nullptr);
180 4 : CATCH_REQUIRE(memcmp(p.data(), secrets[idx], len) == 0);
181 4 : p.clear();
182 4 : CATCH_REQUIRE(is_zero(ptr, len));
183 4 : }
184 : }
185 10 : CATCH_END_SECTION()
186 :
187 10 : CATCH_START_SECTION("string: append one character at a time")
188 : {
189 1 : char const * secrets[] = {
190 : "clear",
191 : "this",
192 : "secret",
193 : "now",
194 : };
195 5 : for(std::size_t idx(0); idx < std::size(secrets); ++idx)
196 : {
197 4 : std::size_t const len(strlen(secrets[idx]));
198 4 : safepasswords::string p;
199 4 : CATCH_REQUIRE(p.empty());
200 22 : for(std::size_t pos(0); pos < len; ++pos)
201 : {
202 18 : p += secrets[idx][pos];
203 : }
204 4 : CATCH_REQUIRE(p.length() == len);
205 4 : CATCH_REQUIRE(p.to_std_string() == secrets[idx]);
206 4 : char const * ptr(p.data());
207 4 : CATCH_REQUIRE(ptr != nullptr);
208 4 : CATCH_REQUIRE(memcmp(p.data(), secrets[idx], len) == 0);
209 4 : p.clear();
210 4 : CATCH_REQUIRE(is_zero(ptr, len));
211 :
212 : // try, but this time transform the character in a char32_t
213 : //
214 4 : p.clear();
215 4 : CATCH_REQUIRE(p.empty());
216 22 : for(std::size_t pos(0); pos < len; ++pos)
217 : {
218 18 : char32_t c(secrets[idx][pos]);
219 18 : p += c;
220 : }
221 4 : CATCH_REQUIRE(p.length() == len);
222 4 : CATCH_REQUIRE(p.to_std_string() == secrets[idx]);
223 4 : ptr = p.data();
224 4 : CATCH_REQUIRE(ptr != nullptr);
225 4 : CATCH_REQUIRE(memcmp(p.data(), secrets[idx], len) == 0);
226 4 : p.clear();
227 4 : CATCH_REQUIRE(is_zero(ptr, len));
228 4 : }
229 : }
230 10 : CATCH_END_SECTION()
231 :
232 10 : CATCH_START_SECTION("string: append half a string at a time")
233 : {
234 1 : char const * secrets[] = {
235 : "a longer password is better here",
236 : "because we want to add two halves",
237 : "and then make sure that it worked as expected",
238 : "plus the smallest password should be 8 chars.",
239 : };
240 5 : for(std::size_t idx(0); idx < std::size(secrets); ++idx)
241 : {
242 4 : std::size_t const len(strlen(secrets[idx]));
243 4 : char const * ptr(nullptr);
244 : {
245 4 : safepasswords::string p;
246 4 : CATCH_REQUIRE(p.empty());
247 :
248 : // first half
249 : //
250 4 : char buf[256];
251 4 : memcpy(buf, secrets[idx], len / 2);
252 4 : buf[len / 2] = '\0';
253 4 : p += buf;
254 4 : CATCH_REQUIRE(p.length() == len / 2);
255 4 : CATCH_REQUIRE(p.to_std_string() == buf);
256 :
257 : // second half
258 : //
259 4 : p += secrets[idx] + len / 2;
260 4 : CATCH_REQUIRE(p.length() == len);
261 4 : CATCH_REQUIRE(p.to_std_string() == secrets[idx]);
262 4 : ptr = p.data();
263 4 : CATCH_REQUIRE(ptr != nullptr);
264 4 : CATCH_REQUIRE(memcmp(p.data(), secrets[idx], len) == 0);
265 :
266 : // reset
267 : //
268 4 : p.clear();
269 4 : CATCH_REQUIRE(is_zero(ptr, len));
270 4 : }
271 : }
272 : }
273 10 : CATCH_END_SECTION()
274 :
275 10 : CATCH_START_SECTION("string: append passwords together")
276 : {
277 1 : char const * secrets[] = {
278 : "a longer password is better here",
279 : "because we want to add two halves",
280 : "and then make sure that it worked as expected",
281 : "plus the smallest password should be 8 chars.",
282 : };
283 :
284 1 : std::string concatenation;
285 :
286 1 : safepasswords::string p;
287 1 : CATCH_REQUIRE(p.empty());
288 :
289 5 : for(std::size_t idx(0); idx < std::size(secrets); ++idx)
290 : {
291 4 : safepasswords::string end(secrets[idx], strlen(secrets[idx]));
292 4 : p += end;
293 4 : concatenation += secrets[idx];
294 :
295 4 : CATCH_REQUIRE(p.to_std_string() == concatenation);
296 4 : }
297 1 : }
298 10 : CATCH_END_SECTION()
299 :
300 10 : CATCH_START_SECTION("string: append passwords to new ones")
301 : {
302 1 : char const * secrets[] = {
303 : "A longer password is better here",
304 : "because we want to add two halves",
305 : "and then make sure that it worked as expected",
306 : "plus the smallest password should be 8 chars.",
307 : "This time we want an even number of passwords",
308 : "so we can pair them into one",
309 : };
310 1 : CATCH_REQUIRE((std::size(secrets) & 1) == 0);
311 :
312 1 : safepasswords::string p;
313 1 : CATCH_REQUIRE(p.empty());
314 :
315 4 : for(std::size_t idx(0); idx < std::size(secrets); idx += 2)
316 : {
317 3 : safepasswords::string b(secrets[idx], strlen(secrets[idx]));
318 3 : safepasswords::string c(secrets[idx + 1], strlen(secrets[idx + 1]));
319 3 : safepasswords::string a;
320 3 : a = b + c;
321 :
322 6 : std::string concatenation(secrets[idx]);
323 3 : concatenation += secrets[idx + 1];
324 :
325 3 : CATCH_REQUIRE(a.to_std_string() == concatenation);
326 3 : CATCH_REQUIRE(b.to_std_string() == std::string(secrets[idx]));
327 3 : CATCH_REQUIRE(c.to_std_string() == std::string(secrets[idx + 1]));
328 :
329 3 : safepasswords::string d(b + "string");
330 3 : concatenation = secrets[idx];
331 3 : concatenation += "string";
332 3 : CATCH_REQUIRE(d.to_std_string() == concatenation);
333 :
334 3 : safepasswords::string e(c + '!');
335 3 : concatenation = secrets[idx + 1];
336 3 : concatenation += "!";
337 3 : CATCH_REQUIRE(e.to_std_string() == concatenation);
338 :
339 3 : char32_t wc(SNAP_CATCH2_NAMESPACE::random_char(SNAP_CATCH2_NAMESPACE::character_t::CHARACTER_UNICODE));
340 3 : safepasswords::string f(b + wc);
341 3 : concatenation = secrets[idx];
342 3 : concatenation += libutf8::to_u8string(wc);
343 3 : CATCH_REQUIRE(f.to_std_string() == concatenation);
344 3 : }
345 1 : }
346 10 : CATCH_END_SECTION()
347 :
348 10 : CATCH_START_SECTION("string: compare")
349 : {
350 1 : char const * secrets[] = {
351 : "A longer password is better here",
352 : "because we want to add two halves",
353 : "and then make sure that it worked as expected",
354 : "plus the smallest password should be 8 chars.",
355 : "This time we want an even number of passwords",
356 : "so we can pair them into one",
357 : };
358 :
359 6 : for(std::size_t idx(0); idx < std::size(secrets) - 1; ++idx)
360 : {
361 5 : safepasswords::string a(secrets[idx], strlen(secrets[idx]));
362 5 : safepasswords::string b(secrets[idx + 1], strlen(secrets[idx + 1]));
363 :
364 5 : auto const r(a <=> b);
365 5 : auto const q(std::string(secrets[idx]) <=> std::string(secrets[idx + 1]));
366 :
367 5 : CATCH_REQUIRE(r == q);
368 5 : CATCH_REQUIRE(a != b);
369 :
370 5 : auto const s(a <=> a);
371 5 : CATCH_REQUIRE(s == std::strong_ordering::equal);
372 5 : CATCH_REQUIRE(a == a);
373 :
374 5 : safepasswords::string p;
375 200 : for(std::size_t pos(0); pos < a.length() - 1; ++pos)
376 : {
377 195 : p += a.data()[pos];
378 :
379 195 : auto const t(a <=> p);
380 195 : CATCH_REQUIRE(t == std::strong_ordering::greater);
381 :
382 195 : auto const u(p <=> a);
383 195 : CATCH_REQUIRE(u == std::strong_ordering::less);
384 :
385 195 : safepasswords::string c(a);
386 195 : c.resize(pos);
387 :
388 195 : auto const v(a <=> c);
389 195 : CATCH_REQUIRE(v == std::strong_ordering::greater);
390 :
391 195 : auto const w(c <=> a);
392 195 : CATCH_REQUIRE(w == std::strong_ordering::less);
393 195 : }
394 5 : }
395 : }
396 10 : CATCH_END_SECTION()
397 10 : }
398 :
399 :
400 1 : CATCH_TEST_CASE("large_string", "[string]")
401 : {
402 1 : safepasswords::detail::set_free_callback(free_callback);
403 :
404 1 : CATCH_START_SECTION("large_string: create a very large string spanning multiple pages")
405 : {
406 1 : long const page_size(sysconf(_SC_PAGESIZE));
407 1 : std::size_t const max_size(page_size * 5 + 100);
408 1 : safepasswords::string large;
409 1 : std::string copy;
410 5226 : while(large.length() < max_size)
411 : {
412 5225 : char32_t const wc(SNAP_CATCH2_NAMESPACE::random_char(SNAP_CATCH2_NAMESPACE::character_t::CHARACTER_UNICODE));
413 5225 : large += wc;
414 5225 : copy += libutf8::to_u8string(wc);
415 :
416 5225 : CATCH_REQUIRE_FALSE(large.empty());
417 5225 : CATCH_REQUIRE(large.to_std_string() == copy);
418 : }
419 1 : }
420 1 : CATCH_END_SECTION()
421 1 : }
422 :
423 :
424 1 : CATCH_TEST_CASE("string_with_errors", "[string][error]")
425 : {
426 1 : CATCH_START_SECTION("string_with_errors: verify invalid unicode character")
427 : {
428 1 : safepasswords::string p;
429 1 : char32_t wc(0x2022); // valid
430 1 : p += wc;
431 1 : wc = 0xD999; // surrogate not valid in char32_t
432 1 : CATCH_REQUIRE_THROWS_MATCHES(
433 : p += wc
434 : , safepasswords::invalid_parameter
435 : , Catch::Matchers::ExceptionMessage(
436 : "safepasswords_exception: wc passed to this function does not represent a"
437 : " valid Unicode character."));
438 1 : }
439 1 : CATCH_END_SECTION()
440 1 : }
441 :
442 :
443 :
444 : // vim: ts=4 sw=4 et
|