Line data Source code
1 : /* TLD library -- test the TLD interface for emails
2 : * Copyright (C) 2013-2015 Made to Order Software Corp.
3 : *
4 : * Permission is hereby granted, free of charge, to any person obtaining a
5 : * copy of this software and associated documentation files (the
6 : * "Software"), to deal in the Software without restriction, including
7 : * without limitation the rights to use, copy, modify, merge, publish,
8 : * distribute, sublicense, and/or sell copies of the Software, and to
9 : * permit persons to whom the Software is furnished to do so, subject to
10 : * the following conditions:
11 : *
12 : * The above copyright notice and this permission notice shall be included
13 : * in all copies or substantial portions of the Software.
14 : *
15 : * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
16 : * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 : * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18 : * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
19 : * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
20 : * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
21 : * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 : */
23 :
24 : /** \file
25 : * \brief Test the tld_email_list class.
26 : *
27 : * This file implements various tests to verify that the
28 : * tld_email_list functions as expected.
29 : */
30 :
31 : #include "libtld/tld.h"
32 : #include <stdlib.h>
33 : #include <stdio.h>
34 : #include <string.h>
35 : #include <sstream>
36 :
37 : /// The number of errors encountered before exiting.
38 : int err_count = 0;
39 :
40 : /// Whether to be verbose, turned off by default.
41 : int verbose = 0;
42 :
43 :
44 : /** \brief Print an error.
45 : *
46 : * This function prints the specified \p msg in stderr and increases
47 : * the error counter by one.
48 : *
49 : * \param[in] msg The message to be printed.
50 : */
51 0 : void error(const std::string& msg)
52 : {
53 0 : fprintf(stderr, "%s\n", msg.c_str());
54 0 : ++err_count;
55 0 : }
56 :
57 :
58 : /// Macro to check that exceptions are raised without having to write the try/catch each time.
59 : #define EXPECTED_THROW(s, e) \
60 : try \
61 : { \
62 : static_cast<void>(s); \
63 : error("error: bad." #s "() of \"\" did not throw an error."); \
64 : } \
65 : catch(const e&) \
66 : { \
67 : }
68 :
69 :
70 : /** \brief Define a valid email string.
71 : *
72 : * This structure is used to define a valid email string. The string may
73 : * include any number of emails as defined by the \p f_count field. Note
74 : * that the count is increased by 1 for each group definition in the list
75 : * defined in the \p f_input_email string.
76 : *
77 : * This structure is used to validate many different types of email
78 : * addresses to make sure that our parser works properly.
79 : */
80 : struct valid_email
81 : {
82 : /// The valid emails to be parsed.
83 : const char * f_input_email;
84 : /// The number of emails returned on f_input_email was parsed, plus one per group.
85 : int f_count;
86 : };
87 :
88 : //const char * f_group;
89 : //const char * f_original_email;
90 : //const char * f_fullname;
91 : //const char * f_username;
92 : //const char * f_domain;
93 : //const char * f_email_only;
94 : //const char * f_canonicalized_email;
95 :
96 : /// List of results to verify all the fields of the parser output. There is one entry per group and email.
97 : const tld_email list_of_results[] =
98 : {
99 : { "", "alexis@m2osw.com",
100 : "", "alexis", "m2osw.com", "alexis@m2osw.com", "alexis@m2osw.com" },
101 : { "", "a@m2osw.com",
102 : "", "a", "m2osw.com", "a@m2osw.com", "a@m2osw.com" },
103 : { "", "b@c.com",
104 : "", "b", "c.com", "b@c.com", "b@c.com" },
105 : { "", "alexis@m2osw.com",
106 : "", "alexis", "m2osw.com", "alexis@m2osw.com", "alexis@m2osw.com" },
107 : { "", "\"Wilke, Alexis\" <alexis@m2osw.com>",
108 : "Wilke, Alexis", "alexis", "m2osw.com", "alexis@m2osw.com", "\"Wilke, Alexis\" <alexis@m2osw.com>" },
109 : { "", "(* Pascal Comments *) \t alexis@m2osw.com\n (Just (kidding) he! he!)",
110 : "", "alexis", "m2osw.com", "alexis@m2osw.com", "alexis@m2osw.com" },
111 : { "", "(Start-Comment)alexis@ \t [ \t m2osw.com \t ] \n (More (comment) here)",
112 : "", "alexis", "m2osw.com", "alexis@m2osw.com", "alexis@m2osw.com" },
113 : { "", "(Test with dots in user name) al.ex.is@ \t [ \t m2osw.com \t ] \n (More (comments) there)",
114 : "", "al.ex.is", "m2osw.com", "al.ex.is@m2osw.com", "al.ex.is@m2osw.com" },
115 : { "", "< (Test with dots in user name) al.ex.is@ \t [ \t m2osw.com \t ] \n (More (comments) there) >",
116 : "", "al.ex.is", "m2osw.com", "al.ex.is@m2osw.com", "al.ex.is@m2osw.com" },
117 : { "", "(With full name) Alexis Wilke < (Test with dots in user name) al.ex.is@ \t [ \t m2osw.com \t ] \n (More (comments) there) >",
118 : "Alexis Wilke", "al.ex.is", "m2osw.com", "al.ex.is@m2osw.com", "Alexis Wilke <al.ex.is@m2osw.com>" },
119 : { "This Group", "",
120 : "", "", "", "", "" },
121 : { "This Group", "(With full name) Alexis Wilke < \n alexis \t @ \t [ \t m2osw.com \t ] \n (Less) >",
122 : "Alexis Wilke", "alexis", "m2osw.com", "alexis@m2osw.com", "Alexis Wilke <alexis@m2osw.com>" },
123 : { "People", "",
124 : "", "", "", "", "" },
125 : { "People", "Alexis Wilke <alexis@m2osw.com>",
126 : "Alexis Wilke", "alexis", "m2osw.com", "alexis@m2osw.com", "Alexis Wilke <alexis@m2osw.com>" },
127 : { "People", "John Smith <john@m2osw.com>",
128 : "John Smith", "john", "m2osw.com", "john@m2osw.com", "John Smith <john@m2osw.com>" },
129 : { "Lists", "",
130 : "", "", "", "", "" },
131 : { "Lists", "Contact <contact@m2osw.com>",
132 : "Contact", "contact", "m2osw.com", "contact@m2osw.com", "Contact <contact@m2osw.com>" },
133 : { "Lists", "Resume <resume@m2osw.com>",
134 : "Resume", "resume", "m2osw.com", "resume@m2osw.com", "Resume <resume@m2osw.com>" },
135 : { "", "normal@m2osw.com",
136 : "", "normal", "m2osw.com", "normal@m2osw.com", "normal@m2osw.com" },
137 : { "No-Reply", "",
138 : "", "", "", "", "" },
139 : { "No-Reply", "no-reply@m2osw.com",
140 : "", "no-reply", "m2osw.com", "no-reply@m2osw.com", "no-reply@m2osw.com" },
141 : { "", "\"Complex <name> for !a! \\\"USER\\\"\" <user@example.co.uk>",
142 : "Complex <name> for !a! \"USER\"", "user", "example.co.uk", "user@example.co.uk", "\"Complex <name> for !a! \\\"USER\\\"\" <user@example.co.uk>" },
143 : { "", "(Comment \n New-Line) alexis@m2osw.com",
144 : "", "alexis", "m2osw.com", "alexis@m2osw.com", "alexis@m2osw.com" },
145 : { "", "(Comment (Sub-Comment (Sub-Sub-Comment (Sub-Sub-Sub-Comment \\) This is still the Sub-Sub-Sub-Comment!!!)))) alexis@m2osw.com",
146 : "", "alexis", "m2osw.com", "alexis@m2osw.com", "alexis@m2osw.com" },
147 : { "Group with some sub-comments", "",
148 : "", "", "", "", "" },
149 : { "Group with some sub-comments", "alexis@m2osw.com",
150 : "", "alexis", "m2osw.com", "alexis@m2osw.com", "alexis@m2osw.com" },
151 : // TBD: since the colons get canonicalized to %3A we do not need the '[' and ']' in the canonicalized version
152 : { "", "\"Wilke, Alexis\" <\"alexis,wilke\"@[:special:.m2osw.com]>",
153 : "Wilke, Alexis", "alexis,wilke", ":special:.m2osw.com", "\"alexis,wilke\"@[:special:.m2osw.com]", "\"Wilke, Alexis\" <\"alexis,wilke\"@%3Aspecial%3A.m2osw.com>" },
154 :
155 : { NULL, NULL, NULL, NULL, NULL, NULL, NULL }
156 : };
157 :
158 : /// The list of valid emails used to check the parser out.
159 : const valid_email list_of_valid_emails[] =
160 : {
161 : { "alexis@m2osw.com", 1 },
162 : { "a@m2osw.com", 1 },
163 : { "b@c.com", 1 },
164 : { " \t alexis@m2osw.com\n \t", 1 },
165 : { "\"Wilke, Alexis\" <alexis@m2osw.com>", 1 },
166 : { " (* Pascal Comments *) \t alexis@m2osw.com\n (Just (kidding) he! he!) \t", 1 },
167 : { "(Start-Comment)alexis@ \t [ \t m2osw.com \t ] \n (More (comment) here) \r\n\t", 1 },
168 : { "(Test with dots in user name) al.ex.is@ \t [ \t m2osw.com \t ] \n (More (comments) there) \r\n\t", 1 },
169 : { "< (Test with dots in user name) al.ex.is@ \t [ \t m2osw.com \t ] \n (More (comments) there) > \r\n\t", 1 },
170 : { "(With full name) Alexis Wilke < (Test with dots in user name) al.ex.is@ \t [ \t m2osw.com \t ] \n (More (comments) there) > \r\n\t", 1 },
171 : { " (Now a group:) This Group: (With full name) Alexis Wilke < \n alexis \t @ \t [ \t m2osw.com \t ] \n (Less) >; \r\n\t", 2 },
172 : { "People: Alexis Wilke <alexis@m2osw.com>, John Smith <john@m2osw.com>; Lists: Contact <contact@m2osw.com>, Resume <resume@m2osw.com>; normal@m2osw.com, No-Reply: no-reply@m2osw.com;", 9 },
173 : { "\"Complex <name> for !a! \\\"USER\\\"\" <user@example.co.uk>", 1 },
174 : { "(Comment \n New-Line) alexis@m2osw.com", 1 },
175 : { "(Comment (Sub-Comment (Sub-Sub-Comment (Sub-Sub-Sub-Comment \\) This is still the Sub-Sub-Sub-Comment!!!)))) alexis@m2osw.com", 1 },
176 : { "Group with (Comment (Sub-Comment (Sub-Sub-Comment (Sub-Sub-Sub-Comment \\) This is still the Sub-Sub-Sub-Comment!!!)))) some sub-comments \t : alexis@m2osw.com;", 2 },
177 : { "\"Wilke, Alexis\" <\"alexis,wilke\"@[:special:.m2osw.com]>", 1 },
178 :
179 : // end of list
180 : { NULL, 0 }
181 : };
182 :
183 :
184 : /** \brief Transform an email string in a C-like string.
185 : *
186 : * This function transforms the characters in \p e into a set of C-like
187 : * escape characters so it can safely be printed in the console.
188 : *
189 : * For example, the character 0x09 is transformed to the character \\t.
190 : *
191 : * \param[in] e The email to be transformed.
192 : *
193 : * \return The transformed email.
194 : */
195 0 : std::string email_to_vstring(const std::string& e)
196 : {
197 0 : std::string result;
198 : char buf[3];
199 :
200 0 : for(const char *s(e.c_str()); *s != '\0'; ++s)
201 : {
202 0 : if(static_cast<unsigned char>(*s) < ' ')
203 : {
204 0 : switch(*s)
205 : {
206 0 : case '\a': result += "\\a"; break;
207 0 : case '\b': result += "\\b"; break;
208 0 : case '\f': result += "\\f"; break;
209 0 : case '\n': result += "\\n"; break;
210 0 : case '\r': result += "\\r"; break;
211 0 : case '\t': result += "\\t"; break;
212 0 : case '\v': result += "\\v"; break;
213 : default:
214 0 : buf[0] = '^';
215 0 : buf[1] = *s + '@';
216 0 : buf[2] = '\0';
217 0 : result += buf;
218 0 : break;
219 :
220 : }
221 : }
222 0 : else if(*s == 0x7F)
223 : {
224 0 : result += "<DEL>";
225 : }
226 0 : else if(static_cast<unsigned char>(*s) > 0x80)
227 : {
228 : static const char *hc = "0123456789ABCDEF";
229 0 : result += "\\x";
230 0 : buf[0] = hc[*s >> 4];
231 0 : buf[1] = hc[*s & 15];
232 0 : buf[2] = '\0';
233 0 : result += buf;
234 : }
235 : else
236 : {
237 0 : result += *s;
238 : }
239 : }
240 :
241 0 : return result;
242 : }
243 :
244 :
245 1 : void test_valid_emails()
246 : {
247 1 : const tld_email *results(list_of_results);
248 18 : for(const valid_email *v(list_of_valid_emails); v->f_input_email != NULL; ++v)
249 : {
250 17 : if(verbose)
251 : {
252 0 : printf("*** testing email \"%s\", start with C++ test\n", email_to_vstring(v->f_input_email).c_str());
253 0 : fflush(stdout);
254 : }
255 :
256 17 : const tld_email * const cresults(results);
257 :
258 : // C++ test
259 : {
260 17 : tld_email_list list;
261 17 : tld_result r(list.parse(v->f_input_email, 0));
262 17 : int max(v->f_count);
263 17 : if(r != TLD_RESULT_SUCCESS)
264 : {
265 0 : error("error: unexpected return value.");
266 : }
267 17 : else if(list.count() != max)
268 : {
269 0 : fprintf(stderr, "parse() returned %d as count, expected %d\n", list.count(), max);
270 0 : error("error: unexpected count");
271 : }
272 : else
273 : {
274 : // test the C++ function first
275 : {
276 17 : tld_email_list::tld_email_t e;
277 44 : for(int i(0); i < max; ++i, ++results)
278 : {
279 27 : if(results->f_group == NULL)
280 : {
281 0 : error("error: end of results array reached before completion of the test.\n");
282 0 : return;
283 : }
284 :
285 27 : if(!list.next(e))
286 : {
287 0 : error("error: next() returned false too soon.");
288 : }
289 27 : if(e.f_group != results->f_group)
290 : {
291 0 : error("error: next() returned the wrong group. Got \"" + e.f_group + "\" instead of \"" + results->f_group + "\".");
292 : }
293 27 : if(e.f_original_email != results->f_original_email)
294 : {
295 0 : error("error: next() returned the wrong original email. Got \"" + e.f_original_email + "\" instead of \"" + results->f_original_email + "\".");
296 : }
297 27 : if(e.f_fullname != results->f_fullname)
298 : {
299 0 : error("error: next() returned the wrong fullname. Got \"" + e.f_fullname + "\" instead of \"" + results->f_fullname + "\".");
300 : }
301 27 : if(e.f_username != results->f_username)
302 : {
303 0 : error("error: next() returned the wrong username. Got \"" + e.f_username + "\" instead of \"" + results->f_username + "\".");
304 : }
305 27 : if(e.f_domain != results->f_domain)
306 : {
307 0 : error("error: next() returned the wrong username. Got \"" + e.f_domain + "\" instead of \"" + results->f_domain + "\".");
308 : }
309 27 : if(e.f_email_only != results->f_email_only)
310 : {
311 0 : error("error: next() returned the wrong email only. Got \"" + e.f_email_only + "\" instead of \"" + results->f_email_only + "\".");
312 : }
313 27 : if(e.f_canonicalized_email != results->f_canonicalized_email)
314 : {
315 0 : error("error: next() returned the wrong canonicalized email. Got \"" + e.f_canonicalized_email + "\" instead of \"" + results->f_canonicalized_email + "\".");
316 : }
317 : }
318 17 : if(list.next(e))
319 : {
320 0 : error("error: next(e) returned the wrong result, it should be false after the whole set of emails were read.");
321 17 : }
322 : }
323 : // try the C function which also allows us to test the rewind()
324 17 : list.rewind();
325 : {
326 17 : results = cresults;
327 : tld_email e;
328 44 : for(int i(0); i < max; ++i, ++results)
329 : {
330 27 : if(!list.next(&e))
331 : {
332 0 : error("error: next() returned false too soon.");
333 : }
334 27 : if(strcmp(e.f_group, results->f_group) != 0)
335 : {
336 0 : error("error: next() returned the wrong group. Got \"" + std::string(e.f_group) + "\" from \"" + results->f_group + "\".");
337 : }
338 27 : if(strcmp(e.f_original_email, results->f_original_email) != 0)
339 : {
340 0 : error("error: next() returned the wrong original email. Got \"" + std::string(e.f_original_email) + "\" instead of \"" + results->f_original_email + "\".");
341 : }
342 27 : if(strcmp(e.f_fullname, results->f_fullname) != 0)
343 : {
344 0 : error("error: next() returned the wrong fullname.");
345 : }
346 27 : if(strcmp(e.f_username, results->f_username) != 0)
347 : {
348 0 : error("error: next() returned the wrong username.");
349 : }
350 27 : if(strcmp(e.f_domain, results->f_domain) != 0)
351 : {
352 0 : error("error: next() returned the wrong username.");
353 : }
354 27 : if(strcmp(e.f_email_only, results->f_email_only) != 0)
355 : {
356 0 : error("error: next() returned the wrong email only.");
357 : }
358 27 : if(strcmp(e.f_canonicalized_email, results->f_canonicalized_email) != 0)
359 : {
360 0 : error("error: next() returned the wrong canonicalized email.");
361 : }
362 : }
363 17 : if(list.next(&e))
364 : {
365 0 : error("error: next(&e) returned the wrong result, it should be false after the whole set of emails were read.");
366 : }
367 : }
368 17 : }
369 : }
370 :
371 17 : if(verbose)
372 : {
373 0 : printf("*** C test now\n");
374 0 : fflush(stdout);
375 : }
376 : // C test
377 : {
378 : tld_email_list *list;
379 17 : list = tld_email_alloc();
380 17 : tld_result r = tld_email_parse(list, v->f_input_email, 0);
381 17 : int max(v->f_count);
382 17 : if(r != TLD_RESULT_SUCCESS)
383 : {
384 0 : error("error: unexpected return value.");
385 : }
386 17 : else if(tld_email_count(list) != max)
387 : {
388 0 : fprintf(stderr, "parse() returned %d as count, expected %d\n", tld_email_count(list), max);
389 0 : error("error: unexpected count");
390 : }
391 : else
392 : {
393 : // test the C++ function first
394 102 : for(int repeat(0); repeat < 2; ++repeat)
395 : {
396 34 : results = cresults;
397 : struct tld_email e;
398 88 : for(int i(0); i < max; ++i, ++results)
399 : {
400 54 : if(results->f_group == NULL)
401 : {
402 0 : error("error: end of results array reached before completion of the test.\n");
403 0 : return;
404 : }
405 :
406 54 : if(tld_email_next(list, &e) != 1)
407 : {
408 0 : error("error: next() returned false too soon.");
409 : }
410 54 : if(strcmp(e.f_group, results->f_group) != 0)
411 : {
412 0 : error("error: next() returned the wrong group. Got \"" + std::string(e.f_group) + "\" from \"" + results->f_group + "\".");
413 : }
414 54 : if(strcmp(e.f_original_email, results->f_original_email) != 0)
415 : {
416 0 : error("error: next() returned the wrong original email. Got \"" + std::string(e.f_original_email) + "\" instead of \"" + results->f_original_email + "\".");
417 : }
418 54 : if(strcmp(e.f_fullname, results->f_fullname) != 0)
419 : {
420 0 : error("error: next() returned the wrong fullname.");
421 : }
422 54 : if(strcmp(e.f_username, results->f_username) != 0)
423 : {
424 0 : error("error: next() returned the wrong username.");
425 : }
426 54 : if(strcmp(e.f_domain, results->f_domain) != 0)
427 : {
428 0 : error("error: next() returned the wrong username.");
429 : }
430 54 : if(strcmp(e.f_email_only, results->f_email_only) != 0)
431 : {
432 0 : error("error: next() returned the wrong email only.");
433 : }
434 54 : if(strcmp(e.f_canonicalized_email, results->f_canonicalized_email) != 0)
435 : {
436 0 : error("error: next() returned the wrong canonicalized email.");
437 : }
438 : }
439 34 : if(tld_email_next(list, &e) != 0)
440 : {
441 0 : error("error: next(&e) returned the wrong result, it should be false after the whole set of emails were read.");
442 : }
443 : // try again
444 34 : tld_email_rewind(list);
445 : }
446 : }
447 17 : tld_email_free(list);
448 : }
449 : }
450 :
451 : {
452 : // all valid atom characters
453 : const char valid_chars[] =
454 : "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
455 : "abcdefghijklmnopqrstuvwxyz"
456 : "0123456789"
457 1 : "!#$%&'*+-/=?^_`{|}~" // here there is a NUL
458 : ;
459 82 : for(size_t i(0); i < sizeof(valid_chars) / sizeof(valid_chars[0]) - 1; ++i)
460 : {
461 81 : tld_email_list list;
462 162 : std::string e("abc");
463 81 : e += valid_chars[i];
464 81 : e += "def@m2osw.com";
465 81 : if(verbose)
466 : {
467 0 : printf("*** testing all atom characters with email \"%s\"\n", email_to_vstring(e).c_str());
468 0 : fflush(stdout);
469 : }
470 81 : tld_result r(list.parse(e, 0));
471 81 : if(r != TLD_RESULT_SUCCESS)
472 : {
473 0 : error("error: unexpected return value.");
474 : }
475 81 : }
476 : }
477 :
478 : {
479 : // all valid quoted characters: " " to "\x7E" except the " and \ characters
480 : if(sizeof(int) < 4)
481 : {
482 : error("error: the ctrl variable needs to be at least 32 bits");
483 : return;
484 : }
485 1 : const int ctrl(1 << '\t');
486 127 : for(size_t i(1); i <= 126; ++i)
487 : {
488 126 : switch(i)
489 : {
490 : case '"':
491 : case '\\':
492 : case 0x7F: // not included in the loop anyway
493 2 : break;
494 :
495 : default:
496 124 : if(i >= ' ' || (ctrl & (1 << i)) != 0)
497 : {
498 94 : tld_email_list list;
499 188 : std::string e("\"abc");
500 94 : e += static_cast<char>(i);
501 94 : e += "def\"@m2osw.com";
502 94 : if(verbose)
503 : {
504 0 : printf("*** testing all atom characters with email \"%s\"\n", email_to_vstring(e).c_str());
505 0 : fflush(stdout);
506 : }
507 94 : tld_result r(list.parse(e, 0));
508 94 : if(r != TLD_RESULT_SUCCESS)
509 : {
510 0 : error("error: unexpected return value.");
511 94 : }
512 : }
513 124 : break;
514 :
515 : }
516 : }
517 : }
518 :
519 : {
520 : // all valid quoted pair: '\t' and " " to "\x7E"
521 97 : for(size_t i(31); i <= 126; ++i)
522 : {
523 96 : tld_email_list list;
524 192 : std::string e("\"abc\\");
525 96 : if(i == 31)
526 : {
527 1 : e += static_cast<char>('\t');
528 : }
529 : else
530 : {
531 95 : e += static_cast<char>(i);
532 : }
533 96 : e += "def\"@m2osw.com";
534 96 : if(verbose)
535 : {
536 0 : printf("*** testing all atom characters with email \"%s\"\n", email_to_vstring(e).c_str());
537 0 : fflush(stdout);
538 : }
539 96 : tld_result r(list.parse(e, 0));
540 96 : if(r != TLD_RESULT_SUCCESS)
541 : {
542 0 : error("error: unexpected return value.");
543 : }
544 96 : }
545 : }
546 :
547 : {
548 : // all valid comment characters: " " to "\x7E" except the " and \ characters
549 : if(sizeof(int) < 4)
550 : {
551 : error("error: the ctrl variable needs to be at least 32 bits");
552 : return;
553 : }
554 1 : const int ctrl((1 << '\t') | (1 << '\r') | (1 << '\n'));
555 127 : for(size_t i(1); i <= 126; ++i)
556 : {
557 : // we skip all the special characters in a comment since
558 : // those are already tested somewhere else
559 126 : switch(i)
560 : {
561 : case '(': // avoid a sub-comment
562 : case ')': // avoid closing the comment mid-way
563 : case '\\': // tested somewhere else
564 : case 0x7F: // not included in the loop anyway
565 3 : break;
566 :
567 : default:
568 123 : if(i >= ' ' || (ctrl & (1 << i)) != 0)
569 : {
570 95 : tld_email_list list;
571 190 : std::string e("(Comment \"");
572 95 : e += static_cast<char>(i);
573 95 : e += "\" char.) alexis@m2osw.com";
574 95 : if(verbose)
575 : {
576 0 : printf("*** testing all atom characters with email \"%s\"\n", email_to_vstring(e).c_str());
577 0 : fflush(stdout);
578 : }
579 95 : tld_result r(list.parse(e, 0));
580 95 : if(r != TLD_RESULT_SUCCESS)
581 : {
582 0 : error("error: unexpected return value.");
583 95 : }
584 : }
585 123 : break;
586 :
587 : }
588 : }
589 : }
590 :
591 : {
592 : // all valid domain characters: "!" to "\x7E" except the [, ], and \ characters
593 95 : for(size_t i('!'); i <= 126; ++i)
594 : {
595 : // a dot is valid but we cannot test it between two other dots
596 94 : if(i == '[' || i == ']' || i == '\\' || i == '.')
597 : {
598 4 : continue;
599 : }
600 90 : tld_email_list list;
601 180 : std::string e("alexis@[ m2osw.");
602 90 : e += static_cast<char>(i);
603 90 : if(i == '%')
604 : {
605 1 : e += "25";
606 : }
607 90 : e += ".com\t]";
608 90 : if(verbose)
609 : {
610 0 : printf("*** testing all atom characters with email \"%s\"\n", email_to_vstring(e).c_str());
611 0 : fflush(stdout);
612 : }
613 90 : tld_result r(list.parse(e, 0));
614 90 : if(r != TLD_RESULT_SUCCESS)
615 : {
616 0 : error("error: unexpected return value while testing a domain with special character \"" + e + "\"");
617 : }
618 90 : }
619 : }
620 :
621 : {
622 1 : if(tld_email_list::quote_string("Test quoting a simple comment", '(') != "(Test quoting a simple comment)")
623 : {
624 0 : error("error: unexpected return value when testing a simple comment quotation");
625 : }
626 1 : if(tld_email_list::quote_string("Test (quoting) a complex )comment(", '(') != "(Test \\(quoting\\) a complex \\)comment\\()")
627 : {
628 0 : error("error: unexpected return value when testing a complex comment quotation");
629 : }
630 : }
631 : }
632 :
633 :
634 :
635 :
636 : /** \brief Define an invalid email.
637 : *
638 : * This structure is used to list invalid emails in order to test that such
639 : * emails are not accepted by the parser. The structure includes the expected
640 : * result as well as a string pointer to the invalid email.
641 : */
642 : struct invalid_email
643 : {
644 : /// The expected reslut, if the call does not return this exact value the test fails
645 : tld_result f_result;
646 : /// The pointer to the invalid email to be tested
647 : const char * f_input_email;
648 : };
649 :
650 : const invalid_email list_of_invalid_emails[] =
651 : {
652 : { TLD_RESULT_INVALID, "alexism2osw.com (missing @)" },
653 : { TLD_RESULT_INVALID, " \v alexis@m2osw.com\n \t (bad control)" },
654 : { TLD_RESULT_INVALID, " (* Pascal Comments *) \t alexis@m2osw.com\n (missing closing parenthesis\\)" },
655 : { TLD_RESULT_INVALID, "(Start-Comment)alexis@ \t [ \t m2osw.com \t ] \n (extra after domain done) \"more\tdata\" \r\n\t" },
656 : { TLD_RESULT_INVALID, "(Test with dots in user name) al.ex.is@ \t(missing closing bracket ]) [ \t m2osw.com \t " },
657 : { TLD_RESULT_NULL, "< (Test with dots in user name) al.ex.is@ \t [ \t m2osw.com \t ] \n (Missing >) \r\n\t" },
658 : { TLD_RESULT_INVALID, "(Full name with period) Alexis.Wilke < (Test with dots in user name) al.ex.is@ \t [ \t m2osw.com \t ] \n (More (comments) there) > \r\n\t" },
659 : { TLD_RESULT_INVALID, " (Now a group:) This Group: (With full name) Alexis Wilke < \n alexis \t @ \t [ \t m2osw.com \t ] \n (missing ;) > \r\n\t" },
660 : { TLD_RESULT_INVALID, "Good Group: alexis@m2osw.com, bad-group: test@example.com;" },
661 : { TLD_RESULT_INVALID, "(No Group Name): alexis@m2osw.com;" },
662 : { TLD_RESULT_INVALID, " (No Group Name) : alexis@m2osw.com;" },
663 : { TLD_RESULT_INVALID, ": alexis@m2osw.com;" },
664 : { TLD_RESULT_INVALID, "(Group with CTRL) Group \v Unexpected: alexis@m2osw.com;" },
665 : { TLD_RESULT_INVALID, "\"alexis@m2osw.com;" },
666 : { TLD_RESULT_INVALID, "\"alexis@m2osw.com;\v\"" },
667 : { TLD_RESULT_INVALID, "\"Alexis Wilke\\" }, // \ followed by NUL
668 : { TLD_RESULT_INVALID, "(Comment with \\\\ followed by NUL: \\" },
669 : { TLD_RESULT_INVALID, "(Test Errors Once Done) \"Wilke, Alexis\" <alexis@m2osw.com> \"Bad\"" },
670 : { TLD_RESULT_INVALID, "(Comment with CTRL \b) \"Wilke, Alexis\" <alexis@m2osw.com>" },
671 : { TLD_RESULT_INVALID, "[m2osw.com]" },
672 : { TLD_RESULT_INVALID, "good@[bad-slash\\.com]" },
673 : { TLD_RESULT_INVALID, "good@[bad[reopen.com]" },
674 : { TLD_RESULT_INVALID, "(Test Errors Once Done) \"Wilke, Alexis\" <alexis@m2osw.com> [Bad]" },
675 : { TLD_RESULT_INVALID, "(Test Errors Once Done) alexis@start[Bad]" },
676 : { TLD_RESULT_INVALID, "(Test Errors Once Done) alexis@[first][Bad]" },
677 : { TLD_RESULT_INVALID, "(Test Errors Once Done) alexis@[control:\v]" },
678 : { TLD_RESULT_NULL, "(Test Errors Once Done) alexis@[ spaces BAD]" },
679 : { TLD_RESULT_INVALID, "(Spurious Angle) alexis>@m2osw.com" },
680 : { TLD_RESULT_INVALID, "(Spurious Angle) alexis@m2osw.com>" },
681 : { TLD_RESULT_INVALID, "(Double Angle) <alexis@m2osw.com>>" },
682 : { TLD_RESULT_NULL, "(Missing domain) <alexis@>" },
683 : { TLD_RESULT_NULL, "(Missing domain) alexis@" },
684 : { TLD_RESULT_INVALID, "(2 domains) <alexis@[m2osw.com]bad>" },
685 : { TLD_RESULT_INVALID, "(Double @) <alexis@m2osw.com> @" },
686 : { TLD_RESULT_INVALID, "(Double @) alexis@m2osw.com@" },
687 : { TLD_RESULT_INVALID, "(Extra Chars) <alexis@m2osw.com> bad" },
688 : { TLD_RESULT_NULL, "(Empty username within brackets) <@m2osw.com>" },
689 : { TLD_RESULT_NULL, "(Empty User Name) @m2osw.com" },
690 : { TLD_RESULT_INVALID, "(Cannot start with a dot) .alexis@m2osw.com" },
691 : { TLD_RESULT_INVALID, "(Cannot start with a dot) <.alexis@m2osw.com>" },
692 : { TLD_RESULT_INVALID, "(Cannot end with a dot) alexis.@m2osw.com" },
693 : { TLD_RESULT_INVALID, "(Cannot end with a dot) <alexis.@m2osw.com>" },
694 : { TLD_RESULT_INVALID, "(Cannot include double dots) ale..xis@m2osw.com" },
695 : //{ TLD_RESULT_INVALID, "(End domain with dot not considered valid!) alexis@m2osw.com." }, viewed as valid! (that bad?)
696 : { TLD_RESULT_INVALID, "(End domain with dot not considered valid!) <alexis@m2osw.com.>" },
697 : { TLD_RESULT_NULL, "(Bad Emails) alexis,m2osw.com" },
698 : { TLD_RESULT_INVALID, "(Bad Char) alexis@m2osw\001com" },
699 : { TLD_RESULT_NOT_FOUND, "(Bad Extension) alexis@m2osw.comm" },
700 : { TLD_RESULT_INVALID, "(Bad Extension) alexis@m2osw.ar" },
701 : { TLD_RESULT_INVALID, "(Bad Extension) alexis@m2osw.nom.ar" },
702 : { TLD_RESULT_NO_TLD, "(Bad Extension) alexis@m2osw" },
703 : { TLD_RESULT_BAD_URI, "(Bad Extension) alexis@[m2osw..com]" },
704 :
705 : // end of list
706 : { TLD_RESULT_SUCCESS, NULL }
707 : };
708 :
709 1 : void test_invalid_emails()
710 : {
711 52 : for(const invalid_email *v(list_of_invalid_emails); v->f_input_email != NULL; ++v)
712 : {
713 51 : if(verbose)
714 : {
715 0 : printf("+++ testing email \"%s\"\n", email_to_vstring(v->f_input_email).c_str());
716 : }
717 :
718 : // C++ test
719 : {
720 51 : tld_email_list list;
721 51 : tld_result r(list.parse(v->f_input_email, 0));
722 51 : if(r != v->f_result)
723 : {
724 0 : std::stringstream ss;
725 0 : ss << "error: unexpected return value. Got " << static_cast<int>(r) << ", expected " << static_cast<int>(v->f_result) << " for \"" << v->f_input_email << "\" (C++)";
726 0 : error(ss.str());
727 51 : }
728 : }
729 :
730 : // C test
731 : {
732 : tld_email_list *list;
733 51 : list = tld_email_alloc();
734 51 : tld_result r = tld_email_parse(list, v->f_input_email, 0);
735 51 : if(r != v->f_result)
736 : {
737 0 : std::stringstream ss;
738 0 : ss << "error: unexpected return value. Got " << static_cast<int>(r) << ", expected " << static_cast<int>(v->f_result) << " for \"" << v->f_input_email << "\" (C)";
739 0 : error(ss.str());
740 : }
741 51 : tld_email_free(list);
742 51 : list = NULL;
743 : }
744 : }
745 1 : }
746 :
747 :
748 6 : void contract_furfilled(tld_email_list::tld_email_t& e)
749 : {
750 12 : if(!e.f_group.empty()
751 6 : || !e.f_original_email.empty()
752 6 : || !e.f_fullname.empty()
753 6 : || !e.f_username.empty()
754 6 : || !e.f_domain.empty()
755 6 : || !e.f_email_only.empty()
756 12 : || !e.f_canonicalized_email.empty())
757 : {
758 0 : error("error: one of the structure parameters was modified on error!");
759 : }
760 6 : }
761 :
762 :
763 1 : void test_direct_email()
764 : {
765 1 : tld_email_list::tld_email_t email;
766 :
767 : ////////////// EMAILS
768 : // missing closing \"
769 2 : EXPECTED_THROW(email.parse("\"blah alexis@m2osw.com"), std::logic_error);
770 1 : contract_furfilled(email);
771 :
772 : // missing closing )
773 2 : EXPECTED_THROW(email.parse("(comment alexis@m2osw.com"), std::logic_error);
774 1 : contract_furfilled(email);
775 :
776 : // use of \ at the end of the comment
777 2 : EXPECTED_THROW(email.parse("(comment\\"), std::logic_error);
778 1 : contract_furfilled(email);
779 :
780 : // missing closing ]
781 2 : EXPECTED_THROW(email.parse("alexis@[m2osw.com"), std::logic_error);
782 1 : contract_furfilled(email);
783 :
784 : ////////////// GROUP
785 : // missing closing )
786 2 : EXPECTED_THROW(email.parse_group("Group (comment"), std::logic_error);
787 1 : contract_furfilled(email);
788 :
789 : // use of \ at the end of the comment
790 2 : EXPECTED_THROW(email.parse_group("Group (comment \\"), std::logic_error);
791 1 : contract_furfilled(email);
792 1 : }
793 :
794 :
795 :
796 : /** \brief Structure used to define a set of fields to test.
797 : *
798 : * This structure is used in this test to define a list of fields
799 : * to test against the library.
800 : */
801 : struct email_field_types
802 : {
803 : const char * f_field;
804 : tld_email_field_type f_type;
805 : };
806 :
807 : /** \var email_field_types::f_field
808 : * \brief The name of the field to be tested.
809 : */
810 : /** \var email_field_types::f_type
811 : * \brief The type we expect the library to return for that field.
812 : */
813 :
814 : const email_field_types list_of_email_field_types[] =
815 : {
816 : // make sure case does not have side effects
817 : { "to", TLD_EMAIL_FIELD_TYPE_ADDRESS_LIST },
818 : { "To", TLD_EMAIL_FIELD_TYPE_ADDRESS_LIST },
819 : { "tO", TLD_EMAIL_FIELD_TYPE_ADDRESS_LIST },
820 : { "TO", TLD_EMAIL_FIELD_TYPE_ADDRESS_LIST },
821 :
822 : // check all fields that are expected to include emails
823 : { "from", TLD_EMAIL_FIELD_TYPE_MAILBOX_LIST },
824 : { "resent-from", TLD_EMAIL_FIELD_TYPE_MAILBOX_LIST },
825 : { "sender", TLD_EMAIL_FIELD_TYPE_MAILBOX },
826 : { "resent-sender", TLD_EMAIL_FIELD_TYPE_MAILBOX },
827 : { "to", TLD_EMAIL_FIELD_TYPE_ADDRESS_LIST },
828 : { "cc", TLD_EMAIL_FIELD_TYPE_ADDRESS_LIST },
829 : { "reply-to", TLD_EMAIL_FIELD_TYPE_ADDRESS_LIST },
830 : { "resent-to", TLD_EMAIL_FIELD_TYPE_ADDRESS_LIST },
831 : { "resent-cc", TLD_EMAIL_FIELD_TYPE_ADDRESS_LIST },
832 : { "bcc", TLD_EMAIL_FIELD_TYPE_ADDRESS_LIST_OPT },
833 : { "resent-bcc", TLD_EMAIL_FIELD_TYPE_ADDRESS_LIST_OPT },
834 :
835 : // check all fields with a colon
836 : { "from: someone", TLD_EMAIL_FIELD_TYPE_MAILBOX_LIST },
837 : { "resent-from: someone", TLD_EMAIL_FIELD_TYPE_MAILBOX_LIST },
838 : { "sender: someone", TLD_EMAIL_FIELD_TYPE_MAILBOX },
839 : { "resent-sender: someone", TLD_EMAIL_FIELD_TYPE_MAILBOX },
840 : { "to: someone", TLD_EMAIL_FIELD_TYPE_ADDRESS_LIST },
841 : { "cc: someone", TLD_EMAIL_FIELD_TYPE_ADDRESS_LIST },
842 : { "reply-to: someone", TLD_EMAIL_FIELD_TYPE_ADDRESS_LIST },
843 : { "resent-to: someone", TLD_EMAIL_FIELD_TYPE_ADDRESS_LIST },
844 : { "resent-cc: someone", TLD_EMAIL_FIELD_TYPE_ADDRESS_LIST },
845 : { "bcc: someone", TLD_EMAIL_FIELD_TYPE_ADDRESS_LIST_OPT },
846 : { "resent-bcc: someone", TLD_EMAIL_FIELD_TYPE_ADDRESS_LIST_OPT },
847 :
848 : // check other fields
849 : { "message-id", TLD_EMAIL_FIELD_TYPE_UNKNOWN },
850 : { "date", TLD_EMAIL_FIELD_TYPE_UNKNOWN },
851 : { "subject", TLD_EMAIL_FIELD_TYPE_UNKNOWN },
852 : { "x-extension", TLD_EMAIL_FIELD_TYPE_UNKNOWN },
853 :
854 : // check other fields with a colon
855 : { "message-id: something", TLD_EMAIL_FIELD_TYPE_UNKNOWN },
856 : { "date: something", TLD_EMAIL_FIELD_TYPE_UNKNOWN },
857 : { "subject: something", TLD_EMAIL_FIELD_TYPE_UNKNOWN },
858 : { "x-extension: something", TLD_EMAIL_FIELD_TYPE_UNKNOWN },
859 :
860 : // check for invalid field names
861 : { "s\xfc\x62ject", TLD_EMAIL_FIELD_TYPE_INVALID },
862 : { "subj\xe9\x63t", TLD_EMAIL_FIELD_TYPE_INVALID },
863 : { "-bad-dash", TLD_EMAIL_FIELD_TYPE_INVALID },
864 : { "0bad-digit", TLD_EMAIL_FIELD_TYPE_INVALID },
865 : { "1bad-digit", TLD_EMAIL_FIELD_TYPE_INVALID },
866 : { "2bad-digit", TLD_EMAIL_FIELD_TYPE_INVALID },
867 : { "3bad-digit", TLD_EMAIL_FIELD_TYPE_INVALID },
868 : { "4bad-digit", TLD_EMAIL_FIELD_TYPE_INVALID },
869 : { "5bad-digit", TLD_EMAIL_FIELD_TYPE_INVALID },
870 : { "6bad-digit", TLD_EMAIL_FIELD_TYPE_INVALID },
871 : { "7bad-digit", TLD_EMAIL_FIELD_TYPE_INVALID },
872 : { "8bad-digit", TLD_EMAIL_FIELD_TYPE_INVALID },
873 : { "9bad-digit", TLD_EMAIL_FIELD_TYPE_INVALID },
874 : { "" /*empty*/, TLD_EMAIL_FIELD_TYPE_INVALID },
875 : };
876 :
877 1 : void test_email_field_types()
878 : {
879 49 : for(size_t i(0); i < sizeof(list_of_email_field_types) / sizeof(list_of_email_field_types[0]); ++i)
880 : {
881 48 : tld_email_field_type type(tld_email_list::email_field_type(list_of_email_field_types[i].f_field));
882 48 : if(type != list_of_email_field_types[i].f_type)
883 : {
884 0 : std::stringstream ss;
885 0 : ss << "error: email type mismatch for \"" << list_of_email_field_types[i].f_field
886 0 : << "\", expected " << static_cast<int>(list_of_email_field_types[i].f_type)
887 0 : << ", got " << static_cast<int>(type) << " instead.";
888 0 : error(ss.str());
889 : }
890 : }
891 1 : }
892 :
893 :
894 :
895 1 : int main(int argc, char *argv[])
896 : {
897 1 : printf("testing tld emails version %s\n", tld_version());
898 :
899 1 : if(argc > 1)
900 : {
901 0 : if(strcmp(argv[1], "-v") == 0)
902 : {
903 0 : verbose = 1;
904 : }
905 : }
906 :
907 : /* Call all the tests, one by one.
908 : *
909 : * Failures are "recorded" in the err_count global variable
910 : * and the process stops with an error message and exit(1)
911 : * if err_count is not zero.
912 : *
913 : * Exceptions that should not occur are expected to also
914 : * be caught and reported as errors.
915 : */
916 : try
917 : {
918 1 : test_valid_emails();
919 1 : test_invalid_emails();
920 1 : test_direct_email();
921 1 : test_email_field_types();
922 : }
923 : catch(const invalid_domain&)
924 : {
925 : error("error: caught an exception when all emails are expected to be valid.");
926 : }
927 :
928 1 : if(err_count)
929 : {
930 : fprintf(stderr, "%d error%s occured.\n",
931 0 : err_count, err_count != 1 ? "s" : "");
932 : }
933 1 : exit(err_count ? 1 : 0);
934 : }
935 :
936 : /* vim: ts=4 sw=4 et
937 : */
|