Line data Source code
1 : // Snap Websites Server -- snap websites serving children
2 : // Copyright (c) 2011-2019 Made to Order Software Corp. All Rights Reserved
3 : //
4 : // https://snapwebsites.org/
5 : // contact@m2osw.com
6 : //
7 : // This program is free software; you can redistribute it and/or modify
8 : // it under the terms of the GNU General Public License as published by
9 : // the Free Software Foundation; either version 2 of the License, or
10 : // (at your option) any later version.
11 : //
12 : // This program is distributed in the hope that it will be useful,
13 : // but WITHOUT ANY WARRANTY; without even the implied warranty of
14 : // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 : // GNU General Public License for more details.
16 : //
17 : // You should have received a copy of the GNU General Public License
18 : // along with this program; if not, write to the Free Software
19 : // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
20 :
21 :
22 : // self
23 : //
24 : #include "snapwebsites/snap_child.h"
25 :
26 :
27 : // snapwebsites lib
28 : //
29 : #include "snapwebsites/compression.h"
30 : #include "snapwebsites/flags.h"
31 : #include "snapwebsites/http_strings.h"
32 : #include "snapwebsites/log.h"
33 : #include "snapwebsites/mail_exchanger.h"
34 : #include "snapwebsites/mkgmtime.h"
35 : #include "snapwebsites/process.h"
36 : #include "snapwebsites/qdomhelpers.h"
37 : #include "snapwebsites/qlockfile.h"
38 : #include "snapwebsites/snap_image.h"
39 : #include "snapwebsites/snapwebsites.h"
40 : #include "snapwebsites/snap_lock.h"
41 : #include "snapwebsites/snap_magic.h"
42 :
43 :
44 : // snapdev lib
45 : //
46 : #include <snapdev/not_used.h>
47 :
48 :
49 : // dbproxy lib
50 : //
51 : #include <libdbproxy/exception.h>
52 :
53 :
54 : // Qt Serialization lib
55 : //
56 : #include <QtSerialization/QSerialization.h>
57 :
58 :
59 : // libutf8 lib
60 : //
61 : #include <libutf8/libutf8.h>
62 :
63 :
64 : // tld lib
65 : //
66 : #include <libtld/tld.h>
67 :
68 :
69 : // C++ lib
70 : //
71 : #include <sstream>
72 :
73 :
74 : // C lib
75 : //
76 : #include <errno.h>
77 : #include <signal.h>
78 : #include <stdio.h>
79 : #include <wait.h>
80 : #include <sys/prctl.h>
81 : #include <sys/syscall.h>
82 :
83 :
84 : // Qt lib
85 : //
86 : #include <QDirIterator>
87 :
88 :
89 : // last include
90 : //
91 : #include <snapdev/poison.h>
92 :
93 :
94 :
95 :
96 : namespace snap
97 : {
98 :
99 :
100 :
101 :
102 : /** \class snap_child
103 : * \brief Child process class.
104 : *
105 : * This class handles child objects that process queries from the Snap
106 : * CGI tool.
107 : *
108 : * The children appear in the Snap Server and themselves. The server is
109 : * the parent that handles the lifetime of the child. The parent also
110 : * holds the child process identifier and it waits on the child for its
111 : * death.
112 : *
113 : * The child itself has its f_child_pid set to zero.
114 : *
115 : * Some of the functions will react with an error if called from the
116 : * wrong process (i.e. parent calling a child process function and vice
117 : * versa.)
118 : */
119 :
120 : /** \fn int64_t get_start_date() const
121 : * \brief Retrieve the date when the child process started.
122 : *
123 : * This function returns the date, in micro seconds (seconds x 1,000,000)
124 : * when the child was forked from the server.
125 : *
126 : * In some situation, it may be useful to reset this time to the clock.
127 : * In most cases this is done in backends. This is done by calling
128 : * init_start_date().
129 : *
130 : * \sa init_start_date()
131 : * \sa get_start_time()
132 : */
133 :
134 : /** \fn time_t get_start_time() const
135 : * \brief Retrieve the date when the child process started in seconds.
136 : *
137 : * This function returns the date in seconds (same as a Unix date)
138 : * when the child was forked from the server.
139 : *
140 : * This is the same date as get_start_date() would returned, divided
141 : * by 1 million (rounded down).
142 : *
143 : * \sa get_start_date()
144 : */
145 :
146 : namespace
147 : {
148 :
149 :
150 : /** \brief Retrieve the current thread identifier.
151 : *
152 : * This function retrieves the current thread identifier.
153 : *
154 : * \return The thread identifier, which is a pid_t specific to each thread
155 : * of a process.
156 : */
157 0 : pid_t gettid()
158 : {
159 0 : return syscall(SYS_gettid);
160 : }
161 :
162 :
163 : // list of plugins that we cannot do without
164 : char const * g_minimum_plugins[] =
165 : {
166 : "attachment",
167 : "content",
168 : "editor",
169 : "filter",
170 : "form", // this one will be removed completely (phased out)
171 : "info",
172 : "javascript",
173 : "layout",
174 : "links",
175 : "list",
176 : "listener",
177 : "locale",
178 : "menu",
179 : "messages",
180 : "mimetype", // this should not be required
181 : "output",
182 : "password",
183 : "path",
184 : "permissions",
185 : "sendmail",
186 : "server_access",
187 : "sessions",
188 : "taxonomy",
189 : "users",
190 : "users_ui"
191 : };
192 :
193 : char const * g_week_day_name[] =
194 : {
195 : "Sunday", "Monday", "Tuesday", "Wedneday", "Thursday", "Friday", "Saturday"
196 : };
197 : int const g_week_day_length[] = { 6, 6, 7, 8, 8, 6, 8 }; // strlen() of g_week_day_name's
198 : char const * g_month_name[] =
199 : {
200 : "January", "February", "Marsh", "April", "May", "June",
201 : "July", "August", "September", "October", "November", "December"
202 : };
203 : int const g_month_length[] = { 7, 8, 5, 5, 3, 4, 4, 6, 9, 7, 8, 8 }; // strlen() of g_month_name
204 : int const g_month_days[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
205 : signed char const g_timezone_adjust[26] =
206 : {
207 : /* A */ -1,
208 : /* B */ -2,
209 : /* C */ -3,
210 : /* D */ -4,
211 : /* E */ -5,
212 : /* F */ -6,
213 : /* G */ -7,
214 : /* H */ -8,
215 : /* I */ -9,
216 : /* J */ 0, // not used
217 : /* K */ -10,
218 : /* L */ -11,
219 : /* M */ -12,
220 : /* N */ 1,
221 : /* O */ 2,
222 : /* P */ 3,
223 : /* Q */ 4,
224 : /* R */ 5,
225 : /* S */ 6,
226 : /* T */ 7,
227 : /* U */ 8,
228 : /* V */ 9,
229 : /* W */ 10,
230 : /* X */ 11,
231 : /* Y */ 12,
232 : /* Z */ 0, // Zulu time is zero
233 : };
234 :
235 :
236 :
237 : // valid language names
238 : // a full language definition is xx_YY where xx is the two letter name
239 : // of a language and YY is a two letter name of a country; we also support
240 : // 3 letter language names, and full country names (for now)
241 : snap_child::language_name_t const g_language_names[] =
242 : {
243 : {
244 : "Abkhaz",
245 : u8"\u0430\u04A7\u0441\u0443\u0430 \u0431\u044B\u0437\u0448\u04D9\u0430, \u0430\u04A7\u0441\u0448\u04D9\u0430",
246 : { 'a', 'b', '\0' },
247 : ",abk,abks,"
248 : },
249 : {
250 : "Afar",
251 : u8"Afaraf",
252 : { 'a', 'a', '\0' },
253 : ",aar,aars,"
254 : },
255 : {
256 : "Afrikaans",
257 : u8"Afrikaans",
258 : { 'a', 'f', '\0' },
259 : ",afr,afrs,"
260 : },
261 : {
262 : "Akan",
263 : u8"Akan",
264 : { 'a', 'k', '\0' },
265 : ",aka,"
266 : },
267 : {
268 : "Albanian",
269 : u8"gjuha shqipe",
270 : { 's', 'q', '\0' },
271 : ",sqi,alb,"
272 : },
273 : {
274 : "Amharic",
275 : u8"\u12A0\u121B\u122D\u129B",
276 : { 'a', 'm', '\0' },
277 : ",amh,"
278 : },
279 : {
280 : "Arabic",
281 : u8"\u0627\u0644\u0639\u0631\u0628\u064A\u0629",
282 : { 'a', 'r', '\0' },
283 : ",ara,"
284 : },
285 : {
286 : "Aragonese",
287 : u8"aragon\u00E9s",
288 : { 'a', 'n', '\0' },
289 : ",arg,"
290 : },
291 : {
292 : "Armenian",
293 : u8"\u0540\u0561\u0575\u0565\u0580\u0565\u0576",
294 : { 'h', 'y', '\0' },
295 : ",hye,arm,"
296 : },
297 : {
298 : "Assamese",
299 : u8"\u0985\u09B8\u09AE\u09C0\u09AF\u09BC\u09BE",
300 : { 'a', 's', '\0' },
301 : ",asm,"
302 : },
303 : {
304 : "Avaric",
305 : u8"\u0430\u0432\u0430\u0440 \u043C\u0430\u0446\u04C0, \u043C\u0430\u0433\u04C0\u0430\u0440\u0443\u043B \u043C\u0430\u0446\u04C0",
306 : { 'a', 'v', '\0' },
307 : ",ava,"
308 : },
309 : {
310 : "Avestan",
311 : u8"avesta",
312 : { 'a', 'e', '\0' },
313 : ",ave,"
314 : },
315 : {
316 : "Aymara",
317 : u8"aymar aru",
318 : { 'a', 'y', '\0' },
319 : ",aym,"
320 : },
321 : {
322 : "Azerbaijani",
323 : u8"az\u0259rbaycan dili",
324 : { 'a', 'z', '\0' },
325 : ",aze,"
326 : },
327 : {
328 : "Bambara",
329 : u8"bamanankan",
330 : { 'b', 'm', '\0' },
331 : ",bam,"
332 : },
333 : {
334 : "Bashkir",
335 : u8"\u0431\u0430\u0448\u04A1\u043E\u0440\u0442 \u0442\u0435\u043B\u0435",
336 : { 'b', 'a', '\0' },
337 : ",bak,"
338 : },
339 : {
340 : "Basque",
341 : u8"euskara",
342 : { 'e', 'u', '\0' },
343 : ",eus,baq,"
344 : },
345 : {
346 : "Belarusian",
347 : u8"\u0431\u0435\u043B\u0430\u0440\u0443\u0441\u043A\u0430\u044F \u043C\u043E\u0432\u0430",
348 : { 'b', 'e', '\0' },
349 : ",bel,"
350 : },
351 : {
352 : "Bengali",
353 : u8"\u09AC\u09BE\u0982\u09B2\u09BE",
354 : { 'b', 'n', '\0' },
355 : ",ben,"
356 : },
357 : {
358 : "Bihari",
359 : u8"\u092D\u094B\u091C\u092A\u0941\u0930\u0940",
360 : { 'b', 'h', '\0' },
361 : ",bih,"
362 : },
363 : {
364 : "Bislama",
365 : u8"Bislama",
366 : { 'b', 'i', '\0' },
367 : ",bis,"
368 : },
369 : {
370 : "Bosnian",
371 : u8"bosanski jezik",
372 : { 'b', 's', '\0' },
373 : ",bos,boss,"
374 : },
375 : {
376 : "Breton",
377 : u8"brezhoneg",
378 : { 'b', 'r', '\0' },
379 : ",bre,"
380 : },
381 : {
382 : "Bulgarian",
383 : u8"\u0431\u044A\u043B\u0433\u0430\u0440\u0441\u043A\u0438 \u0435\u0437\u0438\u043A",
384 : { 'b', 'g', '\0' },
385 : ",bul,buls,"
386 : },
387 : {
388 : "Burmese",
389 : u8"\u1017\u1019\u102C\u1005\u102C",
390 : { 'm', 'y', '\0' },
391 : ",mya,bur,"
392 : },
393 : {
394 : "Catalan; Valencian",
395 : u8"catal\u00E0, valanci\u00E0",
396 : { 'c', 'a', '\0' },
397 : ",cat,"
398 : },
399 : {
400 : "Chamorro",
401 : u8"Chamoru",
402 : { 'c', 'h', '\0' },
403 : ",cha,"
404 : },
405 : {
406 : "Chechen",
407 : u8"\u043D\u043E\u0445\u0447\u0438\u0439\u043D \u043C\u043E\u0442\u0442",
408 : { 'c', 'e', '\0' },
409 : ",che,"
410 : },
411 : {
412 : "Chichewa; Chewa; Nyanja",
413 : u8"chiChe\u0175a, chinyanja",
414 : { 'n', 'y', '\0' },
415 : ",nya,"
416 : },
417 : {
418 : "Chinese",
419 : u8"\u4E2D\u6587 (Zh\u014Dngw\u00E9n), \u6C49\u8BED, \u6F22\u8A9E",
420 : { 'z', 'h', '\0' },
421 : ",zho,chi,"
422 : },
423 : {
424 : "Chuvash",
425 : u8"\u0447\u04D1\u0432\u0430\u0448 \u0447\u04D7\u043B\u0445\u0438",
426 : { 'c', 'v', '\0' },
427 : ",chv,"
428 : },
429 : {
430 : "Cornish",
431 : u8"Kernewek",
432 : { 'k', 'w', '\0' },
433 : ",cor,"
434 : },
435 : {
436 : "Corsican",
437 : u8"corsu, lingua corsa",
438 : { 'c', 'o', '\0' },
439 : ",cos,"
440 : },
441 : {
442 : "Cree",
443 : u8"\u14C0\u1426\u1403\u152D\u140D\u140F\u1423",
444 : { 'c', 'r', '\0' },
445 : ",cre,"
446 : },
447 : {
448 : "Croatian",
449 : u8"hrvatski jezik",
450 : { 'h', 'r', '\0' },
451 : ",hrv,"
452 : },
453 : {
454 : "Czech",
455 : u8"\u010De\u0161tina, \u010Desk\u00FD jazyk",
456 : { 'c', 's', '\0' },
457 : ",ces,cze,"
458 : },
459 : {
460 : "Danish",
461 : u8"dansk",
462 : { 'd', 'a', '\0' },
463 : ",dan,"
464 : },
465 : {
466 : "Divehi; Dhivehi; Maldivian",
467 : u8"\u078B\u07A8\u0788\u07AC\u0780\u07A8",
468 : { 'd', 'v', '\0' },
469 : ",div,"
470 : },
471 : {
472 : "Dutch",
473 : u8"Nederlands, Vlaams",
474 : { 'n', 'l', '\0' },
475 : ",nld,dut,"
476 : },
477 : {
478 : "Dzongkha",
479 : u8"\u0F62\u0FAB\u0F7C\u0F44\u0F0B\u0F41",
480 : { 'd', 'z', '\0' },
481 : ",dzo,"
482 : },
483 : {
484 : "English",
485 : u8"English",
486 : { 'e', 'n', '\0' },
487 : ",eng,engs,"
488 : },
489 : {
490 : "Esperanto",
491 : u8"Esperanto",
492 : { 'e', 'o', '\0' },
493 : ",epo,"
494 : },
495 : {
496 : "Estonian",
497 : u8"eesti, eesti keel",
498 : { 'e', 't', '\0' },
499 : ",est,"
500 : },
501 : {
502 : "Ewe",
503 : u8"E\u028Begbe",
504 : { 'e', 'e', '\0' },
505 : ",ewe,"
506 : },
507 : {
508 : "Faroese",
509 : u8"f\u00F8royskt",
510 : { 'f', 'o', '\0' },
511 : ",fao,"
512 : },
513 : {
514 : "Fijian",
515 : u8"vosa Vakaviti",
516 : { 'f', 'j', '\0' },
517 : ",fij,"
518 : },
519 : {
520 : "Finnish",
521 : u8"suomi, suomen kieli",
522 : { 'f', 'i', '\0' },
523 : ",fin,"
524 : },
525 : {
526 : "French",
527 : u8"fran\u00E7ais, langue fran\u00E7aise",
528 : { 'f', 'r', '\0' },
529 : ",fra,fras,"
530 : },
531 : {
532 : "Fula; Fulah; Pulaar; Pular",
533 : u8"Fulfulde, Pulaar, Pular",
534 : { 'f', 'f', '\0' },
535 : ",ful,"
536 : },
537 : {
538 : "Galician",
539 : u8"galego",
540 : { 'g', 'l', '\0' },
541 : ",glg,"
542 : },
543 : {
544 : "Georgian",
545 : u8"\u10E5\u10D0\u10E0\u10D7\u10E3\u10DA\u10D8",
546 : { 'k', 'a', '\0' },
547 : ",kat,geo,"
548 : },
549 : {
550 : "German",
551 : u8"Deutsch",
552 : { 'd', 'e', '\0' },
553 : ",deu,ger,deus,"
554 : },
555 : {
556 : "Greek, Modern",
557 : u8"\u03B5\u03BB\u03BB\u03B7\u03BD\u03B9\u03BA\u03AC",
558 : { 'e', 'l', '\0' },
559 : ",ell,gre,ells,"
560 : },
561 : {
562 : u8"Guaran\u00ED",
563 : u8"Ava\u00F1e'\u1EBD",
564 : { 'g', 'n', '\0' },
565 : ",grn,"
566 : },
567 : {
568 : "Gujarati",
569 : u8"\u0A97\u0AC1\u0A9C\u0AB0\u0ABE\u0AA4\u0AC0",
570 : { 'g', 'u', '\0' },
571 : ",guj,"
572 : },
573 : {
574 : "Haitian; Haitian Creole",
575 : u8"Krey\u00F2l ayisyen",
576 : { 'h', 't', '\0' },
577 : ",hat,"
578 : },
579 : {
580 : "Hausa",
581 : u8"Hause, \u0647\u064E\u0648\u064F\u0633\u064E",
582 : { 'h', 'a', '\0' },
583 : ",hau,"
584 : },
585 : {
586 : "Hebrew (modern)",
587 : u8"\u05E2\u05D1\u05E8\u05D9\u05EA",
588 : { 'h', 'e', '\0' },
589 : ",heb,"
590 : },
591 : {
592 : "Herero",
593 : u8"Otjiherero",
594 : { 'h', 'z', '\0' },
595 : ",her,"
596 : },
597 : {
598 : "Hindi",
599 : u8"\u0939\u093F\u0928\u094D\u0926\u0940, \u0939\u093F\u0902\u0926\u0940",
600 : { 'h', 'i', '\0' },
601 : ",hin,"
602 : },
603 : {
604 : "Hiri Motu",
605 : u8"Hiri Motu",
606 : { 'h', 'o', '\0' },
607 : ",hmo,"
608 : },
609 : {
610 : "Hungarian",
611 : u8"magyar",
612 : { 'h', 'u', '\0' },
613 : ",hun,"
614 : },
615 : {
616 : "Interlingua",
617 : u8"Interlingua",
618 : { 'i', 'a', '\0' },
619 : ",ina,"
620 : },
621 : {
622 : "Indonesian",
623 : u8"Bahasa Indonesia",
624 : { 'i', 'd', '\0' },
625 : ",ind,"
626 : },
627 : {
628 : "Interlingue",
629 : u8"Interlingue",
630 : { 'i', 'e', '\0' },
631 : ",ile,"
632 : },
633 : {
634 : "Irish",
635 : u8"Gaeilge",
636 : { 'g', 'a', '\0' },
637 : ",gle,"
638 : },
639 : {
640 : "Igbo",
641 : u8"As\u1E85s\u1E85 Igbo",
642 : { 'i', 'g', '\0' },
643 : ",ibo,"
644 : },
645 : {
646 : "Inupiaq",
647 : u8"I\u00F1upiaq, I\u00F1upiatun",
648 : { 'i', 'k', '\0' },
649 : ",ipk,"
650 : },
651 : {
652 : "Ido",
653 : u8"Ido",
654 : { 'i', 'o', '\0' },
655 : ",ido,"
656 : },
657 : {
658 : "Icelandic",
659 : u8"\u00CDslenska",
660 : { 'i', 's', '\0' },
661 : ",isl,ice,"
662 : },
663 : {
664 : "Italian",
665 : u8"italiano",
666 : { 'i', 't', '\0' },
667 : ",ita,itas,"
668 : },
669 : {
670 : "Inuktitut",
671 : u8"\u1403\u14C4\u1483\u144E\u1450\u1466",
672 : { 'i', 'u', '\0' },
673 : ",iku,"
674 : },
675 : {
676 : "Japanese",
677 : u8"\u65E5\u672C\u8A9E (\u306B\u307B\u3093\u3054)",
678 : { 'j', 'a', '\0' },
679 : ",jpn,"
680 : },
681 : {
682 : "Javanese",
683 : u8"basa Jawa",
684 : { 'j', 'v', '\0' },
685 : ",jav,"
686 : },
687 : {
688 : "Kalaallisut, Greenlandic",
689 : u8"kalaallisut, kalaallit oqaasii",
690 : { 'k', 'l', '\0' },
691 : ",kal,"
692 : },
693 : {
694 : "Kannada",
695 : u8"\u0C95\u0CA8\u0CCD\u0CA8\u0CA1",
696 : { 'k', 'n', '\0' },
697 : ",kan,"
698 : },
699 : {
700 : "Kanuri",
701 : u8"Kanuri",
702 : { 'k', 'r', '\0' },
703 : ",kau,"
704 : },
705 : {
706 : "Kashmiri",
707 : u8"\u0915\u0936\u094D\u092E\u0940\u0930\u0940, \u0643\u0634\u0645\u064A\u0631\u064A",
708 : { 'k', 's', '\0' },
709 : ",kas,"
710 : },
711 : {
712 : "Kazakh",
713 : u8"\u049B\u0430\u0437\u0430\u049B \u0442\u0456\u043B\u0456",
714 : { 'k', 'k', '\0' },
715 : ",kaz,"
716 : },
717 : {
718 : "Khmer",
719 : u8"\u1781\u17D2\u1798\u17C2\u179A, \u1781\u17C1\u1798\u179A\u1797\u17B6\u179F\u17B6, \u1797\u17B6\u179F\u17B6\u1781\u17D2\u1798\u17C2\u179A",
720 : { 'k', 'm', '\0' },
721 : ",khm,"
722 : },
723 : {
724 : "Kikuyu, Gikuyu",
725 : u8"G\u0129k\u0169y\u0169",
726 : { 'k', 'i', '\0' },
727 : ",kik,"
728 : },
729 : {
730 : "Kinyarwanda",
731 : u8"Ikinyarwanda",
732 : { 'r', 'w', '\0' },
733 : ",kin,"
734 : },
735 : {
736 : "Kyrgyz",
737 : u8"\u041A\u044B\u0440\u0433\u044B\u0437\u0447\u0430, \u041A\u044B\u0440\u0433\u044B\u0437 \u0442\u0438\u043B\u0438",
738 : { 'k', 'y', '\0' },
739 : ",kir,"
740 : },
741 : {
742 : "Komi",
743 : u8"\u043A\u043E\u043C\u0438 \u043A\u044B\u0432",
744 : { 'k', 'v', '\0' },
745 : ",kom,"
746 : },
747 : {
748 : "Kongo",
749 : u8"KiKongo",
750 : { 'k', 'g', '\0' },
751 : ",kon,"
752 : },
753 : {
754 : "Korean",
755 : u8"\uD55C\uAD6D\uC5B4 (\u97D3\u570B\u8A9E), \uC870\uC120\uC5B4 (\u671D\u9BAE\u8A9E)",
756 : { 'k', 'o', '\0' },
757 : ",kor,"
758 : },
759 : {
760 : "Kurdish",
761 : u8"Kurd\u00EE, \u0643\u0648\u0631\u062F\u06CC",
762 : { 'k', 'u', '\0' },
763 : ",kur,"
764 : },
765 : {
766 : "Kwanyama, Kuanyama",
767 : u8"Kuanyama",
768 : { 'k', 'j', '\0' },
769 : ",kua,"
770 : },
771 : {
772 : "Latin",
773 : u8"latine, lingua latina",
774 : { 'l', 'a', '\0' },
775 : ",lat,lats,"
776 : },
777 : {
778 : "Luxembourgish, Letzeburgesch",
779 : u8"L\u00EBtzebuergesch",
780 : { 'l', 'b', '\0' },
781 : ",ltz,"
782 : },
783 : {
784 : "Ganda",
785 : u8"Luganda",
786 : { 'l', 'g', '\0' },
787 : ",lug,"
788 : },
789 : {
790 : "Limburgish, Limburgan, Limburger",
791 : u8"Limburgs",
792 : { 'l', 'i', '\0' },
793 : ",lim,"
794 : },
795 : {
796 : "Lingala",
797 : u8"Ling\u00E1la",
798 : { 'l', 'n', '\0' },
799 : ",lin,"
800 : },
801 : {
802 : "Lao",
803 : u8"\u0E9E\u0EB2\u0EAA\u0EB2\u0EA5\u0EB2\u0EA7",
804 : { 'l', 'o', '\0' },
805 : ",lao,"
806 : },
807 : {
808 : "Lithuanian",
809 : u8"lietuvi\u0173 kalba",
810 : { 'l', 't', '\0' },
811 : ",lit,"
812 : },
813 : {
814 : "Luba-Katanga",
815 : u8"Tshiluba",
816 : { 'l', 'u', '\0' },
817 : ",lub,"
818 : },
819 : {
820 : "Latvian",
821 : u8"latvie\u0161u valoda",
822 : { 'l', 'v', '\0' },
823 : ",lav,"
824 : },
825 : {
826 : "Manx",
827 : u8"Gaelg, Gailck",
828 : { 'g', 'v', '\0' },
829 : ",glv,"
830 : },
831 : {
832 : "Macedonian",
833 : u8"\u043C\u0430\u043A\u0435\u0434\u043E\u043D\u0441\u043A\u0438 \u0458\u0430\u0437\u0438\u043A",
834 : { 'm', 'k', '\0' },
835 : ",mkd,mac,"
836 : },
837 : {
838 : "Malagasy",
839 : u8"fiteny malagasy",
840 : { 'm', 'g', '\0' },
841 : ",mlg,"
842 : },
843 : {
844 : "Malay",
845 : u8"bahasa Melayu, \u0628\u0647\u0627\u0633 \u0645\u0644\u0627\u064A\u0648",
846 : { 'm', 's', '\0' },
847 : ",msa,may,"
848 : },
849 : {
850 : "Malayalam",
851 : u8"\u0D2E\u0D32\u0D2F\u0D3E\u0D33\u0D02",
852 : { 'm', 'l', '\0' },
853 : ",mal,"
854 : },
855 : {
856 : "Maltese",
857 : u8"Malti",
858 : { 'm', 't', '\0' },
859 : ",mlt,"
860 : },
861 : {
862 : u8"M\u0101ori",
863 : u8"te reo M\u0101ori",
864 : { 'm', 'i', '\0' },
865 : ",mri,mao,"
866 : },
867 : {
868 : u8"Marathi (Mar\u0101\u1E6Dh\u012B)",
869 : u8"\u092E\u0930\u093E\u0920\u0940",
870 : { 'm', 'r', '\0' },
871 : ",mar,"
872 : },
873 : {
874 : "Marshallese",
875 : u8"Kajin M\u0327aje\u013C",
876 : { 'm', 'h', '\0' },
877 : ",mah,"
878 : },
879 : {
880 : "Mongolian",
881 : u8"\u043C\u043E\u043D\u0433\u043E\u043B",
882 : { 'm', 'n', '\0' },
883 : ",mon,"
884 : },
885 : {
886 : "Nauru",
887 : u8"Ekakair\u0169 Naoero",
888 : { 'n', 'a', '\0' },
889 : ",nau,"
890 : },
891 : {
892 : "Navajo, Navaho",
893 : u8"Din\u00E9 bizaad, Din\u00E9k\u02BCeh\u01F0\u00ED",
894 : { 'n', 'v', '\0' },
895 : ",nav,"
896 : },
897 : {
898 : "Norwegian Bokm\u00E5l",
899 : u8"Norsk bokm\u00E5l",
900 : { 'n', 'b', '\0' },
901 : ",nob,"
902 : },
903 : {
904 : "North Ndebele",
905 : u8"isiNdebele",
906 : { 'n', 'd', '\0' },
907 : ",nde,"
908 : },
909 : {
910 : "Nepali",
911 : u8"\u0928\u0947\u092A\u093E\u0932\u0940",
912 : { 'n', 'e', '\0' },
913 : ",nep,"
914 : },
915 : {
916 : "Ndonga",
917 : u8"Owambo",
918 : { 'n', 'g', '\0' },
919 : ",ndo,"
920 : },
921 : {
922 : "Norwegian Nynorsk",
923 : u8"Norsk nynorsk",
924 : { 'n', 'n', '\0' },
925 : ",nno,"
926 : },
927 : {
928 : "Norwegian",
929 : u8"Norsk",
930 : { 'n', 'o', '\0' },
931 : ",nor,"
932 : },
933 : {
934 : "Nuosu",
935 : u8"\uA188\uA320\uA4BF Nuosuhxop",
936 : { 'i', 'i', '\0' },
937 : ",iii,"
938 : },
939 : {
940 : "South Ndebele",
941 : u8"isiNdebele",
942 : { 'n', 'r', '\0' },
943 : ",nbl,"
944 : },
945 : {
946 : "Occitan",
947 : u8"occitan, lenga d'\u00F2c",
948 : { 'o', 'c', '\0' },
949 : ",oci,"
950 : },
951 : {
952 : "Ojibwe, Ojibwa",
953 : u8"\u140A\u14C2\u1511\u14C8\u142F\u14A7\u140E\u14D0",
954 : { 'o', 'j', '\0' },
955 : ",oji,"
956 : },
957 : {
958 : "Old Church Slavonic, Church Slavic, Church Slavonic, Old Bulgarian, Old Slavonic",
959 : u8"\u0469\u0437\u044B\u043A\u044A \u0441\u043B\u043E\u0432\u0463\u043D\u044C\u0441\u043A\u044A",
960 : { 'c', 'u', '\0' },
961 : ",chu,"
962 : },
963 : {
964 : "Oromo",
965 : u8"Afaan Oromoo",
966 : { 'o', 'm', '\0' },
967 : ",orm,"
968 : },
969 : {
970 : "Oriya",
971 : u8"\u0B13\u0B21\u0B3C\u0B3F\u0B06",
972 : { 'o', 'r', '\0' },
973 : ",ori,"
974 : },
975 : {
976 : "Ossetian, Ossetic",
977 : u8"\u0438\u0440\u043E\u043D \u00E6\u0432\u0437\u0430\u0433",
978 : { 'o', 's', '\0' },
979 : ",oss,"
980 : },
981 : {
982 : "Panjabi, Punjabi",
983 : u8"\u0A2A\u0A70\u0A1C\u0A3E\u0A2C\u0A40, \u067E\u0646\u062C\u0627\u0628\u06CC",
984 : { 'p', 'a', '\0' },
985 : ",pan,"
986 : },
987 : {
988 : "P\u0101li",
989 : u8"\u092A\u093E\u0934\u093F",
990 : { 'p', 'i', '\0' },
991 : ",pli,"
992 : },
993 : {
994 : "Persian (Farsi)",
995 : u8"\u0641\u0627\u0631\u0633\u06CC",
996 : { 'f', 'a', '\0' },
997 : ",fas,per,"
998 : },
999 : {
1000 : "Polish",
1001 : u8"j\u0119zyk polski, polszczyzna",
1002 : { 'p', 'l', '\0' },
1003 : ",pol,pols,"
1004 : },
1005 : {
1006 : "Pashto, Pushto",
1007 : u8"\u067E\u069A\u062A\u0648",
1008 : { 'p', 's', '\0' },
1009 : ",pus,"
1010 : },
1011 : {
1012 : "Portuguese",
1013 : u8"portugu\u00EAs",
1014 : { 'p', 't', '\0' },
1015 : ",por,"
1016 : },
1017 : {
1018 : "Quechua",
1019 : u8"Runa Simi, Kichwa",
1020 : { 'q', 'u', '\0' },
1021 : ",que,"
1022 : },
1023 : {
1024 : "Romansh",
1025 : u8"rumantsch grischun",
1026 : { 'r', 'm', '\0' },
1027 : ",roh,"
1028 : },
1029 : {
1030 : "Kirundi",
1031 : u8"Ikirundi",
1032 : { 'r', 'n', '\0' },
1033 : ",run,"
1034 : },
1035 : {
1036 : "Romanian",
1037 : u8"limba rom\u00E2n\u0103",
1038 : { 'r', 'o', '\0' },
1039 : ",ron,rum,"
1040 : },
1041 : {
1042 : "Russian",
1043 : u8"\u0440\u0443\u0441\u0441\u043A\u0438\u0439 \u044F\u0437\u044B\u043A",
1044 : { 'r', 'u', '\0' },
1045 : ",rus,"
1046 : },
1047 : {
1048 : "Sanskrit (Sa\u1E41sk\u1E5Bta)",
1049 : u8"\u0938\u0902\u0938\u094D\u0915\u0943\u0924\u092E\u094D",
1050 : { 's', 'a', '\0' },
1051 : ",san,"
1052 : },
1053 : {
1054 : "Sardinian",
1055 : u8"sardu",
1056 : { 's', 'c', '\0' },
1057 : ",srd,"
1058 : },
1059 : {
1060 : "Sindhi",
1061 : u8"\u0938\u093F\u0928\u094D\u0927\u0940, \u0633\u0646\u068C\u064A\u060C \u0633\u0646\u062F\u06BE\u06CC",
1062 : { 's', 'd', '\0' },
1063 : ",snd,"
1064 : },
1065 : {
1066 : "Northern Sami",
1067 : u8"Davvis\u00E1megiella",
1068 : { 's', 'e', '\0' },
1069 : ",sme,"
1070 : },
1071 : {
1072 : "Samoan",
1073 : u8"gagana fa'a Samoa",
1074 : { 's', 'm', '\0' },
1075 : ",smo,"
1076 : },
1077 : {
1078 : "Sango",
1079 : u8"y\u00E2ng\u00E2 t\u00EE s\u00E3ng\u00F6",
1080 : { 's', 'g', '\0' },
1081 : ",sag,"
1082 : },
1083 : {
1084 : "Serbian",
1085 : u8"\u0441\u0440\u043F\u0441\u043A\u0438 \u0458\u0435\u0437\u0438\u043A",
1086 : { 's', 'r', '\0' },
1087 : ",srp,"
1088 : },
1089 : {
1090 : "Scottish Gaelic; Gaelic",
1091 : u8"G\u00E0idhlig",
1092 : { 'g', 'd', '\0' },
1093 : ",gla,"
1094 : },
1095 : {
1096 : "Shona",
1097 : u8"chiShona",
1098 : { 's', 'n', '\0' },
1099 : ",sna,"
1100 : },
1101 : {
1102 : "Sinhala, Sinhalese",
1103 : u8"\u0DC3\u0DD2\u0D82\u0DC4\u0DBD",
1104 : { 's', 'i', '\0' },
1105 : ",sin,"
1106 : },
1107 : {
1108 : "Slovak",
1109 : u8"sloven\u010Dina, slovensk\u00FD jazyk",
1110 : { 's', 'k', '\0' },
1111 : ",slk,slo,"
1112 : },
1113 : {
1114 : "Slovene",
1115 : u8"slovenski jezik, sloven\u0161\u010Dina",
1116 : { 's', 'l', '\0' },
1117 : ",slv,"
1118 : },
1119 : {
1120 : "Somali",
1121 : u8"Soomaaliga, af Soomaali",
1122 : { 's', 'o', '\0' },
1123 : ",som,"
1124 : },
1125 : {
1126 : "Southern Sotho",
1127 : u8"Sesotho",
1128 : { 's', 't', '\0' },
1129 : ",sot,"
1130 : },
1131 : {
1132 : "South Azerbaijani",
1133 : u8"\u062A\u0648\u0631\u06A9\u062C\u0647",
1134 : { 'a', 'z', '\0' },
1135 : ",azb,"
1136 : },
1137 : {
1138 : "Spanish; Castilian",
1139 : u8"espa\u00F1ol, castellano",
1140 : { 'e', 's', '\0' },
1141 : ",spa,"
1142 : },
1143 : {
1144 : "Sundanese",
1145 : u8"Basa Sunda",
1146 : { 's', 'u', '\0' },
1147 : ",sun,"
1148 : },
1149 : {
1150 : "Swahili",
1151 : u8"Kiswahili",
1152 : { 's', 'w', '\0' },
1153 : ",swa,"
1154 : },
1155 : {
1156 : "Swati",
1157 : u8"SiSwati",
1158 : { 's', 's', '\0' },
1159 : ",ssw,"
1160 : },
1161 : {
1162 : "Swedish",
1163 : u8"Svenska",
1164 : { 's', 'v', '\0' },
1165 : ",swe,"
1166 : },
1167 : {
1168 : "Tamil",
1169 : u8"\u0BA4\u0BAE\u0BBF\u0BB4\u0BCD",
1170 : { 't', 'a', '\0' },
1171 : ",tam,"
1172 : },
1173 : {
1174 : "Telugu",
1175 : u8"\u0C24\u0C46\u0C32\u0C41\u0C17\u0C41",
1176 : { 't', 'e', '\0' },
1177 : ",tel,"
1178 : },
1179 : {
1180 : "Tajik",
1181 : u8"\u0442\u043E\u04B7\u0438\u043A\u04E3, to\u011Fik\u012B, \u062A\u0627\u062C\u06CC\u06A9\u06CC",
1182 : { 't', 'g', '\0' },
1183 : ",tgk,"
1184 : },
1185 : {
1186 : "Thai",
1187 : u8"\u0E44\u0E17\u0E22",
1188 : { 't', 'h', '\0' },
1189 : ",tha,"
1190 : },
1191 : {
1192 : "Tigrinya",
1193 : u8"\u1275\u130D\u122D\u129B",
1194 : { 't', 'i', '\0' },
1195 : ",tir,"
1196 : },
1197 : {
1198 : "Tibetan Standard, Tibetan, Central",
1199 : u8"\u0F56\u0F7C\u0F51\u0F0B\u0F61\u0F72\u0F42",
1200 : { 'b', 'o', '\0' },
1201 : ",bod,tib,"
1202 : },
1203 : {
1204 : "Turkmen",
1205 : u8"T\u00FCrkmen, \u0422\u04AF\u0440\u043A\u043C\u0435\u043D",
1206 : { 't', 'k', '\0' },
1207 : ",tuk,"
1208 : },
1209 : {
1210 : "Tagalog",
1211 : u8"Wikang Tagalog, \u170F\u1712\u1703\u1705\u1714 \u1706\u1704\u170E\u1713\u1704\u1714",
1212 : { 't', 'l', '\0' },
1213 : ",tgl,"
1214 : },
1215 : {
1216 : "Tswana",
1217 : u8"Setswana",
1218 : { 't', 'n', '\0' },
1219 : ",tsn,"
1220 : },
1221 : {
1222 : "Tonga (Tonga Islands)",
1223 : u8"faka Tonga",
1224 : { 't', 'o', '\0' },
1225 : ",ton,"
1226 : },
1227 : {
1228 : "Turkish",
1229 : u8"T\u00FCrk\u00E7e",
1230 : { 't', 'r', '\0' },
1231 : ",tur,"
1232 : },
1233 : {
1234 : "Tsonga",
1235 : u8"Xitsonga",
1236 : { 't', 's', '\0' },
1237 : ",tso,"
1238 : },
1239 : {
1240 : "Tatar",
1241 : u8"\u0442\u0430\u0442\u0430\u0440 \u0442\u0435\u043B\u0435, tatar tele",
1242 : { 't', 't', '\0' },
1243 : ",tat,"
1244 : },
1245 : {
1246 : "Twi",
1247 : u8"Twi",
1248 : { 't', 'w', '\0' },
1249 : ",twi,"
1250 : },
1251 : {
1252 : "Tahitian",
1253 : u8"Reo Tahiti",
1254 : { 't', 'y', '\0' },
1255 : ",tah,"
1256 : },
1257 : {
1258 : "Uyghur, Uighur",
1259 : u8"Uy\u01A3urq\u0259, \u0626\u06C7\u064A\u063A\u06C7\u0631\u0686\u06D5",
1260 : { 'u', 'g', '\0' },
1261 : ",uig,"
1262 : },
1263 : {
1264 : "Ukrainian",
1265 : u8"\u0443\u043A\u0440\u0430\u0457\u043D\u0441\u044C\u043A\u0430 \u043C\u043E\u0432\u0430",
1266 : { 'u', 'k', '\0' },
1267 : ",ukr,"
1268 : },
1269 : {
1270 : "Urdu",
1271 : u8"\u0627\u0631\u062F\u0648",
1272 : { 'u', 'r', '\0' },
1273 : ",urd,"
1274 : },
1275 : {
1276 : "Uzbek",
1277 : u8"O\u2018zbek, \u040E\u0437\u0431\u0435\u043A, \u0623\u06C7\u0632\u0628\u06D0\u0643",
1278 : { 'u', 'z', '\0' },
1279 : ",uzb,"
1280 : },
1281 : {
1282 : "Venda",
1283 : u8"Tshiven\u1E13a",
1284 : { 'v', 'e', '\0' },
1285 : ",ven,"
1286 : },
1287 : {
1288 : "Vietnamese",
1289 : u8"Ti\u1EBFng Vi\u1EC7t",
1290 : { 'v', 'i', '\0' },
1291 : ",vie,"
1292 : },
1293 : {
1294 : "Volap\u00FCk",
1295 : u8"Volap\u00FCk",
1296 : { 'v', 'o', '\0' },
1297 : ",vol,"
1298 : },
1299 : {
1300 : "Walloon",
1301 : u8"walon",
1302 : { 'w', 'a', '\0' },
1303 : ",wln,"
1304 : },
1305 : {
1306 : "Welsh",
1307 : u8"Cymraeg",
1308 : { 'c', 'y', '\0' },
1309 : ",cym,wel,"
1310 : },
1311 : {
1312 : "Wolof",
1313 : u8"Wollof",
1314 : { 'w', 'o', '\0' },
1315 : ",wol,"
1316 : },
1317 : {
1318 : "Western Frisian",
1319 : u8"Frysk",
1320 : { 'f', 'y', '\0' },
1321 : ",fry,"
1322 : },
1323 : {
1324 : "Xhosa",
1325 : u8"isiXhosa",
1326 : { 'x', 'o', '\0' },
1327 : ",xho,"
1328 : },
1329 : {
1330 : "Undefined",
1331 : u8"undefined",
1332 : { 'x', 'x', '\0' },
1333 : ",,"
1334 : },
1335 : {
1336 : "Yiddish",
1337 : u8"\u05D9\u05D9\u05B4\u05D3\u05D9\u05E9",
1338 : { 'y', 'i', '\0' },
1339 : ",yid,"
1340 : },
1341 : {
1342 : "Yoruba",
1343 : u8"Yor\u00F9b\u00E1",
1344 : { 'y', 'o', '\0' },
1345 : ",yor,"
1346 : },
1347 : {
1348 : "Zhuang, Chuang",
1349 : u8"Sa\u026F cue\u014B\u0185, Saw cuengh",
1350 : { 'z', 'a', '\0' },
1351 : ",zha,"
1352 : },
1353 : {
1354 : "Zulu",
1355 : u8"isiZulu",
1356 : { 'z', 'u', '\0' },
1357 : ",zul,"
1358 : },
1359 : {
1360 : nullptr,
1361 : nullptr,
1362 : { '\0', '\0', '\0' },
1363 : nullptr
1364 : }
1365 : };
1366 :
1367 : // this is the list of 2 letter country names
1368 : snap_child::country_name_t const g_country_names[] =
1369 : {
1370 : {
1371 : { 'A', 'D', '\0' },
1372 : "Andorra"
1373 : },
1374 : {
1375 : { 'A', 'E', '\0' },
1376 : "United Arab Emirates"
1377 : },
1378 : {
1379 : { 'A', 'F', '\0' },
1380 : "Afghanistan"
1381 : },
1382 : {
1383 : { 'A', 'G', '\0' },
1384 : "Antigua and Barbuda"
1385 : },
1386 : {
1387 : { 'A', 'I', '\0' },
1388 : "Anguilla"
1389 : },
1390 : {
1391 : { 'A', 'L', '\0' },
1392 : "Albania"
1393 : },
1394 : {
1395 : { 'A', 'M', '\0' },
1396 : "Armenia"
1397 : },
1398 : {
1399 : { 'A', 'O', '\0' },
1400 : "Angola"
1401 : },
1402 : {
1403 : { 'A', 'Q', '\0' },
1404 : "Antarctica"
1405 : },
1406 : {
1407 : { 'A', 'R', '\0' },
1408 : "Argentina"
1409 : },
1410 : {
1411 : { 'A', 'S', '\0' },
1412 : "American Samoa"
1413 : },
1414 : {
1415 : { 'A', 'T', '\0' },
1416 : "Austria"
1417 : },
1418 : {
1419 : { 'A', 'U', '\0' },
1420 : "Australia"
1421 : },
1422 : {
1423 : { 'A', 'W', '\0' },
1424 : "Aruba"
1425 : },
1426 : {
1427 : { 'A', 'X', '\0' },
1428 : u8"\00C5land Islands"
1429 : },
1430 : {
1431 : { 'A', 'Z', '\0' },
1432 : "Azerbaijan"
1433 : },
1434 : {
1435 : { 'B', 'A', '\0' },
1436 : "Bosnia and Herzegovina"
1437 : },
1438 : {
1439 : { 'B', 'B', '\0' },
1440 : "Barbados"
1441 : },
1442 : {
1443 : { 'B', 'D', '\0' },
1444 : "Bangladesh"
1445 : },
1446 : {
1447 : { 'B', 'E', '\0' },
1448 : "Belgium"
1449 : },
1450 : {
1451 : { 'B', 'F', '\0' },
1452 : "Burkina Faso"
1453 : },
1454 : {
1455 : { 'B', 'G', '\0' },
1456 : "Bulgaria"
1457 : },
1458 : {
1459 : { 'B', 'H', '\0' },
1460 : "Bahrain"
1461 : },
1462 : {
1463 : { 'B', 'I', '\0' },
1464 : "Burundi"
1465 : },
1466 : {
1467 : { 'B', 'J', '\0' },
1468 : "Benin"
1469 : },
1470 : {
1471 : { 'B', 'L', '\0' },
1472 : u8"Saint Barth\u00E9lemy"
1473 : },
1474 : {
1475 : { 'B', 'M', '\0' },
1476 : "Bermuda"
1477 : },
1478 : {
1479 : { 'B', 'N', '\0' },
1480 : "Brunei Darussalam"
1481 : },
1482 : {
1483 : { 'B', 'O', '\0' },
1484 : "Bolivia, Plurinational State of"
1485 : },
1486 : {
1487 : { 'B', 'Q', '\0' },
1488 : "Bonaire, Sint Eustatius and Saba"
1489 : },
1490 : {
1491 : { 'B', 'R', '\0' },
1492 : "Brazil"
1493 : },
1494 : {
1495 : { 'B', 'S', '\0' },
1496 : "Bahamas"
1497 : },
1498 : {
1499 : { 'B', 'T', '\0' },
1500 : "Bhutan"
1501 : },
1502 : {
1503 : { 'B', 'V', '\0' },
1504 : "Bouvet Island"
1505 : },
1506 : {
1507 : { 'B', 'W', '\0' },
1508 : "Botswana"
1509 : },
1510 : {
1511 : { 'B', 'Y', '\0' },
1512 : "Belarus"
1513 : },
1514 : {
1515 : { 'B', 'Z', '\0' },
1516 : "Belize"
1517 : },
1518 : {
1519 : { 'C', 'A', '\0' },
1520 : "Canada"
1521 : },
1522 : {
1523 : { 'C', 'C', '\0' },
1524 : "Cocos (Keeling) Islands"
1525 : },
1526 : {
1527 : { 'C', 'D', '\0' },
1528 : "Congo, the Democratic Republic of the"
1529 : },
1530 : {
1531 : { 'C', 'F', '\0' },
1532 : "Central African Republic"
1533 : },
1534 : {
1535 : { 'C', 'G', '\0' },
1536 : "Congo"
1537 : },
1538 : {
1539 : { 'C', 'H', '\0' },
1540 : "Switzerland"
1541 : },
1542 : {
1543 : { 'C', 'I', '\0' },
1544 : "C\u00F4te d'Ivoire"
1545 : },
1546 : {
1547 : { 'C', 'K', '\0' },
1548 : "Cook Islands"
1549 : },
1550 : {
1551 : { 'C', 'L', '\0' },
1552 : "Chile"
1553 : },
1554 : {
1555 : { 'C', 'M', '\0' },
1556 : "Cameroon"
1557 : },
1558 : {
1559 : { 'C', 'N', '\0' },
1560 : "China"
1561 : },
1562 : {
1563 : { 'C', 'O', '\0' },
1564 : "Colombia"
1565 : },
1566 : {
1567 : { 'C', 'R', '\0' },
1568 : "Costa Rica"
1569 : },
1570 : {
1571 : { 'C', 'U', '\0' },
1572 : "Cuba"
1573 : },
1574 : {
1575 : { 'C', 'V', '\0' },
1576 : "Cape Verde"
1577 : },
1578 : {
1579 : { 'C', 'W', '\0' },
1580 : "Cura\u00E7ao"
1581 : },
1582 : {
1583 : { 'C', 'X', '\0' },
1584 : "Christmas Island"
1585 : },
1586 : {
1587 : { 'X', 'Y', '\0' },
1588 : "Cyprus"
1589 : },
1590 : {
1591 : { 'C', 'Z', '\0' },
1592 : "Czech Republic"
1593 : },
1594 : {
1595 : { 'D', 'E', '\0' },
1596 : "Germany"
1597 : },
1598 : {
1599 : { 'D', 'J', '\0' },
1600 : "Djibouti"
1601 : },
1602 : {
1603 : { 'D', 'K', '\0' },
1604 : "Denmark"
1605 : },
1606 : {
1607 : { 'D', 'M', '\0' },
1608 : "Dominica"
1609 : },
1610 : {
1611 : { 'D', 'O', '\0' },
1612 : "Dominican Republic"
1613 : },
1614 : {
1615 : { 'D', 'Z', '\0' },
1616 : "Algeria"
1617 : },
1618 : {
1619 : { 'E', 'C', '\0' },
1620 : "Ecuador"
1621 : },
1622 : {
1623 : { 'E', 'E', '\0' },
1624 : "Estonia"
1625 : },
1626 : {
1627 : { 'E', 'G', '\0' },
1628 : "Egypt"
1629 : },
1630 : {
1631 : { 'E', 'H', '\0' },
1632 : "Western Sahara"
1633 : },
1634 : {
1635 : { 'E', 'R', '\0' },
1636 : "Eritrea"
1637 : },
1638 : {
1639 : { 'E', 'S', '\0' },
1640 : "Spain"
1641 : },
1642 : {
1643 : { 'E', 'T', '\0' },
1644 : "Ethiopia"
1645 : },
1646 : {
1647 : { 'F', 'I', '\0' },
1648 : "Finland"
1649 : },
1650 : {
1651 : { 'F', 'J', '\0' },
1652 : "Fiji"
1653 : },
1654 : {
1655 : { 'F', 'K', '\0' },
1656 : "Falkland Islands (Malvinas)"
1657 : },
1658 : {
1659 : { 'F', 'M', '\0' },
1660 : "Micronesia, Federated States of"
1661 : },
1662 : {
1663 : { 'F', 'O', '\0' },
1664 : "Faroe Islands"
1665 : },
1666 : {
1667 : { 'F', 'R', '\0' },
1668 : "France"
1669 : },
1670 : {
1671 : { 'G', 'A', '\0' },
1672 : "Gabon"
1673 : },
1674 : {
1675 : { 'G', 'B', '\0' },
1676 : "United Kingdom"
1677 : },
1678 : {
1679 : { 'G', 'D', '\0' },
1680 : "Grenada"
1681 : },
1682 : {
1683 : { 'G', 'E', '\0' },
1684 : "Georgia"
1685 : },
1686 : {
1687 : { 'G', 'F', '\0' },
1688 : "French Guiana"
1689 : },
1690 : {
1691 : { 'G', 'G', '\0' },
1692 : "Guernsey"
1693 : },
1694 : {
1695 : { 'G', 'H', '\0' },
1696 : "Ghana"
1697 : },
1698 : {
1699 : { 'G', 'I', '\0' },
1700 : "Gibraltar"
1701 : },
1702 : {
1703 : { 'G', 'L', '\0' },
1704 : "Greenland"
1705 : },
1706 : {
1707 : { 'G', 'M', '\0' },
1708 : "Gambia"
1709 : },
1710 : {
1711 : { 'G', 'N', '\0' },
1712 : "Guinea"
1713 : },
1714 : {
1715 : { 'G', 'P', '\0' },
1716 : "Guadeloupe"
1717 : },
1718 : {
1719 : { 'G', 'Q', '\0' },
1720 : "Equatorial Guinea"
1721 : },
1722 : {
1723 : { 'G', 'R', '\0' },
1724 : "Greece"
1725 : },
1726 : {
1727 : { 'G', 'S', '\0' },
1728 : "South Georgia and the South Sandwich Islands"
1729 : },
1730 : {
1731 : { 'G', 'T', '\0' },
1732 : "Guatemala"
1733 : },
1734 : {
1735 : { 'G', 'U', '\0' },
1736 : "Guam"
1737 : },
1738 : {
1739 : { 'G', 'W', '\0' },
1740 : "Guinea-Bissau"
1741 : },
1742 : {
1743 : { 'G', 'Y', '\0' },
1744 : "Guyana"
1745 : },
1746 : {
1747 : { 'H', 'K', '\0' },
1748 : "Hong Kong"
1749 : },
1750 : {
1751 : { 'H', 'M', '\0' },
1752 : "Heard Island and McDonald Islands"
1753 : },
1754 : {
1755 : { 'H', 'N', '\0' },
1756 : "Honduras"
1757 : },
1758 : {
1759 : { 'H', 'R', '\0' },
1760 : "Croatia"
1761 : },
1762 : {
1763 : { 'H', 'T', '\0' },
1764 : "Haiti"
1765 : },
1766 : {
1767 : { 'H', 'U', '\0' },
1768 : "Hungary"
1769 : },
1770 : {
1771 : { 'I', 'D', '\0' },
1772 : "Indonesia"
1773 : },
1774 : {
1775 : { 'I', 'E', '\0' },
1776 : "Ireland"
1777 : },
1778 : {
1779 : { 'I', 'L', '\0' },
1780 : "Israel"
1781 : },
1782 : {
1783 : { 'I', 'M', '\0' },
1784 : "Isle of Man"
1785 : },
1786 : {
1787 : { 'I', 'N', '\0' },
1788 : "India"
1789 : },
1790 : {
1791 : { 'I', 'O', '\0' },
1792 : "British Indian Ocean Territory"
1793 : },
1794 : {
1795 : { 'I', 'Q', '\0' },
1796 : "Iraq"
1797 : },
1798 : {
1799 : { 'I', 'R', '\0' },
1800 : "Iran, Islamic Republic of"
1801 : },
1802 : {
1803 : { 'I', 'S', '\0' },
1804 : "Iceland"
1805 : },
1806 : {
1807 : { 'I', 'T', '\0' },
1808 : "Italy"
1809 : },
1810 : {
1811 : { 'J', 'E', '\0' },
1812 : "Jersey"
1813 : },
1814 : {
1815 : { 'J', 'M', '\0' },
1816 : "Jamaica"
1817 : },
1818 : {
1819 : { 'J', 'O', '\0' },
1820 : "Jordan"
1821 : },
1822 : {
1823 : { 'J', 'P', '\0' },
1824 : "Japan"
1825 : },
1826 : {
1827 : { 'K', 'E', '\0' },
1828 : "Kenya"
1829 : },
1830 : {
1831 : { 'K', 'G', '\0' },
1832 : "Kyrgyzstan"
1833 : },
1834 : {
1835 : { 'K', 'H', '\0' },
1836 : "Cambodia"
1837 : },
1838 : {
1839 : { 'K', 'I', '\0' },
1840 : "Kiribati"
1841 : },
1842 : {
1843 : { 'K', 'M', '\0' },
1844 : "Comoros"
1845 : },
1846 : {
1847 : { 'K', 'N', '\0' },
1848 : "Saint Kitts and Nevis"
1849 : },
1850 : {
1851 : { 'K', 'P', '\0' },
1852 : "Korea, Democratic People's Republic of"
1853 : },
1854 : {
1855 : { 'K', 'R', '\0' },
1856 : "Korea, Republic of"
1857 : },
1858 : {
1859 : { 'K', 'W', '\0' },
1860 : "Kuwait"
1861 : },
1862 : {
1863 : { 'K', 'Y', '\0' },
1864 : "Cayman Islands"
1865 : },
1866 : {
1867 : { 'K', 'Z', '\0' },
1868 : "Kazakhstan"
1869 : },
1870 : {
1871 : { 'L', 'A', '\0' },
1872 : "Lao People's Democratic Republic"
1873 : },
1874 : {
1875 : { 'L', 'B', '\0' },
1876 : "Lebanon"
1877 : },
1878 : {
1879 : { 'L', 'C', '\0' },
1880 : "Saint Lucia"
1881 : },
1882 : {
1883 : { 'L', 'I', '\0' },
1884 : "Liechtenstein"
1885 : },
1886 : {
1887 : { 'L', 'K', '\0' },
1888 : "Sri Lanka"
1889 : },
1890 : {
1891 : { 'L', 'R', '\0' },
1892 : "Liberia"
1893 : },
1894 : {
1895 : { 'L', 'S', '\0' },
1896 : "Lesotho"
1897 : },
1898 : {
1899 : { 'L', 'T', '\0' },
1900 : "Lithuania"
1901 : },
1902 : {
1903 : { 'L', 'U', '\0' },
1904 : "Luxembourg"
1905 : },
1906 : {
1907 : { 'L', 'V', '\0' },
1908 : "Latvia"
1909 : },
1910 : {
1911 : { 'L', 'Y', '\0' },
1912 : "Libya"
1913 : },
1914 : {
1915 : { 'M', 'A', '\0' },
1916 : "Morocco"
1917 : },
1918 : {
1919 : { 'M', 'C', '\0' },
1920 : "Monaco"
1921 : },
1922 : {
1923 : { 'M', 'D', '\0' },
1924 : "Moldova, Republic of"
1925 : },
1926 : {
1927 : { 'M', 'E', '\0' },
1928 : "Montenegro"
1929 : },
1930 : {
1931 : { 'M', 'F', '\0' },
1932 : "Saint Martin (French part)"
1933 : },
1934 : {
1935 : { 'M', 'G', '\0' },
1936 : "Madagascar"
1937 : },
1938 : {
1939 : { 'M', 'H', '\0' },
1940 : "Marshall Islands"
1941 : },
1942 : {
1943 : { 'M', 'K', '\0' },
1944 : "Macedonia, the former Yugoslav Republic of"
1945 : },
1946 : {
1947 : { 'M', 'L', '\0' },
1948 : "Mali"
1949 : },
1950 : {
1951 : { 'M', 'M', '\0' },
1952 : "Myanmar"
1953 : },
1954 : {
1955 : { 'M', 'N', '\0' },
1956 : "Mongolia"
1957 : },
1958 : {
1959 : { 'M', 'O', '\0' },
1960 : "Macao"
1961 : },
1962 : {
1963 : { 'M', 'P', '\0' },
1964 : "Northern Mariana Islands"
1965 : },
1966 : {
1967 : { 'M', 'Q', '\0' },
1968 : "Martinique"
1969 : },
1970 : {
1971 : { 'M', 'R', '\0' },
1972 : "Mauritania"
1973 : },
1974 : {
1975 : { 'M', 'D', '\0' },
1976 : "Montserrat"
1977 : },
1978 : {
1979 : { 'M', 'T', '\0' },
1980 : "Malta"
1981 : },
1982 : {
1983 : { 'M', 'U', '\0' },
1984 : "Mauritius"
1985 : },
1986 : {
1987 : { 'M', 'V', '\0' },
1988 : "Maldives"
1989 : },
1990 : {
1991 : { 'M', 'W', '\0' },
1992 : "Malawi"
1993 : },
1994 : {
1995 : { 'M', 'X', '\0' },
1996 : "Mexico"
1997 : },
1998 : {
1999 : { 'M', 'Y', '\0' },
2000 : "Malaysia"
2001 : },
2002 : {
2003 : { 'M', 'Z', '\0' },
2004 : "Mozambique"
2005 : },
2006 : {
2007 : { 'N', 'A', '\0' },
2008 : "Namibia"
2009 : },
2010 : {
2011 : { 'N', 'C', '\0' },
2012 : "New Caledonia"
2013 : },
2014 : {
2015 : { 'N', 'E', '\0' },
2016 : "Niger"
2017 : },
2018 : {
2019 : { 'N', 'F', '\0' },
2020 : "Norfolk Island"
2021 : },
2022 : {
2023 : { 'N', 'G', '\0' },
2024 : "Nigeria"
2025 : },
2026 : {
2027 : { 'N', 'I', '\0' },
2028 : "Nicaragua"
2029 : },
2030 : {
2031 : { 'N', 'L', '\0' },
2032 : "Netherlands"
2033 : },
2034 : {
2035 : { 'N', 'O', '\0' },
2036 : "Norway"
2037 : },
2038 : {
2039 : { 'N', 'P', '\0' },
2040 : "Nepal"
2041 : },
2042 : {
2043 : { 'N', 'R', '\0' },
2044 : "Nauru"
2045 : },
2046 : {
2047 : { 'N', 'U', '\0' },
2048 : "Niue"
2049 : },
2050 : {
2051 : { 'N', 'Z', '\0' },
2052 : "New Zealand"
2053 : },
2054 : {
2055 : { 'O', 'M', '\0' },
2056 : "Oman"
2057 : },
2058 : {
2059 : { 'P', 'A', '\0' },
2060 : "Panama"
2061 : },
2062 : {
2063 : { 'P', 'E', '\0' },
2064 : "Peru"
2065 : },
2066 : {
2067 : { 'P', 'F', '\0' },
2068 : "French Polynesia"
2069 : },
2070 : {
2071 : { 'P', 'G', '\0' },
2072 : "Papua New Guinea"
2073 : },
2074 : {
2075 : { 'P', 'H', '\0' },
2076 : "Philippines"
2077 : },
2078 : {
2079 : { 'P', 'K', '\0' },
2080 : "Pakistan"
2081 : },
2082 : {
2083 : { 'P', 'L', '\0' },
2084 : "Poland"
2085 : },
2086 : {
2087 : { 'P', 'M', '\0' },
2088 : "Saint Pierre and Miquelon"
2089 : },
2090 : {
2091 : { 'P', 'N', '\0' },
2092 : "Pitcairn"
2093 : },
2094 : {
2095 : { 'P', 'R', '\0' },
2096 : "Puerto Rico"
2097 : },
2098 : {
2099 : { 'P', 'S', '\0' },
2100 : "Palestine, State of"
2101 : },
2102 : {
2103 : { 'P', 'T', '\0' },
2104 : "Portugal"
2105 : },
2106 : {
2107 : { 'P', 'W', '\0' },
2108 : "Palau"
2109 : },
2110 : {
2111 : { 'P', 'Y', '\0' },
2112 : "Paraguay"
2113 : },
2114 : {
2115 : { 'Q', 'A', '\0' },
2116 : "Qatar"
2117 : },
2118 : {
2119 : { 'R', 'E', '\0' },
2120 : "R\u00E9union"
2121 : },
2122 : {
2123 : { 'R', 'O', '\0' },
2124 : "Romania"
2125 : },
2126 : {
2127 : { 'R', 'S', '\0' },
2128 : "Serbia"
2129 : },
2130 : {
2131 : { 'R', 'U', '\0' },
2132 : "Russian Federation"
2133 : },
2134 : {
2135 : { 'R', 'W', '\0' },
2136 : "Rwanda"
2137 : },
2138 : {
2139 : { 'S', 'A', '\0' },
2140 : "Saudi Arabia"
2141 : },
2142 : {
2143 : { 'S', 'B', '\0' },
2144 : "Solomon Islands"
2145 : },
2146 : {
2147 : { 'S', 'C', '\0' },
2148 : "Seychelles"
2149 : },
2150 : {
2151 : { 'S', 'D', '\0' },
2152 : "Sudan"
2153 : },
2154 : {
2155 : { 'S', 'E', '\0' },
2156 : "Sweden"
2157 : },
2158 : {
2159 : { 'S', 'G', '\0' },
2160 : "Singapore"
2161 : },
2162 : {
2163 : { 'S', 'H', '\0' },
2164 : "Saint Helena, Ascension and Tristan da Cunha"
2165 : },
2166 : {
2167 : { 'S', 'I', '\0' },
2168 : "Slovenia"
2169 : },
2170 : {
2171 : { 'S', 'J', '\0' },
2172 : "Svalbard and Jan Mayen"
2173 : },
2174 : {
2175 : { 'S', 'K', '\0' },
2176 : "Slovakia"
2177 : },
2178 : {
2179 : { 'S', 'L', '\0' },
2180 : "Sierra Leone"
2181 : },
2182 : {
2183 : { 'S', 'M', '\0' },
2184 : "San Marino"
2185 : },
2186 : {
2187 : { 'S', 'N', '\0' },
2188 : "Senegal"
2189 : },
2190 : {
2191 : { 'S', 'O', '\0' },
2192 : "Somalia"
2193 : },
2194 : {
2195 : { 'S', 'R', '\0' },
2196 : "Suriname"
2197 : },
2198 : {
2199 : { 'S', 'S', '\0' },
2200 : "South Sudan"
2201 : },
2202 : {
2203 : { 'S', 'T', '\0' },
2204 : "Sao Tome and Principe"
2205 : },
2206 : {
2207 : { 'S', 'V', '\0' },
2208 : "El Salvador"
2209 : },
2210 : {
2211 : { 'S', 'X', '\0' },
2212 : "Sint Maarten (Dutch part)"
2213 : },
2214 : {
2215 : { 'S', 'Y', '\0' },
2216 : "Syrian Arab Republic"
2217 : },
2218 : {
2219 : { 'S', 'Z', '\0' },
2220 : "Swaziland"
2221 : },
2222 : {
2223 : { 'T', 'C', '\0' },
2224 : "Turks and Caicos Islands"
2225 : },
2226 : {
2227 : { 'T', 'D', '\0' },
2228 : "Chad"
2229 : },
2230 : {
2231 : { 'T', 'F', '\0' },
2232 : "French Southern Territories"
2233 : },
2234 : {
2235 : { 'T', 'G', '\0' },
2236 : "Togo"
2237 : },
2238 : {
2239 : { 'T', 'H', '\0' },
2240 : "Thailand"
2241 : },
2242 : {
2243 : { 'T', 'J', '\0' },
2244 : "Tajikistan"
2245 : },
2246 : {
2247 : { 'T', 'K', '\0' },
2248 : "Tokelau"
2249 : },
2250 : {
2251 : { 'T', 'L', '\0' },
2252 : "Timor-Leste"
2253 : },
2254 : {
2255 : { 'T', 'M', '\0' },
2256 : "Turkmenistan"
2257 : },
2258 : {
2259 : { 'T', 'N', '\0' },
2260 : "Tunisia"
2261 : },
2262 : {
2263 : { 'T', 'O', '\0' },
2264 : "Tonga"
2265 : },
2266 : {
2267 : { 'T', 'R', '\0' },
2268 : "Turkey"
2269 : },
2270 : {
2271 : { 'T', 'T', '\0' },
2272 : "Trinidad and Tobago"
2273 : },
2274 : {
2275 : { 'T', 'V', '\0' },
2276 : "Tuvalu"
2277 : },
2278 : {
2279 : { 'T', 'W', '\0' },
2280 : "Taiwan, Province of China"
2281 : },
2282 : {
2283 : { 'T', 'Z', '\0' },
2284 : "Tanzania, United Republic of"
2285 : },
2286 : {
2287 : { 'U', 'A', '\0' },
2288 : "Ukraine"
2289 : },
2290 : {
2291 : { 'U', 'G', '\0' },
2292 : "Uganda"
2293 : },
2294 : {
2295 : { 'U', 'M', '\0' },
2296 : "United States Minor Outlying Islands"
2297 : },
2298 : {
2299 : { 'U', 'S', '\0' },
2300 : "United States"
2301 : },
2302 : {
2303 : { 'U', 'Y', '\0' },
2304 : "Uruguay"
2305 : },
2306 : {
2307 : { 'U', 'Z', '\0' },
2308 : "Uzbekistan"
2309 : },
2310 : {
2311 : { 'V', 'A', '\0' },
2312 : "Holy See (Vatican City State)"
2313 : },
2314 : {
2315 : { 'V', 'C', '\0' },
2316 : "Saint Vincent and the Grenadines"
2317 : },
2318 : {
2319 : { 'V', 'E', '\0' },
2320 : "Venezuela, Bolivarian Republic of"
2321 : },
2322 : {
2323 : { 'V', 'G', '\0' },
2324 : "Virgin Islands, British"
2325 : },
2326 : {
2327 : { 'V', 'I', '\0' },
2328 : "Virgin Islands, U.S."
2329 : },
2330 : {
2331 : { 'V', 'N', '\0' },
2332 : "Viet Nam"
2333 : },
2334 : {
2335 : { 'V', 'U', '\0' },
2336 : "Vanuatu"
2337 : },
2338 : {
2339 : { 'W', 'F', '\0' },
2340 : "Wallis and Futuna"
2341 : },
2342 : {
2343 : { 'W', 'S', '\0' },
2344 : "Samoa"
2345 : },
2346 : {
2347 : { 'Y', 'E', '\0' },
2348 : "Yemen"
2349 : },
2350 : {
2351 : { 'Y', 'T', '\0' },
2352 : "Mayotte"
2353 : },
2354 : {
2355 : { 'Z', 'A', '\0' },
2356 : "South Africa"
2357 : },
2358 : {
2359 : { 'Z', 'M', '\0' },
2360 : "Zambia"
2361 : },
2362 : {
2363 : { 'Z', 'W', '\0' },
2364 : "Zimbabwe"
2365 : },
2366 : {
2367 : { 'Z', 'Z', '\0' },
2368 : "Unknown" // unknown, invalid, undefined
2369 : },
2370 : {
2371 : { '\0', '\0', '\0' },
2372 : nullptr
2373 : }
2374 : };
2375 :
2376 :
2377 : } // no name namespace
2378 :
2379 :
2380 :
2381 : /** \brief Get a composed version of this locale.
2382 : *
2383 : * The locale can be empty, just a language name, or a language
2384 : * named, an underscore, and a country name.
2385 : *
2386 : * \return The composed locale.
2387 : */
2388 0 : QString snap_child::locale_info_t::get_composed() const
2389 : {
2390 0 : if(f_country.isEmpty())
2391 : {
2392 0 : return f_language;
2393 : }
2394 :
2395 0 : return QString("%1_%2").arg(f_language).arg(f_country);
2396 : }
2397 :
2398 :
2399 :
2400 : /** \brief Save the file data in the post_file_t object.
2401 : *
2402 : * This function makes a copy of the data in the post_file_t
2403 : * object. Note that since we're using a QByteArray, the
2404 : * real copy is done only on write and since we do not modify
2405 : * the buffer, it should only copy the buffer pointer, not
2406 : * the content.
2407 : *
2408 : * The function also computes the "real" MIME type using the
2409 : * magic library.
2410 : *
2411 : * \param[in] data The data of the file to save in this object.
2412 : *
2413 : * \sa snap::get_mime_type();
2414 : */
2415 0 : void snap_child::post_file_t::set_data(QByteArray const & data)
2416 : {
2417 0 : f_data = data;
2418 0 : f_size = data.size();
2419 :
2420 : // namespace required otherwise we'd call the get_mime_type
2421 : // function of the post_file_t class!
2422 0 : f_mime_type = snap::get_mime_type(data);
2423 :
2424 : //printf("mime: %s\n", f_mime_type.toUtf8().data());
2425 0 : }
2426 :
2427 :
2428 : /** \brief Retrieve the basename.
2429 : *
2430 : * This function returns the filename without any path.
2431 : * The extension is not removed.
2432 : *
2433 : * \return The filename without a path
2434 : */
2435 0 : QString snap_child::post_file_t::get_basename() const
2436 : {
2437 0 : int const last_slash(f_filename.lastIndexOf('/'));
2438 0 : if(last_slash >= 0)
2439 : {
2440 0 : return f_filename.mid(last_slash + 1);
2441 : }
2442 0 : return f_filename;
2443 : }
2444 :
2445 :
2446 : /** \brief Get the size of the buffer.
2447 : *
2448 : * This function retrieves the real size of the data buffer.
2449 : * When loading an attachment from Cassandra, it is possible to
2450 : * ask the system to not load the data to save time (i.e. if you
2451 : * are just looking into showing a list of attachments and you
2452 : * do not need the actual data...) In that case the load_attachment()
2453 : * function of the content plugin only sets the size and no data.
2454 : *
2455 : * This function makes sure that the correct size gets returned.
2456 : *
2457 : * \return The size of the data.
2458 : */
2459 0 : int snap_child::post_file_t::get_size() const
2460 : {
2461 0 : int size(f_data.size());
2462 0 : if(size == 0)
2463 : {
2464 0 : size = f_size;
2465 : }
2466 0 : return size;
2467 : }
2468 :
2469 :
2470 : /** \brief Initialize a child process.
2471 : *
2472 : * This function initializes a child process object. At this point it
2473 : * marked as a parent process child instance (i.e. calling child process
2474 : * functions will generate an error.)
2475 : *
2476 : * Whenever the parent Snap Server receives a connect from the Snap CGI
2477 : * tool, it calls the process() function which creates the child and starts
2478 : * processing the TCP request.
2479 : *
2480 : * Note that at this point there is not communication between the parent
2481 : * and child processes other than the child process death that the parent
2482 : * acknowledge at some point.
2483 : */
2484 0 : snap_child::snap_child(server_pointer_t s)
2485 : : f_server(s)
2486 : , f_start_date(0)
2487 : , f_output() // -Weffc++ forces us to have this one here
2488 : , f_messenger_runner(this)
2489 0 : , f_messenger_thread("background messenger connection thread", &f_messenger_runner)
2490 : {
2491 0 : }
2492 :
2493 :
2494 : /** \brief Clean up a child process.
2495 : *
2496 : * This function cleans up a child process. For the parent this means waiting
2497 : * on the child, assuming that the child process was successfully started.
2498 : * This also means that the function may block until the child dies...
2499 : */
2500 0 : snap_child::~snap_child()
2501 : {
2502 : // detach or wait till it dies?
2503 0 : if(f_child_pid > 0)
2504 : {
2505 : int status;
2506 0 : wait(&status);
2507 : }
2508 : //else
2509 : //{ // this is the child process deleting itself
2510 : // ...
2511 : // if(f_socket != -1)
2512 : // {
2513 : // // this is automatic anyway (we're in Unix)
2514 : // // and if not already cleared, we've got more serious problems
2515 : // // (see the process() function for more info)
2516 : // close(f_socket);
2517 : // }
2518 : //}
2519 0 : }
2520 :
2521 : /** \brief Get the current date.
2522 : *
2523 : * PLEASE consider use get_start_date() instead of the current date.
2524 : * 99.999% of the time, you do NOT want to use the current date or
2525 : * all the dates you will manipulate will be different.
2526 : *
2527 : * In some really rare circumstance, you may want to make use of the
2528 : * "exact" current date instead of the start date. This function
2529 : * can be used in those rare cases.
2530 : *
2531 : * This function does not modify the start date of the current process.
2532 : *
2533 : * \sa init_start_date()
2534 : * \sa get_start_date()
2535 : * \sa get_start_time()
2536 : */
2537 0 : int64_t snap_child::get_current_date()
2538 : {
2539 : struct timeval tv;
2540 0 : if(gettimeofday(&tv, nullptr) != 0)
2541 : {
2542 0 : int const err(errno);
2543 0 : SNAP_LOG_FATAL("gettimeofday() failed with errno: ")(err);
2544 0 : throw std::runtime_error("gettimeofday() failed");
2545 : }
2546 0 : return static_cast<int64_t>(tv.tv_sec) * static_cast<int64_t>(1000000)
2547 0 : + static_cast<int64_t>(tv.tv_usec);
2548 : }
2549 :
2550 :
2551 : /** \brief Reset the start date to now.
2552 : *
2553 : * This function is called by the processing functions to reset the
2554 : * start date. This is important because child objects may be reused
2555 : * multiple times instead of allocated and deallocated by the server.
2556 : *
2557 : * Also, if you are writing a backend process, it should call this function
2558 : * on each loop to make sure it gets updated. This is quite important because
2559 : * you cannot be in control of all the functions that are going to be called
2560 : * from your process and some of them may actually be using this value
2561 : * without you having control over it.
2562 : */
2563 0 : void snap_child::init_start_date()
2564 : {
2565 0 : f_start_date = get_current_date();
2566 0 : }
2567 :
2568 :
2569 : /** \brief Change the current timezone to the newly specified timezone.
2570 : *
2571 : * \warning
2572 : * This is a low level function to set a timezone. You are actually
2573 : * expected to call the locale::set_timezone() event instead. That way
2574 : * you will be setup with the right timezone as expected by the various
2575 : * plugins.
2576 : *
2577 : * We force the server to run using the UTC timezone. This function can
2578 : * be used to change the timezone to a user timezone.
2579 : *
2580 : * To restore the timezone to the default server timezone, you may call this
2581 : * function with an empty string.
2582 : *
2583 : * Source:
2584 : * http://stackoverflow.com/questions/10378093/c-api-to-return-timestring-in-specified-time-zone/10378513#10378513
2585 : *
2586 : * \param[in] timezone Name of the timezone as understood by tzset(3).
2587 : */
2588 0 : void snap_child::set_timezone(QString const& timezone)
2589 : {
2590 : // force new timezone
2591 0 : setenv("TZ", timezone.toUtf8().data(), 1);
2592 0 : tzset();
2593 0 : }
2594 :
2595 :
2596 : /** \brief Change the current locale to the newly specified locale.
2597 : *
2598 : * \warning
2599 : * You want to look into the locale plugin instead. This function
2600 : * currently does nothing because it is very sensitive to the name
2601 : * of the locale. See the locale::set_locale() function instead.
2602 : *
2603 : * We force the server to run using the C locale. This function can
2604 : * be used to change the locale to a user locale or the website locale.
2605 : *
2606 : * To restore the locale to the default server locale, you may call this
2607 : * function with an empty string.
2608 : *
2609 : * \note
2610 : * The locale is not set to the empty string. This would set the locale
2611 : * to the current $LANG parameter and since we have no control over that
2612 : * parameter, we do not use it. It could otherwise have side effects over
2613 : * our server which we do not want to have. Instead we use "C" as the
2614 : * default locale. It may not do what you expected, but we can be sure to
2615 : * control what happens in that situation.
2616 : *
2617 : * Source:
2618 : * http://stackoverflow.com/questions/10378093/c-api-to-return-timestring-in-specified-time-zone/10378513#10378513
2619 : *
2620 : * \todo
2621 : * It looks like we cannot use this one unless we compile all the locales
2622 : * when the ICU library does not require such (TBD). Also the basic locale
2623 : * requires the encoding which we could force to UTF-8, but we cannot
2624 : * assume that every server would have UTF-8... So at this point this
2625 : * function does nothing. You are expected to call locale functions
2626 : * instead in order to get the correct string formatting behavior.
2627 : *
2628 : * \param[in] locale Name of the locale as understood by setlocale(3).
2629 : */
2630 0 : void snap_child::set_locale(QString const & locale)
2631 : {
2632 0 : NOTUSED(locale);
2633 : // force new locale
2634 : // Note: empty ("") is like using $LANG to setup the locale
2635 : //std::cerr << "***\n*** set_locale(" << locale << ")\n***\n";
2636 : //if(locale.isEmpty())
2637 : //{
2638 : // // use "C" as the default (which is different from "" which
2639 : // // uses $LANG as the locale and since that could be tainted
2640 : // // or have unwanted side effects, we do not use it.)
2641 : // if(std::setlocale(LC_ALL, "C.UTF-8"))
2642 : // {
2643 : // // it looks like C.UTF-8 is available, use that instead
2644 : // // so we avoid downgrading to just an ANSII console
2645 : // std::locale::global(std::locale("C.UTF-8"));
2646 : // }
2647 : // else
2648 : // {
2649 : // std::locale::global(std::locale("C"));
2650 : // }
2651 : //}
2652 : //else
2653 : //{
2654 : // // this function throws if the locale() is not available...
2655 : // std::locale::global(std::locale(locale.toUtf8().data()));
2656 : //}
2657 0 : }
2658 :
2659 :
2660 0 : void snap_child::connect_messenger()
2661 : {
2662 0 : auto server( f_server.lock() );
2663 0 : f_communicator = snap_communicator::instance();
2664 :
2665 0 : QString communicator_addr("127.0.0.1");
2666 0 : int communicator_port(4040);
2667 : tcp_client_server::get_addr_port
2668 0 : ( QString::fromUtf8(server->get_parameters()("snapcommunicator", "local_listen").c_str())
2669 : , communicator_addr
2670 : , communicator_port
2671 : , "tcp"
2672 : );
2673 :
2674 : // Create a new child_messenger object
2675 : //
2676 0 : f_messenger.reset(new child_messenger(this, communicator_addr.toUtf8().data(), communicator_port ));
2677 0 : f_messenger->set_name("child_messenger");
2678 0 : f_messenger->set_priority(50);
2679 :
2680 : // Add it into the instance list.
2681 : //
2682 0 : f_communicator->add_connection( f_messenger );
2683 :
2684 : // Add this to the logging facility so we can broadcast logs to snaplog via snapcommunicator.
2685 : //
2686 0 : server->configure_messenger_logging(
2687 0 : std::static_pointer_cast<snap_communicator::snap_tcp_client_permanent_message_connection>( f_messenger )
2688 : );
2689 :
2690 0 : if(!f_messenger_thread.start())
2691 : {
2692 0 : SNAP_LOG_ERROR("The thread used to run the background connection process did not start.");
2693 : }
2694 0 : }
2695 :
2696 :
2697 0 : void snap_child::stop_messenger()
2698 : {
2699 0 : if(f_communicator)
2700 : {
2701 0 : if(f_messenger)
2702 : {
2703 : // since we have the messenger, we should unregister
2704 : // but the truth is that the following would just cache
2705 : // the message and exepect the `snap_communicator::run()`
2706 : // to process the message at leisure...
2707 : //
2708 : // but since we are exiting, we ignore that step at the
2709 : // moment...
2710 : //
2711 : //snap::snap_communicator_message unregister_snapchild;
2712 : //unregister_snapchild.set_command("UNREGISTER");
2713 : //unregister_snapchild.add_parameter("service", f_service_name);
2714 : //send_message(unregister_snapchild);
2715 :
2716 : // This causes the background thread to stop.
2717 : //
2718 0 : f_messenger->mark_done();
2719 0 : f_communicator->remove_connection( f_messenger );
2720 0 : f_messenger.reset();
2721 : }
2722 :
2723 0 : f_communicator.reset();
2724 : }
2725 :
2726 0 : f_messenger_thread.stop();
2727 0 : }
2728 :
2729 :
2730 : /** \brief Initialize the child_messenger connection.
2731 : *
2732 : * This function initializes the child_messenger connection. It saves
2733 : * a pointer to the main Snap! server so it can react appropriately
2734 : * whenever a message is received.
2735 : *
2736 : * \param[in] s A pointer to the server so we can send messages there.
2737 : * \param[in] addr The address of the snapcommunicator server.
2738 : * \param[in] port The port of the snapcommunicator server.
2739 : * \param[in] use_thread If set to true, a thread is being used to connect to the server.
2740 : */
2741 0 : snap_child::child_messenger::child_messenger(snap_child * s, std::string const & addr, int port )
2742 : : snap_tcp_client_permanent_message_connection
2743 : ( addr
2744 : , port
2745 : , tcp_client_server::bio_client::mode_t::MODE_PLAIN
2746 : , snap_communicator::snap_tcp_client_permanent_message_connection::DEFAULT_PAUSE_BEFORE_RECONNECTING
2747 : , true /*use_thread*/
2748 : )
2749 : , f_child(s)
2750 0 : , f_service_name( QString("snap_child_%1").arg(gettid()) )
2751 : {
2752 0 : }
2753 :
2754 :
2755 : /** \brief Process a message we just received.
2756 : *
2757 : * This function is called whenever the snapcommunicator received and
2758 : * decided to forward a message to us.
2759 : *
2760 : * \param[in] message The message we just received.
2761 : */
2762 0 : void snap_child::child_messenger::process_message(snap_communicator_message const & message)
2763 : {
2764 0 : QString const command(message.get_command());
2765 0 : if(command == "HELP")
2766 : {
2767 : // snapcommunicator wants us to tell it what commands
2768 : // we accept
2769 0 : snap_communicator_message commands_message;
2770 0 : commands_message.set_command("COMMANDS");
2771 0 : commands_message.add_parameter("list", "HELP,QUITTING,READY,STOP,UNKNOWN");
2772 0 : send_message(commands_message);
2773 0 : return;
2774 : }
2775 0 : else if(command == "QUITTING")
2776 : {
2777 0 : SNAP_LOG_WARNING("We received the QUITTING command.");
2778 0 : f_child->stop_messenger();
2779 0 : return;
2780 : }
2781 0 : else if(command == "READY")
2782 : {
2783 : // the REGISTER worked, wait for the HELP message
2784 0 : return;
2785 : }
2786 0 : else if(command == "STOP")
2787 : {
2788 0 : SNAP_LOG_WARNING("we received the STOP command.");
2789 0 : f_child->stop_messenger();
2790 0 : return;
2791 : }
2792 0 : else if(command == "UNKNOWN")
2793 : {
2794 : // we sent a command that Snap! Communicator did not understand
2795 : //
2796 0 : SNAP_LOG_ERROR("we sent unknown command \"")(message.get_parameter("command"))("\" and probably did not get the expected result.");
2797 0 : return;
2798 : }
2799 : }
2800 :
2801 :
2802 : /** \brief Process was just connected.
2803 : *
2804 : * This callback happens whenever a new connection is established.
2805 : * It sends a REGISTER command to the snapcommunicator. The READY
2806 : * reply will be received when process_message() gets called. At
2807 : * that point we are fully registered.
2808 : *
2809 : * This callback happens first so if we lose our connection to
2810 : * the snapcommunicator server, it will re-register the snapserver
2811 : * again as expected.
2812 : */
2813 0 : void snap_child::child_messenger::process_connected()
2814 : {
2815 0 : snap_tcp_client_permanent_message_connection::process_connected();
2816 :
2817 0 : snap::snap_communicator_message register_snapchild;
2818 0 : register_snapchild.set_command("REGISTER");
2819 0 : register_snapchild.add_parameter("service", f_service_name);
2820 0 : register_snapchild.add_parameter("version", snap::snap_communicator::VERSION);
2821 0 : send_message(register_snapchild);
2822 0 : }
2823 :
2824 :
2825 0 : snap_child::messenger_runner::messenger_runner( snap_child* sc )
2826 : : snap_runner("background snap_tcp_client_permanent_message_connection for asynchroneous connections")
2827 0 : , f_child(sc)
2828 : {
2829 0 : }
2830 :
2831 :
2832 0 : void snap_child::messenger_runner::run()
2833 : {
2834 0 : f_child->f_communicator->run();
2835 0 : }
2836 :
2837 :
2838 : /** \brief Fork the child process.
2839 : *
2840 : * Use this method to fork child processes. If SNAP_NO_FORK is on,
2841 : * then respect the "--nofork" command line option.
2842 : *
2843 : * The main process should not be running any threads. At this point
2844 : * we only check and post a warning if the number of threads is not
2845 : * exactly 1.
2846 : *
2847 : * In case the snap logger uses threads (i.e. when using a SocketAppender)
2848 : * we stop the logger before calling fork() and then we re-establish the
2849 : * logger on return, whether the fork() worked or not.
2850 : *
2851 : * The return value is exactly the same as what the fork() function would
2852 : * otherwise return.
2853 : *
2854 : * \exception snap_logic_exception
2855 : * This exception is raised if the server weak pointer cannot be locked.
2856 : *
2857 : * \return The child PID, 0 for the child process, -1 if an error occurs.
2858 : */
2859 0 : pid_t snap_child::fork_child()
2860 : {
2861 0 : server::pointer_t server(get_server());
2862 :
2863 : #ifdef SNAP_NO_FORK
2864 : if(server->nofork())
2865 : {
2866 : // simulate a working fork, we return as the child
2867 : return 0;
2868 : }
2869 : #endif
2870 :
2871 : // generate a warning about having more than one thread at this point
2872 : // (it is a huge potential for a crash or lock up, hence the test)
2873 : // See: https://snapwebsites.org/journal/2015/06/using-threads-server-uses-fork-they-dont-mix-well
2874 : // We send the log after we re-established it (See below)
2875 : //
2876 0 : size_t const count(server->thread_count());
2877 :
2878 0 : pid_t const parent_pid(getpid());
2879 :
2880 0 : pid_t const p(fork());
2881 :
2882 0 : if(p != 0)
2883 : {
2884 0 : if(count != 1)
2885 : {
2886 0 : SNAP_LOG_WARNING("snap_child::fork_child(): The number of threads before the fork() to create a snap_child is ")(count)(" when it should be 1.");
2887 :
2888 : // TODO: look into having a flag concept in a contrib lib.
2889 : //
2890 : // this is a rather important bug, only developers have a chance
2891 : // to fix it, though... (hence the rather low priority)
2892 : //
2893 0 : SNAP_FLAG_UP(
2894 : "snap_child"
2895 : , "create-child"
2896 : , "thread"
2897 : , "snap_child::fork_child() called with more than one thread running; a developer has to look into why this would happen.")
2898 0 : ->set_priority(35)
2899 0 : .set_manual_down(true)
2900 0 : .add_tag("thread")
2901 0 : .add_tag("fork")
2902 0 : .save();
2903 : }
2904 : }
2905 : else
2906 : {
2907 : // auto-kill child if parent dies
2908 : //
2909 : // TODO: ameliorate by using a "catch SIGHUP" in children via
2910 : // the signal class of snapcommunicator
2911 : //
2912 : // ameliorate by using a SIGUSR1 or SIGUSR2 and implement
2913 : // a way for a possible clean exit (i.e. if child is still
2914 : // working, give it a chance, then after X seconds, still
2915 : // force a kill)
2916 : //
2917 : try
2918 : {
2919 0 : process::set_process_name("snap_child");
2920 :
2921 0 : prctl(PR_SET_PDEATHSIG, SIGHUP);
2922 :
2923 : // Since we are in the child instance, we don't want the same object as was running in the server.
2924 : // So we need to create a new snapcommunicator connection, register it, and add it
2925 : // into the logging facility.
2926 : //
2927 : // At this point we are marking this call off because it breaks
2928 : // the entire child environment. We'll have to find a way to
2929 : // just send messages to snapcommunicator without the need
2930 : // for the complicated messages going back and forth or pile
2931 : // up all the messages and have them handled at the end only
2932 : // (i.e. no threads need in that case...)
2933 : //
2934 : //connect_messenger();
2935 :
2936 : // always reconfigure the logger in the child
2937 0 : logging::reconfigure();
2938 :
2939 0 : SNAP_LOG_TRACE("snap_child::fork_child() just hooked up logging! -- child of ")(getppid());
2940 :
2941 : // it could be that the prctrl() was made after the true parent died...
2942 : // so we have to test the PID of our parent
2943 : //
2944 0 : if(getppid() != parent_pid)
2945 : {
2946 0 : SNAP_LOG_FATAL("snap_child::fork_child() lost parent too soon and did not receive SIGHUP; quit immediately.");
2947 0 : exit(1);
2948 0 : NOTREACHED();
2949 : }
2950 : }
2951 0 : catch( snap_exception const & except )
2952 : {
2953 0 : SNAP_LOG_FATAL("snap_child::fork_child(): snap_exception caught: ")(except.what());
2954 0 : exit(1);
2955 0 : NOTREACHED();
2956 : }
2957 0 : catch( std::exception const & std_except )
2958 : {
2959 : // the snap_logic_exception is not a snap_exception
2960 : // and other libraries may generate other exceptions
2961 : // (i.e. libtld, C++ cassandra driver...)
2962 : //
2963 0 : SNAP_LOG_FATAL("snap_child::fork_child(): std::exception caught: ")(std_except.what());
2964 0 : exit(1);
2965 0 : NOTREACHED();
2966 : }
2967 0 : catch( ... )
2968 : {
2969 0 : SNAP_LOG_FATAL("snap_child::fork_child(): unknown exception caught!");
2970 0 : exit(1);
2971 0 : NOTREACHED();
2972 : }
2973 : }
2974 :
2975 0 : return p;
2976 : }
2977 :
2978 :
2979 0 : void snap_child::output_session_log( QString const& what )
2980 : {
2981 0 : QString const method(snapenv(get_name(name_t::SNAP_NAME_CORE_REQUEST_METHOD)));
2982 0 : QString const agent(snapenv(get_name(name_t::SNAP_NAME_CORE_HTTP_USER_AGENT)));
2983 0 : QString const ip(snapenv(get_name(name_t::SNAP_NAME_CORE_REMOTE_ADDR)));
2984 0 : SNAP_LOG_INFO("------------------------------------ ")(ip)
2985 0 : (" ")(what)(" snap_child session (")(method)(" ")(f_uri.get_uri())(") with ")(agent);
2986 0 : }
2987 :
2988 :
2989 : /** \brief Process a request from the Snap CGI tool.
2990 : *
2991 : * The process function accepts a socket that was just connected.
2992 : * Only the parent (Snap Server) can call this function. Assuming
2993 : * that (1) the parent is calling and (2) that this snap_child
2994 : * object is not already in use, then the function forks a new
2995 : * process (the child).
2996 : *
2997 : * The parent acknowledge by saving the new process identifier
2998 : * and closing its copy of the TCP socket.
2999 : *
3000 : * If the fork() call fails (returning -1) then the parent process
3001 : * writes an HTTP error in the socket (503 Service Unavailable).
3002 : *
3003 : * \param[in] client The client connection to attach to this child.
3004 : *
3005 : * \return true if the child process was successfully created.
3006 : */
3007 0 : bool snap_child::process(tcp_client_server::bio_client::pointer_t client)
3008 : {
3009 0 : if(f_is_child)
3010 : {
3011 : // this is a bug! die() on the spot
3012 : // (here we ARE in the child process!)
3013 : //
3014 0 : die(
3015 : http_code_t::HTTP_CODE_SERVICE_UNAVAILABLE,
3016 : "Server Bug",
3017 : "Your Snap! server detected a serious problem. Please check your logs for more information.",
3018 : "snap_child::process() was called from a child process.");
3019 0 : return false;
3020 : }
3021 :
3022 0 : if(f_child_pid != 0)
3023 : {
3024 : // this is a bug!
3025 : //
3026 : // WARNING: At this point we CANNOT call the die() function
3027 : // (we are not the child and have the wrong socket)
3028 : //
3029 0 : SNAP_LOG_FATAL("BUG: snap_child::process() called when the process is still in use.");
3030 0 : return false;
3031 : }
3032 :
3033 : // to avoid the fork use --nofork on the command line
3034 : //
3035 0 : pid_t const p(fork_child());
3036 0 : if(p != 0)
3037 : {
3038 : // parent process
3039 0 : if(p == -1)
3040 : {
3041 : // WARNING: At this point we CANNOT call the die() function
3042 : // (we are not the child and have the wrong socket)
3043 0 : SNAP_LOG_FATAL("snap_child::process() could not create child process, dropping connection.");
3044 0 : return false;
3045 : }
3046 :
3047 : // save the process identifier since it worked
3048 0 : f_child_pid = p;
3049 :
3050 : // socket is now the responsibility of the child process
3051 : // the accept() call in the parent will close it though
3052 0 : return true;
3053 : }
3054 :
3055 0 : f_client = client;
3056 :
3057 : try
3058 : {
3059 0 : f_ready = false;
3060 :
3061 0 : init_start_date();
3062 :
3063 : // child process
3064 0 : f_is_child = true;
3065 :
3066 0 : read_environment(); // environment to QMap<>
3067 0 : setup_uri(); // the raw URI
3068 :
3069 : // keep that one in release so we can at least know of all
3070 : // the hits to the server
3071 : //
3072 0 : output_session_log( "new" );
3073 :
3074 : // now we connect to the DB
3075 : // move all possible work that does not required the DB before
3076 : // this line so we avoid a network connection altogether
3077 : //
3078 0 : NOTUSED(connect_cassandra(true)); // since we pass 'true', the returned value will always be true
3079 :
3080 0 : canonicalize_domain(); // using the URI, find the domain core::rules and start the canonicalization process
3081 0 : canonicalize_website(); // using the canonicalized domain, find the website core::rules and continue the canonicalization process
3082 :
3083 : // check whether this website has a redirect and apply it if necessary
3084 : // (not a full 301, just show site B instead of site A)
3085 : //
3086 0 : site_redirect();
3087 :
3088 : // start the plugins and their initialization
3089 : //
3090 0 : snap_string_list list_of_plugins(init_plugins(true));
3091 :
3092 : // run updates if any
3093 : //
3094 0 : if(f_is_being_initialized)
3095 : {
3096 0 : update_plugins(list_of_plugins);
3097 : }
3098 : else
3099 : {
3100 : // TODO: to be ameliorated big time:
3101 : // a) we should present a really nice page and not a die()
3102 : // b) we should have a state held by snaplock which will
3103 : // be way faster than the database (but we have to
3104 : // reinitialize that state on a reboot...)
3105 : //
3106 0 : libdbproxy::value const state(get_site_parameter(get_name(name_t::SNAP_NAME_CORE_SITE_STATE)));
3107 0 : if(state.nullValue())
3108 : {
3109 : // after the very first installation, it is always defined
3110 : //
3111 0 : die(http_code_t::HTTP_CODE_SERVICE_UNAVAILABLE, "",
3112 : "The website is being initialized. Please try again in a moment. Thank you.",
3113 : "The site state is undefined. It is not yet initialized.");
3114 0 : NOTREACHED();
3115 : }
3116 0 : if(state.stringValue() != "ready")
3117 : {
3118 : // at this point, we expect "ready" or "updating"
3119 : //
3120 0 : die(http_code_t::HTTP_CODE_SERVICE_UNAVAILABLE, "",
3121 : "The website is being updated. Please try again in a moment. Thank you.",
3122 0 : QString("The site state is \"%1\", expected \"ready\"."
3123 : " It either was not yet initialized or it is being"
3124 : " updated right now (or the update process crashed?)")
3125 0 : .arg(state.stringValue()));
3126 0 : NOTREACHED();
3127 :
3128 : // double protection, this statement is not reachable
3129 : return false;
3130 : }
3131 : }
3132 :
3133 0 : canonicalize_options(); // find the language, branch, and revision specified by the user
3134 :
3135 : // finally, "execute" the page being accessed
3136 : //
3137 0 : execute();
3138 :
3139 : // we could delete ourselves but really only the socket is an
3140 : // object that needs to get cleaned up properly and it is done
3141 : // in the exit() function.
3142 : //delete this;
3143 0 : output_session_log("done");
3144 0 : exit(0);
3145 0 : NOTREACHED();
3146 : }
3147 0 : catch( snap_lock_failed_exception const & except )
3148 : {
3149 0 : SNAP_LOG_FATAL("snap_child::process(): snap_lock_failed_exception caught: ")(except.what());
3150 :
3151 : // die with a "server locked" error instead of a "random" 500
3152 : //
3153 0 : die(http_code_t::HTTP_CODE_LOCKED, "",
3154 : "One of the resources you tried to access is currently locked. Please try again in a moment.",
3155 0 : QString("A lock failed (%1)").arg(except.what()));
3156 0 : NOTREACHED();
3157 : }
3158 0 : catch( snap_exception const & except )
3159 : {
3160 0 : SNAP_LOG_FATAL("snap_child::process(): snap_exception caught: ")(except.what());
3161 : }
3162 0 : catch( libexcept::exception_t const & e )
3163 : {
3164 0 : SNAP_LOG_FATAL("snap_child::process(): libexcept::exception_t caught: ")(e.what());
3165 0 : for( auto const & stack_string : e.get_stack_trace() )
3166 : {
3167 0 : SNAP_LOG_ERROR("libexcept(): backtrace=")( stack_string );
3168 : }
3169 : }
3170 0 : catch( libdbproxy::exception const & e )
3171 : {
3172 0 : SNAP_LOG_FATAL("snap_child::process(): libdbproxy::exception caught: ")(e.what());
3173 0 : for( auto const & stack_string : e.get_stack_trace() )
3174 : {
3175 0 : SNAP_LOG_ERROR("exception(): backtrace=")( stack_string );
3176 : }
3177 : }
3178 0 : catch( std::exception const & std_except )
3179 : {
3180 : // the snap_logic_exception is not a snap_exception
3181 : // and other libraries may generate other exceptions
3182 : // (i.e. libtld, C++ cassandra driver...)
3183 : //
3184 0 : SNAP_LOG_FATAL("snap_child::process(): std::exception caught: ")(std_except.what());
3185 : }
3186 0 : catch( ... )
3187 : {
3188 0 : SNAP_LOG_FATAL("snap_child::process(): unknown exception caught!");
3189 : }
3190 :
3191 0 : exit(1);
3192 0 : NOTREACHED();
3193 :
3194 : // compiler expects a return
3195 : return false;
3196 : }
3197 :
3198 :
3199 : /** \brief Get a shared pointer copy of the server.
3200 : *
3201 : * This function returns a shared pointer of the server.
3202 : *
3203 : * The children only have a weak pointer back to the server. This function
3204 : * attempts a lock. If the lock fails, then the function throws.
3205 : *
3206 : * \return The pointer to the server.
3207 : */
3208 0 : server::pointer_t snap_child::get_server() const
3209 : {
3210 0 : server::pointer_t server(f_server.lock());
3211 0 : if(!server)
3212 : {
3213 0 : throw snap_logic_exception("could not lock server pointer in snap_child::get_server()");
3214 : }
3215 :
3216 0 : return server;
3217 : }
3218 :
3219 :
3220 : /** \brief Retrieve the child process identifier.
3221 : *
3222 : * Once a child process started, it gets assigned a pid_t value. In
3223 : * the parent process, this value can be retrieved using this function.
3224 : * In the child process, just use getpid().
3225 : *
3226 : * \return This snap_child process identifier.
3227 : */
3228 0 : pid_t snap_child::get_child_pid() const
3229 : {
3230 0 : return f_child_pid;
3231 : }
3232 :
3233 :
3234 : /** \brief Kill running process.
3235 : *
3236 : * Use this method to stop a child process. It first sends the SIGTERM
3237 : * message, then if the status doesn't change after a brief interval,
3238 : * SIGKILL is used.
3239 : *
3240 : * \sa check_status()
3241 : */
3242 0 : void snap_child::kill()
3243 : {
3244 0 : if( f_child_pid == 0 )
3245 : {
3246 : // Child is not running
3247 : //
3248 0 : return;
3249 : }
3250 :
3251 0 : ::kill( f_child_pid, SIGTERM );
3252 0 : int timeout = 5;
3253 0 : while( check_status() == status_t::SNAP_CHILD_STATUS_RUNNING )
3254 : {
3255 0 : sleep( 1 );
3256 0 : if( --timeout <= 0 )
3257 : {
3258 0 : ::kill( f_child_pid, SIGKILL );
3259 0 : break;
3260 : }
3261 : }
3262 : }
3263 :
3264 :
3265 : /** \brief Check the status of the child process.
3266 : *
3267 : * This function checks whether the child is still running or not.
3268 : * The function returns the current status such as running,
3269 : * or ready (to process a request.)
3270 : *
3271 : * The child process is not expected to call this function. It knows
3272 : * it is running if it can anyway.
3273 : *
3274 : * The parent uses the wait() instruction to check the current status
3275 : * if the process is running (f_child_pid is not zero.)
3276 : *
3277 : * \return The status of the child.
3278 : */
3279 0 : snap_child::status_t snap_child::check_status()
3280 : {
3281 0 : if(f_is_child)
3282 : {
3283 : // XXX -- call die() instead
3284 0 : SNAP_LOG_FATAL("snap_child::check_status() was called from the child process.");
3285 0 : return status_t::SNAP_CHILD_STATUS_RUNNING;
3286 : }
3287 :
3288 0 : if(f_child_pid != 0)
3289 : {
3290 : int status;
3291 0 : pid_t const r = waitpid(f_child_pid, &status, WNOHANG);
3292 0 : if(r == static_cast<pid_t>(-1))
3293 : {
3294 0 : int const e(errno);
3295 0 : SNAP_LOG_FATAL("a waitpid() returned an error (")(e)(" -- ")(strerror(e))(")");
3296 : }
3297 0 : else if(r == f_child_pid)
3298 : {
3299 : // the status of our child changed
3300 : #pragma GCC diagnostic push
3301 : #pragma GCC diagnostic ignored "-Wold-style-cast"
3302 0 : if(WIFEXITED(status))
3303 : {
3304 : // stopped with exit() or return in main()
3305 : // TODO: log info if exit() != 0?
3306 : }
3307 0 : else if(WIFSIGNALED(status))
3308 : {
3309 : // stopped because of a signal
3310 0 : SNAP_LOG_ERROR("child process ")(f_child_pid)(" exited after it received signal #")(WTERMSIG(status));
3311 : }
3312 : // else -- other statuses are ignored for now
3313 : #pragma GCC diagnostic pop
3314 :
3315 : // mark that child as done
3316 0 : f_child_pid = 0;
3317 : }
3318 0 : else if(r != 0)
3319 : {
3320 : // TODO: throw instead?
3321 0 : SNAP_LOG_ERROR("waitpid() returned ")(r)(", we expected -1, 0 or ")(f_child_pid)(" instead.");
3322 : }
3323 : }
3324 :
3325 0 : return f_child_pid == 0 ? status_t::SNAP_CHILD_STATUS_READY : status_t::SNAP_CHILD_STATUS_RUNNING;
3326 : }
3327 :
3328 :
3329 : /** \brief Read the command and eventually the environment sent by snap.cgi
3330 : *
3331 : * The socket starts with a one line command. The command may be followed
3332 : * by additional entries such as the Apache environment when the Snap CGI
3333 : * connects to us.
3334 : *
3335 : * When the environment is defined, it is saved in a map so all the other
3336 : * functions can later retrieve those values from the child. Note that at
3337 : * this point the script does not tweak that data.
3338 : *
3339 : * To make sure that the entire environment is sent, snap.cgi starts the
3340 : * feed with "\#START\\n" and terminate it with "\#END\\n".
3341 : *
3342 : * Note that unless we are receiving the Apache environment from the snap.cgi
3343 : * tool, we do NOT return. This is important because when returning we start
3344 : * generating a web page which is not what we want for the other instructions
3345 : * such as the \#INFO.
3346 : *
3347 : * \section commands Understood Commands
3348 : *
3349 : * \li \#START
3350 : *
3351 : * Start passing the environment to the server.
3352 : *
3353 : * \li \#INFO
3354 : *
3355 : * Request for information about the server. The result is an environment
3356 : * like variable/value pairs. Mainly versions are returned in that buffer.
3357 : * Use the \#STATS for statistics information.
3358 : *
3359 : * \li \#STATS
3360 : *
3361 : * Request for statistics about this server instance. The result is an
3362 : * environment like variable/value pairs. This command generates values
3363 : * such as the total number of requests received, the number of children
3364 : * currently running, etc.
3365 : */
3366 0 : void snap_child::read_environment()
3367 : {
3368 0 : class read_env
3369 : {
3370 : public:
3371 0 : read_env(snap_child * snap, tcp_client_server::bio_client::pointer_t client, environment_map_t & env, environment_map_t & browser_cookies, environment_map_t & post, post_file_map_t & files)
3372 0 : : f_snap(snap)
3373 : , f_client(client)
3374 : , f_env(env)
3375 : , f_browser_cookies(browser_cookies)
3376 : , f_post(post)
3377 0 : , f_files(files)
3378 : {
3379 0 : }
3380 :
3381 : read_env(read_env const & rhs) = delete;
3382 : read_env & operator = (read_env const & rhs) = delete;
3383 :
3384 0 : void die(QString const & details) const
3385 : {
3386 0 : f_snap->die(http_code_t::HTTP_CODE_SERVICE_UNAVAILABLE, "",
3387 : "Unstable network connection",
3388 0 : QString("an error occurred while reading the environment from the socket in the server child process (%1).").arg(details));
3389 0 : NOTREACHED();
3390 : }
3391 :
3392 0 : char getc() const
3393 : {
3394 : char c;
3395 :
3396 : //if(f_unget != '\0')
3397 : //{
3398 : // c = f_unget;
3399 : // f_unget = '\0';
3400 : // return c;
3401 : //}
3402 :
3403 : // this read blocks, so we read just 1 char. because we
3404 : // want to stop calling read() as soon as possible (otherwise
3405 : // we would be blocked here forever)
3406 0 : if(f_client->read(&c, 1) != 1)
3407 : {
3408 0 : int const e(errno);
3409 0 : die(QString("I/O error, errno: %1").arg(e));
3410 0 : NOTREACHED();
3411 : }
3412 0 : return c;
3413 : }
3414 :
3415 : //void ungetc(char c)
3416 : //{
3417 : // if(f_unget != '\0')
3418 : // {
3419 : // die("too many unget() called");
3420 : // NOTREACHED();
3421 : // }
3422 : // f_unget = c;
3423 : //}
3424 :
3425 0 : void start_process()
3426 : {
3427 : // #INFO
3428 0 : if(f_name == "#INFO")
3429 : {
3430 0 : f_snap->snap_info();
3431 0 : NOTREACHED();
3432 : }
3433 :
3434 : // #STATS
3435 0 : if(f_name == "#STATS")
3436 : {
3437 0 : f_snap->snap_statistics();
3438 0 : NOTREACHED();
3439 : }
3440 :
3441 : // #INIT
3442 0 : if(f_name == "#INIT")
3443 : {
3444 : // user wants to initialize a website
3445 0 : f_snap->mark_for_initialization();
3446 : }
3447 : else
3448 : {
3449 : // #START
3450 0 : if(f_name != "#START")
3451 : {
3452 0 : die("#START or other supported command missing.");
3453 0 : NOTREACHED();
3454 : }
3455 : }
3456 : // TODO add support for a version: #START=1.2 or #INIT=1.2
3457 : // so that way the server can cleanly "break" if the
3458 : // snap.cgi or other client version is not compatible
3459 :
3460 0 : f_started = true;
3461 0 : f_name.clear();
3462 0 : f_value.clear();
3463 0 : }
3464 :
3465 0 : void process_post_variable()
3466 : {
3467 : // here we have a set of post environment variables (header)
3468 : // and the f_post_content which represents the value of the field
3469 : //
3470 : // Content-Disposition: form-data; name="field-name"
3471 : // Content-Type: image/gif
3472 : // Content-Transfer-Encoding: binary
3473 : //
3474 : // The Content-Type cannot be used with plain variables. We
3475 : // distinguish plain variables from files as the
3476 : // Content-Disposition includes a filename="..." parameter.
3477 : //
3478 0 : if(!f_post_environment.contains("CONTENT-DISPOSITION"))
3479 : {
3480 0 : die("multipart posts must have a Content-Disposition header to be considered valid.");
3481 0 : NOTREACHED();
3482 : }
3483 : // TODO: verify and if necessary fix this as the ';' could I think
3484 : // appear in a string; looking at the docs, I'm not too sure
3485 : // but it looks like we would need to support the
3486 : // extended-value and extended-other-values as defined in
3487 : // http://tools.ietf.org/html/rfc2184
3488 0 : snap_string_list disposition(f_post_environment["CONTENT-DISPOSITION"].split(";"));
3489 0 : if(disposition.size() < 2)
3490 : {
3491 0 : die(QString("multipart posts Content-Disposition must at least include \"form-data\" and a name parameter, \"%1\" is not valid.")
3492 0 : .arg(f_post_environment["CONTENT-DISPOSITION"]));
3493 0 : NOTREACHED();
3494 : }
3495 0 : if(disposition[0].trimmed() != "form-data")
3496 : {
3497 : // not happy if we don't get form-data parts
3498 0 : die(QString("multipart posts Content-Disposition must be a \"form-data\", \"%1\" is not valid.")
3499 0 : .arg(f_post_environment["CONTENT-DISPOSITION"]));
3500 0 : NOTREACHED();
3501 : }
3502 : // retrieve all the parameters, then keep those we want to keep
3503 0 : int const max_strings(disposition.size());
3504 0 : environment_map_t params;
3505 0 : for(int i(1); i < max_strings; ++i)
3506 : {
3507 : // each parameter is name=<value>
3508 0 : snap_string_list nv(disposition[i].split('='));
3509 0 : if(nv.size() != 2)
3510 : {
3511 0 : die(QString("parameter %1 in this multipart posts Content-Disposition does not include an equal character so \"%1\" is not valid.")
3512 0 : .arg(i)
3513 0 : .arg(f_post_environment["CONTENT-DISPOSITION"]));
3514 0 : NOTREACHED();
3515 : }
3516 0 : nv[0] = nv[0].trimmed().toLower(); // case insensitive
3517 0 : nv[1] = nv[1].trimmed();
3518 0 : if(nv[1].startsWith("\"")
3519 0 : && nv[1].endsWith("\""))
3520 : {
3521 0 : nv[1] = nv[1].mid(1, nv[1].length() - 2);
3522 : }
3523 0 : params[nv[0]] = nv[1];
3524 : }
3525 0 : if(!params.contains("name"))
3526 : {
3527 0 : die(QString("multipart posts Content-Disposition must include a name=\"...\" parameter, \"%1\" is not valid.")
3528 0 : .arg(f_post_environment["CONTENT-DISPOSITION"]));
3529 0 : NOTREACHED();
3530 : }
3531 : // remove the ending \r\n
3532 0 : if(f_post_content.right(2) == "\r\n")
3533 : {
3534 0 : f_post_content.resize(f_post_content.size() - 2);
3535 : }
3536 0 : else if(f_post_content.right(1) == "\n"
3537 0 : || f_post_content.right(1) == "\r")
3538 : {
3539 0 : f_post_content.resize(f_post_content.size() - 1);
3540 : }
3541 0 : f_name = params["name"];
3542 0 : if(params.contains("filename"))
3543 : {
3544 : // make sure the filename is unique otherwise we'd overwrite
3545 : // the previous file with the same name...
3546 : //
3547 0 : QString filename(params["filename"]);
3548 :
3549 : // this is a file so we want to save it in the f_file and
3550 : // not in the f_post although we do create an f_post entry
3551 : // with the filename
3552 : //
3553 0 : if(f_post.contains(f_name))
3554 : {
3555 0 : die(QString("multipart post variable \"%1\" defined twice")
3556 0 : .arg(f_name));
3557 0 : NOTREACHED();
3558 : }
3559 0 : f_post[f_name] = filename;
3560 :
3561 : // an empty filename means no file was uploaded (which
3562 : // is fine for optional fields or fields that are already
3563 : // attached to a file anyway.)
3564 : //
3565 0 : if(!filename.isEmpty())
3566 : {
3567 0 : post_file_t & file(f_files[f_name]);
3568 0 : file.set_name(f_name);
3569 0 : file.set_filename(filename);
3570 0 : ++f_post_index; // 1-based
3571 0 : file.set_index(f_post_index);
3572 0 : file.set_data(f_post_content);
3573 0 : if(params.contains("creation-date"))
3574 : {
3575 0 : file.set_creation_time(string_to_date(params["creation-date"]));
3576 : }
3577 0 : if(params.contains("modification-date"))
3578 : {
3579 0 : file.set_modification_time(string_to_date(params["modification-date"]));
3580 : }
3581 : // Content-Type is actually expected on this side
3582 0 : if(f_post_environment.contains("CONTENT-TYPE"))
3583 : {
3584 0 : file.set_original_mime_type(f_post_environment["CONTENT-TYPE"]);
3585 : }
3586 : //if(params.contains("description"))
3587 : //{
3588 : // file.set_description(string_to_date(params["description"]));
3589 : //}
3590 : // for images also get the dimensions (width x height)
3591 : // note that some images are not detected properly by the
3592 : // magic library so we ignore the MIME type here
3593 0 : snap_image info;
3594 0 : if(info.get_info(f_post_content))
3595 : {
3596 0 : if(info.get_size() > 0)
3597 : {
3598 0 : smart_snap_image_buffer_t buffer(info.get_buffer(0));
3599 0 : file.set_image_width(buffer->get_width());
3600 0 : file.set_image_height(buffer->get_height());
3601 0 : file.set_mime_type(buffer->get_mime_type());
3602 : }
3603 : }
3604 : #ifdef DEBUG
3605 0 : SNAP_LOG_TRACE() << " f_files[\"" << f_name << "\"] = \"...\" (Filename: \"" << filename
3606 0 : << "\" MIME: " << file.get_mime_type() << ", size: " << f_post_content.size() << ")";
3607 : #endif
3608 : }
3609 : }
3610 : else
3611 : {
3612 : // this is a simple parameter
3613 0 : if(f_post_environment.contains("CONTENT-TYPE"))
3614 : {
3615 : // XXX accept a few valid types? it should not be necessary...
3616 : // the character encoding is defined as the form, page,
3617 : // or UTF-8 encoding; Content-Type not permitted here!
3618 0 : die(QString("multipart posts Content-Type is not allowed with simple parameters."));
3619 0 : NOTREACHED();
3620 : }
3621 : // TODO verify that the content of a post just needs to be
3622 : // decoded or whether it already is UTF-8 as required
3623 : // to be saved in f_post
3624 0 : if(f_post.contains(f_name))
3625 : {
3626 0 : die(QString("multipart post variable \"%1\" defined twice")
3627 0 : .arg(f_name));
3628 0 : NOTREACHED();
3629 : }
3630 : // append a '\0' so we can call is_valid_utf8()
3631 0 : char const nul('\0');
3632 0 : f_post_content.append(nul);
3633 0 : if(!libutf8::is_valid_utf8(f_post_content.data())) // TODO: see whether controls should be forbidden (except tabs?)
3634 : {
3635 0 : f_snap->die(http_code_t::HTTP_CODE_BAD_REQUEST, "Invalid Form Content",
3636 : "Your form includes characters that are not compatible with the UTF-8 encoding. Try to avoid special characters and try again. If you are using Internet Explorer, know that older versions may not be compatible with international characters.",
3637 : "is_valid_utf8() returned false against the user's content");
3638 0 : NOTREACHED();
3639 : }
3640 : // make sure to view the input as UTF-8 characters
3641 0 : f_post[f_name] = QString::fromUtf8(f_post_content.data(), f_post_content.size() - 1); //snap_uri::urldecode(f_post_content, true);?
3642 : #ifdef DEBUG
3643 : //SNAP_LOG_TRACE() << " f_post[\"" << f_name << "\"] = \"" << f_post_content.data() << "\"\n";
3644 : #endif
3645 : }
3646 0 : }
3647 :
3648 0 : bool process_post_line()
3649 : {
3650 : // found a marker?
3651 0 : if(f_post_line.length() >= f_boundary.length())
3652 : {
3653 0 : if(f_post_line == f_end_boundary)
3654 : {
3655 0 : if(f_post_first)
3656 : {
3657 0 : die("got end boundary without a start");
3658 0 : NOTREACHED();
3659 : }
3660 0 : process_post_variable();
3661 0 : return true;
3662 : }
3663 :
3664 0 : if(f_post_line == f_boundary)
3665 : {
3666 : // got the first boundary yet?
3667 0 : if(f_post_first)
3668 : {
3669 : // we got the first boundary
3670 0 : f_post_first = false;
3671 0 : return false;
3672 : }
3673 0 : process_post_variable();
3674 :
3675 : // on next line, we are reading a new header
3676 0 : f_post_header = true;
3677 :
3678 : // we are done with those in this iteration
3679 0 : f_post_environment.clear();
3680 0 : f_post_content.clear();
3681 0 : return false;
3682 : }
3683 : }
3684 :
3685 0 : if(f_post_first)
3686 : {
3687 0 : die("the first POST boundary is missing.");
3688 0 : NOTREACHED();
3689 : }
3690 :
3691 0 : if(f_post_header)
3692 : {
3693 0 : if(f_post_line.isEmpty() || (f_post_line.size() == 1 && f_post_line.at(0) == '\r'))
3694 : {
3695 : // end of the header
3696 0 : f_post_header = false;
3697 0 : return false;
3698 : }
3699 0 : char const nul('\0');
3700 0 : f_post_line.append(nul);
3701 0 : if(!libutf8::is_valid_ascii(f_post_line.data())) // TODO: see whether controls should be forbidden (except tabs?)
3702 : {
3703 0 : f_snap->die(http_code_t::HTTP_CODE_BAD_REQUEST, "Invalid Form Content",
3704 : "Your multi-part form header includes characters that are not compatible with the ASCII encoding.",
3705 : "is_valid_ascii() returned false against a line of the user's multipart form header");
3706 0 : NOTREACHED();
3707 : }
3708 :
3709 : // we got a header (Blah: value)
3710 0 : QString const line(f_post_line);
3711 : #ifdef DEBUG
3712 : //SNAP_LOG_TRACE(" ++ header line [\n")(line.trimmed())("\n] ")(line.size());
3713 : #endif
3714 0 : if(isspace(line.at(0).unicode()))
3715 : {
3716 : // continuation of the previous header, concatenate
3717 0 : f_post_environment[f_name] += " " + line.trimmed();
3718 : }
3719 : else
3720 : {
3721 : // new header
3722 0 : int const p(line.indexOf(':'));
3723 0 : if(p == -1)
3724 : {
3725 0 : die("invalid header variable name/value pair, no ':' found.");
3726 0 : NOTREACHED();
3727 : }
3728 : // render name case insensitive
3729 0 : f_name = line.mid(0, p).trimmed().toUpper();
3730 : // TODO: verify that f_name is a valid header name
3731 0 : f_post_environment[f_name] = line.mid(p + 1).trimmed();
3732 : }
3733 : }
3734 : else
3735 : {
3736 : // this is content for the current variable
3737 0 : f_post_content += f_post_line;
3738 0 : f_post_content += '\n'; // the '\n' was not added to f_post_line
3739 : }
3740 :
3741 0 : return false;
3742 : }
3743 :
3744 0 : void process_post()
3745 : {
3746 : // one POST per request!
3747 0 : if(f_has_post)
3748 : {
3749 0 : die("at most 1 #POST is accepted in the environment.");
3750 0 : NOTREACHED();
3751 : }
3752 0 : f_has_post = true;
3753 :
3754 0 : if(!f_env.contains("CONTENT_TYPE")
3755 0 : || !f_env["CONTENT_TYPE"].startsWith("multipart/form-data"))
3756 : {
3757 : // standard post, just return and let the main loop
3758 : // handle the name/value pairs
3759 0 : return;
3760 : }
3761 :
3762 : // multi-part posts require special handling
3763 : // (i.e. these are not simple VAR=VALUE)
3764 : //
3765 : // the POST is going to be multiple lines with
3766 : // \r characters included! We read then all
3767 : // up to the closing boundary
3768 : //
3769 : // Example of such a variable:
3770 : // CONTENT_TYPE=multipart/form-data; boundary=---------5767747
3771 : //
3772 : // IMPORTANT NOTE:
3773 : // Sub-parts are NOT supported in HTML POST messages. This is
3774 : // clearly mentioned in HTML5 documentations:
3775 : // http://www.w3.org/html/wg/drafts/html/master/forms.html#multipart-form-data
3776 :
3777 : // 1. Get the main boundary from the CONTENT_TYPE
3778 0 : snap_string_list content_info(f_env["CONTENT_TYPE"].split(';'));
3779 0 : QString boundary;
3780 0 : int const max_strings(content_info.size());
3781 0 : for(int i(1); i < max_strings; ++i)
3782 : {
3783 0 : QString param(content_info[i].trimmed());
3784 0 : if(param.startsWith("boundary="))
3785 : {
3786 0 : boundary = param.mid(9).trimmed();
3787 0 : break;
3788 : }
3789 : }
3790 0 : if(boundary.isEmpty())
3791 : {
3792 0 : die("multipart POST does not include a valid boundary.");
3793 0 : NOTREACHED();
3794 : }
3795 0 : f_boundary = ("--" + boundary).toLatin1();
3796 0 : f_end_boundary = f_boundary;
3797 0 : f_end_boundary.append("--\r", 3);
3798 0 : f_boundary += '\r';
3799 : //f_post_first = true; -- function cannot be called more than once
3800 : //f_post_header = true;
3801 :
3802 : for(;;)
3803 : {
3804 0 : char const c(getc());
3805 0 : if(c == '\n')
3806 : {
3807 0 : if(process_post_line())
3808 : {
3809 0 : return;
3810 : }
3811 0 : f_post_line.clear();
3812 : }
3813 : else
3814 : {
3815 0 : f_post_line += c;
3816 : }
3817 0 : }
3818 : }
3819 :
3820 0 : void process_line()
3821 : {
3822 : // not started yet? check low level commands then
3823 0 : if(!f_started)
3824 : {
3825 0 : start_process();
3826 0 : return;
3827 : }
3828 :
3829 : // got to the end?
3830 0 : if(f_name == "#END")
3831 : {
3832 0 : f_running = false;
3833 0 : return;
3834 : }
3835 :
3836 : // got a POST?
3837 0 : if(f_name == "#POST")
3838 : {
3839 0 : process_post();
3840 0 : return;
3841 : }
3842 :
3843 0 : if(f_name.isEmpty())
3844 : {
3845 0 : die("empty lines are not accepted in the child environment.");
3846 0 : NOTREACHED();
3847 : }
3848 0 : if(f_has_post)
3849 : {
3850 0 : f_post[f_name] = snap_uri::urldecode(f_value, true);
3851 : #ifdef DEBUG
3852 : //SNAP_LOG_TRACE("(simple) f_post[\"")(f_name)("\"] = \"")(f_value)("\" (\"")(f_post[f_name])("\");");
3853 : #endif
3854 : }
3855 : else
3856 : {
3857 0 : if(f_name == "HTTP_COOKIE")
3858 : {
3859 : // special case for cookies
3860 0 : snap_string_list cookies(f_value.split(';', QString::SkipEmptyParts));
3861 : #ifdef DEBUG
3862 : //SNAP_LOG_TRACE(" HTTP_COOKIE = [\"")(f_value)("\"]");
3863 : #endif
3864 0 : int const max_strings(cookies.size());
3865 0 : for(int i(0); i < max_strings; ++i)
3866 : {
3867 0 : QString const name_value(cookies[i]);
3868 0 : snap_string_list nv(name_value.trimmed().split('=', QString::SkipEmptyParts));
3869 0 : if(nv.size() == 2)
3870 : {
3871 : // XXX check with other systems to see
3872 : // whether urldecode() is indeed
3873 : // necessary here
3874 0 : QString const cookie_name(snap_uri::urldecode(nv[0], true));
3875 0 : QString const cookie_value(snap_uri::urldecode(nv[1], true));
3876 : //std::cerr << " f_browser_cookies[\"" << cookie_name << "\"] = \"" << cookie_value << "\"; (\"" << cookies[i] << "\")\n";
3877 0 : if(f_browser_cookies.contains(cookie_name))
3878 : {
3879 : // This happens when you have a cookie with the
3880 : // same name defined with multiple domains,
3881 : // multiple paths, multiple expiration dates...
3882 : //
3883 : // Unfortunately, we are not told which one is
3884 : // which when we reach this line of code, yet
3885 : // the last one will be the most qualified one
3886 : // according to most browsers and servers
3887 : //
3888 : // http://tools.ietf.org/html/rfc6265#section-5.4
3889 : //
3890 0 : SNAP_LOG_DEBUG(QString("cookie \"%1\" defined twice")
3891 0 : .arg(cookie_name));
3892 : }
3893 0 : f_browser_cookies[cookie_name] = cookie_value;
3894 : }
3895 : }
3896 : }
3897 : else
3898 : {
3899 : // TODO: verify that f_name is a valid header name
3900 0 : f_env[f_name] = f_value;
3901 : #ifdef DEBUG
3902 : //SNAP_LOG_TRACE(" f_env[\"")(f_name)("\"] = \"")(f_value)("\"");
3903 : #endif
3904 : }
3905 : }
3906 : }
3907 :
3908 0 : void run()
3909 : {
3910 0 : bool reading_name(true);
3911 0 : do
3912 : {
3913 0 : char const c = getc();
3914 0 : if(c == '=' && reading_name)
3915 : {
3916 0 : reading_name = false;
3917 : }
3918 0 : else if(c == '\n')
3919 : {
3920 : #ifdef DEBUG
3921 : //SNAP_LOG_TRACE("f_name=")(f_name)(", f_value=\"")(f_value)("\" when reading the \\n");
3922 : #endif
3923 0 : process_line();
3924 :
3925 : // clear for next line
3926 0 : f_name.clear();
3927 0 : f_value.clear();
3928 0 : reading_name = true;
3929 : }
3930 0 : else if(c == '\r')
3931 : {
3932 0 : die("got a \\r character in the environment (not in a multi-part POST)");
3933 0 : NOTREACHED();
3934 : }
3935 0 : else if(reading_name)
3936 : {
3937 0 : if(isspace(c))
3938 : {
3939 0 : die("spaces are not allowed in environment variable names");
3940 0 : NOTREACHED();
3941 : }
3942 0 : f_name += c;
3943 : }
3944 : else
3945 : {
3946 0 : f_value += c;
3947 : }
3948 : }
3949 0 : while(f_running);
3950 0 : }
3951 :
3952 0 : bool has_post() const
3953 : {
3954 0 : return f_has_post;
3955 : }
3956 :
3957 : #ifdef DEBUG
3958 0 : void output_debug_log()
3959 : {
3960 0 : std::stringstream ss;
3961 0 : ss << "post:" << std::endl;
3962 0 : for( auto const & pair : f_post.toStdMap() )
3963 : {
3964 0 : ss << pair.first << ": " << pair.second << std::endl;
3965 : }
3966 :
3967 : //SNAP_LOG_DEBUG( ss.str().c_str() );
3968 0 : }
3969 : #endif
3970 :
3971 : private:
3972 : mutable snap_child * f_snap = nullptr;
3973 : tcp_client_server::bio_client::pointer_t
3974 : f_client = tcp_client_server::bio_client::pointer_t();
3975 : //char f_unget = 0;
3976 : bool f_running = true;
3977 : bool f_started = false;
3978 :
3979 : environment_map_t & f_env;
3980 : environment_map_t & f_browser_cookies;
3981 : QString f_name = QString();
3982 : QString f_value = QString();
3983 :
3984 : bool f_has_post = false;
3985 : environment_map_t & f_post;
3986 : post_file_map_t & f_files;
3987 : bool f_post_first = true;
3988 : bool f_post_header = true;
3989 : QByteArray f_post_line = QByteArray();
3990 : QByteArray f_post_content = QByteArray();
3991 : QByteArray f_boundary = QByteArray();
3992 : QByteArray f_end_boundary = QByteArray();
3993 : environment_map_t f_post_environment = environment_map_t();
3994 : uint32_t f_post_index = 0;
3995 : };
3996 :
3997 : // reset the old environment
3998 0 : f_env.clear();
3999 0 : f_post.clear();
4000 0 : f_files.clear();
4001 :
4002 : #ifdef DEBUG
4003 0 : SNAP_LOG_TRACE("Read environment variables including POST data.");
4004 : #endif
4005 :
4006 0 : read_env r(this, f_client, f_env, f_browser_cookies, f_post, f_files);
4007 : #ifdef DEBUG
4008 0 : r.output_debug_log();
4009 : #endif
4010 0 : r.run();
4011 0 : f_has_post = r.has_post();
4012 :
4013 : #ifdef DEBUG
4014 0 : SNAP_LOG_TRACE("Done reading environment variables.")(f_has_post ? " (This request includes a POST)" : "");
4015 : #endif
4016 :
4017 0 : trace("#START\n");
4018 0 : }
4019 :
4020 :
4021 : /** \brief Mark the server for initialization.
4022 : *
4023 : * This special flag allows us to initialize the plugins without having
4024 : * to actually try to access a specific website.
4025 : */
4026 0 : void snap_child::mark_for_initialization()
4027 : {
4028 0 : f_is_being_initialized = true;
4029 0 : }
4030 :
4031 :
4032 : /** \brief Write data to the output socket.
4033 : *
4034 : * This function writes data to the output socket.
4035 : *
4036 : * \exception runtime_error
4037 : * This exception is thrown if the write() fails writing all the bytes. This
4038 : * generally means the client closed the socket early.
4039 : *
4040 : * \param[in] data The data pointer.
4041 : * \param[in] size The number of bytes to write from data.
4042 : */
4043 0 : void snap_child::write(char const * data, ssize_t size)
4044 : {
4045 0 : if(!f_client)
4046 : {
4047 : // this happens from backends that do not have snap.cgi running
4048 0 : return;
4049 : }
4050 :
4051 0 : if(f_client->write(data, size) != size)
4052 : {
4053 0 : SNAP_LOG_FATAL("error while sending data to a client.");
4054 : // XXX throw? we cannot call die() because die() calls write()!
4055 0 : throw std::runtime_error("error while sending data to the client");
4056 : }
4057 : }
4058 :
4059 :
4060 : /** \brief Write a string to the socket.
4061 : *
4062 : * This function is an overload of the write() data buffer. It writes the
4063 : * specified string using strlen() to obtain the length. The string must be
4064 : * a valid null terminated C string.
4065 : *
4066 : * \param[in] str The string to write to the socket.
4067 : */
4068 0 : void snap_child::write(char const *str)
4069 : {
4070 0 : write(str, strlen(str));
4071 0 : }
4072 :
4073 :
4074 : /** \brief Write a QString to the socket.
4075 : *
4076 : * This function is an overload that writes the QString to the socket. This
4077 : * QString is transformed to UTF-8 before being processed.
4078 : *
4079 : * \param[in] str The QString to write to the socket.
4080 : */
4081 0 : void snap_child::write(QString const& str)
4082 : {
4083 0 : QByteArray a(str.toUtf8());
4084 0 : write(a.data(), a.size());
4085 0 : }
4086 :
4087 :
4088 : /** \brief Generate the Snap information buffer and return it.
4089 : *
4090 : * This function prints out information about the Snap! Server.
4091 : * This means writing information about all the different libraries
4092 : * in use such as their version, name, etc.
4093 : */
4094 0 : void snap_child::snap_info()
4095 : {
4096 0 : QString version;
4097 :
4098 : // getting started
4099 0 : write("#START\n");
4100 :
4101 : // the library (server) version
4102 0 : write("VERSION=" SNAPWEBSITES_VERSION_STRING "\n");
4103 :
4104 : // operating system
4105 0 : version = "OS=";
4106 : #ifdef Q_OS_LINUX
4107 0 : version += "Linux";
4108 : #else
4109 : #error "Unsupported operating system."
4110 : #endif
4111 0 : version += "\n";
4112 0 : write(version);
4113 :
4114 : // the Qt versions
4115 0 : write("QT=" QT_VERSION_STR "\n");
4116 0 : version = "RUNTIME_QT=";
4117 0 : version += qVersion();
4118 0 : version += "\n";
4119 0 : write(version);
4120 :
4121 : // the libtld version
4122 0 : write("LIBTLD=" LIBTLD_VERSION "\n");
4123 0 : version = "RUNTIME_LIBTLD=";
4124 0 : version += tld_version();
4125 0 : version += "\n";
4126 0 : write(version);
4127 :
4128 : // the libQtCassandra version
4129 0 : version = "LIBQTCASSANDRA=";
4130 0 : version += libdbproxy::LIBDBPROXY_LIBRARY_VERSION_STRING;
4131 0 : version += "\n";
4132 0 : write(version);
4133 0 : version = "RUNTIME_LIBQTCASSANDRA=";
4134 0 : version += libdbproxy::libdbproxy::version();
4135 0 : version += "\n";
4136 0 : write(version);
4137 :
4138 : // the libQtSerialization version
4139 0 : version = "LIBQTSERIALIZATION=";
4140 0 : version += QtSerialization::QT_SERIALIZATION_LIBRARY_VERSION_STRING;
4141 0 : version += "\n";
4142 0 : write(version);
4143 0 : version = "RUNTIME_LIBQTSERIALIZATION=";
4144 0 : version += QtSerialization::QLibraryVersion();
4145 0 : version += "\n";
4146 0 : write(version);
4147 :
4148 : // since we do not have an environment we cannot connect
4149 : // to the Cassandra cluster...
4150 :
4151 : // done
4152 0 : write("#END\n");
4153 :
4154 0 : exit(1);
4155 0 : }
4156 :
4157 :
4158 : /** \brief Return the current stats in name/value pairs format.
4159 : *
4160 : * This command returns the server statistics.
4161 : */
4162 0 : void snap_child::snap_statistics()
4163 : {
4164 0 : server::pointer_t server(get_server());
4165 :
4166 0 : QString s;
4167 :
4168 : // getting started
4169 0 : write("#START\n");
4170 :
4171 : // the library (server) version
4172 0 : write("VERSION=" SNAPWEBSITES_VERSION_STRING "\n");
4173 :
4174 : // the number of connections received by the server up until this child fork()'ed
4175 0 : s = "CONNECTIONS_COUNT=";
4176 0 : s += QString::number(server->connections_count());
4177 0 : s += "\n";
4178 0 : write(s);
4179 :
4180 : // done
4181 0 : write("#END\n");
4182 :
4183 0 : exit(1);
4184 0 : }
4185 :
4186 :
4187 : /** \brief Setup the URI from the environment.
4188 : *
4189 : * This function gets the different variables from the environment
4190 : * it just received from the snap.cgi script and builds the corresponding
4191 : * Snap URI object with it. This will then be used to determine the
4192 : * domain and finally the website.
4193 : */
4194 0 : void snap_child::setup_uri()
4195 : {
4196 : // PROTOCOL
4197 0 : if(f_env.count("HTTPS") == 1)
4198 : {
4199 0 : if(f_env["HTTPS"] == "on")
4200 : {
4201 0 : f_uri.set_protocol("https");
4202 : }
4203 : else
4204 : {
4205 0 : f_uri.set_protocol("http");
4206 : }
4207 : }
4208 : else
4209 : {
4210 0 : f_uri.set_protocol("http");
4211 : }
4212 :
4213 : // HOST (domain name including all sub-domains)
4214 0 : if(f_env.count("HTTP_HOST") != 1)
4215 : {
4216 : // this was tested in snap.cgi, but who knows who connected to us...
4217 : //
4218 0 : die(http_code_t::HTTP_CODE_SERVICE_UNAVAILABLE, "",
4219 : "HTTP_HOST is required but not defined in your request.",
4220 : "HTTP_HOST was not defined in the user request");
4221 0 : NOTREACHED();
4222 : }
4223 0 : QString host(f_env["HTTP_HOST"]);
4224 0 : int const port_pos(host.indexOf(':'));
4225 0 : if(port_pos != -1)
4226 : {
4227 : // remove the port information
4228 0 : host = host.left(port_pos);
4229 : }
4230 0 : if(host.isEmpty())
4231 : {
4232 0 : die(http_code_t::HTTP_CODE_SERVICE_UNAVAILABLE, "",
4233 : "HTTP_HOST is required but is empty in your request.",
4234 : "HTTP_HOST was defined but there was no domain name");
4235 0 : NOTREACHED();
4236 : }
4237 0 : f_uri.set_domain(host);
4238 :
4239 : // PORT
4240 0 : if(f_env.count("SERVER_PORT") != 1)
4241 : {
4242 0 : die(http_code_t::HTTP_CODE_SERVICE_UNAVAILABLE, "",
4243 : "SERVER_PORT is required but not defined in your request.",
4244 : "SERVER_PORT was not defined in the user request");
4245 0 : NOTREACHED();
4246 : }
4247 0 : f_uri.set_port(f_env["SERVER_PORT"]);
4248 :
4249 : // QUERY STRING
4250 0 : if(f_env.count("QUERY_STRING") == 1)
4251 : {
4252 0 : f_uri.set_query_string(f_env["QUERY_STRING"]);
4253 : }
4254 :
4255 : // REQUEST URI
4256 0 : if(f_env.count(snap::get_name(name_t::SNAP_NAME_CORE_REQUEST_URI)) != 1)
4257 : {
4258 0 : die(http_code_t::HTTP_CODE_SERVICE_UNAVAILABLE, "",
4259 : "REQUEST_URI is required but not defined in your request.",
4260 : "REQUEST_URI was not defined in the user's request");
4261 0 : NOTREACHED();
4262 : }
4263 : // For some reasons I was thinking that the q=... was necessary to
4264 : // find the correct REQUEST_URI -- it is only if you want to allow
4265 : // for snap.cgi?q=... syntax in the client's agent, otherwise it
4266 : // is totally useless; since we do not want the ugly snap.cgi syntax
4267 : // we completely removed the need for it
4268 : //
4269 : // Get the path from the REQUEST_URI
4270 : // note that it includes the query string when there is one so we must
4271 : // make sure to remove that part if defined
4272 : //
4273 0 : QString path;
4274 : {
4275 0 : int const p = f_env[snap::get_name(name_t::SNAP_NAME_CORE_REQUEST_URI)].indexOf('?');
4276 0 : if(p == -1)
4277 : {
4278 0 : path = f_env[snap::get_name(name_t::SNAP_NAME_CORE_REQUEST_URI)];
4279 : }
4280 : else
4281 : {
4282 0 : path = f_env[snap::get_name(name_t::SNAP_NAME_CORE_REQUEST_URI)].mid(0, p);
4283 : }
4284 : }
4285 :
4286 0 : QString extension;
4287 0 : if(path != "." && path != "..")
4288 : {
4289 : // TODO: make the size (2048) a variable? (parameter in
4290 : // snapserver.conf)
4291 : //
4292 : // I'm not totally sure this test is really necessary,
4293 : // but it probably won't hurt for a while (Drupal was
4294 : // limiting to 128 in the database and that was way
4295 : // too small, but 2048 is the longest you can use
4296 : // with Internet Explorer)
4297 : //
4298 0 : if(path.length() > 2048)
4299 : {
4300 : // See SNAP-99 for more info about this limit
4301 : //
4302 : // TBD: maybe we should redirect instead of dying in this case?
4303 : //
4304 0 : die(http_code_t::HTTP_CODE_REQUEST_URI_TOO_LONG, "",
4305 : "The path of this request is too long.",
4306 : "We accept paths up to 2048 characters.");
4307 0 : NOTREACHED();
4308 : }
4309 0 : f_uri.set_path(path);
4310 0 : int limit(path.lastIndexOf('/'));
4311 0 : if(limit < 0)
4312 : {
4313 0 : limit = 0;
4314 : }
4315 0 : int const ext(path.lastIndexOf('.'));
4316 0 : if(ext > limit)
4317 : {
4318 0 : extension = path.mid(ext);
4319 : // check for a compression and include that and
4320 : // the previous extension
4321 0 : if(extension == ".gz" // gzip
4322 0 : || extension == ".Z" // Unix compress
4323 0 : || extension == ".bz2") // bzip2
4324 : {
4325 : // we generally expect .gz but we have to take
4326 : // whatever extension the user added to make sure
4327 : // we send the file in the right format
4328 : // we will also need to use the Accept-Encoding
4329 : // and make use of the Content-Encoding
4330 : // TODO: make use of extension instead of Accept-Encoding
4331 0 : f_uri.set_option("compression", extension);
4332 0 : int const real_ext(path.lastIndexOf('.', ext - 1));
4333 0 : if(real_ext > limit)
4334 : {
4335 : // retrieve the extension without the compression
4336 0 : extension = path.mid(real_ext, real_ext - ext);
4337 : }
4338 : else
4339 : {
4340 : // do not view a compression extension by itself
4341 : // as an extension
4342 : //
4343 0 : extension.clear();
4344 : }
4345 : }
4346 : }
4347 : }
4348 0 : f_uri.set_option("extension", extension);
4349 :
4350 : //std::cerr << " set path to: [" << path << "]\n";
4351 : //
4352 : //std::cerr << " original [" << f_uri.get_original_uri() << "]\n";
4353 : //std::cerr << " uri [" << f_uri.get_uri() << "] + #! [" << f_uri.get_uri(true) << "]\n";
4354 : //std::cerr << " protocol [" << f_uri.protocol() << "]\n";
4355 : //std::cerr << " full domain [" << f_uri.full_domain() << "]\n";
4356 : //std::cerr << "top level domain [" << f_uri.top_level_domain() << "]\n";
4357 : //std::cerr << " domain [" << f_uri.domain() << "]\n";
4358 : //std::cerr << " sub-domains [" << f_uri.sub_domains() << "]\n";
4359 : //std::cerr << " port [" << f_uri.get_port() << "]\n";
4360 : //std::cerr << " path [" << f_uri.path() << "] \n";
4361 : //std::cerr << " query string [" << f_uri.query_string() << "]\n";
4362 0 : }
4363 :
4364 :
4365 : /** \brief Change the main path.
4366 : *
4367 : * This function allows a plugin to change the main path. This is very
4368 : * practical to allow one to redirect without changing the path the
4369 : * user sees in his browser. Such has to be done in the
4370 : * on_check_for_redirect() signal of your module, very early on before
4371 : * the path gets used (except in other redirect functions).
4372 : *
4373 : * The path change should not modified anything else in the URI (i.e.
4374 : * options, query string, etc.)
4375 : *
4376 : * \param[in] path The new path to save in f_uri.
4377 : */
4378 0 : void snap_child::set_uri_path(QString const & path)
4379 : {
4380 0 : f_uri.set_path(path);
4381 0 : }
4382 :
4383 :
4384 : /** \brief Get a copy of the URI information.
4385 : *
4386 : * This function returns a constant reference (i.e. a read-only "copy")
4387 : * of the URI used to access the server. This is the request we want to
4388 : * answer.
4389 : *
4390 : * \return The URI reference.
4391 : */
4392 0 : snap_uri const & snap_child::get_uri() const
4393 : {
4394 0 : return f_uri;
4395 : }
4396 :
4397 :
4398 : /** \brief Retrieve the current action.
4399 : *
4400 : * This function is used to retrieve the action defined in the query string.
4401 : * This value is changed with what will be used just before the permission
4402 : * verification process starts.
4403 : *
4404 : * Trying to retrieve the action too soon may give you an invalid value.
4405 : * Note that if you are generating the page contents, then you are past
4406 : * the verification process so this action value was corrected as required.
4407 : *
4408 : * \return The name of the action used to check this page.
4409 : */
4410 0 : QString snap_child::get_action() const
4411 : {
4412 0 : server::pointer_t server(get_server());
4413 0 : return f_uri.query_option(server->get_parameter("qs_action"));
4414 : }
4415 :
4416 :
4417 : /** \brief Define the action.
4418 : *
4419 : * This function is used to save the action being used to check the
4420 : * main page. The actions are defined in the database. These are
4421 : * "view", "edit", "delete", "administer", etc.
4422 : *
4423 : * Any action can be set, the permission plugin verifies that it exists
4424 : * and that the user has permissions before actually apply the action.
4425 : *
4426 : * \param[in] action The action used to check the page.
4427 : */
4428 0 : void snap_child::set_action(QString const& action)
4429 : {
4430 0 : server::pointer_t server(get_server());
4431 0 : f_uri.set_query_option(server->get_parameter("qs_action"), action);
4432 0 : }
4433 :
4434 :
4435 : /** \brief Check that the email is legal.
4436 : *
4437 : * A legal email has a legal name on the left of the @ character
4438 : * and a valid domain and TLD. We do not accept emails that represent
4439 : * local userbox (i.e. webmaster, root, etc. by themselves) because
4440 : * from a website these do not really make sense.
4441 : *
4442 : * If the email address is considered valid, knowing that we also check
4443 : * the Top Level Domain name (TLD), then we further send a request to
4444 : * the domain name handling that domain to make sure we can get information
4445 : * about the MX record. If the domain does not provide an MX record, then
4446 : * there is no emails to be sent.
4447 : *
4448 : * \note
4449 : * If max > 1 you probably want to keep allow_example_domain set to false
4450 : * otherwise you may get verified_email_t::VERIFIED_EMAIL_MIXED as a result
4451 : * which means you cannot know which email is an example and which is not.
4452 : *
4453 : * \todo
4454 : * Determine whether email addresses with the domain name of "example"
4455 : * are allowed. In some cases they are (i.e. we have a customers that
4456 : * has to create some pages linked to an email and at times those are
4457 : * not real email and thus we use `name@example.com` for those...)
4458 : * Especially, if someone registers an account with such an email it
4459 : * is totally useless and we should prevent it.
4460 : *
4461 : * \todo
4462 : * We want to count the number of times a certain IP address tries to
4463 : * verify an email address and fails. That way we can block them after
4464 : * X attempts. Because really someone should not be able to fail to
4465 : * enter their email address so many times.
4466 : *
4467 : * \exception users_exception_invalid_email
4468 : * This function does not return if the email is invalid. Instead it throws.
4469 : * We may later want to have a version that returns true if valid, false
4470 : * otherwise. At the same time, we generate three different errors here.
4471 : *
4472 : * \param[in] email The email to verify.
4473 : * \param[in] max The maximum number of emails supported. May be 0, usually 1.
4474 : * \param[in] allow_example_domain Whether the "example" domain is allowed.
4475 : *
4476 : * \return Whether the email is considered valid, invalid, is an example
4477 : * or a mix of valid and example emails.
4478 : */
4479 0 : snap_child::verified_email_t snap_child::verify_email(QString const & email, size_t const max, bool allow_example_domain)
4480 : {
4481 : // is there an actual email?
4482 : // (we may want to remove standalone and duplicated commas too)
4483 0 : if(email.isEmpty())
4484 : {
4485 0 : if(max == 0)
4486 : {
4487 0 : return verified_email_t::VERIFIED_EMAIL_EMPTY;
4488 : }
4489 0 : throw snap_child_exception_invalid_email("no email defined");
4490 : }
4491 :
4492 : // check the email name, domain, and TLD
4493 0 : tld_email_list tld_emails;
4494 0 : if(tld_emails.parse(email.toUtf8().data(), 0) != TLD_RESULT_SUCCESS)
4495 : {
4496 0 : throw snap_child_exception_invalid_email("invalid user email");
4497 : }
4498 :
4499 : // make sure there is a limit to the number of emails, if max was set
4500 : // to 1 we expect exactly one email (we previously eliminated the
4501 : // case where there would be none)
4502 : //
4503 0 : if(static_cast<size_t>(tld_emails.count()) > max)
4504 : {
4505 0 : throw snap_child_exception_invalid_email(QString("too many emails, excepted up to %1 got %2 instead.").arg(max).arg(tld_emails.count()));
4506 : }
4507 :
4508 : // if the email string is not empty, then there has to be at least one
4509 : // email otherwise the parse() function should have returned an error
4510 : //
4511 0 : if(tld_emails.count() == 0)
4512 : {
4513 0 : throw snap_child_exception_invalid_email(QString("no emails, even though \"%1\" is not empty.").arg(email));
4514 : }
4515 :
4516 0 : libdbproxy::table::pointer_t mx_table(get_table(get_name(name_t::SNAP_NAME_MX)));
4517 :
4518 0 : verified_email_t result(verified_email_t::VERIFIED_EMAIL_UNKNOWN);
4519 :
4520 : // function to set the result to either STANDARD or MIXED
4521 : // (we use a lambda because we use that twice)
4522 : //
4523 0 : auto set_result = [&result]()
4524 0 : {
4525 0 : switch(result)
4526 : {
4527 0 : case verified_email_t::VERIFIED_EMAIL_UNKNOWN:
4528 0 : result = verified_email_t::VERIFIED_EMAIL_STANDARD;
4529 0 : case verified_email_t::VERIFIED_EMAIL_STANDARD:
4530 0 : break;
4531 :
4532 0 : default:
4533 0 : result = verified_email_t::VERIFIED_EMAIL_MIXED;
4534 0 : break;
4535 :
4536 : }
4537 0 : };
4538 :
4539 :
4540 : // finally, check that the MX record exists for that email address, if
4541 : // not then we can immediately say its wrong; not that if multiple emails
4542 : // are defined, you get one throw if any one of them is wrong...
4543 : //
4544 0 : tld_email_list::tld_email_t e;
4545 0 : while(tld_emails.next(e))
4546 : {
4547 0 : if(allow_example_domain)
4548 : {
4549 0 : tld_object const d(e.f_domain.c_str());
4550 0 : if(d.domain_only() == "example")
4551 : {
4552 : // in this case a domain named "example" is considered valid
4553 : // (TODO the libtld should tell us whether this is true
4554 : // or not because this does not apply to all TLDs...)
4555 : //
4556 : // Note: we do not save such in our database, no need.
4557 : //
4558 0 : switch(result)
4559 : {
4560 0 : case verified_email_t::VERIFIED_EMAIL_UNKNOWN:
4561 0 : SNAP_LOG_TRACE("domain \"")(e.f_domain)("\" is considered to represent an example email.");
4562 0 : result = verified_email_t::VERIFIED_EMAIL_EXAMPLE;
4563 0 : case verified_email_t::VERIFIED_EMAIL_EXAMPLE:
4564 0 : break;
4565 :
4566 0 : default:
4567 0 : result = verified_email_t::VERIFIED_EMAIL_MIXED;
4568 0 : break;
4569 :
4570 : }
4571 0 : continue;
4572 : }
4573 : }
4574 :
4575 0 : libdbproxy::row::pointer_t row(mx_table->getRow(QString::fromUtf8(e.f_domain.c_str())));
4576 0 : libdbproxy::value last_checked_value(row->getCell(QString(get_name(name_t::SNAP_NAME_CORE_MX_LAST_CHECKED)))->getValue());
4577 0 : if(last_checked_value.size() == sizeof(int64_t))
4578 : {
4579 0 : time_t const last_update(last_checked_value.int64Value());
4580 0 : if(f_start_date < last_update + 86400LL * 1000000LL)
4581 : {
4582 0 : libdbproxy::value result_value(row->getCell(QString(get_name(name_t::SNAP_NAME_CORE_MX_RESULT)))->getValue());
4583 0 : if(result_value.safeSignedCharValue())
4584 : {
4585 : // considered valid without the need to check again
4586 : //
4587 0 : SNAP_LOG_TRACE("domain \"")(e.f_domain)("\" is considered valid (saved in mx table)");
4588 0 : set_result();
4589 0 : continue;
4590 : }
4591 : // this is not valid
4592 : //
4593 0 : throw snap_child_exception_invalid_email(QString("domain \"%1\" from email \"%2\" does not provide an MX record (from cache).")
4594 0 : .arg(QString::fromUtf8(e.f_domain.c_str()))
4595 0 : .arg(QString::fromUtf8(e.f_canonicalized_email.c_str())));
4596 : }
4597 : }
4598 0 : row->getCell(QString(get_name(name_t::SNAP_NAME_CORE_MX_LAST_CHECKED)))->setValue(f_start_date);
4599 0 : mail_exchangers exchangers(e.f_domain);
4600 0 : if(!exchangers.domain_found())
4601 : {
4602 0 : signed char const failed(0);
4603 0 : row->getCell(QString(get_name(name_t::SNAP_NAME_CORE_MX_RESULT)))->setValue(failed);
4604 0 : throw snap_child_exception_invalid_email(QString("domain \"%1\" from email \"%2\" does not exist.")
4605 0 : .arg(QString::fromUtf8(e.f_domain.c_str()))
4606 0 : .arg(QString::fromUtf8(e.f_canonicalized_email.c_str())));
4607 : }
4608 0 : if(exchangers.size() == 0)
4609 : {
4610 0 : signed char const failed(0);
4611 0 : row->getCell(QString(get_name(name_t::SNAP_NAME_CORE_MX_RESULT)))->setValue(failed);
4612 0 : throw snap_child_exception_invalid_email(QString("domain \"%1\" from email \"%2\" does not provide an MX record.")
4613 0 : .arg(QString::fromUtf8(e.f_domain.c_str()))
4614 0 : .arg(QString::fromUtf8(e.f_canonicalized_email.c_str())));
4615 : }
4616 0 : signed char const succeeded(1);
4617 0 : row->getCell(QString(get_name(name_t::SNAP_NAME_CORE_MX_RESULT)))->setValue(succeeded);
4618 :
4619 0 : SNAP_LOG_TRACE("domain \"")(e.f_domain)("\" was just checked and is considered valid (it has an MX record.)");
4620 :
4621 : // TODO: if we have the timeout, we could save the time when
4622 : // the data goes out of date (instead of using exactly
4623 : // 1 day as we do now) although we probably should use
4624 : // a minimum of 1 day anyway
4625 :
4626 : // TODO: also save the MX info (once we are to make use of any of it...)
4627 :
4628 0 : set_result();
4629 : }
4630 :
4631 0 : return result;
4632 : }
4633 :
4634 :
4635 : /** \brief Connect to the Cassandra database system.
4636 : *
4637 : * This function connects to the Cassandra database system and
4638 : * returns only if the connection succeeds. If it fails, it logs
4639 : * the fact and sends an error back to the user.
4640 : *
4641 : * By default the function calls die() on error. If you would
4642 : * prefer to have the function return with false, then set the
4643 : * child parameter to false.
4644 : *
4645 : * \param[in] child Whether a child is calling this function.
4646 : *
4647 : * \return true if the connection succeeded, false if child is false
4648 : * and the connection failed, does not return if child is true
4649 : * and the connection failed.
4650 : */
4651 0 : bool snap_child::connect_cassandra(bool child)
4652 : {
4653 : // Cassandra already exists?
4654 0 : if(f_cassandra)
4655 : {
4656 0 : if(!child)
4657 : {
4658 0 : SNAP_LOG_DEBUG("snap_child::connect_cassandra() already considered connected.");
4659 :
4660 : // here we return true since the Cassandra connection is
4661 : // already in place, valid and well
4662 : //
4663 0 : return true;
4664 : }
4665 0 : die(http_code_t::HTTP_CODE_SERVICE_UNAVAILABLE, "",
4666 : "Our database is being initialized more than once.",
4667 : "The connect_cassandra() function cannot be called more than once.");
4668 0 : NOTREACHED();
4669 : }
4670 :
4671 : // retrieve the address:port info
4672 : //
4673 : // WARNING: server->get_snapdbproxy_addr/port() do not work in
4674 : // snapbackend so we do it this way instead.
4675 : //
4676 0 : QString snapdbproxy_addr("127.0.0.1");
4677 0 : int snapdbproxy_port(4042);
4678 0 : snap_config config("snapdbproxy");
4679 0 : tcp_client_server::get_addr_port(config["listen"], snapdbproxy_addr, snapdbproxy_port, "tcp");
4680 :
4681 : // connect to Cassandra
4682 0 : f_cassandra = libdbproxy::libdbproxy::create();
4683 0 : bool connected(false);
4684 : try
4685 : {
4686 0 : connected = f_cassandra->connect(snapdbproxy_addr, snapdbproxy_port);
4687 :
4688 : // the connet() calls disconnect() which resets the consistency so
4689 : // if we really want QUORUM we have to set it again after the
4690 : // connect() call
4691 : //
4692 0 : f_cassandra->setDefaultConsistencyLevel(libdbproxy::CONSISTENCY_LEVEL_QUORUM);
4693 : }
4694 0 : catch(std::exception const & e)
4695 : {
4696 0 : connected = false; // make double sure this is still false
4697 0 : SNAP_LOG_FATAL("Could not connect to the snapdbproxy server (")(snapdbproxy_addr)(":")(snapdbproxy_port)("). Reason: ")(e.what());
4698 : }
4699 0 : if(!connected)
4700 : {
4701 : // cassandra is not valid, get rid of it
4702 0 : f_cassandra.reset();
4703 0 : if(!child)
4704 : {
4705 0 : SNAP_LOG_WARNING("snap_child::connect_cassandra() could not connect to snapdbproxy.");
4706 0 : return false;
4707 : }
4708 0 : die(http_code_t::HTTP_CODE_SERVICE_UNAVAILABLE, "",
4709 : "Our database system is temporarilly unavailable.",
4710 : "Could not connect to Cassandra");
4711 0 : NOTREACHED();
4712 : }
4713 :
4714 : // WARNING: The f_casssandra->contexts() function should not be used anymore
4715 : // (only to check whether the context exists,) because the context
4716 : // is normally created by snapmanager[.cgi] now.
4717 0 : SNAP_LOG_WARNING("snap_child::connect_cassandra() should not have to call contexts() anymore...");
4718 :
4719 : try
4720 : {
4721 : // select the Snap! context
4722 0 : f_cassandra->getContexts();
4723 0 : QString const context_name(get_name(name_t::SNAP_NAME_CONTEXT));
4724 0 : f_context = f_cassandra->findContext(context_name);
4725 0 : if(!f_context)
4726 : {
4727 : // we connected to the database, but it is not properly initialized!?
4728 : //
4729 0 : f_cassandra.reset();
4730 0 : if(!child)
4731 : {
4732 0 : SNAP_LOG_WARNING("snap_child::connect_cassandra() could not read the context.");
4733 0 : return false;
4734 : }
4735 0 : die(http_code_t::HTTP_CODE_SERVICE_UNAVAILABLE,
4736 : "",
4737 : "Our database system does not seem to be properly installed.",
4738 0 : QString("The child process connected to Cassandra but it could not find the \"%1\" context.").arg(context_name));
4739 0 : NOTREACHED();
4740 : }
4741 : }
4742 0 : catch(std::exception const & e)
4743 : {
4744 0 : SNAP_LOG_FATAL("Connected to snapdbproxy server, but could not gather the Cassandra metadata (")(snapdbproxy_addr)(":")(snapdbproxy_port)("). Reason: ")(e.what());
4745 :
4746 : // we connected to the database, but it is not properly initialized!?
4747 : //
4748 0 : f_context.reset(); // just in case, also reset the context (it should already be null here)
4749 0 : f_cassandra.reset();
4750 0 : if(!child)
4751 : {
4752 0 : SNAP_LOG_WARNING("snap_child::connect_cassandra() could not read the context metadata.");
4753 0 : return false;
4754 : }
4755 0 : die(http_code_t::HTTP_CODE_SERVICE_UNAVAILABLE,
4756 : "",
4757 : "Our database system is not currently available.",
4758 : "The child process connected to Cassandra through snapdbproxy, but it could not find retrieve the context metadata.");
4759 0 : NOTREACHED();
4760 : }
4761 :
4762 : // TBD -- Is that really the right place for this?
4763 : // (in this way it is done once for any plugin using
4764 : // the snap expression system; but it looks like a hack!)
4765 0 : snap_expr::expr::set_cassandra_context(f_context);
4766 :
4767 0 : return true;
4768 : }
4769 :
4770 :
4771 : /** \brief Completely disconnect from cassandra.
4772 : *
4773 : * Whenever we receive a NOCASSANDRA event in a backend, we want to
4774 : * disconnect completely. We will then reconnect once we receive the
4775 : * CASSANDRAREADY message.
4776 : */
4777 0 : void snap_child::disconnect_cassandra()
4778 : {
4779 0 : snap_expr::expr::set_cassandra_context(nullptr);
4780 :
4781 : // we must get rid of the site table otherwise it will hold a shared
4782 : // pointer to the context and the context to the libdbproxy object
4783 : //
4784 0 : reset_sites_table();
4785 :
4786 : // make sure the context cache is cleared too
4787 : //
4788 0 : f_context.reset();
4789 :
4790 0 : f_cassandra.reset();
4791 0 : }
4792 :
4793 :
4794 : /** \brief Retrieve the pointer to a table.
4795 : *
4796 : * This function is generally used by plugins to retrieve tables they
4797 : * use to manage their data.
4798 : *
4799 : * \exception snap_child_exception_no_cassandra
4800 : * If this function gets called when the Cassandra context is not yet
4801 : * defined, this exception is raised.
4802 : *
4803 : * \exception snap_child_exception_table_missing
4804 : * The table must already exists or this exception is raised. Tables
4805 : * are created by running the snapcreatetables tool before starting
4806 : * the snapserver.
4807 : *
4808 : * \param[in] table_name The name of the table to create.
4809 : */
4810 0 : libdbproxy::table::pointer_t snap_child::get_table(QString const & table_name)
4811 : {
4812 0 : if(f_context == nullptr)
4813 : {
4814 0 : throw snap_child_exception_no_cassandra("table \"" + table_name + "\" cannot be determined, we have no context.");
4815 : }
4816 :
4817 0 : libdbproxy::table::pointer_t table(f_context->findTable(table_name));
4818 0 : if(table == nullptr)
4819 : {
4820 0 : throw snap_child_exception_table_missing("table \"" + table_name + "\" does not exist.");
4821 : }
4822 :
4823 0 : return table;
4824 : }
4825 :
4826 :
4827 : /** \brief Canonalize the domain information.
4828 : *
4829 : * This function uses the URI to find the domain core::rules and
4830 : * start the canonicalization process.
4831 : *
4832 : * The canonicalized domain is a domain name with sub-domains that
4833 : * are required. All the optional sub-domains will be removed.
4834 : *
4835 : * All the variables are saved as options in the f_uri object.
4836 : *
4837 : * \todo
4838 : * The functionality of this function needs to be extracted so it becomes
4839 : * available to others (i.e. probably moved to snap_uri.cpp) that way we
4840 : * can write tools that show the results of this parser.
4841 : */
4842 0 : void snap_child::canonicalize_domain()
4843 : {
4844 : // retrieve domain table
4845 0 : QString const table_name(get_name(name_t::SNAP_NAME_DOMAINS));
4846 0 : libdbproxy::table::pointer_t table(f_context->getTable(table_name));
4847 :
4848 : // row for that domain exists?
4849 : //
4850 0 : f_domain_key = f_uri.domain() + f_uri.top_level_domain();
4851 0 : if(!table->exists(f_domain_key))
4852 : {
4853 : // this domain doesn't exist; i.e. that's a 404
4854 : //
4855 0 : die(http_code_t::HTTP_CODE_NOT_FOUND,
4856 : "Domain Not Found",
4857 : "This website does not exist. Please check the URI and make corrections as required.",
4858 0 : "User attempt to access \"" + f_domain_key + "\" which is not defined as a domain.");
4859 0 : NOTREACHED();
4860 : }
4861 :
4862 : // get the core::rules
4863 : //
4864 0 : libdbproxy::value value(table->getRow(f_domain_key)->getCell(QString(get_name(name_t::SNAP_NAME_CORE_RULES)))->getValue());
4865 0 : if(value.nullValue())
4866 : {
4867 : // Null value means an empty string or undefined column and either
4868 : // way it's wrong here
4869 : //
4870 0 : die(http_code_t::HTTP_CODE_NOT_FOUND,
4871 : "Domain Not Found",
4872 : "This website does not exist. Please check the URI and make corrections as required.",
4873 0 : "User attempt to access domain \"" + f_domain_key + "\" which does not have a valid core::rules entry.");
4874 0 : NOTREACHED();
4875 : }
4876 :
4877 : // parse the rules to our domain structures
4878 : //
4879 0 : domain_rules r;
4880 : // QBuffer takes a non-const QByteArray so we have to create a copy
4881 0 : QByteArray data(value.binaryValue());
4882 0 : QBuffer in(&data);
4883 0 : in.open(QIODevice::ReadOnly);
4884 0 : QtSerialization::QReader reader(in);
4885 0 : r.read(reader);
4886 :
4887 : // we add a dot because the list of variables are expected to
4888 : // end with a dot, but only if sub_domains is not empty
4889 : //
4890 0 : QString sub_domains(f_uri.sub_domains());
4891 0 : if(!sub_domains.isEmpty())
4892 : {
4893 0 : sub_domains += ".";
4894 : }
4895 0 : int const max_rules(r.size());
4896 0 : for(int i = 0; i < max_rules; ++i)
4897 : {
4898 0 : QSharedPointer<domain_info> info(r[i]);
4899 :
4900 : // build the regex (TODO: pre-compile the regex?
4901 : // the problem is the var. name versus data parsed)
4902 : //
4903 0 : QString re;
4904 0 : int vmax(info->size());
4905 0 : for(int v = 0; v < vmax; ++v)
4906 : {
4907 0 : QSharedPointer<domain_variable> var(info->get_variable(v));
4908 :
4909 : // put parameters between () so we get the data in
4910 : // variables (options) later
4911 0 : re += "(" + var->get_value() + ")";
4912 0 : if(!var->get_required())
4913 : {
4914 : // optional sub-domain
4915 0 : re += "?";
4916 : }
4917 : }
4918 0 : QRegExp regex(re);
4919 0 : if(regex.exactMatch(sub_domains))
4920 : {
4921 : // we found the domain!
4922 : //
4923 0 : snap_string_list captured(regex.capturedTexts());
4924 0 : QString canonicalized;
4925 :
4926 : // note captured[0] is the full matching pattern, we ignore it
4927 0 : for(int v = 0; v < vmax; ++v)
4928 : {
4929 0 : QSharedPointer<domain_variable> var(info->get_variable(v));
4930 :
4931 0 : QString sub_domain_value(captured[v + 1]);
4932 : // remove the last dot because in most cases we do not want it
4933 : // in the variable even if it were defined in the regex
4934 0 : if(!sub_domain_value.isEmpty() && sub_domain_value.right(1) == ".")
4935 : {
4936 0 : sub_domain_value = sub_domain_value.left(sub_domain_value.length() - 1);
4937 : }
4938 :
4939 0 : if(var->get_required())
4940 : {
4941 : // required, use default if empty
4942 : // TODO -- test that one, it seems that && should be used, not ||
4943 0 : if(sub_domain_value.isEmpty()
4944 0 : || var->get_type() == domain_variable::DOMAIN_VARIABLE_TYPE_WEBSITE)
4945 : {
4946 0 : sub_domain_value = var->get_default();
4947 : }
4948 0 : f_uri.set_option(var->get_name(), sub_domain_value);
4949 :
4950 : // these make up the final canonicalized domain name
4951 0 : canonicalized += snap_uri::urlencode(sub_domain_value, ".");
4952 : }
4953 0 : else if(!sub_domain_value.isEmpty())
4954 : {
4955 : // optional sub-domain, set only if not empty
4956 0 : if(var->get_type() == domain_variable::DOMAIN_VARIABLE_TYPE_WEBSITE)
4957 : {
4958 0 : sub_domain_value = var->get_default();
4959 : }
4960 0 : f_uri.set_option(var->get_name(), sub_domain_value);
4961 : }
4962 : else
4963 : {
4964 : // optional with a default, use it
4965 0 : sub_domain_value = var->get_default();
4966 0 : if(!sub_domain_value.isEmpty())
4967 : {
4968 0 : f_uri.set_option(var->get_name(), sub_domain_value);
4969 : }
4970 : }
4971 : }
4972 :
4973 : // now we've got the website key
4974 0 : if(canonicalized.isEmpty())
4975 : {
4976 0 : f_website_key = f_domain_key;
4977 : }
4978 : else
4979 : {
4980 0 : f_website_key = canonicalized + "." + f_domain_key;
4981 : }
4982 0 : return;
4983 : }
4984 : }
4985 :
4986 : // no domain match, we're dead meat
4987 0 : die(http_code_t::HTTP_CODE_NOT_FOUND, "Domain Not Found", "This website does not exist. Please check the URI and make corrections as required.", "The domain \"" + f_uri.full_domain() + "\" did not match any domain name defined in your Snap! system. Should you remove it from your DNS?");
4988 : }
4989 :
4990 :
4991 : /** \brief Finish the canonicalization process.
4992 : *
4993 : * The function reads the website core::rules and continue the parsing process
4994 : * of the URI.
4995 : *
4996 : * The sub-domain and domain canonicalization was accomplished in the previous
4997 : * process: canonicalize_domain(). This is not done again in the websites.
4998 : *
4999 : * This process includes the following checks:
5000 : *
5001 : * 1. Protocol
5002 : * 2. Port
5003 : * 3. Query String
5004 : * 4. Path
5005 : *
5006 : * The protocol, port, and query strings are checked as they are found in the
5007 : * website variables of the core::rules.
5008 : *
5009 : * The path is checked once all the variables were checked and if the protocol,
5010 : * port, and query strings were all matching as expected. If any one of them
5011 : * does not match then we don't need to check the path.
5012 : *
5013 : * \note
5014 : * As the checks of the protocol, port, and query strings are found, we cannot
5015 : * put them in the options just yet since if the path check fails, then
5016 : * another entry could be the proper match and that other entry may have
5017 : * completely different variables.
5018 : *
5019 : * \todo
5020 : * The functionality of this function needs to be extracted so it becomes
5021 : * available to others (i.e. probably moved to snap_uri.cpp) that way we
5022 : * can write tools that show the results of this parser.
5023 : */
5024 0 : void snap_child::canonicalize_website()
5025 : {
5026 : // retrieve website table
5027 : //
5028 0 : QString const table_name(get_name(name_t::SNAP_NAME_WEBSITES));
5029 0 : libdbproxy::table::pointer_t table(f_context->getTable(table_name));
5030 :
5031 : // row for that website exists?
5032 : //
5033 0 : if(!table->exists(f_website_key))
5034 : {
5035 : // this website doesn't exist; i.e. that's a 404
5036 0 : die(http_code_t::HTTP_CODE_NOT_FOUND, "Website Not Found", "This website does not exist. Please check the URI and make corrections as required.", "User attempt to access \"" + f_website_key + "\" which was not defined as a website.");
5037 0 : NOTREACHED();
5038 : }
5039 :
5040 : // get the core::rules
5041 : //
5042 0 : libdbproxy::value value(table->getRow(f_website_key)->getCell(QString(get_name(name_t::SNAP_NAME_CORE_RULES)))->getValue());
5043 0 : if(value.nullValue())
5044 : {
5045 : // Null value means an empty string or undefined column and either
5046 : // way it's wrong here
5047 : //
5048 0 : die(http_code_t::HTTP_CODE_NOT_FOUND, "Website Not Found", "This website does not exist. Please check the URI and make corrections as required.", "User attempt to access website \"" + f_website_key + "\" which does not have a valid core::rules entry.");
5049 0 : NOTREACHED();
5050 : }
5051 :
5052 : // parse the rules to our website structures
5053 0 : website_rules r;
5054 0 : QByteArray data(value.binaryValue());
5055 0 : QBuffer in(&data);
5056 0 : in.open(QIODevice::ReadOnly);
5057 0 : QtSerialization::QReader reader(in);
5058 0 : r.read(reader);
5059 :
5060 : // we check decoded paths
5061 0 : QString uri_path(f_uri.path(false));
5062 0 : int const max_rules(r.size());
5063 0 : for(int i = 0; i < max_rules; ++i)
5064 : {
5065 0 : QSharedPointer<website_info> info(r[i]);
5066 :
5067 : // build the regex (TODO: pre-compile the regex?
5068 : // the problem is the var. name versus data parsed)
5069 0 : QString protocol("http");
5070 0 : QString port("80");
5071 0 : QMap<QString, QString> query;
5072 0 : QString re_path;
5073 0 : int vmax(info->size());
5074 0 : bool matching(true);
5075 0 : for(int v = 0; matching && v < vmax; ++v)
5076 : {
5077 0 : QSharedPointer<website_variable> var(info->get_variable(v));
5078 :
5079 : // put parameters between () so we get the data in
5080 : // variables (options) later
5081 0 : QString param_value("(" + var->get_value() + ")");
5082 0 : switch(var->get_part())
5083 : {
5084 0 : case website_variable::WEBSITE_VARIABLE_PART_PATH:
5085 0 : re_path += param_value;
5086 0 : if(!var->get_required())
5087 : {
5088 : // optional sub-domain
5089 0 : re_path += "?";
5090 : }
5091 0 : break;
5092 :
5093 0 : case website_variable::WEBSITE_VARIABLE_PART_PORT:
5094 : {
5095 0 : QRegExp regex(param_value);
5096 0 : if(!regex.exactMatch(QString("%1").arg(f_uri.get_port())))
5097 : {
5098 0 : matching = false;
5099 0 : break;
5100 : }
5101 0 : snap_string_list captured(regex.capturedTexts());
5102 0 : port = captured[1];
5103 : }
5104 0 : break;
5105 :
5106 0 : case website_variable::WEBSITE_VARIABLE_PART_PROTOCOL:
5107 : {
5108 0 : QRegExp regex(param_value);
5109 : // the case of the protocol in the regex doesn't matter
5110 : // TODO (TBD):
5111 : // Although I'm not 100% sure this is correct, we may
5112 : // instead want to use lower case in the source
5113 0 : regex.setCaseSensitivity(Qt::CaseInsensitive);
5114 0 : if(!regex.exactMatch(f_uri.protocol()))
5115 : {
5116 0 : matching = false;
5117 0 : break;
5118 : }
5119 0 : snap_string_list captured(regex.capturedTexts());
5120 0 : protocol = captured[1];
5121 : }
5122 0 : break;
5123 :
5124 0 : case website_variable::WEBSITE_VARIABLE_PART_QUERY:
5125 : {
5126 : // the query string parameters are not ordered...
5127 : // the variable name is 1 to 1 what is expected on the URI
5128 0 : QString name(var->get_name());
5129 0 : if(f_uri.has_query_option(name))
5130 : {
5131 : // make sure it matches first
5132 0 : QRegExp regex(param_value);
5133 0 : if(!regex.exactMatch(f_uri.query_option(name)))
5134 : {
5135 0 : matching = false;
5136 0 : break;
5137 : }
5138 0 : snap_string_list captured(regex.capturedTexts());
5139 0 : query[name] = captured[1];
5140 : }
5141 0 : else if(var->get_required())
5142 : {
5143 : // if required then we want to use the default
5144 0 : query[name] = var->get_default();
5145 0 : }
5146 : }
5147 0 : break;
5148 :
5149 0 : default:
5150 0 : throw std::runtime_error("unknown part specified in website_variable::f_part");
5151 :
5152 : }
5153 : }
5154 0 : if(!matching)
5155 : {
5156 : // one of protocol, port, or query string failed
5157 : // (path is checked below)
5158 : //
5159 0 : continue;
5160 : }
5161 : // now check the path, if empty assume it matches and
5162 : // also we have no extra options
5163 : //
5164 0 : QString canonicalized_path;
5165 0 : if(!re_path.isEmpty())
5166 : {
5167 : // match from the start, but it doesn't need to match the whole path
5168 : //
5169 0 : QRegExp regex("^" + re_path);
5170 0 : if(regex.indexIn(uri_path) != -1)
5171 : {
5172 : // we found the site including a path!
5173 : //
5174 : // TODO: should we keep the length of the captured data and
5175 : // remove it from the path sent down the road?
5176 : // (note: if you have a path such as /blah/foo and
5177 : // you remove it, then what looks like /robots.txt
5178 : // is really /blah/foo/robots.txt which is wrong.)
5179 : // However, if the path is only used for options such
5180 : // as languages, those options should be removed from
5181 : // the original path.
5182 : //
5183 0 : snap_string_list captured(regex.capturedTexts());
5184 :
5185 : // note captured[0] is the full matching pattern, we ignore it
5186 : //
5187 0 : for(int v = 0; v < vmax; ++v)
5188 : {
5189 0 : QSharedPointer<website_variable> var(info->get_variable(v));
5190 :
5191 0 : if(var->get_part() == website_variable::WEBSITE_VARIABLE_PART_PATH)
5192 : {
5193 0 : QString path_value(captured[v + 1]);
5194 :
5195 0 : if(var->get_required())
5196 : {
5197 : // required, use default if empty
5198 : //
5199 0 : if(path_value.isEmpty()
5200 0 : || var->get_type() == website_variable::WEBSITE_VARIABLE_TYPE_WEBSITE)
5201 : {
5202 0 : path_value = var->get_default();
5203 : }
5204 0 : f_uri.set_option(var->get_name(), path_value);
5205 :
5206 : // these make up the final canonicalized domain name
5207 : //
5208 0 : canonicalized_path += "/" + snap_uri::urlencode(path_value, "~");
5209 : }
5210 0 : else if(!path_value.isEmpty())
5211 : {
5212 : // optional path, set only if not empty
5213 : //
5214 0 : if(var->get_type() == website_variable::WEBSITE_VARIABLE_TYPE_WEBSITE)
5215 : {
5216 0 : path_value = var->get_default();
5217 : }
5218 0 : f_uri.set_option(var->get_name(), path_value);
5219 : }
5220 : else
5221 : {
5222 : // optional with a default, use it
5223 : //
5224 0 : path_value = var->get_default();
5225 0 : if(!path_value.isEmpty())
5226 : {
5227 0 : f_uri.set_option(var->get_name(), path_value);
5228 : }
5229 : }
5230 : }
5231 : }
5232 : }
5233 : else
5234 : {
5235 0 : matching = false;
5236 : }
5237 : }
5238 :
5239 0 : if(matching)
5240 : {
5241 : // now we've got the protocol, port, query strings, and paths
5242 : // so we can build the final URI that we'll use as the site key
5243 : //
5244 0 : QString canonicalized;
5245 0 : f_uri.set_option("protocol", protocol);
5246 0 : canonicalized += protocol + "://" + f_website_key;
5247 0 : f_uri.set_option("port", port);
5248 0 : if(port.toInt() != snap_uri::protocol_to_port(protocol))
5249 : {
5250 0 : canonicalized += ":" + port;
5251 : }
5252 0 : if(canonicalized_path.isEmpty())
5253 : {
5254 0 : canonicalized += "/";
5255 : }
5256 : else
5257 : {
5258 0 : canonicalized += canonicalized_path;
5259 : }
5260 0 : QString canonicalized_query;
5261 0 : for(QMap<QString, QString>::const_iterator it(query.begin()); it != query.end(); ++it)
5262 : {
5263 0 : f_uri.set_query_option(it.key(), it.value());
5264 0 : if(!canonicalized_query.isEmpty())
5265 : {
5266 0 : canonicalized_query += "&";
5267 : }
5268 0 : canonicalized_query += snap_uri::urlencode(it.key()) + "=" + snap_uri::urlencode(it.value());
5269 : }
5270 0 : if(!canonicalized_query.isEmpty())
5271 : {
5272 0 : canonicalized += "?" + canonicalized_query;
5273 : }
5274 : // now we've got the site key
5275 0 : f_site_key = canonicalized;
5276 0 : f_original_site_key = f_site_key; // in case of a redirect...
5277 0 : f_site_key_with_slash = f_site_key;
5278 0 : if(f_site_key.right(1) != "/")
5279 : {
5280 0 : f_site_key_with_slash += "/";
5281 : }
5282 0 : return;
5283 : }
5284 : }
5285 :
5286 : // no website match, we're dead meat
5287 0 : die(http_code_t::HTTP_CODE_NOT_FOUND, "Website Not Found", "This website does not exist. Please check the URI and make corrections as required.", "The website \"" + f_website_key + "\" did not match any website defined in your Snap! system. Should you remove it from your DNS?");
5288 : }
5289 :
5290 :
5291 : /** \brief Determine the language, branch, revision, and compression.
5292 : *
5293 : * This function takes parameters as specified by the client and determines
5294 : * the language, branch, revision, and compression that are to be used to
5295 : * select the data to be returned to the client.
5296 : *
5297 : * In most cases visitors can only see the branch and revision that are
5298 : * marked as current. Editors can see all branches and revisions. Everyone
5299 : * can view any language, and translators (who are editors as well) can
5300 : * create new pages in a the languages they were assigned.
5301 : *
5302 : * The compression information is canonicalized here so we do not have to
5303 : * do it over and over again. It initializes the f_compressions vector
5304 : * with the very first entry privileged, as the favored entry. The last
5305 : * entry may be the special value COMPRESSION_INVALID in which case the
5306 : * 406 error (HTTP_CODE_NOT_ACCEPTABLE) should be generated.
5307 : *
5308 : * \todo
5309 : * The user may select a preferred language in his account. That has
5310 : * to be used before the browser information. However, the options
5311 : * (domain, path, or GET variable,) still have higher priority.
5312 : * Unfortunately, although plugins are already initialized, whether
5313 : * the user is logged in is not yet known so we cannot check from
5314 : * this function.
5315 : */
5316 0 : void snap_child::canonicalize_options()
5317 : {
5318 : // *** LANGUAGE / COUNTRY ***
5319 : // first take care of the language
5320 :
5321 : // transform the language specified by the browser in an array
5322 0 : http_strings::WeightedHttpString languages(snapenv(get_name(name_t::SNAP_NAME_CORE_HTTP_ACCEPT_LANGUAGE)));
5323 0 : http_strings::WeightedHttpString::part_t::vector_t const & browser_languages(languages.get_parts());
5324 0 : if(!browser_languages.isEmpty())
5325 : {
5326 : // not sorted by default, make sure it gets sorted
5327 : //
5328 0 : languages.sort_by_level();
5329 :
5330 0 : int const max_languages(browser_languages.size());
5331 0 : for(int i(0); i < max_languages; ++i)
5332 : {
5333 0 : QString browser_lang(browser_languages[i].get_name());
5334 0 : QString browser_country;
5335 0 : if(verify_locale(browser_lang, browser_country, false))
5336 : {
5337 : // only keep those that are considered valid (they all should
5338 : // be, but in case a hacker or strange client access our
5339 : // systems...)
5340 0 : locale_info_t l;
5341 0 : l.set_language(browser_lang);
5342 0 : l.set_country(browser_country);
5343 0 : f_browser_locales.push_back(l);
5344 : // added in order
5345 : }
5346 : // else -- browser sent us something we don't understand
5347 : }
5348 : }
5349 :
5350 0 : server::pointer_t server(get_server());
5351 0 : QString const qs_lang(server->get_parameter("qs_language"));
5352 0 : QString lang(f_uri.query_option(qs_lang));
5353 0 : QString country;
5354 :
5355 0 : if(!lang.isEmpty())
5356 : {
5357 : // user specified locale
5358 0 : verify_locale(lang, country, true);
5359 : }
5360 : //else
5361 : //{
5362 : // // the lang option in the URI is defined from:
5363 : // //
5364 : // // 1. a sub-domain name
5365 : // // 2. a section in the path
5366 : // // 3. a query string parameter
5367 : // //
5368 : // // and that's it, but we also want to support another set of
5369 : // // languages: the ones defined by the browser variable named:
5370 : // // HTTP_ACCEPT_LANGUAGE
5371 : // //
5372 : // // the problem we have here is that the browser variable defines
5373 : // // a set of supported languages and all have a right to be used
5374 : // // in a visitor's request, example:
5375 : // //
5376 : // // HTTP_ACCEPT_LANGUAGE=en-us,en;q=0.8,fr-fr;q=0.5,fr;q=0.3
5377 : // //
5378 : // // To resolve this issue, we do not handle the browser language
5379 : // // here, instead of do it in the snap_child::get_language() function,
5380 : // // which is important because a plugin may force the language too.
5381 : // //
5382 : // if(!f_browser_locales.isEmpty())
5383 : // {
5384 : // // language and country already broken up
5385 : // lang = f_browser_locales[0].get_language();
5386 : // country = f_browser_locales[0].get_country();
5387 : // }
5388 : // else
5389 : // {
5390 : // lang = "xx";
5391 : // }
5392 : //}
5393 :
5394 : // *** BRANCH / REVISION ***
5395 : // now take care of the branch and revision
5396 :
5397 : // current or working branch (working_branch=1)
5398 0 : QString const qs_working_branch(server->get_parameter("qs_working_branch"));
5399 0 : QString const working_branch_entry(f_uri.query_option(qs_working_branch));
5400 0 : bool const working_branch(!working_branch_entry.isEmpty());
5401 :
5402 : // branch (branch=<branch>)
5403 0 : QString const qs_branch(server->get_parameter("qs_branch"));
5404 0 : QString branch(f_uri.query_option(qs_branch));
5405 :
5406 : // rev (rev=<branch>.<revision>)
5407 0 : QString const qs_rev(server->get_parameter("qs_rev"));
5408 0 : QString rev(f_uri.query_option(qs_rev));
5409 :
5410 : // revision (revision=<revision>)
5411 0 : QString const qs_revision(server->get_parameter("qs_revision"));
5412 0 : QString revision(f_uri.query_option(qs_revision));
5413 :
5414 0 : if(branch.isEmpty() && revision.isEmpty())
5415 : {
5416 0 : if(!rev.isEmpty())
5417 : {
5418 0 : snap_string_list r(rev.split('.'));
5419 0 : branch = r[0];
5420 0 : int const size(r.size());
5421 0 : if(size != 1)
5422 : {
5423 0 : if(size == 2)
5424 : {
5425 0 : branch = r[0];
5426 0 : revision = r[1];
5427 : }
5428 : else
5429 : {
5430 : // refuse branch/revision + rev definitions together
5431 0 : die(http_code_t::HTTP_CODE_BAD_REQUEST, "Invalid Revision",
5432 0 : QString("The revision (%1) is not valid. It is expected to be a branch number, a period (.), and a revision number.").arg(rev),
5433 : "We found a number of period other than 1 or 2.");
5434 0 : NOTREACHED();
5435 : }
5436 : }
5437 : }
5438 : // else use the default
5439 : }
5440 0 : else if(!rev.isEmpty())
5441 : {
5442 : // refuse branch/revision + rev definitions together
5443 0 : die(http_code_t::HTTP_CODE_BAD_REQUEST, "Invalid Revision",
5444 : "You defined a rev parameter along a branch and/or revision, which is not supported. Remove one or the other to access your page.",
5445 0 : QString("We only accept a branch+revision (%1.%2) or a rev (%3).").arg(branch).arg(revision).arg(rev));
5446 0 : NOTREACHED();
5447 : }
5448 :
5449 0 : snap_version::version_number_t branch_value(static_cast<snap_version::basic_version_number_t>(snap_version::SPECIAL_VERSION_UNDEFINED));
5450 0 : snap_version::version_number_t revision_value(static_cast<snap_version::basic_version_number_t>(snap_version::SPECIAL_VERSION_UNDEFINED));
5451 0 : if(!branch.isEmpty())
5452 : {
5453 : // now verify that both are formed of digits only
5454 : bool ok;
5455 0 : branch_value = branch.toInt(&ok, 10);
5456 0 : if(!ok)
5457 : {
5458 : // if defined the branch needs to be a valid decimal number
5459 0 : die(http_code_t::HTTP_CODE_BAD_REQUEST, "Invalid Branch",
5460 0 : QString("Branch number \"%1\" is invalid. Only digits are expected.").arg(branch),
5461 : "The user did not give us a number as the branch number.");
5462 0 : NOTREACHED();
5463 : }
5464 : }
5465 0 : if(!revision.isEmpty())
5466 : {
5467 : bool ok;
5468 0 : revision_value = revision.toInt(&ok, 10);
5469 0 : if(!ok)
5470 : {
5471 : // if defined the revision needs to be a valid decimal number
5472 0 : die(http_code_t::HTTP_CODE_BAD_REQUEST, "Invalid Revision",
5473 0 : QString("Revision number \"%1\" is invalid. Only digits are expected.").arg(revision),
5474 : "The user did not give us a number as the revision number.");
5475 0 : NOTREACHED();
5476 : }
5477 : }
5478 :
5479 : // *** COMPRESSION ***
5480 : //
5481 : // compression is called "Encoding" in the HTTP reference
5482 0 : http_strings::WeightedHttpString encodings(snapenv("HTTP_ACCEPT_ENCODING"));
5483 :
5484 : // server defined compression is named "*" (i.e. server chooses)
5485 : // so first we check for gzip because we support that compression
5486 : // and that is our favorite for now (will change later with sdpy
5487 : // support eventually...)
5488 0 : compression_vector_t compressions;
5489 0 : bool got_gzip(false);
5490 0 : float const gzip_level(std::max(std::max(encodings.get_level("gzip"), encodings.get_level("x-gzip")), encodings.get_level("*")));
5491 0 : float const deflate_level(encodings.get_level("deflate"));
5492 0 : if(gzip_level > 0.0f && gzip_level >= deflate_level)
5493 : {
5494 0 : compressions.push_back(compression_t::COMPRESSION_GZIP);
5495 0 : got_gzip = true;
5496 : }
5497 :
5498 : // now check all the other encodings and add them
5499 0 : http_strings::WeightedHttpString::part_t::vector_t browser_compressions(encodings.get_parts());
5500 0 : std::stable_sort(browser_compressions.begin(), browser_compressions.end());
5501 0 : int const max_compressions(browser_compressions.size());
5502 0 : for(int i(0); i < max_compressions; ++i)
5503 : {
5504 0 : QString encoding_name(browser_compressions[i].get_name());
5505 : #pragma GCC diagnostic push
5506 : #pragma GCC diagnostic ignored "-Wfloat-equal"
5507 0 : if(browser_compressions[i].get_level() == 0.0f)
5508 : #pragma GCC diagnostic pop
5509 : {
5510 0 : if(encoding_name == "identity")
5511 : {
5512 : // if you reach this entry, generate a 406!
5513 : // this means the user will not accept uncompressed data
5514 0 : compressions.push_back(compression_t::COMPRESSION_INVALID);
5515 : }
5516 : }
5517 : else
5518 : {
5519 0 : if(encoding_name == "gzip"
5520 0 : || encoding_name == "x-gzip"
5521 0 : || encoding_name == "*")
5522 : {
5523 : // since we support multiple name we make use of a
5524 : // flag to avoid adding gzip more than once
5525 0 : if(!got_gzip)
5526 : {
5527 0 : compressions.push_back(compression_t::COMPRESSION_GZIP);
5528 0 : got_gzip = true;
5529 : }
5530 : }
5531 0 : else if(encoding_name == "deflate")
5532 : {
5533 0 : compressions.push_back(compression_t::COMPRESSION_DEFLATE);
5534 : }
5535 0 : else if(encoding_name == "bz2")
5536 : {
5537 : // not yet implemented though...
5538 0 : compressions.push_back(compression_t::COMPRESSION_BZ2);
5539 : }
5540 0 : else if(encoding_name == "sdch")
5541 : {
5542 : // not yet implemented though...
5543 0 : compressions.push_back(compression_t::COMPRESSION_SDCH);
5544 : }
5545 0 : else if(encoding_name == "identity")
5546 : {
5547 : // identity is acceptable
5548 0 : compressions.push_back(compression_t::COMPRESSION_IDENTITY);
5549 : }
5550 : // else -- we do not support yet...
5551 : }
5552 : }
5553 :
5554 : // *** CACHE CONTROL ***
5555 0 : cache_control_settings cache_control;
5556 0 : cache_control.set_max_age(cache_control_settings::IGNORE_VALUE); // defaults to 0 which is not correct for client's cache information
5557 0 : cache_control.set_cache_info(snapenv("HTTP_CACHE_CONTROL"), false);
5558 :
5559 : // *** SAVE RESULTS ***
5560 0 : f_language = lang;
5561 0 : f_country = country;
5562 : //f_language_key = f_language;
5563 : //if(!f_country.isEmpty())
5564 : //{
5565 : // f_language_key += "_";
5566 : // f_language_key += f_country;
5567 : //}
5568 :
5569 : // TBD: add support for long revisions? (for JS and CSS files)
5570 0 : f_working_branch = working_branch;
5571 0 : f_branch = branch_value;
5572 0 : f_revision = revision_value;
5573 0 : f_revision_key = QString("%1.%2").arg(f_branch).arg(f_revision);
5574 :
5575 0 : f_compressions.swap(compressions);
5576 0 : f_client_cache_control = cache_control;
5577 0 : }
5578 :
5579 :
5580 : /** \brief Verify the locale defined in \p lang.
5581 : *
5582 : * This function cuts the \p lang string in two strings: the language
5583 : * name and one country.
5584 : *
5585 : * In most cases, when you call this function the language parameter
5586 : * (\p lang) includes the entire locale such as: de_DE or am-BY. It is
5587 : * not mandatory so the country can also be define in which case it
5588 : * should not appear in the \p lang parameter.
5589 : *
5590 : * \param[in,out] lang The language to be verified.
5591 : * \param[out] country The resulting country if defined in \p lang.
5592 : * \param[in] generate_errors Whether to call die() on errors.
5593 : *
5594 : * \return true if no errors were detected.
5595 : */
5596 0 : bool snap_child::verify_locale(QString & lang, QString & country, bool generate_errors)
5597 : {
5598 0 : country.clear();
5599 :
5600 : // search for a '-' or '_' separator as in:
5601 : //
5602 : // fr-ca (browser locale)
5603 : // en_US (Unix environment locale)
5604 : //
5605 : // initialize with 'ptr - 1' so we can ++ on entry of the while loop
5606 0 : QChar const * s(lang.data() - 1);
5607 : ushort c;
5608 0 : do
5609 : {
5610 0 : ++s;
5611 0 : c = s->unicode();
5612 : }
5613 0 : while(c != '\0' && c != '-' && c != '_');
5614 :
5615 : // if not '\0' then we have a country specified
5616 0 : if(c != '\0')
5617 : {
5618 0 : if(!country.isEmpty())
5619 : {
5620 0 : if(generate_errors)
5621 : {
5622 : // country should always be empty on entry
5623 0 : die(http_code_t::HTTP_CODE_BAD_REQUEST, "Country Defined Twice",
5624 0 : QString("Country is defined twice.").arg(lang).arg(c),
5625 : "This one may be a programmer mistake. The country parameter was not an empty string on entry of this function.");
5626 0 : NOTREACHED();
5627 : }
5628 0 : return false;
5629 : }
5630 0 : int const pos(static_cast<int>(s - lang.data()));
5631 0 : country = lang.mid(pos + 1);
5632 0 : lang = lang.left(pos);
5633 0 : if(lang.isEmpty())
5634 : {
5635 0 : if(generate_errors)
5636 : {
5637 : // we do not accept entries such as "-br" or "_RU"
5638 0 : die(http_code_t::HTTP_CODE_NOT_FOUND, "Language Not Found",
5639 0 : QString("Language in the locale specification \"%1\" is not defined which is not supported.").arg(lang),
5640 : "Prevented user from accessing the website with no language specified, even though he specified a language entry.");
5641 0 : NOTREACHED();
5642 : }
5643 0 : return false;
5644 : }
5645 0 : if(country.isEmpty())
5646 : {
5647 0 : if(generate_errors)
5648 : {
5649 : // we do not accept entries such as "br-" or "ru_"
5650 0 : die(http_code_t::HTTP_CODE_NOT_FOUND, "Country Not Found",
5651 0 : QString("Country in the locale specification \"%1\" is not defined. Remove '%2' if you do not want to include a country.").arg(lang).arg(c),
5652 : "Prevented user from accessing the website with no country specified, even though he specified a country entry.");
5653 0 : NOTREACHED();
5654 : }
5655 0 : return false;
5656 : }
5657 : }
5658 :
5659 0 : if(lang.isEmpty())
5660 : {
5661 : // use the default
5662 0 : lang = "xx";
5663 : }
5664 : else
5665 : {
5666 0 : if(!verify_language_name(lang))
5667 : {
5668 0 : if(generate_errors)
5669 : {
5670 : // language is not valid; generate an error
5671 0 : die(http_code_t::HTTP_CODE_NOT_FOUND, "Language Not Found",
5672 0 : QString("Language in the locale specification \"%1\" is not currently considered a known or valid language name.").arg(lang),
5673 : "Prevented user from accessing the website with an invalid language.");
5674 0 : NOTREACHED();
5675 : }
5676 0 : return false;
5677 : }
5678 : }
5679 :
5680 : // country can be empty, that is fine; in most cases we recommend users
5681 : // to not use the country name in their language
5682 0 : if(!country.isEmpty())
5683 : {
5684 0 : if(country.contains('.'))
5685 : {
5686 0 : if(generate_errors)
5687 : {
5688 : // charset not supported here for now
5689 0 : die(http_code_t::HTTP_CODE_NOT_FOUND, "Country Not Found",
5690 0 : QString("Locale specification \"%1\" seems to include a charset which is not legal at this time.").arg(lang),
5691 : "Prevented user from accessing the website because a charset was specified with the language.");
5692 0 : NOTREACHED();
5693 : }
5694 0 : return false;
5695 : }
5696 0 : if(!verify_country_name(country))
5697 : {
5698 0 : if(generate_errors)
5699 : {
5700 : // abbreviation not found
5701 0 : die(http_code_t::HTTP_CODE_NOT_FOUND, "Country Not Found",
5702 0 : QString("Country in locale specification \"%1\" does not look like a valid country name.").arg(lang),
5703 : "Prevented user from accessing the website with an invalid country.");
5704 0 : NOTREACHED();
5705 : }
5706 0 : return false;
5707 : }
5708 : }
5709 :
5710 0 : return true;
5711 : }
5712 :
5713 :
5714 : /** \brief Verify that a name is a language name.
5715 : *
5716 : * This function checks whether \p lang is a valid language name. If so
5717 : * then the function returns true and \p lang is set to the corresponding
5718 : * 2 letter abbreviation for that country.
5719 : *
5720 : * Note that calling the function with an empty string will make it
5721 : * return false.
5722 : *
5723 : * \warning
5724 : * The input string is changed even if the language is not found.
5725 : *
5726 : * \param[in,out] lang The language being checked.
5727 : *
5728 : * \return true if the language is valid, false in all other cases.
5729 : */
5730 0 : bool snap_child::verify_language_name(QString & lang)
5731 : {
5732 : // TODO: make use of a fully optimized search with a binary search like
5733 : // capability (i.e. a switch on a per character basis in a
5734 : // sub-function generated using the language table)
5735 : //
5736 0 : lang = lang.toLower();
5737 0 : if(lang.length() == 2)
5738 : {
5739 : // we get the unicode value even though in the loop we compare
5740 : // against ASCII; if l0 or l1 are characters outside of the ASCII
5741 : // character set, then the loop will fail, simply
5742 0 : ushort const l0(lang[0].unicode());
5743 0 : ushort const l1(lang[1].unicode());
5744 0 : for(language_name_t const *l(g_language_names); l->f_short_name[0]; ++l)
5745 : {
5746 0 : if(l0 == l->f_short_name[0]
5747 0 : && l1 == l->f_short_name[1])
5748 : {
5749 : // got it!
5750 0 : return true;
5751 : }
5752 : }
5753 : }
5754 : else
5755 : {
5756 : // TBD: do we really want to support long names?
5757 0 : QString lang_with_commas("," + lang + ",");
5758 0 : QByteArray lang_with_commas_buffer(lang_with_commas.toUtf8());
5759 0 : char const * lwc(lang_with_commas_buffer.data());
5760 0 : for(language_name_t const * l(g_language_names); l->f_language; ++l)
5761 : {
5762 : // the multi-name is already semi-optimize so we test it too
5763 : // TBD -- this should maybe not be checked at all
5764 0 : if(strstr(l->f_other_names, lwc) != nullptr)
5765 : {
5766 0 : lang = l->f_short_name;
5767 0 : return true;
5768 : }
5769 : }
5770 : }
5771 :
5772 : // not found
5773 0 : return false;
5774 : }
5775 :
5776 :
5777 : /** \brief Verify that a name is a country name.
5778 : *
5779 : * This function checks whether \p country is a valid country name. If so
5780 : * then the function returns true and \p country is set to the corresponding
5781 : * 2 letter abbreviation for that country.
5782 : *
5783 : * Note that calling the function with an empty string will make it
5784 : * return false.
5785 : *
5786 : * \warning
5787 : * The input string is changed even if the country is not found.
5788 : *
5789 : * \param[in,out] country The country being checked.
5790 : *
5791 : * \return true if the country is valid, false in all other cases.
5792 : */
5793 0 : bool snap_child::verify_country_name(QString & country)
5794 : {
5795 0 : country = country.toUpper();
5796 0 : if(country.length() == 2)
5797 : {
5798 : // check abbreviations only
5799 0 : ushort const c0 = country[0].unicode();
5800 0 : ushort const c1 = country[1].unicode();
5801 0 : for(country_name_t const *c(g_country_names); c->f_abbreviation[0]; ++c)
5802 : {
5803 0 : if(c0 == c->f_abbreviation[0] && c1 == c->f_abbreviation[1])
5804 : {
5805 : // found it!
5806 0 : return true;
5807 : }
5808 : }
5809 : }
5810 : else
5811 : {
5812 : // check full names only
5813 : //
5814 : // TODO: this could be fully optimized with a set of switch
5815 : // statements and the full country name should be checked
5816 : // without the part after the comma, then in full with the
5817 : // part after the comma put in the front of the other
5818 : // part; i.e. "Bolivia, Plurinational State of" should
5819 : // be checked as any one of the following four entries:
5820 : //
5821 : // BO (abbreviation)
5822 : // Bolivia
5823 : // Plurinational State of Bolivia
5824 : // Bolivia, Plurinational State of
5825 : //
5826 : // TBD -- I'm not so sure we really want to support full names
5827 0 : for(country_name_t const *c(g_country_names); c->f_abbreviation[0]; ++c)
5828 : {
5829 0 : if(QString(c->f_name).toUpper() == country)
5830 : {
5831 0 : country = c->f_abbreviation;
5832 0 : return true;
5833 : }
5834 : }
5835 : }
5836 :
5837 0 : return false;
5838 : }
5839 :
5840 :
5841 : /** \brief Check whether a site needs to be redirected.
5842 : *
5843 : * This function verifies the site we just discovered to see whether
5844 : * the user requested a redirect. If so, then we replace the
5845 : * f_site_key accordingly.
5846 : *
5847 : * Note that this is not a 301 redirect, just an internal remap from
5848 : * site A to site B.
5849 : */
5850 0 : void snap_child::site_redirect()
5851 : {
5852 0 : libdbproxy::value redirect(get_site_parameter(get_name(name_t::SNAP_NAME_CORE_REDIRECT)));
5853 0 : if(redirect.nullValue())
5854 : {
5855 : // no redirect
5856 0 : return;
5857 : }
5858 :
5859 : // redirect now
5860 0 : f_site_key = redirect.stringValue();
5861 :
5862 : // TBD -- should we also redirect the f_domain_key and f_website_key?
5863 :
5864 : // the site table is the old one, we want to switch to the new one
5865 0 : reset_sites_table();
5866 : }
5867 :
5868 :
5869 : /** \brief Reset the site table so one can make sure to use the latest version.
5870 : *
5871 : * In a backend that does not restart all the time, we may need to reset
5872 : * the site table.
5873 : *
5874 : * This function clears the memory cache of the existing site table, if
5875 : * one exists in memory, and then it resets the site table pointer.
5876 : * The next time a user calls the set_site_parameter() or get_site_parameter()
5877 : * a new site table object is created and filled as required.
5878 : */
5879 0 : void snap_child::reset_sites_table()
5880 : {
5881 0 : f_sites_table.reset();
5882 0 : }
5883 :
5884 :
5885 : /** \brief Redirect the user to a new page.
5886 : *
5887 : * This function forcibly redirects a user to a new page. If the path
5888 : * includes a protocol (is a full URI) then it is used as is. If
5889 : * the path includes no protocol, the current site key is prepended.
5890 : *
5891 : * The HTTP code can be specified. By default, 301 is assumed because
5892 : * that's the most prominent redirect code used. If a page is used to
5893 : * redirect dynamically, make sure to use 302 or 303 instead. You can
5894 : * safely use one of the following codes:
5895 : *
5896 : * \li HTTP_CODE_MOVED_PERMANENTLY (301)
5897 : * \li HTTP_CODE_FOUND (302)
5898 : * \li HTTP_CODE_SEE_OTHER (303) -- POST becomes GET
5899 : * \li HTTP_CODE_TEMPORARY_REDIRECT (307) -- keep same method
5900 : * \li HTTP_CODE_PERMANENT_REDIRECT (308) -- keep same method
5901 : *
5902 : * The path may include a query string and an anchor.
5903 : *
5904 : * \note
5905 : * This function does not allow redirecting an application which used a
5906 : * method other than GET, HEAD, and POST. Applications are expected to
5907 : * get their URI right.
5908 : *
5909 : * \warning
5910 : * The function does not return since after sending a redirect to a client
5911 : * there is nothing more you can do. So if you need to save some data, make
5912 : * sure to do it before this call. Note also that this function calls the
5913 : * attach_to_session() signal so any plugin that was not really done has a
5914 : * chance to save its data until the next connection arrives.
5915 : *
5916 : * \param[in] path The full URI or the local website path to redirect the
5917 : * user browser to.
5918 : * \param[in] http_code The code used to send the redirect.
5919 : * \param[in] reason_brief A brief explanation for the redirection.
5920 : * \param[in] reason The long version of the explanation for the redirection.
5921 : */
5922 0 : void snap_child::page_redirect(QString const & path, http_code_t http_code, QString const & reason_brief, QString const & reason)
5923 : {
5924 0 : if(f_site_key_with_slash.isEmpty())
5925 : {
5926 0 : die(http_code_t::HTTP_CODE_INTERNAL_SERVER_ERROR, "Initialization Mismatch",
5927 : "An internal server error was detected while initializing the process.",
5928 : "The server snap_child::page_redirect() function was called before the website got canonicalized.");
5929 0 : NOTREACHED();
5930 : }
5931 0 : QString const method(snapenv(get_name(name_t::SNAP_NAME_CORE_REQUEST_METHOD)));
5932 0 : if(method != "GET"
5933 0 : && method != "POST"
5934 0 : && method != "HEAD")
5935 : {
5936 0 : die(http_code_t::HTTP_CODE_FORBIDDEN, "Method Not Allowed",
5937 0 : QString("Prevented a redirect when using method %1.").arg(method),
5938 : "We do not currently support redirecting users for methods other than GET, POST, and HEAD.");
5939 0 : NOTREACHED();
5940 : }
5941 :
5942 0 : switch(http_code)
5943 : {
5944 0 : case http_code_t::HTTP_CODE_MOVED_PERMANENTLY:
5945 : case http_code_t::HTTP_CODE_FOUND:
5946 : case http_code_t::HTTP_CODE_SEE_OTHER:
5947 : case http_code_t::HTTP_CODE_TEMPORARY_REDIRECT:
5948 : case http_code_t::HTTP_CODE_PERMANENT_REDIRECT:
5949 0 : break;
5950 :
5951 0 : default:
5952 0 : die(http_code_t::HTTP_CODE_FORBIDDEN, "Error Code Not Allowed For Redirect",
5953 0 : QString("Prevented a redirect using HTTP code %1.").arg(static_cast<int>(http_code)),
5954 : "We limit the redirect to using 301, 302, 303, 307, and 308.");
5955 0 : NOTREACHED();
5956 :
5957 : }
5958 :
5959 0 : if(path.contains('\n') || path.contains('\r'))
5960 : {
5961 : // if the path includes a \n or \r then the user could inject
5962 : // a header which could have all sorts of effects we don't even
5963 : // want to think about! just deny it...
5964 0 : die(http_code_t::HTTP_CODE_INTERNAL_SERVER_ERROR, "Hack Prevention",
5965 : "Server prevented a potential hack from being applied.",
5966 0 : "The server snap_child::page_redirect() function was called with a path that includes \n or \r and refused processing it: \"" + path + "\"");
5967 0 : NOTREACHED();
5968 : }
5969 :
5970 0 : snap_uri uri;
5971 0 : if(!uri.set_uri(path))
5972 : {
5973 : // in most cases it fails because the protocol is missing
5974 0 : QString local_path(path);
5975 0 : canonicalize_path(local_path);
5976 0 : if(!uri.set_uri(get_site_key_with_slash() + local_path))
5977 : {
5978 0 : die(http_code_t::HTTP_CODE_ACCESS_DENIED, "Invalid URI",
5979 : "The server prevented a redirect because it could not understand the destination URI.",
5980 0 : "The server snap_child::page_redirect() function was called with a path that it did not like: \"" + path + "\"");
5981 0 : NOTREACHED();
5982 : }
5983 : }
5984 :
5985 0 : attach_to_session();
5986 :
5987 : // redirect the user to the specified path
5988 0 : QString http_name;
5989 0 : define_http_name(http_code, http_name);
5990 :
5991 0 : set_header("Status", QString("%1 %2")
5992 0 : .arg(static_cast<int>(http_code))
5993 0 : .arg(http_name), HEADER_MODE_REDIRECT);
5994 :
5995 0 : server::pointer_t server(get_server());
5996 0 : snap_string_list const show_redirects(server->get_parameter("show_redirects").split(","));
5997 :
5998 0 : if(!show_redirects.contains("refresh-only"))
5999 : {
6000 : // the get_uri() returns an HTTP encoded string
6001 0 : set_header("Location", uri.get_uri(), HEADER_MODE_REDIRECT);
6002 : }
6003 :
6004 : // also the default is already text/html we force it again in case this
6005 : // function is called after someone changed this header
6006 0 : set_header(get_name(name_t::SNAP_NAME_CORE_CONTENT_TYPE_HEADER), "text/html; charset=utf-8", HEADER_MODE_EVERYWHERE);
6007 :
6008 : // compute the body
6009 : // (TBD: should we support getting the content of a page? since 99.9999% of
6010 : // the time this content is ignored, I would say no.)
6011 : //
6012 0 : QString body;
6013 : {
6014 0 : QDomDocument doc;
6015 : // html
6016 0 : QDomElement html(doc.createElement("html"));
6017 0 : doc.appendChild(html);
6018 : // html/head
6019 0 : QDomElement head(doc.createElement("head"));
6020 0 : html.appendChild(head);
6021 : // html/head/meta[@http-equiv=...][@content=...]
6022 0 : QDomElement meta_locale(doc.createElement("meta"));
6023 0 : head.appendChild(meta_locale);
6024 0 : meta_locale.setAttribute("http-equiv", get_name(name_t::SNAP_NAME_CORE_CONTENT_TYPE_HEADER));
6025 0 : meta_locale.setAttribute("content", "text/html; charset=utf-8");
6026 : // html/head/title/...
6027 0 : QDomElement title(doc.createElement("title"));
6028 0 : head.appendChild(title);
6029 0 : QDomText title_text(doc.createTextNode(reason_brief));
6030 0 : title.appendChild(title_text);
6031 : // html/head/meta[@http-equiv=...][@content=...]
6032 0 : if(show_redirects.contains("refresh-only")
6033 0 : || !show_redirects.contains("no-refresh"))
6034 : {
6035 0 : QDomElement meta_refresh(doc.createElement("meta"));
6036 0 : head.appendChild(meta_refresh);
6037 0 : meta_refresh.setAttribute("http-equiv", "Refresh");
6038 0 : int timeout(0);
6039 0 : if(show_redirects.contains("one-minute"))
6040 : {
6041 0 : timeout = 60;
6042 : }
6043 0 : meta_refresh.setAttribute("content", QString("%1; url=%2").arg(timeout).arg(uri.get_uri()));
6044 : }
6045 : // html/head/meta[@http-equiv=...][@content=...]
6046 0 : QDomElement meta_robots(doc.createElement("meta"));
6047 0 : head.appendChild(meta_robots);
6048 0 : meta_robots.setAttribute("name", "ROBOTS");
6049 0 : meta_robots.setAttribute("content", "NOINDEX");
6050 : // html/body
6051 0 : QDomElement body_tag(doc.createElement("body"));
6052 0 : html.appendChild(body_tag);
6053 :
6054 : // include an actual body?
6055 0 : if(show_redirects.contains("include-body"))
6056 : {
6057 : // html/body/h1
6058 0 : QDomElement h1(doc.createElement("h1"));
6059 0 : body_tag.appendChild(h1);
6060 0 : QDomText h1_text(doc.createTextNode(reason_brief));
6061 0 : h1.appendChild(h1_text);
6062 : // html/body/p
6063 0 : QDomElement p(doc.createElement("p"));
6064 0 : body_tag.appendChild(p);
6065 0 : QDomText p_text1(doc.createTextNode(QString("%1 New location: ").arg(reason)));
6066 0 : p.appendChild(p_text1);
6067 : // html/body/a
6068 0 : QDomElement a(doc.createElement("a"));
6069 0 : a.setAttribute("href", uri.get_uri());
6070 0 : p.appendChild(a);
6071 0 : QDomText a_text(doc.createTextNode(uri.get_uri()));
6072 0 : a.appendChild(a_text);
6073 : // html/body/p (text after anchor)
6074 0 : QDomText p_text2(doc.createTextNode("."));
6075 0 : p.appendChild(p_text2);
6076 : }
6077 :
6078 : // save the result
6079 0 : body = doc.toString(-1);
6080 : }
6081 :
6082 0 : output_result(HEADER_MODE_REDIRECT, body.toUtf8());
6083 :
6084 : // XXX should we exit with 1 in this case?
6085 0 : exit(0);
6086 0 : NOTREACHED();
6087 : }
6088 :
6089 :
6090 : /** \brief Attach variables to this session.
6091 : *
6092 : * Once in a while a plugin creates a form that is intermediary. In this
6093 : * case the session variables need to be saved and this function is called.
6094 : *
6095 : * Note that you may want to look into not detaching the variable(s) if at
6096 : * all possible.
6097 : */
6098 0 : void snap_child::attach_to_session()
6099 : {
6100 0 : server::pointer_t server(get_server());
6101 0 : server->attach_to_session();
6102 0 : }
6103 :
6104 :
6105 : /** \brief Load a file.
6106 : *
6107 : * This signal is sent to all the plugins so one can load the file as
6108 : * defined in the file filename.
6109 : *
6110 : * The function returns true if the file was loaded successfully.
6111 : *
6112 : * \param[in,out] file The file that is to be loaded.
6113 : *
6114 : * \return true if the file was found, false otherwise.
6115 : */
6116 0 : bool snap_child::load_file(post_file_t & file)
6117 : {
6118 0 : server::pointer_t server(get_server());
6119 0 : bool found(false);
6120 0 : server->load_file(file, found);
6121 0 : return found;
6122 : }
6123 :
6124 :
6125 : /** \brief Retrieve an environment variable.
6126 : *
6127 : * This function can be used to read an environment variable. It will make
6128 : * sure, in most cases, that the variable is not tented.
6129 : *
6130 : * At this point only the variables defined in the HTTP request are available.
6131 : * Any other variable name will return an empty string.
6132 : *
6133 : * The SERVER_PROTOCOL variable can be retrieved at any time, even before we
6134 : * read the environment. This is done so we can call the die() function and
6135 : * return with a valid protocol and version.
6136 : *
6137 : * \param[in] name The name of the variable to retrieve.
6138 : *
6139 : * \return The value of the specified variable.
6140 : */
6141 0 : QString snap_child::snapenv(QString const & name) const
6142 : {
6143 0 : if(name == get_name(name_t::SNAP_NAME_CORE_SERVER_PROTOCOL))
6144 : {
6145 : // SERVER PROTOCOL
6146 0 : if(false == f_fixed_server_protocol)
6147 : {
6148 0 : f_fixed_server_protocol = true;
6149 : // Drupal does the following
6150 : // TBD can the SERVER_PROTOCOL really be wrong?
6151 0 : if(f_env.count(get_name(name_t::SNAP_NAME_CORE_SERVER_PROTOCOL)) != 1)
6152 : {
6153 : // if undefined, set a default protocol
6154 0 : const_cast<snap_child *>(this)->f_env[get_name(name_t::SNAP_NAME_CORE_SERVER_PROTOCOL)] = "HTTP/1.0";
6155 : }
6156 : else
6157 : {
6158 : // note that HTTP/0.9 could be somewhat supported but that's
6159 : // most certainly totally useless
6160 0 : if("HTTP/1.0" != f_env.value(get_name(name_t::SNAP_NAME_CORE_SERVER_PROTOCOL))
6161 0 : && "HTTP/1.1" != f_env.value(get_name(name_t::SNAP_NAME_CORE_SERVER_PROTOCOL)))
6162 : {
6163 : // environment is no good!?
6164 0 : const_cast<snap_child *>(this)->f_env[get_name(name_t::SNAP_NAME_CORE_SERVER_PROTOCOL)] = "HTTP/1.0";
6165 : }
6166 : }
6167 : }
6168 0 : return f_env.value(get_name(name_t::SNAP_NAME_CORE_SERVER_PROTOCOL), "HTTP/1.0");
6169 : }
6170 :
6171 0 : return f_env.value(name, "");
6172 : }
6173 :
6174 :
6175 : /** \brief Check whether a POST variable was defined.
6176 : *
6177 : * Check whether the named POST variable was defined. This can be useful if
6178 : * you have some optional fields in a form. Also in some places where the
6179 : * code does not know about all the widgets.
6180 : *
6181 : * Note that the functions that directly access the post environment should
6182 : * not be used by most as the form plugin already does what is necessary.
6183 : *
6184 : * \param[in] name The name of the POST variable to fetch.
6185 : *
6186 : * \return true if the value is defined, false otherwise.
6187 : */
6188 0 : bool snap_child::postenv_exists(QString const & name) const
6189 : {
6190 0 : return f_post.contains(name);
6191 : }
6192 :
6193 :
6194 : /** \brief Retrieve a POST variable.
6195 : *
6196 : * Return the content of one of the POST variables. Post variables are defined
6197 : * only if the method used to access the site was a POST.
6198 : *
6199 : * \warning
6200 : * This function returns the RAW data from a POST. You should instead use the
6201 : * data returned by your form which will have been validated and fixed up as
6202 : * required (decoded, etc.)
6203 : *
6204 : * \param[in] name The name of the POST variable to fetch.
6205 : * \param[in] default_value The value to return if the POST variable is not defined.
6206 : *
6207 : * \return The value of that POST variable or the \p default_value.
6208 : */
6209 0 : QString snap_child::postenv(QString const & name, QString const & default_value) const
6210 : {
6211 0 : return f_post.value(name, default_value);
6212 : }
6213 :
6214 :
6215 : /** \brief Replace the value of a POST variable.
6216 : *
6217 : * Once in a while you may want to change a POST variable. One reason might
6218 : * be to apply a filter on a field before it gets saved in the database. This
6219 : * is accessible by any plugin so any one of those can do some work as
6220 : * required.
6221 : *
6222 : * \param[in] name The name of the POST variable to fetch.
6223 : * \param[in] value The new value for this POST variable.
6224 : */
6225 0 : void snap_child::replace_postenv(QString const & name, QString const & value)
6226 : {
6227 0 : f_post[name] = value;
6228 0 : }
6229 :
6230 :
6231 : /** \brief Check whether a file from the POST request is defined.
6232 : *
6233 : * This function is expected to be called to verify that a file was
6234 : * indeed uploaded for the named widget.
6235 : *
6236 : * If this function returns false, then the postfile() function should not
6237 : * be called.
6238 : *
6239 : * \param[in] name The id (name) of the Input File widget.
6240 : *
6241 : * \sa postfile()
6242 : */
6243 0 : bool snap_child::postfile_exists(QString const & name) const
6244 : {
6245 : // the QMap only returns a reference if this is not constant
6246 : //
6247 0 : return f_files.contains(name) && f_files[name].get_size() != 0;
6248 : }
6249 :
6250 :
6251 : /** \brief Retrieve a file from the POST.
6252 : *
6253 : * This function can be called if this request included a POST with a
6254 : * file attached.
6255 : *
6256 : * Note that the files are saved by widget identifier. This means if
6257 : * you check a post with postenv("file") (which returns the filename),
6258 : * then you can get the actual file with postfile("file").
6259 : *
6260 : * \note
6261 : * If the file was not sent by the browser, then this function creates
6262 : * an entry in the global table which is probably not what you want.
6263 : * Make sure to call the postfile_exists() function first.
6264 : *
6265 : * \param[in] name The id (name) of the Input File widget.
6266 : *
6267 : * \sa postfile_exists()
6268 : */
6269 0 : snap_child::post_file_t const & snap_child::postfile(QString const & name) const
6270 : {
6271 : // the QMap only returns a reference if this is not constant
6272 0 : return const_cast<snap_child *>(this)->f_files[name];
6273 : }
6274 :
6275 :
6276 : /** \brief Check whether a cookie was sent to us by the browser.
6277 : *
6278 : * This function checks whether a cookie was defined and returned by the
6279 : * browser. This is different from testing whether the value returned by
6280 : * cookie() is an empty string.
6281 : *
6282 : * \note
6283 : * Doing a set_cookie() does not interfere with this list of cookies
6284 : * which represent the list of cookies the browser sent to us.
6285 : *
6286 : * \param[in] name The name of the cookie to retrieve.
6287 : *
6288 : * \return true if the cookie was sent to us, false otherwise.
6289 : */
6290 0 : bool snap_child::cookie_is_defined(QString const & name) const
6291 : {
6292 0 : return f_browser_cookies.contains(name);
6293 : }
6294 :
6295 :
6296 : /** \brief Return the contents of a cookie.
6297 : *
6298 : * This function returns the contents of the named cookie.
6299 : *
6300 : * Note that this function is not the counterpart of the set_cookie()
6301 : * function. The set_cookie() accepts an http_cookie object, whereas
6302 : * this function only returns a string (because that's all we get
6303 : * from the browser.)
6304 : *
6305 : * \param[in] name The name of the cookie to retrieve.
6306 : *
6307 : * \return The content of the cookie, an empty string if the cookie is not defined.
6308 : */
6309 0 : QString snap_child::cookie(QString const & name) const
6310 : {
6311 0 : if(f_browser_cookies.contains(name))
6312 : {
6313 0 : return f_browser_cookies[name];
6314 : }
6315 0 : return QString();
6316 : }
6317 :
6318 :
6319 : /** \brief Make sure to clean up then exit the child process.
6320 : *
6321 : * This function cleans up the child and then calls the
6322 : * server::exit() function to give the server a chance to
6323 : * also clean up. Then it exists by calling the exit(3)
6324 : * function of the C library.
6325 : *
6326 : * \param[in] code The exit code, generally 0 or 1.
6327 : */
6328 0 : void snap_child::exit(int code)
6329 : {
6330 : // if we have a messenger, make sure to get rid of it
6331 : //
6332 0 : stop_messenger();
6333 :
6334 : // make sure the socket data is pushed to the caller
6335 : //
6336 0 : f_client.reset();
6337 :
6338 : // after we close the socket the answer is sent to the client so
6339 : // we can take a little time to gather some statistics.
6340 : //
6341 0 : server::pointer_t server( f_server.lock() );
6342 0 : if(server)
6343 : {
6344 0 : server->udp_rusage("snap_child");
6345 : }
6346 :
6347 0 : server::exit(code);
6348 : NOTREACHED();
6349 : }
6350 :
6351 :
6352 : /** \brief Check whether the server was started in debug mode.
6353 : *
6354 : * With this function any plugin can determine whether the server was
6355 : * started with the --debug command line option and act accordingly
6356 : * (i.e. show a certain number of debug in stdout or stderr).
6357 : *
6358 : * It should not be used to display debug data in the HTML output.
6359 : * Other parameters can be used for that purpose (specifically, that
6360 : * very plugin can make use of its own debug parameter for that because
6361 : * having the debug turned on for all modules would be a killer.)
6362 : *
6363 : * \return true if the --debug (-d) command line option was used to start
6364 : * the server
6365 : */
6366 0 : bool snap_child::is_debug() const
6367 : {
6368 0 : server::pointer_t server(get_server());
6369 0 : return server->is_debug();
6370 : }
6371 :
6372 :
6373 : /** \brief This function tells you whether the child is ready or not.
6374 : *
6375 : * The child prepares the database using a lot of complicated add_content()
6376 : * calls in the content plugin. Once all those calls were made, the system
6377 : * finally tells the world that it is ready and then it triggers the
6378 : * execute() signal.
6379 : *
6380 : * The flag is false up until just before the execute() function of
6381 : * the server gets called. It is a good way to know whether you are
6382 : * still in the initialization process or you are already working
6383 : * from within the path plugin (which is the one plugin that captures
6384 : * the execute() signal).
6385 : *
6386 : * \return true if the snap_child object triggered the execute() signal.
6387 : */
6388 0 : bool snap_child::is_ready() const
6389 : {
6390 0 : return f_ready;
6391 : }
6392 :
6393 :
6394 : /** \brief Retrieve the library (server) version.
6395 : *
6396 : * Retrieve the version of the snapwebsites library.
6397 : *
6398 : * \return A pointer to the version of the running server library.
6399 : */
6400 0 : char const * snap_child::get_running_server_version()
6401 : {
6402 0 : return server::version();
6403 : }
6404 :
6405 :
6406 : /** \brief Check whether the plugin is considered a Core Plugin.
6407 : *
6408 : * Plugins can be forced defined by the administrator in the
6409 : * snapserver.conf under the name "plugins". In that case,
6410 : * only those specific plugins are loaded. This is quite practicle
6411 : * if a plugin is generating problems and you cannot otherwise
6412 : * check out your website.
6413 : *
6414 : * The list of plugins can be soft defined in the default_plugins
6415 : * variable. This variable is used by new websites until the
6416 : * user adds and removes plugins to his website by editing the
6417 : * list of plugins via the Plugin Selector ("/admin/plugin").
6418 : *
6419 : * However, in all cases, the system will not work if you do not
6420 : * have a certain number of low level plugins running such as the
6421 : * content and users plugins. These are considered Core Plugins.
6422 : * This function returns true whenever the specified \p name
6423 : * represents a Core Plugin.
6424 : *
6425 : * \param[in] name The name of the plugin to check.
6426 : *
6427 : * \return true if the plugin is a Core Plugin.
6428 : */
6429 0 : bool snap_child::is_core_plugin(QString const & name) const
6430 : {
6431 : // a special case because "server" is not listed in the g_minimum_plugins
6432 : // list (because it is already loaded since it is an object in the library)
6433 : //
6434 0 : if(name == "server")
6435 : {
6436 0 : return true;
6437 : }
6438 :
6439 : // TODO: make sure the table is sorted alphabetically and use
6440 : // a binary search
6441 : //
6442 0 : for(size_t i(0); i < sizeof(g_minimum_plugins) / sizeof(g_minimum_plugins[0]); ++i)
6443 : {
6444 0 : if(name == g_minimum_plugins[i])
6445 : {
6446 0 : return true;
6447 : }
6448 : }
6449 :
6450 0 : return false;
6451 : }
6452 :
6453 :
6454 : /** \brief Retrieve a server parameter.
6455 : *
6456 : * This function calls the get_parameter() function of the server. This
6457 : * gives you access to all the parameters defined in the server
6458 : * configuration file.
6459 : *
6460 : * This gives you access to parameters such as the qs_action and
6461 : * the default_plugins list.
6462 : *
6463 : * \param[in] name The name of the parameter to retrieve.
6464 : */
6465 0 : QString snap_child::get_server_parameter(QString const & name)
6466 : {
6467 : #ifdef DEBUG
6468 0 : if(name.isEmpty())
6469 : {
6470 0 : throw snap_logic_exception("get_server_parameter() called with an empty string as the name of the parameter to be retrieved");
6471 : }
6472 : #endif
6473 0 : server::pointer_t server(get_server());
6474 0 : return server->get_parameter(name);
6475 : }
6476 :
6477 :
6478 : /** \brief Retrieve the path to the snapwebsites data folder.
6479 : *
6480 : * This function retrieve the path to the data used by various processes
6481 : * to save data accross runs.
6482 : *
6483 : * For example, we save our counter.u64 used to generate unique numbers
6484 : * on any one computer.
6485 : *
6486 : * By default the path is `"/var/lib/snapwebsites"`.
6487 : *
6488 : * \note
6489 : * The function never returns an empty path.
6490 : *
6491 : * \return The path to the data directory.
6492 : */
6493 0 : QString snap_child::get_data_path()
6494 : {
6495 : // get the data_path variable from the configuration file
6496 : //
6497 0 : server::pointer_t server(get_server());
6498 0 : QString path(server->get_parameter(get_name(name_t::SNAP_NAME_CORE_DATA_PATH)));
6499 :
6500 : // if not defined by end user, return the default value
6501 : //
6502 0 : return path.isEmpty()
6503 : ? QString("/var/lib/snapwebsites")
6504 0 : : path;
6505 : }
6506 :
6507 :
6508 : /** \brief Retrieve the path to the list data.
6509 : *
6510 : * This function retrieve the path to the data used by the list environment.
6511 : * The list plugin and backends make use of this path to handle the
6512 : * journal and database that they manage.
6513 : *
6514 : * By default the path is `"/var/lib/snapwebsites/list"`.
6515 : *
6516 : * \return The path to the list data directory.
6517 : */
6518 0 : QString snap_child::get_list_data_path()
6519 : {
6520 : // try the most specific path first
6521 : //
6522 0 : QString path(get_server_parameter(get_name(name_t::SNAP_NAME_CORE_LIST_DATA_PATH)));
6523 :
6524 0 : if(path.isEmpty())
6525 : {
6526 : // if the most specific is not defined, then maybe the basic
6527 : // data_path is, we need to add "list" at the end, though
6528 : //
6529 0 : path = get_data_path();
6530 :
6531 : // get_data_path() never returns an empty path so no need to test
6532 : //
6533 0 : path += "/list";
6534 : }
6535 :
6536 : // if not defined by end user, return the default value
6537 : //
6538 0 : return path;
6539 : }
6540 :
6541 :
6542 : /** \brief Change the status of the user.
6543 : *
6544 : * This message is used to send a copy of the user status whenever it
6545 : * changes.
6546 : *
6547 : * \param[in] status The new user status.
6548 : * \param[in] id The user identifier.
6549 : */
6550 0 : void snap_child::user_status(user_status_t status, user_identifier_t id)
6551 : {
6552 0 : server::pointer_t server(get_server());
6553 0 : return server->user_status(status, id);
6554 : }
6555 :
6556 :
6557 : /** \brief Improve signature to place at the bottom of a page.
6558 : *
6559 : * This signal can be used to generate a signature to place at the bottom
6560 : * of a page. In most cases, this very simple signature function is only
6561 : * used on error pages, although plugins that generate rather generic
6562 : * content are welcome to use it too.
6563 : *
6564 : * \param[in] path The path to the page that is being generated.
6565 : * \param[in] doc The document holding the data.
6566 : * \param[in,out] signature_tag The signature tag which is to be improved.
6567 : */
6568 0 : void snap_child::improve_signature(QString const & path, QDomDocument doc, QDomElement signature_tag)
6569 : {
6570 0 : server::pointer_t server(get_server());
6571 0 : return server->improve_signature(path, doc, signature_tag);
6572 : }
6573 :
6574 :
6575 : /** \brief Retreive a website wide parameter.
6576 : *
6577 : * This function reads a column from the sites table using the site key as
6578 : * defined by the canonicalization process. The function cannot be called
6579 : * before the canonicalization process ends.
6580 : *
6581 : * The table is opened once and remains opened so calling this function
6582 : * many times is not a problem. Also the libQtCassandra library caches
6583 : * all the data. Reading the same field multiple times is not a concern
6584 : * at all.
6585 : *
6586 : * If the value is undefined, the result is a null value.
6587 : *
6588 : * \param[in] name The name of the parameter to retrieve.
6589 : *
6590 : * \return The content of the row as a Cassandra value.
6591 : */
6592 0 : libdbproxy::value snap_child::get_site_parameter(QString const & name)
6593 : {
6594 : // retrieve site table if not there yet
6595 0 : if(!f_sites_table)
6596 : {
6597 0 : QString const table_name(get_name(name_t::SNAP_NAME_SITES));
6598 0 : libdbproxy::table::pointer_t table(f_context->findTable(table_name));
6599 0 : if(!table)
6600 : {
6601 : // the whole table is still empty
6602 0 : libdbproxy::value value;
6603 0 : return value;
6604 : }
6605 0 : f_sites_table = table;
6606 : }
6607 :
6608 0 : if(!f_sites_table->exists(f_site_key))
6609 : {
6610 : // an empty value is considered to be a null value
6611 0 : libdbproxy::value value;
6612 0 : return value;
6613 : }
6614 0 : libdbproxy::row::pointer_t row(f_sites_table->getRow(f_site_key));
6615 0 : if(!row->exists(name))
6616 : {
6617 : // an empty value is considered to be a null value
6618 0 : libdbproxy::value value;
6619 0 : return value;
6620 : }
6621 :
6622 0 : return row->getCell(name)->getValue();
6623 : }
6624 :
6625 :
6626 : /** \brief Save a website wide parameter.
6627 : *
6628 : * This function writes a column to the sites table using the site key as
6629 : * defined by the canonicalization process. The function cannot be called
6630 : * before the canonicalization process ends.
6631 : *
6632 : * The table is opened once and remains opened so calling this function
6633 : * many times is not a problem.
6634 : *
6635 : * If the value was still undefined, then it is created.
6636 : *
6637 : * \param[in] name The name of the parameter to save.
6638 : * \param[in] value The new value for this parameter.
6639 : */
6640 0 : void snap_child::set_site_parameter(QString const & name, libdbproxy::value const & value)
6641 : {
6642 : // retrieve site table if not there yet
6643 : //
6644 0 : if(!f_sites_table)
6645 : {
6646 : // get a pointer to the "sites" table
6647 : //
6648 0 : f_sites_table = get_table(get_name(name_t::SNAP_NAME_SITES));
6649 : }
6650 :
6651 0 : f_sites_table->getRow(f_site_key)->getCell(name)->setValue(value);
6652 0 : }
6653 :
6654 :
6655 : /** \brief Retrieve a current copy of the output buffer.
6656 : *
6657 : * This function returns a copy of the current snap_child output buffer.
6658 : *
6659 : * The buffer cannot be changed using the returned buffer. Use the
6660 : * write() functions to appened to the buffer.
6661 : *
6662 : * \return A copy of the output buffer.
6663 : */
6664 0 : QByteArray snap_child::get_output() const
6665 : {
6666 0 : return f_output.buffer();
6667 : }
6668 :
6669 :
6670 : /** \brief Write the buffer to the output.
6671 : *
6672 : * This function writes the specified buffer (array of bytes) to
6673 : * the output of the snap child. When the execute function returns
6674 : * from running all the plugins, the data in the buffer is sent to
6675 : * Apache (through snap.cgi).
6676 : *
6677 : * This function is most often used when the process is replying with
6678 : * data other than text (i.e. images, PDF documents, etc.)
6679 : *
6680 : * \param[in] data The array of byte to append to the buffer.
6681 : */
6682 0 : void snap_child::output(QByteArray const & data)
6683 : {
6684 0 : f_output.write(data);
6685 0 : }
6686 :
6687 :
6688 : /** \brief Write the string to the output buffer.
6689 : *
6690 : * This function writes the specified string to the output buffer
6691 : * of the snap child. When the execute function returns from running
6692 : * all the plugins, the data in the buffer is sent to Apache.
6693 : *
6694 : * The data is always written in UTF-8.
6695 : *
6696 : * \param[in] data The string data to append to the buffer.
6697 : */
6698 0 : void snap_child::output(QString const & data)
6699 : {
6700 0 : f_output.write(data.toUtf8());
6701 0 : }
6702 :
6703 :
6704 : /** \brief Write the string to the output buffer.
6705 : *
6706 : * This function writes the specified string to the output buffer
6707 : * of the snap child. When the execute function returns from running
6708 : * all the plugins, the data in the buffer is sent to Apache.
6709 : *
6710 : * The data is viewed as UTF-8 characters and it is sent as is to the
6711 : * buffer.
6712 : *
6713 : * \param[in] data The string data to append to the buffer.
6714 : */
6715 0 : void snap_child::output(std::string const & data)
6716 : {
6717 0 : f_output.write(data.c_str(), data.length());
6718 0 : }
6719 :
6720 :
6721 : /** \brief Write the string to the output buffer.
6722 : *
6723 : * This function writes the specified string to the output buffer
6724 : * of the snap child. When the execute function returns from running
6725 : * all the plugins, the data in the buffer is sent to Apache.
6726 : *
6727 : * The data is viewed as UTF-8 characters and it is sent as is to the
6728 : * buffer.
6729 : *
6730 : * \param[in] data The string data to append to the buffer.
6731 : */
6732 0 : void snap_child::output(char const * data)
6733 : {
6734 0 : f_output.write(data);
6735 0 : }
6736 :
6737 :
6738 : /** \brief Write the string to the output buffer.
6739 : *
6740 : * This function writes the specified string to the output buffer
6741 : * of the snap child. When the execute function returns from running
6742 : * all the plugins, the data in the buffer is sent to Apache.
6743 : *
6744 : * The data is viewed as UTF-8 characters and it is sent as is to the
6745 : * buffer.
6746 : *
6747 : * \param[in] data The string data to append to the buffer.
6748 : */
6749 0 : void snap_child::output(wchar_t const * data)
6750 : {
6751 0 : f_output.write(QString::fromWCharArray(data).toUtf8());
6752 0 : }
6753 :
6754 :
6755 : /** \brief Check whether someone wrote any output yet.
6756 : *
6757 : * This function checks whether any output was written or not.
6758 : *
6759 : * \return true if the output buffer is still empty.
6760 : */
6761 0 : bool snap_child::empty_output() const
6762 : {
6763 0 : return f_output.buffer().size() == 0;
6764 : }
6765 :
6766 :
6767 : /** \brief Trace in case we are initializing the website.
6768 : *
6769 : * While initializing (when a request was sent using #INIT instead of
6770 : * #START) then we immediately send data back using the trace() functions.
6771 : *
6772 : * \param[in] data The data to send to the listener.
6773 : * Generally a printable string.
6774 : */
6775 0 : void snap_child::trace(QString const & data)
6776 : {
6777 0 : trace(std::string(data.toUtf8().data()));
6778 0 : }
6779 :
6780 :
6781 : /** \brief Trace in case we are initializing the website.
6782 : *
6783 : * While initializing (when a request was sent using #INIT instead of
6784 : * #START) then we immediately send data back using the trace() functions.
6785 : *
6786 : * \param[in] data The data to send to the listener.
6787 : * Generally a printable string.
6788 : */
6789 0 : void snap_child::trace(std::string const & data)
6790 : {
6791 0 : if(f_is_being_initialized)
6792 : {
6793 : // keep a copy in the server logs too
6794 : //
6795 0 : if(data.back() == '\n')
6796 : {
6797 0 : SNAP_LOG_INFO("trace() from installation: ")(data.substr(0, data.length() - 1));
6798 : }
6799 : else
6800 : {
6801 0 : SNAP_LOG_INFO("trace() from installation: ")(data);
6802 : }
6803 :
6804 0 : write(data.c_str(), data.size());
6805 : }
6806 0 : }
6807 :
6808 :
6809 : /** \brief Trace in case we are initializing the website.
6810 : *
6811 : * While initializing (when a request was sent using #INIT instead of
6812 : * #START) then we immediately send data back using the trace() functions.
6813 : *
6814 : * \param[in] data The data to send to the listener.
6815 : * Generally a printable string.
6816 : */
6817 0 : void snap_child::trace(char const * data)
6818 : {
6819 0 : trace(std::string(data));
6820 0 : }
6821 :
6822 :
6823 : /** \brief Generate an HTTP error and exit the child process.
6824 : *
6825 : * This function kills the child process after sending an HTTP
6826 : * error message to the user and to the logger.
6827 : *
6828 : * The \p err_name parameter is optional in that it can be set to
6829 : * the empty string ("") and let the die() function make use of
6830 : * the default error message for the specified \p err_code.
6831 : *
6832 : * The error description message can include HTML tags to change
6833 : * the basic format of the text (i.e. bold, italic, underline, and
6834 : * other inline tags.) The message is printed inside a paragraph
6835 : * tag (<p>) and thus it should not include block tags.
6836 : * The message is expected to be UTF-8 encoded, although in general
6837 : * it should be in English so only using ASCII.
6838 : *
6839 : * The \p err_details parameter is the message to write to the
6840 : * log. It should be as detailed as possible so it makes it
6841 : * easy to know what's wrong and eventually needs attention.
6842 : *
6843 : * \note
6844 : * You can trick the description paragraph by adding a closing
6845 : * paragraph tag (</p>) at the start and an opening paragraph
6846 : * tag (<p>) at the end of your description.
6847 : *
6848 : * \warning
6849 : * This function does NOT return. It calls exit(1) once done.
6850 : *
6851 : * \param[in] err_code The error code such as 501 or 503.
6852 : * \param[in] err_name The name of the error such as "Service Not Available".
6853 : * \param[in] err_description HTML message about the problem.
6854 : * \param[in] err_details Server side text message with details that are logged only.
6855 : */
6856 0 : void snap_child::die(http_code_t err_code, QString err_name, QString const & err_description, QString const & err_details)
6857 : {
6858 0 : if(f_died)
6859 : {
6860 : // avoid loops
6861 : //
6862 0 : return;
6863 : }
6864 0 : f_died = true;
6865 :
6866 : try
6867 : {
6868 : // define a default error name if undefined
6869 : //
6870 0 : define_http_name(err_code, err_name);
6871 :
6872 : // log the error
6873 : //
6874 0 : SNAP_LOG_FATAL("snap child process: ")(err_details)(" (")(static_cast<int>(err_code))(" ")(err_name)(": ")(err_description)(")");
6875 :
6876 0 : if(f_is_being_initialized)
6877 : {
6878 : // send initialization process the info about the error
6879 : //
6880 0 : trace(QString("Error: die() called: %1 (%2 %3: %4)\n").arg(err_details).arg(static_cast<int>(err_code)).arg(err_name).arg(err_description));
6881 0 : trace("#END\n");
6882 : }
6883 : else
6884 : {
6885 : // On error we do not return the HTTP protocol, only the Status field
6886 : // it just needs to be first to make sure it works right
6887 : //
6888 : // IMPORTANT NOTE: we WANT the header to be set when we call
6889 : // the attach_to_session() function so someone
6890 : // can at least peruse it
6891 : //
6892 0 : set_header(get_name(name_t::SNAP_NAME_CORE_STATUS_HEADER),
6893 0 : QString("%1 %2").arg(static_cast<int>(err_code)).arg(err_name),
6894 : HEADER_MODE_ERROR);
6895 :
6896 : // Make sure that the session is re-attached
6897 : //
6898 0 : if(f_cassandra)
6899 : {
6900 0 : attach_to_session();
6901 : }
6902 :
6903 : // content type is HTML, we reset this header because it could have
6904 : // been changed to something else and prevent the error from showing
6905 : // up in the browser
6906 : //
6907 0 : set_header(get_name(name_t::SNAP_NAME_CORE_CONTENT_TYPE_HEADER),
6908 : "text/html; charset=utf8",
6909 : HEADER_MODE_EVERYWHERE);
6910 :
6911 : // TODO: the HTML could also come from a user defined
6912 : // page so that way it can get a translated
6913 : // message without us having to do anything
6914 : // (but probably only for 403 and 404 pages?)
6915 :
6916 0 : QString const html(error_body(err_code, err_name, err_description));
6917 :
6918 : // In case someone changed the cache controls, make sure the
6919 : // cache is turned off and errors are public
6920 : //
6921 0 : f_server_cache_control.set_no_cache(true);
6922 0 : f_page_cache_control.set_no_cache(true);
6923 :
6924 : // in case there are any cookies, send them along too
6925 : //
6926 0 : output_result(HEADER_MODE_ERROR, html.toUtf8());
6927 : }
6928 : }
6929 0 : catch(std::exception const & e)
6930 : {
6931 : // ignore all errors because at this point we must die quickly.
6932 0 : SNAP_LOG_FATAL("snap_child.cpp:die(): try/catch caught an exception. What: ")(e.what());
6933 : }
6934 0 : catch(...)
6935 : {
6936 : // ignore all errors because at this point we must die quickly.
6937 0 : SNAP_LOG_FATAL("snap_child.cpp:die(): try/catch caught an exception");
6938 : }
6939 :
6940 : // exit with an error
6941 0 : exit(1);
6942 : }
6943 :
6944 :
6945 : /** \brief Create the body of an HTTP error.
6946 : *
6947 : * This function generates an error page for an HTTP error. Thus far,
6948 : * it is used by the die() function and an equivalent in the attachment
6949 : * plugin.
6950 : *
6951 : * \param[in] err_code The error code such as 501 or 503.
6952 : * \param[in] err_name The name of the error such as "Service Not Available".
6953 : * \param[in] err_description HTML message about the problem.
6954 : */
6955 0 : QString snap_child::error_body(http_code_t err_code, QString const & err_name, QString const & err_description)
6956 : {
6957 0 : QString const title(QString("%1 %2").arg(static_cast<int>(err_code)).arg(err_name));
6958 :
6959 : // html
6960 0 : QDomDocument doc;
6961 0 : QDomElement html(doc.createElement("html"));
6962 0 : doc.appendChild(html);
6963 :
6964 : // html/head
6965 0 : QDomElement head(doc.createElement("head"));
6966 0 : html.appendChild(head);
6967 :
6968 : // html/head/meta[@http-equiv=...][@content=...]
6969 0 : QDomElement meta_locale(doc.createElement("meta"));
6970 0 : head.appendChild(meta_locale);
6971 0 : meta_locale.setAttribute("http-equiv", get_name(name_t::SNAP_NAME_CORE_CONTENT_TYPE_HEADER));
6972 0 : meta_locale.setAttribute("content", "text/html; charset=utf-8");
6973 :
6974 : // html/head/meta[@name=...][@content=...]
6975 0 : QDomElement meta_robots(doc.createElement("meta"));
6976 0 : head.appendChild(meta_robots);
6977 0 : meta_robots.setAttribute("name", "ROBOTS");
6978 0 : meta_robots.setAttribute("content", "NOINDEX");
6979 :
6980 : // html/head/title/...
6981 0 : QDomElement title_tag(doc.createElement("title"));
6982 0 : head.appendChild(title_tag);
6983 0 : snap_dom::append_plain_text_to_node(title_tag, title);
6984 :
6985 : // html/head/style/...
6986 0 : QDomElement style_tag(doc.createElement("style"));
6987 0 : head.appendChild(style_tag);
6988 0 : snap_dom::append_plain_text_to_node(style_tag, "body{font-family:sans-serif}");
6989 :
6990 : // html/body
6991 0 : QDomElement body_tag(doc.createElement("body"));
6992 0 : html.appendChild(body_tag);
6993 :
6994 : // html/body/h1/...
6995 0 : QDomElement h1_tag(doc.createElement("h1"));
6996 0 : h1_tag.setAttribute("class", "error");
6997 0 : body_tag.appendChild(h1_tag);
6998 0 : snap_dom::append_plain_text_to_node(h1_tag, title);
6999 :
7000 : // html/body/p[@class=description]/...
7001 0 : QDomElement description_tag(doc.createElement("p"));
7002 0 : description_tag.setAttribute("class", "description");
7003 0 : body_tag.appendChild(description_tag);
7004 : // the description may include HTML tags
7005 0 : snap_dom::insert_html_string_to_xml_doc(description_tag, err_description);
7006 :
7007 : // html/body/p[@class=signature]/...
7008 0 : QDomElement signature_tag(doc.createElement("p"));
7009 0 : signature_tag.setAttribute("class", "signature");
7010 0 : body_tag.appendChild(signature_tag);
7011 :
7012 : // now generate the signature tag anchors
7013 0 : QString const site_key(get_site_key());
7014 0 : if(f_cassandra)
7015 : {
7016 0 : libdbproxy::value const site_name(get_site_parameter(get_name(name_t::SNAP_NAME_CORE_SITE_NAME)));
7017 0 : QDomElement a_tag(doc.createElement("a"));
7018 0 : a_tag.setAttribute("class", "home");
7019 0 : a_tag.setAttribute("target", "_top");
7020 0 : a_tag.setAttribute("href", site_key);
7021 0 : signature_tag.appendChild(a_tag);
7022 0 : snap_dom::append_plain_text_to_node(a_tag, site_name.stringValue());
7023 :
7024 0 : improve_signature(f_uri.path(), doc, signature_tag);
7025 : }
7026 0 : else if(!site_key.isEmpty())
7027 : {
7028 0 : QDomElement a_tag(doc.createElement("a"));
7029 0 : a_tag.setAttribute("class", "home");
7030 0 : a_tag.setAttribute("target", "_top");
7031 0 : a_tag.setAttribute("href", site_key);
7032 0 : signature_tag.appendChild(a_tag);
7033 0 : snap_dom::append_plain_text_to_node(a_tag, site_key);
7034 :
7035 0 : improve_signature(f_uri.path(), doc, signature_tag);
7036 : }
7037 : // else -- no signature...
7038 :
7039 0 : return doc.toString(-1);
7040 : }
7041 :
7042 :
7043 : /** \brief Ensure that the http_name variable is not empty.
7044 : *
7045 : * This function sets the content of the \p http_name variable if empty. It
7046 : * uses the \p http_code value to define a default message in \p http_name.
7047 : *
7048 : * If the \p http_name string is not empty then it is not modified.
7049 : *
7050 : * \param[in] http_code The code used to determine the http_name value.
7051 : * \param[in,out] http_name The http request name to set if not already defined.
7052 : */
7053 0 : void snap_child::define_http_name(http_code_t http_code, QString & http_name)
7054 : {
7055 0 : if(http_name.isEmpty())
7056 : {
7057 0 : switch(http_code)
7058 : {
7059 : // 1xx
7060 0 : case http_code_t::HTTP_CODE_CONTINUE: http_name = "Continue"; break;
7061 0 : case http_code_t::HTTP_CODE_SWITCHING_PROTOCOLS: http_name = "Switching Protocols"; break;
7062 0 : case http_code_t::HTTP_CODE_PROCESSING: http_name = "Processing"; break;
7063 :
7064 : // 2xx
7065 0 : case http_code_t::HTTP_CODE_OK: http_name = "OK"; break;
7066 0 : case http_code_t::HTTP_CODE_CREATED: http_name = "Created"; break;
7067 0 : case http_code_t::HTTP_CODE_ACCEPTED: http_name = "Accepted"; break;
7068 0 : case http_code_t::HTTP_CODE_NON_AUTHORITATIVE_INFORMATION: http_name = "Non-Authoritative Information"; break;
7069 0 : case http_code_t::HTTP_CODE_NO_CONTENT: http_name = "No Content"; break;
7070 0 : case http_code_t::HTTP_CODE_RESET_CONTENT: http_name = "Reset Content"; break;
7071 0 : case http_code_t::HTTP_CODE_PARTIAL_CONTENT: http_name = "Partial Content"; break;
7072 0 : case http_code_t::HTTP_CODE_MULTI_STATUS: http_name = "Multi-Status"; break;
7073 0 : case http_code_t::HTTP_CODE_ALREADY_REPORTED: http_name = "Already Reported"; break;
7074 0 : case http_code_t::HTTP_CODE_IM_USED: http_name = "Instance-Manipulation Used"; break;
7075 :
7076 : // 3xx
7077 0 : case http_code_t::HTTP_CODE_MULTIPLE_CHOICE: http_name = "Multiple Choice"; break;
7078 0 : case http_code_t::HTTP_CODE_MOVED_PERMANENTLY: http_name = "Moved Permanently"; break;
7079 0 : case http_code_t::HTTP_CODE_FOUND: http_name = "Found"; break;
7080 0 : case http_code_t::HTTP_CODE_SEE_OTHER: http_name = "See Other"; break; // POST becomes GET
7081 0 : case http_code_t::HTTP_CODE_NOT_MODIFIED: http_name = "Not Modified"; break;
7082 0 : case http_code_t::HTTP_CODE_USE_PROXY: http_name = "Use Proxy"; break;
7083 0 : case http_code_t::HTTP_CODE_SWITCH_PROXY: http_name = "Switch Proxy"; break;
7084 0 : case http_code_t::HTTP_CODE_TEMPORARY_REDIRECT: http_name = "Temporary Redirect"; break; // keep same method
7085 0 : case http_code_t::HTTP_CODE_PERMANENT_REDIRECT: http_name = "Permanent Redirect"; break; // keep same method
7086 :
7087 : // 4xx
7088 0 : case http_code_t::HTTP_CODE_BAD_REQUEST: http_name = "Bad Request"; break;
7089 0 : case http_code_t::HTTP_CODE_UNAUTHORIZED: http_name = "Unauthorized"; break;
7090 0 : case http_code_t::HTTP_CODE_PAYMENT_REQUIRED: http_name = "Payment Required"; break;
7091 0 : case http_code_t::HTTP_CODE_FORBIDDEN: http_name = "Forbidden"; break;
7092 0 : case http_code_t::HTTP_CODE_NOT_FOUND: http_name = "Not Found"; break;
7093 0 : case http_code_t::HTTP_CODE_METHOD_NOT_ALLOWED: http_name = "Method Not Allowed"; break;
7094 0 : case http_code_t::HTTP_CODE_NOT_ACCEPTABLE: http_name = "Not Acceptable"; break;
7095 0 : case http_code_t::HTTP_CODE_PROXY_AUTHENTICATION_REQUIRED: http_name = "Proxy Authentication Required"; break;
7096 0 : case http_code_t::HTTP_CODE_REQUEST_TIMEOUT: http_name = "Request Timeout"; break;
7097 0 : case http_code_t::HTTP_CODE_CONFLICT: http_name = "Conflict"; break;
7098 0 : case http_code_t::HTTP_CODE_GONE: http_name = "Gone"; break;
7099 0 : case http_code_t::HTTP_CODE_LENGTH_REQUIRED: http_name = "Length Required"; break;
7100 0 : case http_code_t::HTTP_CODE_PRECONDITION_FAILED: http_name = "Precondition Failed"; break;
7101 0 : case http_code_t::HTTP_CODE_REQUEST_ENTITY_TOO_LARGE: http_name = "Request Entity Too Large"; break;
7102 0 : case http_code_t::HTTP_CODE_REQUEST_URI_TOO_LONG: http_name = "Request-URI Too Long"; break;
7103 0 : case http_code_t::HTTP_CODE_UNSUPPORTED_MEDIA_TYPE: http_name = "Unsupported Media Type"; break;
7104 0 : case http_code_t::HTTP_CODE_REQUESTED_RANGE_NOT_SATISFIABLE: http_name = "Requested Range Not Satisfiable"; break;
7105 0 : case http_code_t::HTTP_CODE_EXPECTATION_FAILED: http_name = "Expectation Failed"; break;
7106 0 : case http_code_t::HTTP_CODE_I_AM_A_TEAPOT: http_name = "I'm a teapot"; break;
7107 0 : case http_code_t::HTTP_CODE_ENHANCE_YOUR_CALM: http_name = "Enhance Your Calm"; break;
7108 : //case http_code_t::HTTP_CODE_METHOD_FAILURE: http_name = "Method Failure"; break;
7109 0 : case http_code_t::HTTP_CODE_UNPROCESSABLE_ENTITY: http_name = "Unprocessable Entity"; break;
7110 0 : case http_code_t::HTTP_CODE_LOCKED: http_name = "Locked"; break;
7111 0 : case http_code_t::HTTP_CODE_FAILED_DEPENDENCY: http_name = "Failed Dependency"; break;
7112 0 : case http_code_t::HTTP_CODE_UNORDERED_COLLECTION: http_name = "Unordered Collection"; break;
7113 0 : case http_code_t::HTTP_CODE_UPGRADE_REQUIRED: http_name = "Upgrade Required"; break;
7114 0 : case http_code_t::HTTP_CODE_PRECONDITION_REQUIRED: http_name = "Precondition Required"; break;
7115 0 : case http_code_t::HTTP_CODE_TOO_MANY_REQUESTS: http_name = "Too Many Requests"; break;
7116 0 : case http_code_t::HTTP_CODE_REQUEST_HEADER_FIELDS_TOO_LARGE: http_name = "Request Header Fields Too Large"; break;
7117 0 : case http_code_t::HTTP_CODE_NO_RESPONSE: http_name = "No Response"; break;
7118 0 : case http_code_t::HTTP_CODE_RETRY_WITH: http_name = "Retry With"; break;
7119 0 : case http_code_t::HTTP_CODE_BLOCKED_BY_WINDOWS_PARENTAL_CONTROLS: http_name = "Blocked by Windows Parental Controls"; break;
7120 0 : case http_code_t::HTTP_CODE_UNAVAILABLE_FOR_LEGAL_REASONS: http_name = "Unavailable For Legal Reasons"; break;
7121 : //case http_code_t::HTTP_CODE_REDIRECT: http_name = "Redirect"; break;
7122 0 : case http_code_t::HTTP_CODE_REQUEST_HEADER_TOO_LARGE: http_name = "Request Header Too Large"; break;
7123 0 : case http_code_t::HTTP_CODE_CERT_ERROR: http_name = "Cert Error"; break;
7124 0 : case http_code_t::HTTP_CODE_NO_CERT: http_name = "No Cert"; break;
7125 0 : case http_code_t::HTTP_CODE_HTTP_TO_HTTPS: http_name = "HTTP to HTTPS"; break;
7126 0 : case http_code_t::HTTP_CODE_TOKEN_EXPIRED: http_name = "Token Expired"; break;
7127 0 : case http_code_t::HTTP_CODE_CLIENT_CLOSED_REQUEST: http_name = "Client Closed Request"; break;
7128 : //case http_code_t::HTTP_CODE_TOKEN_REQUIRED: http_name = "Token Required"; break;
7129 :
7130 : // 5xx
7131 0 : case http_code_t::HTTP_CODE_INTERNAL_SERVER_ERROR: http_name = "Internal Server Error"; break;
7132 0 : case http_code_t::HTTP_CODE_NOT_IMPLEMENTED: http_name = "Not Implemented"; break;
7133 0 : case http_code_t::HTTP_CODE_BAD_GATEWAY: http_name = "Bad Gateway"; break;
7134 0 : case http_code_t::HTTP_CODE_SERVICE_UNAVAILABLE: http_name = "Service Unavailable"; break;
7135 0 : case http_code_t::HTTP_CODE_GATEWAY_TIMEOUT: http_name = "Gateway Timeout"; break;
7136 0 : case http_code_t::HTTP_CODE_HTTP_VERSION_NOT_SUPPORTED: http_name = "HTTP Version Not Supported"; break;
7137 0 : case http_code_t::HTTP_CODE_VARIANTS_ALSO_NEGOTIATES: http_name = "Variants Also Negotiates"; break;
7138 0 : case http_code_t::HTTP_CODE_INSUFFICIANT_STORAGE: http_name = "Insufficiant Storage"; break;
7139 0 : case http_code_t::HTTP_CODE_LOOP_DETECTED: http_name = "Loop Detected"; break;
7140 0 : case http_code_t::HTTP_CODE_BANDWIDTH_LIMIT_EXCEEDED: http_name = "Bandwidth Limit Exceeded"; break;
7141 0 : case http_code_t::HTTP_CODE_NOT_EXTENDED: http_name = "Not Extended"; break;
7142 0 : case http_code_t::HTTP_CODE_NETWORK_AUTHENTICATION_REQUIRED: http_name = "Network Authentication Required"; break;
7143 0 : case http_code_t::HTTP_CODE_ACCESS_DENIED: http_name = "Access Denied"; break;
7144 0 : case http_code_t::HTTP_CODE_NETWORK_READ_TIMEOUT_ERROR: http_name = "Network read timeout error"; break;
7145 0 : case http_code_t::HTTP_CODE_NETWORK_CONNECT_TIMEOUT_ERROR: http_name = "Network connect timeout error"; break;
7146 :
7147 0 : default: // plugins can always use other codes
7148 0 : http_name = "Unknown HTTP Code";
7149 0 : break;
7150 :
7151 : }
7152 : }
7153 0 : }
7154 :
7155 : /** \brief Set an HTTP header.
7156 : *
7157 : * This function sets the specified HTTP header to the specified value.
7158 : * This function overwrites the existing value if any. To append to the
7159 : * existing value, use the append_header() function instead. Note that
7160 : * append only works with fields that supports lists (comma separated
7161 : * values, etc.)
7162 : *
7163 : * The value is trimmed of LWS (SP, HT, CR, LF) characters on both ends.
7164 : * Also, if the value includes CR or LF characters, it must be followed
7165 : * by at least one SP or HT. Note that all CR are transformed to LF and
7166 : * double LFs are replaced by one LF.
7167 : *
7168 : * The definition of an HTTP header is message-header as found
7169 : * in the snippet below:
7170 : *
7171 : * \code
7172 : * OCTET = <any 8-bit sequence of data>
7173 : * CHAR = <any US-ASCII character (octets 0 - 127)>
7174 : * CTL = <any US-ASCII control character
7175 : * (octets 0 - 31) and DEL (127)>
7176 : * CR = <US-ASCII CR, carriage return (13)>
7177 : * LF = <US-ASCII LF, linefeed (10)>
7178 : * SP = <US-ASCII SP, space (32)>
7179 : * HT = <US-ASCII HT, horizontal-tab (9)>
7180 : * CRLF = CR LF
7181 : * LWS = [CRLF] 1*( SP | HT )
7182 : * TEXT = <any OCTET except CTLs,
7183 : * but including LWS>
7184 : * token = 1*<any CHAR except CTLs or separators>
7185 : * separators = "(" | ")" | "<" | ">" | "@"
7186 : * | "," | ";" | ":" | "\" | <">
7187 : * | "/" | "[" | "]" | "?" | "="
7188 : * | "{" | "}" | SP | HT
7189 : * message-header = field-name ":" [ field-value ]
7190 : * field-name = token
7191 : * field-value = *( field-content | LWS )
7192 : * field-content = <the OCTETs making up the field-value
7193 : * and consisting of either *TEXT or combinations
7194 : * of token, separators, and quoted-string>
7195 : * \endcode
7196 : *
7197 : * To remove a header, set the value to the empty string.
7198 : *
7199 : * References: http://www.w3.org/Protocols/rfc2616/rfc2616-sec2.html
7200 : * and http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html
7201 : *
7202 : * When adding a header, it is expected to only be used when no error
7203 : * occurs (HEADER_MODE_NO_ERROR). However, in some circumstances it
7204 : * is useful to send additional headers with errors or redirects.
7205 : * These headers can use a different mode so they appear in those other
7206 : * locations.
7207 : *
7208 : * \note
7209 : * The key of the f_header map is the name in lowercase. For this
7210 : * reason we save the field name as defined by the user in the
7211 : * value as expected in the final result (i.e. "Blah: " + value.)
7212 : *
7213 : * \todo
7214 : * Added a separate function so we can add multiple HTTP Link entries.
7215 : *
7216 : * \param[in] name The name of the header.
7217 : * \param[in] value The value to assign to that header. Set to an empty
7218 : * string to remove the header.
7219 : * \param[in] modes Where the header will be used.
7220 : */
7221 0 : void snap_child::set_header(QString const & name, QString const & value, header_mode_t modes)
7222 : {
7223 : {
7224 : // name cannot include controls or separators and only CHARs
7225 0 : std::vector<wchar_t> ws;
7226 0 : ws.resize(name.length());
7227 0 : name.toWCharArray(&ws[0]);
7228 0 : int p(name.length());
7229 0 : while(p > 0)
7230 : {
7231 0 : --p;
7232 0 : wchar_t wc(ws[p]);
7233 0 : bool valid(true);
7234 0 : if(wc < 0x21 || wc > 0x7E)
7235 : {
7236 0 : valid = false;
7237 : }
7238 0 : else switch(wc)
7239 : {
7240 0 : case L'(': case L')': case L'<': case L'>': case L'@':
7241 : case L',': case L';': case L':': case L'\\': case L'"':
7242 : case L'/': case L'[': case L']': case L'?': case L'=':
7243 : case L'{': case L'}': // SP & HT are checked in previous if()
7244 0 : valid = false;
7245 0 : break;
7246 :
7247 0 : default:
7248 : //valid = true; -- default to true
7249 0 : break;
7250 :
7251 : }
7252 0 : if(!valid)
7253 : {
7254 : // more or less ASCII except well defined separators
7255 0 : throw snap_child_exception_invalid_header_field_name(QString("header field name \"%1\" is not valid, found unwanted character: '%2'").arg(name).arg(QChar(wc)));
7256 : }
7257 : }
7258 : }
7259 :
7260 0 : QString v;
7261 : {
7262 : // value cannot include controls except LWS (\r, \n and \t)
7263 0 : std::vector<wchar_t> ws;
7264 0 : ws.resize(value.length());
7265 0 : value.toWCharArray(&ws[0]);
7266 0 : int const max_length(value.length());
7267 0 : wchar_t lc(L'\0');
7268 0 : for(int p(0); p < max_length; ++p)
7269 : {
7270 0 : wchar_t wc(ws[p]);
7271 0 : if((wc < 0x20 || wc == 127) && wc != L'\r' && wc != L'\n' && wc != L'\t')
7272 : {
7273 : // refuse controls except \r, \n, \t
7274 0 : throw snap_child_exception_invalid_header_value("header field value \"" + value + "\" is not valid, found unwanted character: '" + QChar(wc) + "'");
7275 : }
7276 : // we MUST have a space or tab after a newline
7277 0 : if(wc == L'\r' || wc == L'\n')
7278 : {
7279 : // if p + 1 == max_length then the user supplied the ending "\r\n"
7280 0 : if(p + 1 < max_length)
7281 : {
7282 0 : if(ws[p] != L' ' && ws[p] != L'\t' && ws[p] != L'\r' && ws[p] != L'\n')
7283 : {
7284 : // missing space or tab after a "\r\n" sequence
7285 : // (we also accept \r or \n although empty lines are
7286 : // forbidden but we'll remove them anyway)
7287 0 : throw snap_child_exception_invalid_header_value("header field value \"" + value + "\" is not valid, found a \\r pr \\n not followed by a space");
7288 : }
7289 : }
7290 : }
7291 0 : if(v.isEmpty() && (wc == L' ' || wc == L'\t' || wc == L'\r' || wc == L'\n'))
7292 : {
7293 : // trim on the left (that's easy and fast to do here)
7294 0 : continue;
7295 : }
7296 0 : if(wc == L'\r')
7297 : {
7298 0 : wc = L'\n';
7299 : }
7300 0 : if(lc == L'\n' && wc == L'\n')
7301 : {
7302 : // do not double '\n' (happens when user sends us "\r\n")
7303 0 : continue;
7304 : }
7305 0 : v += QChar(wc);
7306 0 : lc = wc;
7307 : }
7308 : // remove ending spaces which would otherwise cause problems in
7309 : // the HTTP header
7310 0 : while(!v.isEmpty())
7311 : {
7312 0 : QChar c(v.right(1)[0]);
7313 : // we skip the '\r' because those were removed anyway
7314 0 : if(c != ' ' && c != '\t' /*&& c != '\r'*/ && c != '\n')
7315 : {
7316 0 : break;
7317 : }
7318 0 : v.remove(v.length() - 1, 1);
7319 : }
7320 : }
7321 :
7322 0 : if(v.isEmpty())
7323 : {
7324 0 : f_header.remove(name.toLower());
7325 : }
7326 : else
7327 : {
7328 : // Note that even the Status needs to be a field
7329 : // because we are using Apache and they expect such
7330 0 : http_header_t header;
7331 0 : header.f_header = name + ": " + v;
7332 0 : header.f_modes = modes;
7333 0 : f_header[name.toLower()] = header;
7334 : }
7335 0 : }
7336 :
7337 :
7338 : /** \brief Check whether a header is defined.
7339 : *
7340 : * This function searches for the specified name in the list of
7341 : * headers and returns true if it finds it.
7342 : *
7343 : * \warning
7344 : * Cookies are headers, but these are managed using the cookie manager
7345 : * which offers functions such as set_cookie(), cookie_is_defined(),
7346 : * and cookie().
7347 : *
7348 : * \warning
7349 : * Links are headers, but these are managed using the link manager
7350 : * which offers functions such as set_link(), link_is_defined(),
7351 : * and link().
7352 : *
7353 : * \param[in] name Name of the header to check for.
7354 : *
7355 : * \return false if the header was not defined yet, true otherwise.
7356 : */
7357 0 : bool snap_child::has_header(QString const & name) const
7358 : {
7359 0 : return f_header.find(name.toLower()) != f_header.end();
7360 : }
7361 :
7362 :
7363 : /** \brief Retrieve the current value of the given header.
7364 : *
7365 : * This function returns the value of the specified header, if
7366 : * it exists. You may want to first call has_header() to know
7367 : * whether the header exists. It is not an error to get a header
7368 : * that was not yet defined, you get an empty string as a result.
7369 : *
7370 : * \note
7371 : * We only return the value of the header even though the
7372 : * header field name is included in the f_header value,
7373 : * we simply skip that information.
7374 : *
7375 : * \param[in] name The name of the header to query.
7376 : *
7377 : * \return The value of this header, "" if undefined.
7378 : */
7379 0 : QString snap_child::get_header(QString const & name) const
7380 : {
7381 0 : header_map_t::const_iterator it(f_header.find(name.toLower()));
7382 0 : if(it == f_header.end())
7383 : {
7384 : // it is not defined
7385 0 : return "";
7386 : }
7387 :
7388 : // return the value without the field
7389 0 : return it->f_header.mid(name.length() + 2);
7390 : }
7391 :
7392 :
7393 : /** \brief Output the HTTP headers.
7394 : *
7395 : * This function prints the HTTP headers to the output.
7396 : *
7397 : * The headers are defined with a mode (a set of flags really) which
7398 : * can be used to tell the server when such and such header is to
7399 : * be output.
7400 : *
7401 : * Note that the Cookies headers are never printed by this function.
7402 : *
7403 : * \note
7404 : * Headers are NOT encoded in UTF-8, we output them as Latin1, this is
7405 : * VERY important; headers are checked at the time you do the set_header
7406 : * to ensure that only Latin1 characters are used.
7407 : *
7408 : * \todo
7409 : * Any header that a path other than the default (see the die() and
7410 : * page_redirect() functions) uses should not be printed by this
7411 : * function. At this point there is no real protection against that
7412 : * yet it should be protected. An idea is for us to change all those
7413 : * functions to use the set_header() first, then call this function
7414 : * because that way the set_header() will have overwritten whatever
7415 : * other plugins would have defined there.
7416 : *
7417 : * \param[in] modes Print the headers for these modes.
7418 : */
7419 0 : void snap_child::output_headers(header_mode_t modes)
7420 : {
7421 : // The Cache-Control information are not output until here because
7422 : // we have a couple of settings (page and server) and it is just
7423 : // way to complicated to recompute the correct caches each time
7424 : //
7425 0 : set_cache_control();
7426 :
7427 : // Output the status first (we may want to order the HTTP header
7428 : // fields by type and output them ordered by type as defined in
7429 : // the HTTP reference chapter 4.2)
7430 : //
7431 0 : if(has_header(get_name(name_t::SNAP_NAME_CORE_STATUS_HEADER))
7432 0 : && (f_header["status"].f_modes & modes) != 0)
7433 : {
7434 : // If status is defined, it should not be 200
7435 : //
7436 0 : write((f_header["status"].f_header + "\n").toLatin1().data());
7437 : }
7438 :
7439 : // Now output all the other headers except the cookies
7440 : //
7441 0 : for(header_map_t::const_iterator it(f_header.begin());
7442 0 : it != f_header.end();
7443 : ++it)
7444 : {
7445 0 : if((it.value().f_modes & modes) != 0 && it.key() != "status")
7446 : {
7447 0 : write((it.value().f_header + "\n").toLatin1().data());
7448 : }
7449 : }
7450 :
7451 : // Output the links
7452 : //
7453 0 : output_http_links(modes);
7454 :
7455 : // Finally output the cookies
7456 : //
7457 0 : output_cookies();
7458 :
7459 : // Done with the headers
7460 0 : write("\n");
7461 0 : }
7462 :
7463 :
7464 : /** \brief Add the HTTP link to this snap_child.
7465 : *
7466 : * This function saves the specified link to the snap_child object.
7467 : *
7468 : * If the link was already defined, then it gets overwritten by the
7469 : * new link. (i.e. the "rel" parameter is used to distinguish between
7470 : * each link and only one can exist with a given name.)
7471 : *
7472 : * \param[in] link The link to be added to this snap_child.
7473 : */
7474 0 : void snap_child::add_http_link(http_link const & link)
7475 : {
7476 0 : f_http_links[link.get_name()] = link;
7477 0 : }
7478 :
7479 :
7480 : /** \brief Check whether a link is defined.
7481 : *
7482 : * This function searches the list of links defined in snap_child, if
7483 : * defined, the function returns true.
7484 : *
7485 : * \param[in] name The name of the link (i.e. the "rel" parameter.)
7486 : *
7487 : * \return true if the snap_child is defined.
7488 : */
7489 0 : bool snap_child::http_link_is_defined(std::string const & name)
7490 : {
7491 0 : return f_http_links.find(name) != f_http_links.end();
7492 : }
7493 :
7494 :
7495 : /** \brief Get an HTTP link.
7496 : *
7497 : * This function returns a constant reference to the named HTTP link.
7498 : *
7499 : * If the link does not exist, then the function throws. To avoid the
7500 : * throw, use the http_link_is_defined() first and if false avoid
7501 : * calling this function.
7502 : *
7503 : * \param[in] name The name of the link to retrieve a reference to.
7504 : *
7505 : * \return A reference to the named link.
7506 : */
7507 0 : http_link const & snap_child::get_http_link(std::string const & name)
7508 : {
7509 0 : auto const & l(f_http_links.find(name));
7510 0 : if(l == f_http_links.end())
7511 : {
7512 0 : throw snap_child_exception_invalid_header_field_name(QString("link \"%1\" is not defined, you could check first using http_link_is_defined()").arg(QString::fromUtf8(name.c_str())));
7513 : }
7514 0 : return l->second;
7515 : }
7516 :
7517 :
7518 : /** \brief Transform the HTTP links to a header.
7519 : *
7520 : * This function generates the "Link: ..." header from the various
7521 : * links plugins added to snap_child.
7522 : *
7523 : * The data is directly send to the output with the write() function.
7524 : *
7525 : * \param[in] modes Print the headers for these modes.
7526 : */
7527 0 : void snap_child::output_http_links(header_mode_t modes)
7528 : {
7529 : // completely empty? avoid generating strings
7530 : //
7531 0 : if(f_http_links.empty())
7532 : {
7533 0 : return;
7534 : }
7535 :
7536 : // geneteate the "Link: ..." string
7537 : //
7538 0 : std::string result(get_name(name_t::SNAP_NAME_CORE_HTTP_LINK_HEADER));
7539 0 : result += ": ";
7540 :
7541 0 : char const * sep = "";
7542 0 : for(auto const & l : f_http_links)
7543 : {
7544 0 : if(modes == HEADER_MODE_EVERYWHERE // allow anything
7545 0 : || (modes & ~HEADER_MODE_NO_ERROR) == 0 // normal circumstances
7546 0 : || (l.second.get_redirect() && (modes & HEADER_MODE_REDIRECT) != 0)) // allowed in redirects
7547 : {
7548 0 : result += sep;
7549 0 : result += l.second.to_http_header();
7550 :
7551 0 : sep = ", ";
7552 : }
7553 : }
7554 :
7555 : // got any links?
7556 : // if not, then don't emit the header
7557 : //
7558 0 : if(*sep != '\0')
7559 : {
7560 0 : result += "\n";
7561 0 : write(result.c_str());
7562 : }
7563 : }
7564 :
7565 :
7566 : /** \brief Add a cookie.
7567 : *
7568 : * This function adds a cookie to send to the user.
7569 : *
7570 : * Contrary to most other headers, there may be more than one cookie
7571 : * in a reply and the set_header() does not support such. Plus cookies
7572 : * have a few other parameters so this function is used to save those
7573 : * in a separate vector of cookies.
7574 : *
7575 : * The input cookie information is copied in the vector of cookies so
7576 : * you can modify it.
7577 : *
7578 : * The same cookie can be redefined multiple times. Calling the function
7579 : * again overwrites a previous call with the same "name" parameter.
7580 : *
7581 : * \param[in] cookie_info The cookie value, expiration, etc.
7582 : *
7583 : * \sa cookie()
7584 : * \sa cookie_is_defined()
7585 : * \sa output_cookies()
7586 : */
7587 0 : void snap_child::set_cookie(http_cookie const & cookie_info)
7588 : {
7589 0 : f_cookies[cookie_info.get_name()] = cookie_info;
7590 :
7591 : // TODO: the privacy-policy URI should be locked if we want this to
7592 : // continue to work forever (or have a way to retrieve and
7593 : // cache that value; the snapserver.conf is probably not a
7594 : // good idea because you could have thousands of websites
7595 : // all with a different path)
7596 0 : f_cookies[cookie_info.get_name()].set_comment_url(f_site_key_with_slash + "terms-and-conditions/privacy-policy");
7597 0 : }
7598 :
7599 :
7600 : /** \brief Ask for the cookies to not be output.
7601 : *
7602 : * When implementing an interface such as REST or SOAP you do not want
7603 : * to output the cookies. It would not be safe to send cookies to the
7604 : * browser. This function should be called by any such implementation
7605 : * to make sure that the cookies do not go back out.
7606 : */
7607 0 : void snap_child::set_ignore_cookies()
7608 : {
7609 0 : f_ignore_cookies = true;
7610 0 : }
7611 :
7612 :
7613 : /** \brief Output the cookies in your header.
7614 : *
7615 : * Since we generate HTTP headers in different places but still want to
7616 : * always generate the cookies if possible (if they are available) we
7617 : * have this function to add the cookies.
7618 : *
7619 : * This function directly outputs the cookies to the socket of the snap.cgi
7620 : * tool.
7621 : *
7622 : * \sa cookie()
7623 : * \sa set_cookie()
7624 : * \sa cookie_is_defined()
7625 : */
7626 0 : void snap_child::output_cookies()
7627 : {
7628 0 : if(f_ignore_cookies)
7629 : {
7630 0 : return;
7631 : }
7632 :
7633 0 : if(!f_cookies.isEmpty())
7634 : {
7635 0 : for(cookie_map_t::const_iterator it(f_cookies.begin());
7636 0 : it != f_cookies.end();
7637 : ++it)
7638 : {
7639 : // the to_http_header() ensures only ASCII characters
7640 : // are used so we can use toLatin1() below
7641 : //
7642 0 : QString cookie_header(it.value().to_http_header() + "\n");
7643 : //SNAP_LOG_DEBUG("snap child output cookie = [")(cookie_header.toLatin1().data())("]?");
7644 :
7645 0 : write(cookie_header.toLatin1().data());
7646 : }
7647 : }
7648 : }
7649 :
7650 :
7651 : /** \brief Generate a unique number.
7652 : *
7653 : * This function uses a counter in a text file to generate a unique number.
7654 : * The file is a 64 bit long number (binary) which gets locked to ensure
7655 : * that the number coming out is unique.
7656 : *
7657 : * The resulting number is composed of the server name a dash and the
7658 : * unique number generated from the unique number file.
7659 : *
7660 : * At this point it is not expected that we'd ever run out of unique
7661 : * numbers. 2^64 per server is a really large number. However, you do
7662 : * want to limit calls as much as possible (if you can reuse the same
7663 : * number or check all possibilities that could cause an error before
7664 : * getting the unique numbers so as to avoid wasting too many of them.)
7665 : *
7666 : * The server name is expected to be a unique name defined in the settings.
7667 : * (the .conf file for the server.)
7668 : *
7669 : * \todo
7670 : * All the servers in a given realm should all be given a unique name and
7671 : * information about the other servers (i.e. at least the address of one
7672 : * other server) so that way all the servers can communicate and make sure
7673 : * that their name is indeed unique.
7674 : *
7675 : * \return A string with \<server name\>-\<unique number\>
7676 : */
7677 0 : QString snap_child::get_unique_number()
7678 : {
7679 0 : server::pointer_t server(get_server());
7680 0 : QString const data_path(get_data_path());
7681 :
7682 0 : quint64 c(0);
7683 : {
7684 0 : const QString name(data_path + "/counter.u64");
7685 0 : QLockFile counter(name);
7686 0 : if(!counter.open(QIODevice::ReadWrite))
7687 : {
7688 0 : throw snap_child_exception_unique_number_error("count not open counter file \"" + name + "\"");
7689 : }
7690 : // the very first time the size is zero (empty)
7691 0 : if(counter.size() != 0)
7692 : {
7693 0 : if(counter.read(reinterpret_cast<char *>(&c), sizeof(c)) != sizeof(c))
7694 : {
7695 0 : throw snap_child_exception_unique_number_error("count not read the counter file \"" + name + "\"");
7696 : }
7697 : }
7698 0 : ++c;
7699 0 : counter.reset();
7700 0 : if(counter.write(reinterpret_cast<char *>(&c), sizeof(c)) != sizeof(c))
7701 : {
7702 0 : throw snap_child_exception_unique_number_error("count not write to the counter file \"" + name + "\"");
7703 : }
7704 : // close the file now; we do not want to hold the file for too long
7705 : }
7706 0 : return QString("%1-%2").arg(server->get_server_name().c_str()).arg(c);
7707 : }
7708 :
7709 : /** \brief Initialize the plugins.
7710 : *
7711 : * Each site may make use of a different set of plugins. This function
7712 : * gathers the list of available plugins and loads them as expected.
7713 : *
7714 : * The bare minimum is hard coded here in order to ensure that minimum
7715 : * functionality of a website. At this time, this list is:
7716 : *
7717 : * \li path
7718 : * \li filter
7719 : * \li robotstxt
7720 : *
7721 : * \param[in] add_defaults Whether the default Snap! plugins are to be
7722 : * added to the list of plugins (important for
7723 : * the Snap! server itself, not so much for other
7724 : * servers using plugins.)
7725 : */
7726 0 : snap_string_list snap_child::init_plugins(bool const add_defaults, QString const & introducer)
7727 : {
7728 0 : server::pointer_t server(get_server());
7729 :
7730 : // load the plugins for this website
7731 : //
7732 0 : bool need_cleanup(true);
7733 0 : QString site_plugins(server->get_parameter(get_name(name_t::SNAP_NAME_CORE_PARAM_PLUGINS))); // forced by .conf?
7734 0 : if(site_plugins.isEmpty())
7735 : {
7736 : // maybe user defined his list of plugins in his website
7737 : //
7738 0 : libdbproxy::value plugins(get_site_parameter(get_name(name_t::SNAP_NAME_CORE_PLUGINS)));
7739 0 : site_plugins = plugins.stringValue();
7740 0 : if(site_plugins.isEmpty())
7741 : {
7742 : // if the list of plugins is empty in the site parameters
7743 : // then get the default from the server configuration
7744 : //
7745 0 : site_plugins = server->get_parameter(get_name(name_t::SNAP_NAME_CORE_PARAM_DEFAULT_PLUGINS));
7746 : }
7747 : else
7748 : {
7749 : // we assume that the list of plugins in the database is already
7750 : // cleaned up and thus avoid an extra loop (see below)
7751 : //
7752 0 : need_cleanup = false;
7753 : }
7754 : }
7755 0 : snap_string_list list_of_plugins(site_plugins.split(',', QString::SkipEmptyParts));
7756 :
7757 : // clean up the list
7758 : //
7759 0 : if(need_cleanup)
7760 : {
7761 0 : if(introducer.isEmpty())
7762 : {
7763 0 : for(int i(0); i < list_of_plugins.length(); ++i)
7764 : {
7765 0 : list_of_plugins[i] = list_of_plugins[i].trimmed();
7766 0 : if(list_of_plugins.at(i).isEmpty())
7767 : {
7768 : // remove parts that the trimmed() rendered empty
7769 : //
7770 0 : list_of_plugins.removeAt(i);
7771 0 : --i;
7772 : }
7773 : }
7774 : }
7775 : else
7776 : {
7777 0 : for(int i(0); i < list_of_plugins.length(); ++i)
7778 : {
7779 0 : list_of_plugins[i] = list_of_plugins[i].trimmed();
7780 0 : if(list_of_plugins.at(i).isEmpty())
7781 : {
7782 : // remove parts that the trimmed() rendered empty
7783 : //
7784 0 : list_of_plugins.removeAt(i);
7785 0 : --i;
7786 : }
7787 : else
7788 : {
7789 0 : list_of_plugins[i] = introducer + "_" + list_of_plugins[i];
7790 : }
7791 : }
7792 : }
7793 : }
7794 :
7795 : // ensure a certain minimum number of plugins
7796 : //
7797 0 : if(add_defaults)
7798 : {
7799 0 : for(size_t i(0); i < sizeof(g_minimum_plugins) / sizeof(g_minimum_plugins[0]); ++i)
7800 : {
7801 0 : if(!list_of_plugins.contains(g_minimum_plugins[i]))
7802 : {
7803 0 : list_of_plugins << g_minimum_plugins[i];
7804 : }
7805 : }
7806 : }
7807 :
7808 : // load the plugins
7809 : //
7810 0 : QString const plugins_path( server->get_parameter("plugins_path") );
7811 0 : if( plugins_path.isEmpty() )
7812 : {
7813 : // Sanity check
7814 : //
7815 0 : die( http_code_t::HTTP_CODE_SERVICE_UNAVAILABLE
7816 : , "Plugin path not configured"
7817 : , "Server cannot find any plugins because the path is not properly configured."
7818 : , "An error occured loading the server plugins (plugins_path parameter in snapserver.conf is undefined)."
7819 : );
7820 0 : NOTREACHED();
7821 : }
7822 :
7823 0 : if(!snap::plugins::load(plugins_path, this, std::static_pointer_cast<snap::plugins::plugin>(server), list_of_plugins, introducer))
7824 : {
7825 0 : die( http_code_t::HTTP_CODE_SERVICE_UNAVAILABLE
7826 : , "Plugin Unavailable"
7827 : , "Server encountered problems with its plugins."
7828 : , "An error occurred while loading the server plugins. See other errors from the plugin implementation for details."
7829 : );
7830 0 : NOTREACHED();
7831 : }
7832 : // at this point each plugin was allocated through their factory
7833 : // but they are not really usable yet because we did not initialize them
7834 :
7835 : // now boot the plugin system (send signals)
7836 : //
7837 0 : server->init();
7838 :
7839 0 : return list_of_plugins;
7840 : }
7841 :
7842 :
7843 : /** \brief Run all the updates as required.
7844 : *
7845 : * This function checks when the updates were last run. If never, then it
7846 : * runs the update immediately. Otherwise, it waits at least 10 minutes
7847 : * between running again to avoid overloading the server. We may increase
7848 : * (i.e. wait more than 10 minutes) that amount of time as we get a better
7849 : * feel of the necessity.
7850 : *
7851 : * The update is done by going through all the modules and checking their
7852 : * modification date and time. If newer than what was registered for
7853 : * them so far, then we call their do_update() function. When it never
7854 : * ran, the modification date and time is always seen as \em newer and
7855 : * thus all updates are run.
7856 : *
7857 : * \todo
7858 : * We may want to look into a way to "install" a plugin which would have
7859 : * the side effect of setting a flag requesting an update instead of
7860 : * relying on the plugin .so file modification date and other such
7861 : * tricks. A clear signal sent via a command line tool or directly
7862 : * from a website could be a lot more effective.
7863 : *
7864 : * \todo
7865 : * The current implementation makes use of a "plugin threshold" which
7866 : * is wrong because when you update plugin A with a threshold P, then
7867 : * later update plugin B with a threshold Q and Q < P, plugin B does
7868 : * not get updated (we'd have to test to prove this, but from what I
7869 : * can see in the algorithm that specific case is not assume to happen
7870 : * because we're expected to upgrade all the plugins at the same time
7871 : * which unfortunately is never the case if you keep the server running
7872 : * while upgrading your installation--which is not really recommended,
7873 : * but I'm sure we'll be doing that all the time...) At this point I
7874 : * removed the test so whether Q = P, Q < P or Q > P has no more
7875 : * effect on the update process.
7876 : *
7877 : * \param[in] list_of_plugins The list of plugin names that were loaded
7878 : * for this run.
7879 : */
7880 0 : void snap_child::update_plugins(snap_string_list const & list_of_plugins)
7881 : {
7882 0 : SNAP_LOG_INFO("update_plugins() called with \"")(list_of_plugins.join(", "))("\"");
7883 :
7884 : // system updates run at most once every 10 minutes
7885 0 : QString const core_last_updated(get_name(name_t::SNAP_NAME_CORE_LAST_UPDATED));
7886 0 : QString const core_last_dynamic_update(get_name(name_t::SNAP_NAME_CORE_LAST_DYNAMIC_UPDATE));
7887 0 : libdbproxy::value last_updated(get_site_parameter(core_last_updated));
7888 0 : if(last_updated.nullValue())
7889 : {
7890 : // use an "old" date (631152000)
7891 0 : last_updated.setInt64Value(SNAP_UNIX_TIMESTAMP(1990, 1, 1, 0, 0, 0) * 1000000LL);
7892 : }
7893 : //int64_t const last_update_timestamp(last_updated.int64Value());
7894 : // 10 min. elapsed since last update?
7895 : //if(is_debug() // force update in debug mode so we don't have to wait 10 min.!
7896 : //|| f_start_date - static_cast<int64_t>(last_update_timestamp) > static_cast<int64_t>(10 * 60 * 1000000))
7897 : {
7898 : // this can be called more than once in debug mode whenever multiple
7899 : // files are being loaded for a page being accessed (i.e. the main
7900 : // page and then the .js, .css, .jpg, etc.)
7901 : //
7902 : // if is_debug() returns false, it should be useless unless the
7903 : // process takes over 10 minutes
7904 0 : snap_lock lock(QString("%1#snap-child-updating").arg(get_site_key_with_slash()), 60 * 60); // lock for up to 1h
7905 :
7906 : // set the state to "updating" if it currently is "ready"
7907 0 : libdbproxy::value state(get_site_parameter(get_name(name_t::SNAP_NAME_CORE_SITE_STATE)));
7908 0 : if(state.nullValue())
7909 : {
7910 : // set the state to "initializing if it is undefined
7911 : //
7912 0 : state.setStringValue("initializing");
7913 0 : set_site_parameter(get_name(name_t::SNAP_NAME_CORE_SITE_STATE), state);
7914 : }
7915 0 : else if(state.stringValue() == "ready"
7916 0 : || state.stringValue() == "updating" // check this one in case a previous update failed
7917 0 : || state.stringValue() == "initializing") // check this one in case a previous initialization failed
7918 : {
7919 : // set the state to "initializing if it is undefined
7920 : //
7921 0 : state.setStringValue("updating");
7922 0 : set_site_parameter(get_name(name_t::SNAP_NAME_CORE_SITE_STATE), state);
7923 :
7924 : // in this case we want existing user requests to have a chance
7925 : // to end before we proceed with the update
7926 : //
7927 : // right now, this is a really ugly way of doing things we
7928 : // should instead be told once the last user left
7929 : //
7930 0 : sleep(10); // yeah... that's a hack which should work 99% of the time though
7931 : }
7932 : else
7933 : {
7934 : // unknown state... what to do? what to do?
7935 : //
7936 0 : SNAP_LOG_ERROR("Updating website failed as we do not understand its current state: \"")(state.stringValue())("\".");
7937 0 : return;
7938 : }
7939 :
7940 : // we do not allow a null value in the core::site_name field,
7941 : // so we set it on initialization, user can replace the name
7942 : // later from the UI
7943 : //
7944 0 : libdbproxy::value site_name(get_site_parameter(get_name(name_t::SNAP_NAME_CORE_SITE_NAME)));
7945 0 : if(site_name.nullValue())
7946 : {
7947 0 : site_name.setStringValue("Website Name");
7948 0 : set_site_parameter(get_name(name_t::SNAP_NAME_CORE_SITE_NAME), site_name);
7949 : }
7950 :
7951 : // save that last time we checked for an update
7952 : //
7953 0 : last_updated.setInt64Value(f_start_date);
7954 0 : QString const core_plugin_threshold(get_name(name_t::SNAP_NAME_CORE_PLUGIN_THRESHOLD));
7955 0 : set_site_parameter(core_last_updated, last_updated);
7956 0 : libdbproxy::value threshold(get_site_parameter(core_plugin_threshold));
7957 0 : if(threshold.nullValue())
7958 : {
7959 : // same old date...
7960 0 : threshold.setInt64Value(SNAP_UNIX_TIMESTAMP(1990, 1, 1, 0, 0, 0) * 1000000LL);
7961 : }
7962 0 : int64_t const plugin_threshold(threshold.int64Value());
7963 0 : int64_t new_plugin_threshold(plugin_threshold);
7964 :
7965 : // first run through the plugins to know whether one or more
7966 : // has changed since the last website update
7967 0 : for(snap_string_list::const_iterator it(list_of_plugins.begin());
7968 0 : it != list_of_plugins.end();
7969 : ++it)
7970 : {
7971 0 : QString const plugin_name(*it);
7972 0 : plugins::plugin * p(plugins::get_plugin(plugin_name));
7973 0 : if(p != nullptr)
7974 : {
7975 0 : SNAP_LOG_INFO("update_plugins() called with \"")(plugin_name)("\"");
7976 0 : trace(QString("Updating plugin \"%1\"\n").arg(plugin_name));
7977 :
7978 : // the plugin changed, we want to call do_update() on it!
7979 0 : if(p->last_modification() > new_plugin_threshold)
7980 : {
7981 0 : new_plugin_threshold = p->last_modification();
7982 : }
7983 : // run the updates as required
7984 : // we have a date/time for each plugin since each has
7985 : // its own list of date/time checks
7986 0 : QString const specific_param_name(QString("%1::%2").arg(core_last_updated).arg(plugin_name));
7987 0 : libdbproxy::value specific_last_updated(get_site_parameter(specific_param_name));
7988 0 : int64_t const old_last_updated(specific_last_updated.safeInt64Value());
7989 0 : if(specific_last_updated.nullValue())
7990 : {
7991 : // use an "old" date (631152000)
7992 0 : specific_last_updated.setInt64Value(SNAP_UNIX_TIMESTAMP(1990, 1, 1, 0, 0, 0) * 1000000LL);
7993 : }
7994 : try
7995 : {
7996 0 : specific_last_updated.setInt64Value(p->do_update(old_last_updated));
7997 :
7998 : // avoid the database access if the value did not change
7999 : //
8000 0 : if(specific_last_updated.int64Value() != old_last_updated)
8001 : {
8002 0 : set_site_parameter(specific_param_name, specific_last_updated);
8003 : }
8004 : }
8005 0 : catch(std::exception const & e)
8006 : {
8007 0 : SNAP_LOG_ERROR("Updating ")(plugin_name)(" failed with an exception: ")(e.what());
8008 : }
8009 : }
8010 : }
8011 :
8012 : // this finishes the content.xml updates
8013 0 : SNAP_LOG_INFO("update_plugins() finalize the content.xml (a.k.a. static) update.");
8014 0 : finish_update();
8015 :
8016 : // now allow plugins to have a more dynamic set of updates
8017 0 : for(snap_string_list::const_iterator it(list_of_plugins.begin());
8018 0 : it != list_of_plugins.end();
8019 : ++it)
8020 : {
8021 0 : QString const plugin_name(*it);
8022 0 : plugins::plugin * p(plugins::get_plugin(plugin_name));
8023 0 : if(p != nullptr)
8024 : {
8025 0 : trace(QString("Dynamically update plugin \"%1\"\n").arg(plugin_name));
8026 :
8027 : // run the updates as required
8028 : // we have a date/time for each plugin since each has
8029 : // its own list of date/time checks
8030 0 : QString const specific_param_name(QString("%1::%2").arg(core_last_dynamic_update).arg(plugin_name));
8031 0 : libdbproxy::value specific_last_updated(get_site_parameter(specific_param_name));
8032 0 : int64_t const old_last_updated(specific_last_updated.safeInt64Value());
8033 0 : if(specific_last_updated.nullValue())
8034 : {
8035 : // use an "old" date (631152000)
8036 0 : specific_last_updated.setInt64Value(SNAP_UNIX_TIMESTAMP(1990, 1, 1, 0, 0, 0) * 1000000LL);
8037 : }
8038 : // IMPORTANT: Note that we save the newest date found in the
8039 : // do_update() to make 100% sure we catch all the
8040 : // updates every time (using "now" would often mean
8041 : // missing many updates!)
8042 : try
8043 : {
8044 0 : specific_last_updated.setInt64Value(p->do_dynamic_update(old_last_updated));
8045 :
8046 : // avoid the database access if the value did not change
8047 : //
8048 0 : if(specific_last_updated.int64Value() != old_last_updated)
8049 : {
8050 0 : set_site_parameter(specific_param_name, specific_last_updated);
8051 : }
8052 : }
8053 0 : catch(std::exception const & e)
8054 : {
8055 0 : SNAP_LOG_ERROR("Dynamically updating ")(plugin_name)(" failed with an exception: ")(e.what());
8056 : }
8057 : }
8058 : }
8059 :
8060 : // save the new threshold if larger
8061 : //
8062 0 : if(new_plugin_threshold > plugin_threshold)
8063 : {
8064 0 : set_site_parameter(core_plugin_threshold, new_plugin_threshold);
8065 : }
8066 :
8067 : // always mark when the site was last updated
8068 : //
8069 : // get the date right now, DO NOT RESET START DATE (f_start_date)
8070 : //
8071 0 : set_site_parameter(get_name(name_t::SNAP_NAME_CORE_SITE_READY), get_current_date());
8072 :
8073 : // change the state while still locked
8074 : //
8075 0 : state.setStringValue("ready");
8076 0 : set_site_parameter(get_name(name_t::SNAP_NAME_CORE_SITE_STATE), state);
8077 : }
8078 : }
8079 :
8080 :
8081 : /** \brief After adding content, call this function to save it.
8082 : *
8083 : * When we add content with the add_xml() and add_xml_dom() functions,
8084 : * the snap_child object records the fact and is then capable of knowing
8085 : * that it needs to be saved.
8086 : *
8087 : * All the adds are expected to be called before the data gets saved
8088 : * which allows us to not have to know in which order the XML files need
8089 : * to be to add all of their contents at once. In other words, the save
8090 : * function will automatically be capable of figuring out the right order
8091 : * if it has all the data, hence this function getting called last.
8092 : *
8093 : * \note
8094 : * This was separated so the layouts can be added at any time since we
8095 : * do not activate all the layouts automatically in all of our users'
8096 : * websites.
8097 : */
8098 0 : void snap_child::finish_update()
8099 : {
8100 : // if content was prepared for the database, save it now
8101 0 : if(f_new_content)
8102 : {
8103 0 : f_new_content = false;
8104 0 : server::pointer_t server(get_server());
8105 0 : trace("Save content in database.\n");
8106 0 : server->save_content();
8107 0 : trace("Initialization content now saved in database.\n");
8108 : }
8109 0 : }
8110 :
8111 :
8112 : /** \brief Called whenever a plugin prepares some content to the database.
8113 : *
8114 : * This function is called by the content plugin whenever one of its
8115 : * add_...() function is called. This way the child knows that it has
8116 : * to request the content to save the resulting content.
8117 : *
8118 : * The flag is first checked after the updates are run and the save is
8119 : * called then. The check is done again at the end of the execute function
8120 : * just in case some dynamic data was added while we were running.
8121 : */
8122 0 : void snap_child::new_content()
8123 : {
8124 0 : f_new_content = true;
8125 0 : }
8126 :
8127 :
8128 : /** \brief Canonalize a path or URL for this plugin.
8129 : *
8130 : * This function is used to canonicalize the paths used to check
8131 : * URLs. This is used against the paths offered by other plugins
8132 : * and the paths arriving from the HTTP server. This way, we know
8133 : * that two paths will match 1 to 1.
8134 : *
8135 : * The canonicalization is done in place.
8136 : *
8137 : * Note that the canonicalization needs to occur before the
8138 : * regular expresions are checked. Also, internal paths that
8139 : * include regular expressions are not getting canonicalized
8140 : * since we may otherwise break the regular expression
8141 : * (i.e. unwillingly remove periods and slashes.)
8142 : * This can explain why one of your paths doesn't work right.
8143 : *
8144 : * The function is really fast if the path is already canonicalized.
8145 : *
8146 : * \note
8147 : * There is one drawback with "fixing" the URL from the user.
8148 : * Two paths that look different will return the same page.
8149 : * Instead we probably want to return an error (505 or 404
8150 : * or 302.) This may be an dynamic setting too.
8151 : *
8152 : * \note
8153 : * The remove() function on a QString is faster than most other
8154 : * options because it is directly applied to the string data.
8155 : *
8156 : * \param[in,out] path The path to canonicalize.
8157 : */
8158 0 : void snap_child::canonicalize_path(QString & path)
8159 : {
8160 : // we get the length on every loop because it could be reduced!
8161 0 : int i(0);
8162 0 : while(i < path.length())
8163 : {
8164 0 : switch(path[i].cell())
8165 : {
8166 0 : case '\\':
8167 0 : path[i] = '/';
8168 0 : break;
8169 :
8170 0 : case ' ':
8171 : case '+':
8172 : //case '_': -- this should probably be a flag?
8173 0 : path[i] = '-';
8174 0 : break;
8175 :
8176 0 : default:
8177 : // other characters are kept as is
8178 0 : break;
8179 :
8180 : }
8181 : // here we do not have to check for \ since we just replaced it with /
8182 0 : if(i == 0 && (path[0].cell() == '.' || path[0].cell() == '/' /*|| path[0].cell() == '\\'*/))
8183 : {
8184 0 : do
8185 : {
8186 0 : path.remove(0, 1);
8187 : }
8188 0 : while(!path.isEmpty() && (path[0].cell() == '.' || path[0].cell() == '/' || path[0].cell() == '\\'));
8189 : // however, in the while we do since later characters were not yet
8190 : // modified to just '/'
8191 : }
8192 0 : else if(path[i].cell() == '/' && i + 1 < path.length())
8193 : {
8194 0 : if(path[i + 1].cell() == '/')
8195 : {
8196 : // remove double '/' in filename
8197 0 : path.remove(i + 1, 1);
8198 : }
8199 0 : else if(path[i + 1].cell() == '.')
8200 : {
8201 : // Unix hidden files are forbidden (., .. and .<name>)
8202 : // (here we remove 1 period, on next loop we may remove others)
8203 0 : path.remove(i + 1, 1);
8204 : }
8205 : else
8206 : {
8207 0 : ++i;
8208 : }
8209 : }
8210 0 : else if((path[i].cell() == '.' || path[i].cell() == '-' || path[i].cell() == '/') && i + 1 >= path.length())
8211 : {
8212 : // Filename cannot end with a period, dash (space,) or slash
8213 0 : path.remove(i, 1);
8214 : }
8215 : else
8216 : {
8217 0 : ++i;
8218 : }
8219 : }
8220 0 : }
8221 :
8222 :
8223 : /** \brief We're ready to execute the page, do so.
8224 : *
8225 : * This time we're ready to execute the page the user is trying
8226 : * to access.
8227 : *
8228 : * The function first prepares the HTTP request which includes
8229 : * setting up default headers and the output buffer.
8230 : *
8231 : * Note that by default we expect text/html in the output. If a
8232 : * different type of data is being processed, you are responsible
8233 : * for changing the Content-type field.
8234 : *
8235 : * \todo
8236 : * Take the Origin header in account. If it is not the right
8237 : * origin, especially for log in, registration, and related
8238 : * parges, then we may want to generate an error.
8239 : */
8240 0 : void snap_child::execute()
8241 : {
8242 : // prepare the output buffer
8243 : // reserver 64Kb at once to avoid many tiny realloc()
8244 : //
8245 0 : f_output.buffer().reserve(64 * 1024);
8246 0 : f_output.open(QIODevice::ReadWrite);
8247 :
8248 : // TODO if the client says HTTP/1.0 and offers an Upgrade of 1.1, then
8249 : // we should force switch to 1.1 with a 101 response about here
8250 : // TBD Apache may already take care of such things
8251 : // TBD It may also be used to switch between HTTP and SHTTP
8252 : //
8253 : //if(snapenv("SERVER_PROTOCOL").toUpper() == "HTTP/1.0")
8254 : //{
8255 : // // if the Upgrade header was specified check whether it includes
8256 : // // HTTP/1.1 as one of the supported protocols
8257 : // // Note: we may want to make use of the http_string::WeightedHttpString
8258 : // // object to split that client parameter
8259 : // if(snapenv("?REQUEST_UPGRADE?").simplified().replace(QRegExp("[ \t]?,[ \t]?"), ",").toLower().split(',', QString::SkipEmptyParts).contains("HTTP/1.1"))
8260 : // {
8261 : // set_header("Status", "HTTP/1.1 101 Switching Protocols");
8262 : // }
8263 : //}
8264 :
8265 : // TODO: Check the cache request status from the client, if not defined
8266 : // or set to "max-age=0" or some other such value, then check whether
8267 : // the current page is cached and can safely be resent to the client
8268 : // (i.e. a public page without form...) if so send the cached version
8269 : // which will allow us to avoid all the processing.
8270 : //
8271 : // Note: the cached versions are saved really only if the page is
8272 : // public, mostly non-dynamic, and has no forms other than Search
8273 : // and similar...
8274 :
8275 : // prepare the default headers
8276 : // Status is set to HTTP/1.1 or 1.0 depending on the incoming protocol
8277 : // DO NOT PUT A STATUS OF 200 FOR FastCGI TAKES CARE OF IT
8278 : // Sending a status of 200 to Apache results in a status of 500 Internal Server Error
8279 : //
8280 : //set_header("Status", QString("%1 200 OK").arg(snapenv("SERVER_PROTOCOL")));
8281 :
8282 : // Normally Apache overwrites this information
8283 : //
8284 0 : set_header("Server", "Snap! C++", HEADER_MODE_EVERYWHERE);
8285 :
8286 : // The Date field is added by Apache automatically
8287 : // adding it here generates a 500 Internal Server Error
8288 : //
8289 : //QDateTime date(QDateTime().toUTC());
8290 : //date.setTime_t(f_start_date / 1000000); // micro-seconds
8291 : //set_header("Date", date.toString("ddd, dd MMM yyyy hh:mm:ss' GMT'"));
8292 :
8293 : // cache controls are now defined in f_server_cache_control
8294 : // and f_page_cache_control; the defaults are not exactly what
8295 : // we want, so we change them in the f_page_cache_control
8296 : //
8297 : //set_cache_control(0, false, true);
8298 0 : f_page_cache_control.set_no_store(true);
8299 0 : f_page_cache_control.set_must_revalidate(true);
8300 :
8301 : // We are snapwebsites
8302 : //
8303 0 : set_header(get_name(name_t::SNAP_NAME_CORE_X_POWERED_BY_HEADER), "snapwebsites", HEADER_MODE_EVERYWHERE);
8304 :
8305 : // By default we expect [X]HTML 5 in the output
8306 : //
8307 0 : set_header(get_name(name_t::SNAP_NAME_CORE_CONTENT_TYPE_HEADER), "text/html; charset=utf-8", HEADER_MODE_EVERYWHERE);
8308 :
8309 : // XXX I moved that up here from just before sending the output because
8310 : // it seems that all answers should use this flag (because even pages
8311 : // that represent errors may end up reusing the same connection.)
8312 : //
8313 0 : QString const connection(snapenv("HTTP_CONNECTION"));
8314 : //std::cout << "HTTP_CONNECTION=[" << connection << "]\n";
8315 0 : if(connection.toLower() == "keep-alive")
8316 : {
8317 0 : set_header("Connection", "keep-alive");
8318 : }
8319 : else
8320 : {
8321 0 : set_header("Connection", "close");
8322 : }
8323 :
8324 : // Let the caches know that the cookie changes all the time
8325 : // (the content is likely to change too, but it could still be cached)
8326 : // TBD -- I'm not entirely sure that this is smart; another default is
8327 : // to use "Vary: *" so all fields are considered as varying.
8328 : //set_header("Vary", "Cookie");
8329 :
8330 0 : if(f_uri.protocol() == "https")
8331 : {
8332 : // this is used by different load balancers as an indication that
8333 : // the request is secure
8334 : //
8335 0 : set_header("Front-End-Https", "on", HEADER_MODE_EVERYWHERE);
8336 : }
8337 :
8338 : // give a chance to the system to use cookies such as the
8339 : // cookie used to mark a user as logged in to kick in early
8340 : //
8341 0 : server::pointer_t server(get_server());
8342 0 : server->process_cookies();
8343 :
8344 : // the cookie handling may generate an immediate response in which case
8345 : // we want to exit ASAP and not execute anything
8346 : //
8347 0 : if(f_output.buffer().size() == 0)
8348 : {
8349 : class attach_session
8350 : {
8351 : public:
8352 0 : attach_session(server::pointer_t server)
8353 0 : : f_server(server)
8354 : {
8355 : // let plugins detach whatever data they attached to the user session
8356 : // (note: nothing to do with the fork() which was called a while back)
8357 : //
8358 0 : server->detach_from_session();
8359 0 : }
8360 :
8361 0 : ~attach_session()
8362 0 : {
8363 : // TODO: look into having this call to the exit() function too
8364 : // since it should be called no matter what--at that
8365 : // point we will need a flag because we can't call the
8366 : // function more than once
8367 : //
8368 : // now that execution is over, we want to re-attach whatever did
8369 : // not make it in this session (i.e. a message that was posted
8370 : // after messages were added to the current page, or this page
8371 : // did not make use of the messages that were detached earlier)
8372 : //
8373 0 : f_server->attach_to_session();
8374 0 : }
8375 :
8376 : private:
8377 : server::pointer_t f_server = server::pointer_t();
8378 : };
8379 :
8380 : // detach/re-attach the session using this RAII class within
8381 : // the following block
8382 : {
8383 0 : attach_session raii_session(server);
8384 :
8385 0 : f_ready = true;
8386 :
8387 : // generate the output
8388 : //
8389 : // on_execute() is defined in the path plugin which retrieves the
8390 : // path::primary_owner of the content that match f_uri.path() and
8391 : // then calls the corresponding on_path_execute() function of that
8392 : // primary owner
8393 : //
8394 0 : server->execute(f_uri.path());
8395 : }
8396 :
8397 0 : if(f_output.buffer().size() == 0)
8398 : {
8399 : // somehow nothing was output...
8400 : // (that should not happen because the path::on_execute() function
8401 : // checks and generates a Page Not Found on empty content)
8402 : //
8403 0 : die(http_code_t::HTTP_CODE_NOT_FOUND, "Page Empty",
8404 : "Somehow this page could not be generated.",
8405 : "the execute() command ran but the output is empty (this is never correct with HTML data, it could be with text/plain responses though)");
8406 0 : NOTREACHED();
8407 : }
8408 : }
8409 :
8410 0 : if(f_is_being_initialized)
8411 : {
8412 0 : trace("Initialization succeeded.\n");
8413 0 : trace("#END\n");
8414 :
8415 : // TODO: should we also sent the headers and output buffer?
8416 : // we could send that righ after the #END mark
8417 : // (this could be done by removing the if/else so the
8418 : // output_result() function gets called either way!)
8419 : }
8420 : else
8421 : {
8422 : // created a page, output it now
8423 : //
8424 0 : output_result(HEADER_MODE_NO_ERROR, f_output.buffer());
8425 : }
8426 0 : }
8427 :
8428 :
8429 : /** \brief Output the resulting buffer.
8430 : *
8431 : * This function takes all the data generated by the different processes
8432 : * run and generates the final output.
8433 : *
8434 : * It must be used by all the functions that are ready to send their
8435 : * result to the client (assuming the default scheme cannot be used,
8436 : * somehow.) This is important because this function makes sure to:
8437 : *
8438 : * \li Signal all plugins of the event,
8439 : * \li Only send the headers if the method was HEAD,
8440 : * \li Answer AJAX requests as expected.
8441 : *
8442 : * \param[in] modes Print the headers for these modes.
8443 : * \param[in] output_data The output being process.
8444 : */
8445 0 : void snap_child::output_result(header_mode_t modes, QByteArray output_data)
8446 : {
8447 : // give plugins a chance to tweak the output one more time
8448 0 : server::pointer_t server(get_server());
8449 0 : server->output_result(f_uri.path(), output_data);
8450 :
8451 : // Handling the compression has to be done before defining the
8452 : // Content-Length header since that represents the compressed
8453 : // data and not the full length
8454 :
8455 : // TODO make use of the pre-canonicalized compression information
8456 : // (but also look into the identity handling... at this point
8457 : // we do not generate a 406 if identity is defined and set to
8458 : // zero if another compression is available...)
8459 :
8460 : // TODO when downloading a file (an attachment) that's already
8461 : // compressed we need to specify the compression of the output
8462 : // buffer so that way here we can "adjust" the compression as
8463 : // required -- some of that is already done/supported TBD
8464 :
8465 : // was the output buffer generated from an already compressed file?
8466 : // if so, then skip the encoding handling
8467 0 : QString const current_content_encoding(get_header("Content-Encoding"));
8468 0 : if(current_content_encoding.isEmpty())
8469 : {
8470 : // TODO image file formats that are already compressed should not be
8471 : // recompressed (i.e. JPEG, GIF, PNG...)
8472 :
8473 : // at this point we only attempt to compress known text formats
8474 : // (should we instead have a list of mime types that we do not want to
8475 : // compress? before, attempting to compress the "wrong" files would
8476 : // make the browsers fail badly... but today not so much.)
8477 : //
8478 0 : QString const content_type(get_header(get_name(name_t::SNAP_NAME_CORE_CONTENT_TYPE_HEADER)));
8479 0 : if(content_type.startsWith("text/plain;")
8480 0 : || content_type.startsWith("text/html;")
8481 0 : || content_type.startsWith("text/xml;")
8482 0 : || content_type.startsWith("text/css;")
8483 0 : || content_type.startsWith("text/javascript;"))
8484 : {
8485 : // TODO add compression capabilities with bz2, lzma and sdch as
8486 : // may be supported by the browser (although sdch is not
8487 : // possible here as long as we require/use Apache2)
8488 0 : http_strings::WeightedHttpString encodings(snapenv("HTTP_ACCEPT_ENCODING"));
8489 :
8490 : // it looks like some browsers use "x-gzip" instead of
8491 : // plain "gzip"; also our default (i.e. "*") is gzip so
8492 : // check that value here too
8493 : //
8494 0 : float const gzip_level(std::max(std::max(encodings.get_level("gzip"), encodings.get_level("x-gzip")), encodings.get_level("*")));
8495 :
8496 : // plain zlib data is named "deflate"
8497 0 : float const deflate_level(encodings.get_level("deflate"));
8498 :
8499 0 : if(gzip_level > 0.0f && gzip_level >= deflate_level)
8500 : {
8501 : // browser asked for gzip with higher preference
8502 0 : QString compressor("gzip");
8503 0 : output_data = compression::compress(compressor, output_data, 100, true);
8504 0 : if(compressor == "gzip")
8505 : {
8506 : // compression succeeded
8507 0 : set_header("Content-Encoding", "gzip", HEADER_MODE_EVERYWHERE);
8508 0 : }
8509 : }
8510 0 : else if(deflate_level > 0.0f)
8511 : {
8512 0 : QString compressor("deflate");
8513 0 : output_data = compression::compress(compressor, output_data, 100, true);
8514 0 : if(compressor == "deflate")
8515 : {
8516 : // compression succeeded
8517 0 : set_header("Content-Encoding", "deflate", HEADER_MODE_EVERYWHERE);
8518 : }
8519 : }
8520 : else
8521 : {
8522 : // Code 406 is in the spec. (RFC2616) but frankly?!
8523 0 : float const identity_level(encodings.get_level("identity"));
8524 : #pragma GCC diagnostic push
8525 : #pragma GCC diagnostic ignored "-Wfloat-equal"
8526 0 : if(!f_died && identity_level == 0.0f)
8527 : #pragma GCC diagnostic pop
8528 : {
8529 0 : die(http_code_t::HTTP_CODE_NOT_ACCEPTABLE, "No Acceptable Compression Encoding",
8530 : "Your client requested a compression that we do not offer and it does not accept content without compression.",
8531 : "a client requested content with Accept-Encoding: identity;q=0 and no other compression we understand");
8532 0 : NOTREACHED();
8533 : }
8534 : // The "identity" SHOULD NOT be used with the Content-Encoding
8535 : // (RFC 2616 -- https://tools.ietf.org/html/rfc2616)
8536 : //set_header("Content-Encoding", "identity", HEADER_MODE_EVERYWHERE);
8537 : }
8538 : }
8539 : else
8540 : {
8541 : // note that "html"-output is NOT html in this case!
8542 : // (most likely an image... but any document really)
8543 : }
8544 : }
8545 :
8546 0 : QString const size(QString("%1").arg(output_data.size()));
8547 0 : set_header("Content-Length", size, HEADER_MODE_EVERYWHERE);
8548 :
8549 0 : output_headers(modes);
8550 :
8551 : // write the body in all circumstances unless
8552 : // the method is HEAD and the request did not fail
8553 : //
8554 : // IMPORTANT NOTE: it looks like Apache2 removes the body no matter what
8555 : // which is probably sensible... if a HEAD is used it is
8556 : // not a browser anyway
8557 0 : if(snapenv(get_name(name_t::SNAP_NAME_CORE_REQUEST_METHOD)) != "HEAD"
8558 : /*|| (modes & HEADER_MODE_NO_ERROR) == 0 -- not working... */)
8559 : {
8560 0 : write(output_data, output_data.size());
8561 : // Warning: do not use this std::cout if you allow compression... 8-)
8562 : //std::cout << "output [" << output_data.data() << "] [" << output_data.size() << "]\n";
8563 : }
8564 0 : }
8565 :
8566 :
8567 : /** \brief Process the post if there was one.
8568 : *
8569 : * This function processes the post, as in checks all the validity of
8570 : * all parameters and save the data in the database, and then returns.
8571 : *
8572 : * \note
8573 : * If the post was successful it is possible that the function generates
8574 : * an redirect and then exit immediately.
8575 : */
8576 0 : void snap_child::process_post()
8577 : {
8578 0 : if(f_has_post)
8579 : {
8580 0 : server::pointer_t server(get_server());
8581 0 : server->process_post(f_uri.path());
8582 : }
8583 0 : }
8584 :
8585 :
8586 : /** \brief Get the language defined by the user or browser.
8587 : *
8588 : * This function retrieves the language to be used in this
8589 : * request:
8590 : *
8591 : * \li the user defined language (sub-domain, path, or a GET variable)
8592 : * \li the preferred language of that user (user account)
8593 : * \li the language that the browser sent (as a last resort.)
8594 : *
8595 : * The user preferred language requires the user to be logged in. This
8596 : * call only works 100% correctly only if made the server::execute()
8597 : * call started (f_ready is true in snap_child).
8598 : *
8599 : * The language information is expected to be used to return the
8600 : * proper content of a page. However, you probably want to use the
8601 : * get_language_key() function instead as it will properly concatenate
8602 : * the language and the country exactly as required.
8603 : *
8604 : * If no language were defined, this function returns the "default"
8605 : * (as in neutral) language which is "xx". This may be returned if
8606 : * the function is called to early and no browser defaults were
8607 : * found in the request.
8608 : *
8609 : * \note
8610 : * The test to know whether the user has a preferred language selection
8611 : * is done in this function because the canonicalize_options() function
8612 : * runs too soon for such that query to function then (i.e. the plugins
8613 : * are defined, but the process_cookies() signal was not yet sent.)
8614 : *
8615 : * \return The language (2 letter canonicalized language name.)
8616 : */
8617 0 : QString snap_child::get_language()
8618 : {
8619 : // was the language defined as a sub-domain, a path segment, or
8620 : // a query string parameter? if so f_language is not empty
8621 0 : if(f_language.isEmpty())
8622 : {
8623 0 : get_plugins_locales();
8624 0 : if(!f_plugins_locales.isEmpty())
8625 : {
8626 : // a plugin defined a language
8627 : //
8628 0 : f_language = f_plugins_locales[0].get_language();
8629 0 : f_country = f_plugins_locales[0].get_country();
8630 : }
8631 0 : else if(!f_browser_locales.isEmpty())
8632 : {
8633 : // use client locale as provided by the browser
8634 : //
8635 0 : f_language = f_browser_locales[0].get_language();
8636 0 : f_country = f_browser_locales[0].get_country();
8637 : }
8638 : else
8639 : {
8640 : // no language defined, use the "no language" default in that case
8641 : //
8642 0 : f_language = "xx";
8643 : }
8644 : }
8645 :
8646 0 : return f_language;
8647 : }
8648 :
8649 :
8650 : /** \brief Retrieve all the languages in the order of preference.
8651 : *
8652 : * This function returns a vector of languages in the preference order
8653 : * which can be used to search for a match.
8654 : *
8655 : * \todo
8656 : * Look into whether we could have a vector of references instead because
8657 : * right now we copy all the languages twice!
8658 : *
8659 : * \todo
8660 : * Look into converting all the language/location to use the
8661 : * WeightedHttpStrings so we can have additional parameters
8662 : * attached to each language.
8663 : *
8664 : * \return An array of locales (language/country pairs).
8665 : */
8666 0 : snap_child::locale_info_vector_t snap_child::get_all_locales()
8667 : {
8668 0 : if(f_all_locales.isEmpty())
8669 : {
8670 : // first we want to check with the f_language and f_country
8671 : //
8672 0 : if(!f_language.isEmpty())
8673 : {
8674 0 : locale_info_t lang;
8675 0 : lang.set_language(f_language);
8676 0 : lang.set_country(f_country);
8677 :
8678 0 : f_all_locales.push_back(lang);
8679 : }
8680 :
8681 : // next we want to add the plugin definitions because a logged
8682 : // in user (or known, at least) who has preferences, we have to
8683 : // respect those preferences
8684 : //
8685 0 : get_plugins_locales();
8686 0 : for(auto const & l : f_plugins_locales)
8687 : {
8688 0 : if(std::find(f_all_locales.begin(), f_all_locales.end(), l) == f_all_locales.end())
8689 : {
8690 : // not in the list yet, add it
8691 : //
8692 0 : f_all_locales.push_back(l);
8693 : }
8694 : }
8695 :
8696 : // next we use the browser defined languages
8697 : //
8698 0 : for(auto const & l : f_browser_locales)
8699 : {
8700 0 : if(std::find(f_all_locales.begin(), f_all_locales.end(), l) == f_all_locales.end())
8701 : {
8702 : // not in the list yet, add it
8703 : //
8704 0 : f_all_locales.push_back(l);
8705 : }
8706 : }
8707 :
8708 : // now we want to re-add all those languages but without a country
8709 : // it is important before we add tests for default languages
8710 : //
8711 0 : if(!f_language.isEmpty()
8712 0 : && !f_country.isEmpty())
8713 : {
8714 0 : locale_info_t l;
8715 0 : l.set_language(f_language);
8716 0 : if(std::find(f_all_locales.begin(), f_all_locales.end(), l) == f_all_locales.end())
8717 : {
8718 0 : f_all_locales.push_back(l);
8719 : }
8720 : }
8721 :
8722 0 : for(auto p : f_plugins_locales)
8723 : {
8724 0 : if(!p.get_country().isEmpty())
8725 : {
8726 0 : locale_info_t l;
8727 0 : l.set_language(p.get_language());
8728 0 : f_all_locales.push_back(l);
8729 : }
8730 : }
8731 :
8732 0 : for(auto p : f_browser_locales)
8733 : {
8734 0 : if(!p.get_country().isEmpty())
8735 : {
8736 0 : locale_info_t l;
8737 0 : l.set_language(p.get_language());
8738 0 : if(std::find(f_all_locales.begin(), f_all_locales.end(), l) == f_all_locales.end())
8739 : {
8740 0 : f_all_locales.push_back(l);
8741 : }
8742 : }
8743 : }
8744 :
8745 : // finally, we add a few defaults in case no other languages
8746 : // matches we want to have those fallbacks
8747 : //
8748 : {
8749 : // Any English
8750 : //
8751 0 : locale_info_t l;
8752 0 : l.set_language("en");
8753 0 : if(std::find(f_all_locales.begin(), f_all_locales.end(), l) == f_all_locales.end())
8754 : {
8755 0 : f_all_locales.push_back(l);
8756 : }
8757 : }
8758 : {
8759 : // Try without a language ("neutral" for pages like a photo)
8760 : //
8761 0 : locale_info_t l;
8762 0 : l.set_language("xx");
8763 0 : if(std::find(f_all_locales.begin(), f_all_locales.end(), l) == f_all_locales.end())
8764 : {
8765 0 : f_all_locales.push_back(l);
8766 : }
8767 : }
8768 : {
8769 : // Finally we want to try with no language / no country
8770 : // (it should never already be in the list?!)
8771 : //
8772 0 : locale_info_t l;
8773 0 : if(std::find(f_all_locales.begin(), f_all_locales.end(), l) == f_all_locales.end())
8774 : {
8775 0 : f_all_locales.push_back(l);
8776 : }
8777 : }
8778 : }
8779 :
8780 0 : return f_all_locales;
8781 : }
8782 :
8783 :
8784 : /** \brief Get the name of the country.
8785 : *
8786 : * This function returns the name of the country linked with the language
8787 : * that the user requested. For example, in the locale "en_US" the country
8788 : * code is US. This is what this function returns.
8789 : *
8790 : * In most cases this function is only used internally when getting the
8791 : * language key. You should not make use of this information as meaning
8792 : * that the user is from that country.
8793 : *
8794 : * \warning
8795 : * You should always call get_language() FIRST because it will define
8796 : * the country if the language was not defined as a sub-domain, a path
8797 : * segment, or a query string parameter.
8798 : *
8799 : * \return The country (2 letter canonicalized country name.)
8800 : *
8801 : * \sa get_language()
8802 : * \sa get_language_key()
8803 : */
8804 0 : QString snap_child::get_country() const
8805 : {
8806 0 : return f_country;
8807 : }
8808 :
8809 :
8810 : /** \brief The "language" (or locale) the user or browser defined.
8811 : *
8812 : * This function returns the language and country merged as a one key.
8813 : * The name of the language and the name of the country are always
8814 : * exactly 2 characters. The language name is in lowercase and the
8815 : * country is in uppercase. The two names are separated by an
8816 : * underscore (_).
8817 : *
8818 : * For example, English in the USA would be returned as: "en_US".
8819 : * German in Germany is defined as "de_DE".
8820 : *
8821 : * \return The language key or "locale" for this request.
8822 : */
8823 0 : QString snap_child::get_language_key()
8824 : {
8825 0 : if(f_language_key.isEmpty())
8826 : {
8827 0 : f_language_key = get_language();
8828 0 : QString country(get_country());
8829 0 : if(!country.isEmpty())
8830 : {
8831 0 : f_language_key += '_';
8832 0 : f_language_key += country;
8833 : }
8834 : }
8835 0 : return f_language_key;
8836 : }
8837 :
8838 :
8839 : /** \brief Get the list of locales defined by plugins.
8840 : *
8841 : * At this point the main plugin defining locales to be used is the users
8842 : * plugin which offers the user to edit a set his settings and as one
8843 : * of the entries offers the user to enter one or more locales.
8844 : *
8845 : * The array of user locales is properly defined only if the f_ready
8846 : * flag is true. Otherwise it won't be proper since the user won't already
8847 : * be logged in.
8848 : *
8849 : * \return An array of locales.
8850 : */
8851 0 : snap_child::locale_info_vector_t const& snap_child::get_plugins_locales()
8852 : {
8853 0 : if(f_plugins_locales.isEmpty())
8854 : {
8855 0 : if(!f_ready)
8856 : {
8857 : // I do not think this happens, but just in case warn myself
8858 : //
8859 0 : SNAP_LOG_WARNING("get_plugins_locales() called before f_ready was set to true");
8860 : }
8861 :
8862 : // retrieve the locales from all the plugins
8863 : // we expect a string of locales defined as weighted HTTP strings
8864 : // remember that without a proper weight the algorithm uses 1.0
8865 : //
8866 0 : server::pointer_t server(get_server());
8867 0 : http_strings::WeightedHttpString locales;
8868 0 : server->define_locales(locales);
8869 :
8870 0 : http_strings::WeightedHttpString::part_t::vector_t const & plugins_languages(locales.get_parts());
8871 0 : if(!plugins_languages.isEmpty())
8872 : {
8873 : // not sorted by default, make sure it gets sorted
8874 : // (if empty we can avoid this call and since the plugins_languages
8875 : // parameter is a reference, we will still see the result)
8876 : //
8877 0 : locales.sort_by_level();
8878 :
8879 0 : int const max_languages(plugins_languages.size());
8880 0 : for(int i(0); i < max_languages; ++i)
8881 : {
8882 0 : QString plugins_lang(plugins_languages[i].get_name());
8883 0 : QString plugins_country;
8884 0 : if(verify_locale(plugins_lang, plugins_country, false))
8885 : {
8886 : // only keep those that are considered valid (they all should
8887 : // be, but in case a hacker or strange client access our
8888 : // systems...)
8889 : //
8890 0 : locale_info_t l;
8891 0 : l.set_language(plugins_lang);
8892 0 : l.set_country(plugins_country);
8893 0 : f_plugins_locales.push_back(l);
8894 : // added in order
8895 : }
8896 : else
8897 : {
8898 : // plugin sent us something we do not understand
8899 : //
8900 0 : SNAP_LOG_WARNING(QString("plugin locale \"%1\" does not look like a legal locale, ignore")
8901 0 : .arg(plugins_languages[i].get_name()));
8902 : }
8903 : }
8904 : }
8905 : }
8906 :
8907 0 : return f_plugins_locales;
8908 : }
8909 :
8910 :
8911 : /** \brief Retrieve the list of locales as defined in the browser.
8912 : *
8913 : * This function returns the array of locales as defined in the user's
8914 : * browser. This is considered the fallback in case the user did not
8915 : * force a language in his request (sub-domain, path segment, or query
8916 : * string parameter.)
8917 : *
8918 : * \return A constant reference to the browser locales.
8919 : */
8920 0 : snap_child::locale_info_vector_t const & snap_child::get_browser_locales() const
8921 : {
8922 0 : return f_browser_locales;
8923 : }
8924 :
8925 :
8926 : /** \brief Check whether the current (false) or current working (true) branch should be used.
8927 : *
8928 : * This function returns true if the user requested the working branch.
8929 : *
8930 : * \return true if the current working branch should be used.
8931 : */
8932 0 : bool snap_child::get_working_branch() const
8933 : {
8934 0 : return f_working_branch;
8935 : }
8936 :
8937 :
8938 : /** \brief Retrieve the branch number to be accessed.
8939 : *
8940 : * This function returns SPECIAL_VERSION_UNDEFINED (which is -1) if the
8941 : * branch was not defined by the user.
8942 : */
8943 0 : snap_version::version_number_t snap_child::get_branch() const
8944 : {
8945 0 : return f_branch;
8946 : }
8947 :
8948 :
8949 : /** \brief Get the revision number that the user defined.
8950 : *
8951 : * This function returns zero if the user has not set the revision.
8952 : * Note that zero is also a valid revision.
8953 : */
8954 0 : snap_version::version_number_t snap_child::get_revision() const
8955 : {
8956 0 : return f_revision;
8957 : }
8958 :
8959 :
8960 : /** \brief Retrieve the full revision key.
8961 : *
8962 : * The branch and revision numbers combined with a period as the separator
8963 : * represents the revision key of this request.
8964 : *
8965 : * Note that if the user did not specify a revision, then the string
8966 : * returned is just the branch. If no branch and no revision were
8967 : * specified, then the key is empty and the default for that page is to
8968 : * be used.
8969 : *
8970 : * \return A string defined as "\<branch>.\<revision>".
8971 : */
8972 0 : QString snap_child::get_revision_key() const
8973 : {
8974 0 : return f_revision_key;
8975 : }
8976 :
8977 :
8978 : /** \brief Get compression information.
8979 : *
8980 : * This function retrieves a reference to the array of compressions accepted
8981 : * by the client. This can be used by plugins that generate the output
8982 : * directly and the snap_child functions won't get a chance to compress
8983 : * the data later or for such a plugin to let the snap_child object
8984 : * know that the data is pre-compressed (i.e. when we pre-compress data
8985 : * with gzip we can just return that data, it means we read less from
8986 : * the database and we may have compressed that data on a backend
8987 : * server leaving the front end running full speed.)
8988 : *
8989 : * The array may end with COMPRESSION_INVALID in which case one of the
8990 : * compressions defined before MUST have been a match. If not, then the
8991 : * file cannot be returned to the user and a 406 error
8992 : * (HTTP_CODE_NOT_ACCEPTABLE) should be returned to the client.
8993 : *
8994 : * \return The array of compressions expected by the client, preferred first.
8995 : */
8996 0 : snap_child::compression_vector_t snap_child::get_compression() const
8997 : {
8998 0 : return f_compressions;
8999 : }
9000 :
9001 :
9002 : /** \brief Convert a time/date value to a string.
9003 : *
9004 : * This function transform a date such as the content::modified field
9005 : * to a format that is useful to the XSL parser. It supports various
9006 : * formats:
9007 : *
9008 : * \li DATE_FORMAT_SHORT -- YYYY-MM-DD
9009 : * \li DATE_FORMAT_LONG -- YYYY-MM-DDTHH:MM:SSZ
9010 : * \li DATE_FORMAT_TIME -- HH:MM:SS
9011 : * \li DATE_FORMAT_EMAIL -- dd MMM yyyy hh:mm:ss +0000
9012 : * \li DATE_FORMAT_HTTP -- ddd, dd MMM yyyy hh:mm:ss +0000
9013 : *
9014 : * The long format includes the time.
9015 : *
9016 : * The HTTP format uses the day and month names in English only since
9017 : * the HTTP protocol only expects English.
9018 : *
9019 : * The date is always output as UTC (opposed to local time.)
9020 : *
9021 : * \note
9022 : * In order to display a date to the end user (in the HTML for the users)
9023 : * you may want to setup the timezone information first and then use
9024 : * the various functions supplied by the locale plugin to generate the
9025 : * date. The locale plugin also supports formatting numbers, time,
9026 : * messages, etc. going both ways (from the formatted data to internal
9027 : * data and vice versa.) There is also JavaScript support for editor
9028 : * widgets.
9029 : *
9030 : * \param[in] v A 64 bit time / date value in microseconds, although we
9031 : * really only use precision to the second.
9032 : * \param[in] date_format Which format should be used.
9033 : *
9034 : * \return The formatted date and time.
9035 : */
9036 0 : QString snap_child::date_to_string(int64_t v, date_format_t date_format)
9037 : {
9038 : // go to seconds
9039 0 : time_t seconds(v / 1000000);
9040 :
9041 : struct tm time_info;
9042 0 : gmtime_r(&seconds, &time_info);
9043 :
9044 : char buf[256];
9045 0 : buf[0] = '\0';
9046 :
9047 0 : switch(date_format)
9048 : {
9049 0 : case date_format_t::DATE_FORMAT_SHORT:
9050 0 : strftime(buf, sizeof(buf), "%Y-%m-%d", &time_info);
9051 0 : break;
9052 :
9053 0 : case date_format_t::DATE_FORMAT_SHORT_US:
9054 0 : strftime(buf, sizeof(buf), "%m-%d-%Y", &time_info);
9055 0 : break;
9056 :
9057 0 : case date_format_t::DATE_FORMAT_LONG:
9058 : // TBD do we want the Z when generating time for HTML headers?
9059 : // (it is useful for the sitemap.xml at this point)
9060 0 : strftime(buf, sizeof(buf), "%Y-%m-%dT%H:%M:%SZ", &time_info);
9061 0 : break;
9062 :
9063 0 : case date_format_t::DATE_FORMAT_TIME:
9064 0 : strftime(buf, sizeof(buf), "%H:%M:%S", &time_info);
9065 0 : break;
9066 :
9067 0 : case date_format_t::DATE_FORMAT_EMAIL:
9068 : { // dd MMM yyyy hh:mm:ss +0000
9069 : // do it manually so the date is ALWAYS in English
9070 0 : return QString("%1 %2 %3 %4:%5:%6 +0000")
9071 0 : .arg(time_info.tm_mday, 2, 10, QChar('0'))
9072 0 : .arg(QString::fromLatin1(g_month_name[time_info.tm_mon], 3))
9073 0 : .arg(time_info.tm_year + 1900, 4, 10, QChar('0'))
9074 0 : .arg(time_info.tm_hour, 2, 10, QChar('0'))
9075 0 : .arg(time_info.tm_min, 2, 10, QChar('0'))
9076 0 : .arg(time_info.tm_sec, 2, 10, QChar('0'));
9077 : }
9078 : break;
9079 :
9080 0 : case date_format_t::DATE_FORMAT_HTTP:
9081 : { // ddd, dd MMM yyyy hh:mm:ss GMT
9082 : // do it manually so the date is ALWAYS in English
9083 0 : return QString("%1, %2 %3 %4 %5:%6:%7 +0000")
9084 0 : .arg(QString::fromLatin1(g_week_day_name[time_info.tm_wday], 3))
9085 0 : .arg(time_info.tm_mday, 2, 10, QChar('0'))
9086 0 : .arg(QString::fromLatin1(g_month_name[time_info.tm_mon], 3))
9087 0 : .arg(time_info.tm_year + 1900, 4, 10, QChar('0'))
9088 0 : .arg(time_info.tm_hour, 2, 10, QChar('0'))
9089 0 : .arg(time_info.tm_min, 2, 10, QChar('0'))
9090 0 : .arg(time_info.tm_sec, 2, 10, QChar('0'));
9091 : }
9092 : break;
9093 :
9094 : }
9095 :
9096 0 : return buf;
9097 : }
9098 :
9099 :
9100 : /** \brief Convert a date from a string to a time_t.
9101 : *
9102 : * This function transforms a date received by the client to a Unix
9103 : * time_t value. We programmed our own because several fields are
9104 : * optional and the strptime() function does not support such. Also
9105 : * the strptime() uses the locale() for the day and month check
9106 : * which is not expected for HTTP. The QDateTime object has similar
9107 : * flaws.
9108 : *
9109 : * The function supports the RFC822, RFC850, and ANSI formats. On top
9110 : * of these formats, the function understands the month name in full,
9111 : * and the week day name and timezone parameters are viewed as optional.
9112 : *
9113 : * See the document we used to make sure we'd support pretty much all the
9114 : * dates that a client might send to us:
9115 : * http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1
9116 : *
9117 : * Formats that we support:
9118 : *
9119 : * \code
9120 : * YYYY-MM-DD
9121 : * DD-MMM-YYYY HH:MM:SS TZ
9122 : * DD-MMM-YYYY HH:MM:SS TZ
9123 : * WWW, DD-MMM-YYYY HH:MM:SS TZ
9124 : * MMM-DD-YYYY HH:MM:SS TZ
9125 : * WWW MMM-DD HH:MM:SS YYYY
9126 : * \endcode
9127 : *
9128 : * The month and weekday may be a 3 letter abbreviation or the full English
9129 : * name. The month must use letters. We support the ANSI format which must
9130 : * start with the month or week name in letters. We distinguish the ANSI
9131 : * format from the other RFC-2616 date if it starts with a week day and is
9132 : * followed by a space, or it directly starts with the month name.
9133 : *
9134 : * The year may be 2 or 4 digits.
9135 : *
9136 : * The timezone is optional. It may use a space or a + or a - as a separator.
9137 : * The timezone may be an abbreviation or a 4 digit number.
9138 : *
9139 : * \param[in] date The date to convert to a time_t.
9140 : *
9141 : * \return The date and time as a Unix time_t number, -1 if the convertion fails.
9142 : */
9143 0 : time_t snap_child::string_to_date(QString const & date)
9144 : {
9145 0 : struct parser_t
9146 : {
9147 0 : parser_t(QString const & date)
9148 0 : : f_date(date.simplified().toLower().toUtf8())
9149 0 : , f_s(f_date.data())
9150 : {
9151 0 : }
9152 :
9153 : parser_t(parser_t const & rhs) = delete;
9154 : parser_t & operator = (parser_t const & rhs) = delete;
9155 :
9156 0 : void skip_spaces()
9157 : {
9158 0 : while(isspace(*f_s))
9159 : {
9160 0 : ++f_s;
9161 : }
9162 0 : }
9163 :
9164 0 : bool parse_week_day()
9165 : {
9166 : // day = "Mon" / "Tue" / "Wed" / "Thu"
9167 : // / "Fri" / "Sat" / "Sun"
9168 0 : if(f_s[0] == 'm' && f_s[1] == 'o' && f_s[2] == 'n')
9169 : {
9170 0 : f_time_info.tm_wday = 1;
9171 : }
9172 0 : else if(f_s[0] == 't' && f_s[1] == 'u' && f_s[2] == 'e')
9173 : {
9174 0 : f_time_info.tm_wday = 2;
9175 : }
9176 0 : else if(f_s[0] == 'w' && f_s[1] == 'e' && f_s[2] == 'd')
9177 : {
9178 0 : f_time_info.tm_wday = 3;
9179 : }
9180 0 : else if(f_s[0] == 't' && f_s[1] == 'h' && f_s[2] == 'u')
9181 : {
9182 0 : f_time_info.tm_wday = 4;
9183 : }
9184 0 : else if(f_s[0] == 'f' && f_s[1] == 'r' && f_s[2] == 'i')
9185 : {
9186 0 : f_time_info.tm_wday = 5;
9187 : }
9188 0 : else if(f_s[0] == 's' && f_s[1] == 'a' && f_s[2] == 't')
9189 : {
9190 0 : f_time_info.tm_wday = 6;
9191 : }
9192 0 : else if(f_s[0] == 's' && f_s[1] == 'u' && f_s[2] == 'n')
9193 : {
9194 0 : f_time_info.tm_wday = 0;
9195 : }
9196 : else
9197 : {
9198 0 : return false;
9199 : }
9200 :
9201 : // check whether the other characters follow
9202 0 : if(strncmp(f_s + 3, g_week_day_name[f_time_info.tm_wday] + 3, g_week_day_length[f_time_info.tm_wday] - 3) == 0)
9203 : {
9204 : // full day (RFC850)
9205 0 : f_s += g_week_day_length[f_time_info.tm_wday];
9206 : }
9207 : else
9208 : {
9209 0 : f_s += 3;
9210 : }
9211 :
9212 0 : return true;
9213 : }
9214 :
9215 0 : bool parse_month()
9216 : {
9217 : // month = "Jan" / "Feb" / "Mar" / "Apr"
9218 : // / "May" / "Jun" / "Jul" / "Aug"
9219 : // / "Sep" / "Oct" / "Nov" / "Dec"
9220 0 : if(f_s[0] == 'j' && f_s[1] == 'a' && f_s[2] == 'n')
9221 : {
9222 0 : f_time_info.tm_mon = 0;
9223 : }
9224 0 : else if(f_s[0] == 'f' && f_s[1] == 'e' && f_s[2] == 'b')
9225 : {
9226 0 : f_time_info.tm_mon = 1;
9227 : }
9228 0 : else if(f_s[0] == 'm' && f_s[1] == 'a' && f_s[2] == 'r')
9229 : {
9230 0 : f_time_info.tm_mon = 2;
9231 : }
9232 0 : else if(f_s[0] == 'a' && f_s[1] == 'p' && f_s[2] == 'r')
9233 : {
9234 0 : f_time_info.tm_mon = 3;
9235 : }
9236 0 : else if(f_s[0] == 'm' && f_s[1] == 'a' && f_s[2] == 'y')
9237 : {
9238 0 : f_time_info.tm_mon = 4;
9239 : }
9240 0 : else if(f_s[0] == 'j' && f_s[1] == 'u' && f_s[2] == 'n')
9241 : {
9242 0 : f_time_info.tm_mon = 5;
9243 : }
9244 0 : else if(f_s[0] == 'j' && f_s[1] == 'u' && f_s[2] == 'l')
9245 : {
9246 0 : f_time_info.tm_mon = 6;
9247 : }
9248 0 : else if(f_s[0] == 'a' && f_s[1] == 'u' && f_s[2] == 'g')
9249 : {
9250 0 : f_time_info.tm_mon = 7;
9251 : }
9252 0 : else if(f_s[0] == 's' && f_s[1] == 'e' && f_s[2] == 'p')
9253 : {
9254 0 : f_time_info.tm_mon = 8;
9255 : }
9256 0 : else if(f_s[0] == 'o' && f_s[1] == 'c' && f_s[2] == 't')
9257 : {
9258 0 : f_time_info.tm_mon = 9;
9259 : }
9260 0 : else if(f_s[0] == 'n' && f_s[1] == 'o' && f_s[2] == 'v')
9261 : {
9262 0 : f_time_info.tm_mon = 10;
9263 : }
9264 0 : else if(f_s[0] == 'd' && f_s[1] == 'e' && f_s[2] == 'c')
9265 : {
9266 0 : f_time_info.tm_mon = 11;
9267 : }
9268 : else
9269 : {
9270 0 : return false;
9271 : }
9272 :
9273 : // check whether the other characters follow
9274 0 : if(strncmp(f_s + 3, g_month_name[f_time_info.tm_mon] + 3, g_month_length[f_time_info.tm_mon] - 3) == 0)
9275 : {
9276 : // full month (not in the specs)
9277 0 : f_s += g_month_length[f_time_info.tm_mon];
9278 : }
9279 : else
9280 : {
9281 0 : f_s += 3;
9282 : }
9283 :
9284 0 : skip_spaces();
9285 0 : return true;
9286 : }
9287 :
9288 0 : bool integer(unsigned int min_len, unsigned int max_len, unsigned int min_value, unsigned int max_value, int & result)
9289 : {
9290 0 : unsigned int u_result = 0;
9291 0 : unsigned int count(0);
9292 0 : for(; *f_s >= '0' && *f_s <= '9'; ++f_s, ++count)
9293 : {
9294 0 : u_result = u_result * 10 + *f_s - '0';
9295 : }
9296 0 : if(count < min_len || count > max_len
9297 0 : || u_result < min_value || u_result > max_value)
9298 : {
9299 0 : result = static_cast<int>(u_result);
9300 0 : return false;
9301 : }
9302 0 : result = static_cast<int>(u_result);
9303 0 : return true;
9304 : }
9305 :
9306 0 : bool parse_time()
9307 : {
9308 0 : if(!integer(2, 2, 0, 23, f_time_info.tm_hour))
9309 : {
9310 0 : return false;
9311 : }
9312 0 : if(*f_s != ':')
9313 : {
9314 0 : return false;
9315 : }
9316 0 : ++f_s;
9317 0 : if(!integer(2, 2, 0, 59, f_time_info.tm_min))
9318 : {
9319 0 : return false;
9320 : }
9321 0 : if(*f_s != ':')
9322 : {
9323 0 : return false;
9324 : }
9325 0 : ++f_s;
9326 0 : if(!integer(2, 2, 0, 60, f_time_info.tm_sec))
9327 : {
9328 0 : return false;
9329 : }
9330 0 : skip_spaces();
9331 0 : return true;
9332 : }
9333 :
9334 0 : bool parse_timezone()
9335 : {
9336 : // any timezone?
9337 0 : if(*f_s == '\0')
9338 : {
9339 0 : return true;
9340 : }
9341 :
9342 : // XXX not too sure that the zone is properly handled at this point
9343 : // (i.e. should I do += or -=, it may be wrong in many places...)
9344 : //
9345 : // The newest HTTP format is to only support "+/-####"
9346 : //
9347 : // zone = "UT" / "GMT"
9348 : // / "EST" / "EDT"
9349 : // / "CST" / "CDT"
9350 : // / "MST" / "MDT"
9351 : // / "PST" / "PDT"
9352 : // / 1ALPHA
9353 : // / ( ("+" / "-") 4DIGIT )
9354 0 : if((f_s[0] == 'u' && f_s[1] == 't' && f_s[2] == '\0') // UT
9355 0 : || (f_s[0] == 'u' && f_s[1] == 't' && f_s[2] == 'c' && f_s[3] == '\0') // UTC (not in the spec...)
9356 0 : || (f_s[0] == 'g' && f_s[1] == 'm' && f_s[2] == 't' && f_s[3] == '\0')) // GMT
9357 : {
9358 : // no adjustment for UTC (GMT)
9359 : }
9360 0 : else if(f_s[0] == 'e' && f_s[1] == 's' && f_s[2] == 't' && f_s[3] == '\0') // EST
9361 : {
9362 0 : f_time_info.tm_hour -= 5;
9363 : }
9364 0 : else if(f_s[0] == 'e' && f_s[1] == 'd' && f_s[2] == 't' && f_s[3] == '\0') // EDT
9365 : {
9366 0 : f_time_info.tm_hour -= 4;
9367 : }
9368 0 : else if(f_s[0] == 'c' && f_s[1] == 's' && f_s[2] == 't' && f_s[3] == '\0') // CST
9369 : {
9370 0 : f_time_info.tm_hour -= 6;
9371 : }
9372 0 : else if(f_s[0] == 'c' && f_s[1] == 'd' && f_s[2] == 't' && f_s[3] == '\0') // CDT
9373 : {
9374 0 : f_time_info.tm_hour -= 5;
9375 : }
9376 0 : else if(f_s[0] == 'm' && f_s[1] == 's' && f_s[2] == 't' && f_s[3] == '\0') // MST
9377 : {
9378 0 : f_time_info.tm_hour -= 7;
9379 : }
9380 0 : else if(f_s[0] == 'm' && f_s[1] == 'd' && f_s[2] == 't' && f_s[3] == '\0') // MDT
9381 : {
9382 0 : f_time_info.tm_hour -= 6;
9383 : }
9384 0 : else if(f_s[0] == 'p' && f_s[1] == 's' && f_s[2] == 't' && f_s[3] == '\0') // PST
9385 : {
9386 0 : f_time_info.tm_hour -= 8;
9387 : }
9388 0 : else if(f_s[0] == 'p' && f_s[1] == 'd' && f_s[2] == 't' && f_s[3] == '\0') // PDT
9389 : {
9390 0 : f_time_info.tm_hour -= 7;
9391 : }
9392 0 : else if(f_s[0] >= 'a' && f_s[0] <= 'z' && f_s[0] != 'j' && f_s[1] == '\0')
9393 : {
9394 0 : f_time_info.tm_hour += g_timezone_adjust[f_s[0] - 'a'];
9395 : }
9396 0 : else if((f_s[0] == '+' || f_s[0] == '-')
9397 0 : && f_s[1] >= '0' && f_s[1] <= '9'
9398 0 : && f_s[2] >= '0' && f_s[2] <= '9'
9399 0 : && f_s[3] >= '0' && f_s[3] <= '9'
9400 0 : && f_s[4] >= '0' && f_s[4] <= '9'
9401 0 : && f_s[5] == '\0')
9402 : {
9403 0 : f_time_info.tm_hour += ((f_s[1] - '0') * 10 + f_s[2] - '0') * (f_s[0] == '+' ? 1 : -1);
9404 0 : f_time_info.tm_min += ((f_s[3] - '0') * 10 + f_s[4] - '0') * (f_s[0] == '+' ? 1 : -1);
9405 : }
9406 : else
9407 : {
9408 : // invalid time zone
9409 0 : return false;
9410 : }
9411 :
9412 : // WARNING: the time zone doesn't get skipped!
9413 0 : return true;
9414 : }
9415 :
9416 0 : bool parse_ansi()
9417 : {
9418 0 : skip_spaces();
9419 0 : if(!parse_month())
9420 : {
9421 0 : return false;
9422 : }
9423 0 : if(!integer(1, 2, 1, 31, f_time_info.tm_mday))
9424 : {
9425 0 : return false;
9426 : }
9427 0 : skip_spaces();
9428 0 : if(!parse_time())
9429 : {
9430 0 : return false;
9431 : }
9432 0 : if(!integer(2, 4, 0, 3000, f_time_info.tm_year))
9433 : {
9434 0 : return false;
9435 : }
9436 0 : skip_spaces();
9437 0 : return parse_timezone();
9438 : }
9439 :
9440 0 : bool parse_us()
9441 : {
9442 0 : skip_spaces();
9443 0 : if(!parse_month())
9444 : {
9445 0 : return false;
9446 : }
9447 0 : skip_spaces();
9448 0 : if(!integer(1, 2, 1, 31, f_time_info.tm_mday))
9449 : {
9450 0 : return false;
9451 : }
9452 0 : skip_spaces();
9453 0 : if(!integer(2, 4, 0, 3000, f_time_info.tm_year))
9454 : {
9455 0 : return false;
9456 : }
9457 0 : skip_spaces();
9458 0 : return parse_time();
9459 : }
9460 :
9461 0 : bool parse()
9462 : {
9463 : // support for YYYY-MM-DD
9464 0 : if(f_date.size() == 10
9465 0 : && f_s[4] == '-'
9466 0 : && f_s[7] == '-')
9467 : {
9468 0 : if(!integer(4, 4, 0, 3000, f_time_info.tm_year))
9469 : {
9470 0 : return false;
9471 : }
9472 0 : if(*f_s != '-')
9473 : {
9474 0 : return false;
9475 : }
9476 0 : ++f_s;
9477 0 : if(!integer(2, 2, 1, 12, f_time_info.tm_mon))
9478 : {
9479 0 : return false;
9480 : }
9481 0 : --f_time_info.tm_mon; // expect 0 to 11 in final structure
9482 0 : if(*f_s != '-')
9483 : {
9484 0 : return false;
9485 : }
9486 0 : ++f_s;
9487 0 : if(!integer(2, 2, 1, 31, f_time_info.tm_mday))
9488 : {
9489 0 : return false;
9490 : }
9491 0 : return true;
9492 : }
9493 :
9494 : // week day (optional in RFC822)
9495 0 : if(*f_s >= 'a' && *f_s <= 'z')
9496 : {
9497 0 : if(!parse_week_day())
9498 : {
9499 : // maybe that was the month, not the day
9500 : // if the time is last, we have a preprocessor date/time
9501 : // the second test is needed because the string gets
9502 : // simplified and thus numbers 1 to 9 generate a string
9503 : // one shorter
9504 0 : if((strlen(f_s) == 11 + 1 + 8
9505 0 : && f_s[11 + 1 + 8 - 6] == ':'
9506 0 : && f_s[11 + 1 + 8 - 3] == ':')
9507 0 : ||
9508 0 : (strlen(f_s) == 10 + 1 + 8
9509 0 : && f_s[10 + 1 + 8 - 6] == ':'
9510 0 : && f_s[10 + 1 + 8 - 3] == ':'))
9511 : {
9512 0 : return parse_us();
9513 : }
9514 0 : return parse_ansi();
9515 : }
9516 :
9517 0 : if(f_s[0] == ' ')
9518 : {
9519 : // the ANSI format is completely random!
9520 0 : return parse_ansi();
9521 : }
9522 :
9523 0 : if(f_s[0] != ',')
9524 : {
9525 0 : return false;
9526 : }
9527 0 : ++f_s; // skip the comma
9528 0 : skip_spaces();
9529 : }
9530 :
9531 0 : if(!integer(1, 2, 1, 31, f_time_info.tm_mday))
9532 : {
9533 0 : return false;
9534 : }
9535 :
9536 0 : if(*f_s == '-')
9537 : {
9538 0 : ++f_s;
9539 : }
9540 0 : skip_spaces();
9541 :
9542 0 : if(!parse_month())
9543 : {
9544 0 : return false;
9545 : }
9546 0 : if(*f_s == '-')
9547 : {
9548 0 : ++f_s;
9549 0 : skip_spaces();
9550 : }
9551 0 : if(!integer(2, 4, 0, 3000, f_time_info.tm_year))
9552 : {
9553 0 : return false;
9554 : }
9555 0 : skip_spaces();
9556 0 : if(!parse_time())
9557 : {
9558 0 : return false;
9559 : }
9560 :
9561 0 : return parse_timezone();
9562 : }
9563 :
9564 : struct tm f_time_info = tm();
9565 : QByteArray f_date = QByteArray();
9566 : char const * f_s = nullptr;
9567 0 : } parser(date);
9568 :
9569 0 : if(!parser.parse())
9570 : {
9571 0 : return -1;
9572 : }
9573 :
9574 : // 2 digit year?
9575 : // How to handle this one? At this time I do not expect our software
9576 : // to work beyond 2070 which is probably short sighted (ha! ha!)
9577 : // However, that way we avoid calling time() and transform that in
9578 : // a tm structure and check that date
9579 0 : if(parser.f_time_info.tm_year < 100)
9580 : {
9581 0 : parser.f_time_info.tm_year += 1900;
9582 0 : if(parser.f_time_info.tm_year < 1970)
9583 : {
9584 0 : parser.f_time_info.tm_year += 100;
9585 : }
9586 : }
9587 :
9588 : // make sure the day is valid for that month/year
9589 0 : if(parser.f_time_info.tm_mday > last_day_of_month(parser.f_time_info.tm_mon + 1, parser.f_time_info.tm_year))
9590 : {
9591 0 : return -1;
9592 : }
9593 :
9594 : // now we have a time_info which is fully adjusted except for DST...
9595 : // let's make time
9596 0 : parser.f_time_info.tm_year -= 1900;
9597 0 : return mkgmtime(&parser.f_time_info);
9598 : }
9599 :
9600 :
9601 : /** \brief From a month and year, get the last day of the month.
9602 : *
9603 : * This function gives you the number of the last day of the month.
9604 : * In all cases, except February, it returns 30 or 31.
9605 : *
9606 : * For the month of February, we first compute the leap year flag.
9607 : * If the year is a leap year, then it returns 29, otherwise it
9608 : * returns 28.
9609 : *
9610 : * The leap year formula is:
9611 : *
9612 : * \code
9613 : * leap = !(year % 4) && (year % 100 || !(year % 400));
9614 : * \endcode
9615 : *
9616 : * \warning
9617 : * This function throws if called with September 1752 because the
9618 : * month has missing days within the month (days 3 to 13).
9619 : *
9620 : * \param[in] month A number from 1 to 12 representing a month.
9621 : * \param[in] year A year, including the century.
9622 : *
9623 : * \return Last day of month, 30, 31, or in February, 28 or 29.
9624 : */
9625 0 : int snap_child::last_day_of_month(int month, int year)
9626 : {
9627 0 : if(month < 1 || month > 12)
9628 : {
9629 0 : throw snap_logic_exception(QString("last_day_of_month called with %1 as the month number").arg(month));
9630 : }
9631 :
9632 0 : if(month == 2)
9633 : {
9634 : // special case for February
9635 : //
9636 : // The time when people switch from Julian to Gregorian is country
9637 : // dependent, Great Britain changed on September 2, 1752, but some
9638 : // countries changed as late as 1952...
9639 : //
9640 : // For now, we use the GB date. Once we have a valid way to handle
9641 : // this with the locale, we can look into updating the code. That
9642 : // being said, it should not matter too much because most dates on
9643 : // the Internet are past 2000.
9644 : //
9645 0 : if(year <= 1752)
9646 : {
9647 0 : return year % 4 == 0 ? 29 : 28;
9648 : }
9649 0 : return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0) ? 29 : 28;
9650 : }
9651 :
9652 0 : if(month == 9 && year == 1752)
9653 : {
9654 : // we cannot handle this nice one here, days 3 to 13 are missing on
9655 : // this month... (to adjust the calendar all at once!)
9656 0 : throw snap_logic_exception(QString("last_day_of_month called with %1 as the year number").arg(year));
9657 : }
9658 :
9659 0 : return g_month_days[month - 1];
9660 : }
9661 :
9662 :
9663 : /** \brief Get a list of all language names.
9664 : *
9665 : * This function returns the current list of language names as defined
9666 : * in ISO639 and similar documents.
9667 : *
9668 : * The table returned ends with a nullptr entry. You may use it in this
9669 : * way:
9670 : *
9671 : * \code
9672 : * for(snap_child::language_name_t const *l(snap_child::get_languages()); l->f_name; ++l)
9673 : * \endcode
9674 : *
9675 : * See also https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes
9676 : *
9677 : * \return A pointer to the table of languages.
9678 : */
9679 0 : snap_child::language_name_t const * snap_child::get_languages()
9680 : {
9681 0 : return g_language_names;
9682 : }
9683 :
9684 :
9685 : /** \brief Get a list of all country names.
9686 : *
9687 : * This function returns the current list of country names as defined in
9688 : * ISO3166 and similar documents.
9689 : *
9690 : * See also https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2
9691 : *
9692 : * \return A pointer to the table of countries.
9693 : */
9694 0 : snap_child::country_name_t const * snap_child::get_countries()
9695 : {
9696 0 : return g_country_names;
9697 : }
9698 :
9699 :
9700 : /** \brief Send the backend_process() signal to all plugins.
9701 : *
9702 : * This function sends the server::backend_process() signal to all
9703 : * the currently registered plugins.
9704 : *
9705 : * This is called by the content plugin whenever the action is set
9706 : * to "snapbackend" which is the default when no action was specified
9707 : * and someone started the snapbackend process:
9708 : *
9709 : * \code
9710 : * snapbackend
9711 : * [or]
9712 : * snapbackend --action snapbackend
9713 : * \endcode
9714 : */
9715 0 : void snap_child::backend_process()
9716 : {
9717 0 : server::pointer_t server(get_server());
9718 0 : server->backend_process();
9719 0 : }
9720 :
9721 :
9722 : /** \brief Send a PING message to the specified service.
9723 : *
9724 : * This function sends a PING message to the specified service.
9725 : * This is used to wake up a backend process after you saved
9726 : * data in the Cassandra cluster. That backend can then "slowly"
9727 : * process the data further.
9728 : *
9729 : * The message is sent using a UDP packet. It is sent to the
9730 : * Snap! Communicator running on the same server as this
9731 : * child.
9732 : *
9733 : * Remember that UDP is not reliable so we do not in any way
9734 : * guarantee that this goes anywhere. The function returns no
9735 : * feedback at all. We do not wait for a reply since at the time
9736 : * we send the message the listening server may be busy. The
9737 : * idea of this ping is just to make sure that if the backend is
9738 : * sleeping at the time, it wakes up sooner rather than later
9739 : * so it can immediately start processing the data we just added
9740 : * to Cassandra.
9741 : *
9742 : * The \p service_name is the name of the backend as it appears
9743 : * when you run the following command:
9744 : *
9745 : * \code
9746 : * snapbackend --action list
9747 : * \endcode
9748 : *
9749 : * At time of writing, we have the following action backends:
9750 : *
9751 : * \li images::images
9752 : * \li list::listjournal
9753 : * \li list::pagelist
9754 : * \li sendmail::sendmail
9755 : *
9756 : * Note that the CRON like backend is not listed here. It does not itself
9757 : * use a name. However, the list of backend will be longer, only most of
9758 : * the other names are just functions (actions) that can be run once and
9759 : * not an actual backend that will run until disabled.
9760 : *
9761 : * For example, the "permissions::makeroot" is used to mark a registered
9762 : * use as a root user on Snap! That action does not accept a PING since
9763 : * it runs once and quits immediately.
9764 : *
9765 : * \param[in] service_name The name of the backend (service) to ping.
9766 : */
9767 0 : void snap_child::udp_ping(char const * service_name)
9768 : {
9769 0 : server::pointer_t server(get_server());
9770 :
9771 : // the URI to be used with PING is the website URI, not the full page
9772 : // URI (otherwise it does not work as expected)
9773 : //
9774 0 : server->udp_ping_server(service_name, f_uri.get_website_uri());
9775 0 : }
9776 :
9777 :
9778 : /** \brief Check a tag
9779 : *
9780 : * This function determines whether the input named tag is an inline tag.
9781 : * Note that CSS can transform an inline tag in a block so the result of
9782 : * this function are only relatively correct.
9783 : *
9784 : * The function returns true for what is considered neutral tags. For example,
9785 : * the \<map\> and \<script\> tags are viewed as neutral. They do not affect
9786 : * the output just by their presence in the flow. (Although a script may
9787 : * affect the flow at run time by writing to it.)
9788 : *
9789 : * \param[in] tag The name of the tag in a C-string.
9790 : * \param[in] length Use -1 if \p tag is null terminated, otherwise the length of the string.
9791 : *
9792 : * \return true if the tag is considered to be inline by default.
9793 : */
9794 0 : bool snap_child::tag_is_inline(char const * tag, int length)
9795 : {
9796 0 : if(tag == nullptr)
9797 : {
9798 0 : throw snap_logic_exception("tag_is_inline() cannot be called with nullptr as the tag pointer");
9799 : }
9800 :
9801 0 : if(length < 0)
9802 : {
9803 0 : length = static_cast<int>(strlen(tag));
9804 : }
9805 :
9806 0 : switch(tag[0])
9807 : {
9808 0 : case 'a':
9809 : // <a>, <abbr>, <acronym>, <area>
9810 0 : if(length == 1
9811 0 : || strncmp(tag + 1, "bbr", length) == 0
9812 0 : || strncmp(tag + 1, "cronym", length) == 0 // deprecated in HTML 5
9813 0 : || strncmp(tag + 1, "rea", length) == 0)
9814 : {
9815 0 : return true;
9816 : }
9817 0 : break;
9818 :
9819 0 : case 'b':
9820 : // <b>, <basefont>, <bb>, <bdi>, <bdo>, <bgsound>, <big>, <blink>, <br>, <button>
9821 0 : if(length == 1
9822 0 : || strncmp(tag + 1, "asefont", length) == 0 // <basefont> deprecated in HTML 4.01
9823 0 : || (length == 2 && (tag[1] == 'b' || tag[1] == 'r'))
9824 0 : || strncmp(tag + 1, "di", length) == 0
9825 0 : || strncmp(tag + 1, "do", length) == 0
9826 0 : || strncmp(tag + 1, "ig", length) == 0 // <big> deprecated in HTML 5
9827 0 : || strncmp(tag + 1, "gsound", length) == 0 // <bgsound> deprecated in HTML 5
9828 0 : || strncmp(tag + 1, "link", length) == 0 // <blink> deprecated in HTML 5
9829 0 : || strncmp(tag + 1, "utton", length) == 0)
9830 : {
9831 0 : return true;
9832 : }
9833 0 : break;
9834 :
9835 0 : case 'c':
9836 : // <cite>, <code>, <command>
9837 0 : if(strncmp(tag + 1, "ite", length) == 0
9838 0 : || strncmp(tag + 1, "ode", length) == 0
9839 0 : || strncmp(tag + 1, "ommand", length) == 0)
9840 : {
9841 0 : return true;
9842 : }
9843 0 : break;
9844 :
9845 0 : case 'd':
9846 : // <data>, <del>, <dfn>
9847 0 : if(strncmp(tag + 1, "ata", length) == 0
9848 0 : || strncmp(tag + 1, "el", length) == 0
9849 0 : || strncmp(tag + 1, "fn", length) == 0)
9850 : {
9851 0 : return true;
9852 : }
9853 0 : break;
9854 :
9855 0 : case 'e':
9856 : // <em>
9857 0 : if(length == 2 && tag[1] == 'm')
9858 : {
9859 0 : return true;
9860 : }
9861 0 : break;
9862 :
9863 0 : case 'f':
9864 : // <font>
9865 0 : if(strncmp(tag + 1, "ont", length) == 0) // <font> deprecated in HTML 4.01
9866 : {
9867 0 : return true;
9868 : }
9869 0 : break;
9870 :
9871 0 : case 'i':
9872 : // <i>, <img>, <ins>, <isindex>
9873 0 : if(tag[1] == '\0'
9874 0 : || strncmp(tag + 1, "mg", length) == 0
9875 0 : || strncmp(tag + 1, "ns", length) == 0
9876 0 : || strncmp(tag + 1, "sindex", length) == 0) // <isindex> deprecated in HTML 4.01
9877 : {
9878 0 : return true;
9879 : }
9880 0 : break;
9881 :
9882 0 : case 'k':
9883 : // <kbd>
9884 0 : if(tag[1] == 'b' && tag[2] == 'd' && tag[3] == '\0')
9885 : {
9886 0 : return true;
9887 : }
9888 0 : break;
9889 :
9890 0 : case 'l':
9891 : // <label>
9892 0 : if(strncmp(tag + 1, "abel", length) == 0)
9893 : {
9894 0 : return true;
9895 : }
9896 0 : break;
9897 :
9898 0 : case 'm':
9899 : // <map>, <mark>, <meter>
9900 0 : if((tag[1] == 'a' && tag[2] == 'p' && tag[3] == '\0')
9901 0 : || strncmp(tag + 1, "ark", length) == 0
9902 0 : || strncmp(tag + 1, "eter", length) == 0)
9903 : {
9904 0 : return true;
9905 : }
9906 0 : break;
9907 :
9908 0 : case 'n':
9909 : // <nobr>
9910 0 : if(strncmp(tag + 1, "obr", length) == 0) // <nobr> deprecated in HTML 5
9911 : {
9912 0 : return true;
9913 : }
9914 0 : break;
9915 :
9916 0 : case 'p':
9917 : // <progress>
9918 0 : if(strncmp(tag + 1, "rogress", length) == 0)
9919 : {
9920 0 : return true;
9921 : }
9922 0 : break;
9923 :
9924 0 : case 'q':
9925 : // <q>
9926 0 : if(tag[1] == '\0')
9927 : {
9928 0 : return true;
9929 : }
9930 0 : break;
9931 :
9932 0 : case 'r':
9933 : // <rb>, <rp>, <rt>, <rtc>, <ruby>
9934 0 : if(((tag[1] == 'b' || tag[1] == 'p' || tag[1] == 't') && tag[2] == '\0')
9935 0 : || (tag[1] == 't' || tag[2] == 'c' || tag[3] == '\0')
9936 0 : || strncmp(tag + 1, "uby", length) == 0)
9937 : {
9938 0 : return true;
9939 : }
9940 0 : break;
9941 :
9942 0 : case 's':
9943 : // <s>, <samp>, <script>, <select>, <small>, <span>, <strike>, <strong>, <style>, <sub>, <sup>
9944 0 : if(length == 1 // <s> deprecated in HTML 4.01
9945 0 : || strncmp(tag + 1, "amp", length) == 0
9946 0 : || strncmp(tag + 1, "cript", length) == 0
9947 0 : || strncmp(tag + 1, "elect", length) == 0
9948 0 : || strncmp(tag + 1, "mall", length) == 0
9949 0 : || strncmp(tag + 1, "pan", length) == 0
9950 0 : || strncmp(tag + 1, "trike", length) == 0 // <strike> depreacated in HTML 4.01
9951 0 : || strncmp(tag + 1, "trong", length) == 0
9952 0 : || strncmp(tag + 1, "tyle", length) == 0
9953 0 : || strncmp(tag + 1, "ub", length) == 0
9954 0 : || strncmp(tag + 1, "up", length) == 0)
9955 : {
9956 0 : return true;
9957 : }
9958 0 : break;
9959 :
9960 0 : case 't':
9961 : // <time>, <tt>
9962 0 : if(strncmp(tag + 1, "ime", length) == 0
9963 0 : || (length == 2 && tag[1] == 't')) // <tt> deprecated in HTML 5
9964 : {
9965 0 : return true;
9966 : }
9967 0 : break;
9968 :
9969 0 : case 'u':
9970 : // <u>
9971 0 : if(length == 1) // <u> deprecated in HTML 4.01
9972 : {
9973 0 : return true;
9974 : }
9975 0 : break;
9976 :
9977 0 : case 'v':
9978 : // <var>
9979 0 : if(length == 3 && tag[1] == 'a' && tag[2] == 'r')
9980 : {
9981 0 : return true;
9982 : }
9983 0 : break;
9984 :
9985 0 : case 'w':
9986 : // <wbr>
9987 0 : if(length == 3 && tag[1] == 'b' && tag[2] == 'r') // somehow <wbr> is marked as obsolete in HTML 5... TBD
9988 : {
9989 0 : return true;
9990 : }
9991 0 : break;
9992 :
9993 : }
9994 :
9995 0 : return false;
9996 : }
9997 :
9998 :
9999 : /** \brief Debug function
10000 : *
10001 : * This function is just for debug purposes. It can be used to make sure
10002 : * that the resources you are expecting to exist are indeed available.
10003 : * You may find it with the wrong path, for example.
10004 : *
10005 : * \param[in] out An output stream such as std::err.
10006 : */
10007 0 : void snap_child::show_resources(std::ostream & out)
10008 : {
10009 0 : QDirIterator it(":", QDirIterator::Subdirectories);
10010 0 : while(it.hasNext())
10011 : {
10012 0 : out << it.next() << "\n";
10013 : }
10014 0 : }
10015 :
10016 :
10017 0 : void snap_child::extract_resource(QString const & resource_name, QString const & output_filename)
10018 : {
10019 : // TBD: should we make sure this is a resource?
10020 0 : QFile resource(resource_name);
10021 0 : if(!resource.open(QIODevice::ReadOnly))
10022 : {
10023 0 : die(snap_child::http_code_t::HTTP_CODE_INTERNAL_SERVER_ERROR,
10024 : "Resource Unavailable",
10025 0 : QString("Somehow resource \"%1\" could not be loaded.").arg(resource_name),
10026 : "The resource name is wrong, maybe the ':' is missing at the start?");
10027 0 : NOTREACHED();
10028 : }
10029 :
10030 : // read the entire file
10031 0 : QByteArray const data(resource.readAll());
10032 :
10033 : // create the output file
10034 0 : QFile out(output_filename);
10035 0 : if(!out.open(QIODevice::WriteOnly))
10036 : {
10037 0 : die(snap_child::http_code_t::HTTP_CODE_INTERNAL_SERVER_ERROR,
10038 : "I/O Error",
10039 0 : QString("Somehow we could not create output file \"%1\".").arg(output_filename),
10040 : "The resource name is wrong, maybe the ':' is missing at the start?");
10041 0 : NOTREACHED();
10042 : }
10043 :
10044 : // save the resource
10045 0 : out.write(data);
10046 :
10047 : // Qt closes both files automatically
10048 0 : }
10049 :
10050 :
10051 6 : } // namespace snap
10052 :
10053 : // vim: ts=4 sw=4 et
|