Commit e88c944b25a154d938edc31c91f017fa5c9660b7

Authored by Totoro
1 parent 5ee747e8

update the calexium lib to be compatible with anubis compiler 1.12.2.0. especial…

…ly replace alert by should_not_happen and "connect to file" by file
Remove obsolete file CXM_html.anubis
web/CXM_cookies.anubis
1 -  
2 - *Project* The Anubis Project  
3 -  
4 - *Title* Managing Cookies.  
5 -  
6 - *Copyright* Copyright (c) Alain Prouté 2001.  
7 -  
8 -  
9 - *Author* Alain Prouté  
10 -  
11 -  
12 -  
13 -  
14 -read tools/basis.anubis  
15 -read system/string.anubis  
16 -read CXM_common.anubis  
17 -read CXM_html.anubis  
18 -read CXM_http_get_common.anubis  
19 -  
20 -  
21 - *Overview*  
22 - Cookies are defined in RFC 2109. Here is the corresponding Anubis type:  
23 -  
24 - Each cookie has an server name (the name of the server who constructed the cookie), a  
25 - name, a value, and several attributes.  
26 -  
27 -public type Cookie:  
28 - cookie(String server_name, // of the server who sent the cookie  
29 - String name, // of the cookie  
30 - String value, // of the cookie  
31 - // attributes:  
32 - Maybe(String) comment, // cookies may have human readable comments  
33 - Maybe(String) domain, // domain name as sent by the server  
34 - Int validity, // cookie still valid if this is > now  
35 - Maybe(String) path, // server path for which the cookie is valid  
36 - Bool secure, // if true, do not send this cookie over an insecure link  
37 - Int version). // Cookie version (normally 1: rfc 2109)  
38 -  
39 -  
40 - Cookies are sent by servers through 'Set-Cookie' HTTP headers. The function  
41 - 'get_cookies' retrieves a list of cookies from a list of HTTP headers.  
42 -  
43 -public define List(Cookie)  
44 - get_cookies  
45 - (  
46 - String server_name, // name of server who sent the cookies  
47 - List(HTTP_header) headers // HTTP headers sent by this server  
48 - ).  
49 -  
50 -public define Maybe(Cookie)  
51 - find_cookie  
52 - (  
53 - String name,  
54 - List(Cookie) cookies  
55 - ).  
56 -  
57 -  
58 -  
59 -  
60 -  
61 - Normally, this function is used on the list of HTTP headers returned by either  
62 - 'http_get' of 'https_get'.  
63 -  
64 -  
65 - Before they can be sent back to their origin server, cookies must be reformated, in  
66 - order to produce 'Cookie' HTTP headers:  
67 -  
68 -public define List(HTTP_header)  
69 - reformat_cookies  
70 - (  
71 - String server_name,  
72 - String uri,  
73 - List(Cookie) cookies  
74 - ).  
75 -  
76 - The result of 'reformat_cookies' may be appended to the list of headers given as  
77 - argument to 'http_get' or to 'https_get'.  
78 -  
79 - In the meantime, you may examine and maybe discard cookies, you may 'save' them into a  
80 - file, and 'retrieve' them later.  
81 -  
82 -  
83 -  
84 -  
85 - ------- That all for the public part. -------------------------------------------------  
86 -  
87 -  
88 - Here is the syntax of a 'Set-Cookie' header (according to RFC 2109):  
89 -  
90 - set-cookie = "Set-Cookie:" cookies  
91 - cookies = 1#cookie  
92 - cookie = NAME "=" VALUE *(";" cookie-av)  
93 - NAME = token  
94 - VALUE = value  
95 - value = token | quoted-string  
96 - cookie-av = "Comment" "=" value  
97 - | "Domain" "=" value  
98 - | "Max-Age" "=" value  
99 - | "Path" "=" value  
100 - | "Secure"  
101 - | "Version" "=" 1*DIGIT  
102 -  
103 -  
104 - According to RFC 2616 (obsolating RFC 2068) defining HTTP 1.1, 'control characters' are  
105 - 0 to 31 and DEL (127). A 'separator' is one of:  
106 -  
107 - ( ) < > @ , ; : \ " / [ ] ? = { } 32(space) and 9(tab) "  
108 -  
109 - Now, a token is a non empty sequence of ASCII characters (0 to 127), but not including  
110 - any control character or any separator. As a consequence, characters admissible in a  
111 - 'RFC 2616 token' are:  
112 -  
113 - 33 !  
114 - 35 to 39 # $ & '  
115 - 42 43 * +  
116 - 45 46 - .  
117 - 48 to 57 0 ... 9  
118 - 65 to 90 A ... Z  
119 - 94 to 122 ^ _ ` a ... z  
120 - 124 126 | ~  
121 -  
122 -define Bool  
123 - is_token_char  
124 - (  
125 - Word8 c  
126 - ) =  
127 - if c +< 33 then false else  
128 - if c +< 34 then true else  
129 - if c +< 35 then false else  
130 - if c +< 40 then true else  
131 - if c +< 42 then false else  
132 - if c +< 44 then true else  
133 - if c +< 45 then false else  
134 - if c +< 47 then true else  
135 - if c +< 48 then false else  
136 - if c +< 58 then true else  
137 - if c +< 65 then false else  
138 - if c +< 91 then true else  
139 - if c +< 94 then false else  
140 - if c +< 123 then true else  
141 - if c +< 124 then false else  
142 - if c = 124 then true else  
143 - c = 126.  
144 -  
145 - value char are token added of =, (, )  
146 -  
147 -define Bool  
148 - is_value_char  
149 - (  
150 - Word8 c  
151 - ) =  
152 - if c +< 33 then false else  
153 - if c +< 34 then true else  
154 - if c +< 35 then false else  
155 - if c +< 44 then true else  
156 - if c +< 45 then false else  
157 - if c +< 47 then true else  
158 - if c +< 48 then false else  
159 - if c +< 58 then true else  
160 - if c +< 61 then false else  
161 - if c +< 62 then true else  
162 - if c +< 65 then false else  
163 - if c +< 91 then true else  
164 - if c +< 94 then false else  
165 - if c +< 123 then true else  
166 - if c +< 124 then false else  
167 - if c = 124 then true else  
168 - c = 126.  
169 -  
170 - From the grammar, it is clear that atomic entities (called 'tokens' by YACC) are:  
171 -  
172 - - tokens (in the sens of RFC 2616) some of which have to be recognized as keywords  
173 - - quoted strings  
174 - - equal sign  
175 - - colon  
176 - - semi-colon  
177 -  
178 - Hence, the following type:  
179 -  
180 -public type Atom:  
181 - end_of_input,  
182 - error,  
183 - comment,  
184 - domain,  
185 - max_age,  
186 - path,  
187 - secure,  
188 - version,  
189 - token(String),  
190 - quoted_string(String),  
191 - equals,  
192 - colon,  
193 - semi_colon.  
194 -  
195 -  
196 -variable List(Atom) unput_atoms = [].  
197 -  
198 -define One  
199 - unput_atom  
200 - (  
201 - Atom a  
202 - ) =  
203 - unput_atoms <- [a . *unput_atoms].  
204 -  
205 -define Atom  
206 - recognize_keyword  
207 - (  
208 - String s  
209 - ) =  
210 - with l = to_lower(s),  
211 - if l = "comment" then comment else  
212 - if l = "domain" then domain else  
213 - if l = "max-age" then max_age else  
214 - if l = "path" then path else  
215 - if l = "secure" then secure else  
216 - if l = "version" then version else  
217 - token(s).  
218 -  
219 -  
220 -variable String input = "". From which cookies will be read.  
221 -variable Int index = 0. Current position within 'input'.  
222 -  
223 -define Maybe(Word8)  
224 - next_char  
225 - =  
226 - if nth(*index,*input) is  
227 - {  
228 - failure then failure,  
229 - success(c) then  
230 - index <- *index+1;  
231 - success(c)  
232 - }.  
233 -  
234 -define One  
235 - unput_char  
236 - =  
237 - index <- *index-1.  
238 -  
239 -define Atom  
240 - read_token  
241 - (  
242 - List(Word8) so_far, // contains at least 1 character  
243 - (Word8) -> Bool is_valid_char  
244 - ) =  
245 - if next_char is  
246 - {  
247 - failure then recognize_keyword(implode(reverse(so_far))),  
248 - success(c) then  
249 - if is_valid_char(c)  
250 - then read_token([c . so_far], is_valid_char)  
251 - else unput_char; recognize_keyword(implode(reverse(so_far)))  
252 - }.  
253 -  
254 -define Atom  
255 - read_quoted_string  
256 - (  
257 - List(Word8) so_far  
258 - ) =  
259 - if next_char is  
260 - {  
261 - failure then quoted_string(implode(reverse(so_far))),  
262 - success(c) then  
263 - if c = '\"'  
264 - then quoted_string(implode(reverse(so_far)))  
265 - else read_quoted_string([c . so_far])  
266 - }.  
267 -  
268 -define Bool  
269 - is_blank  
270 - (  
271 - Word8 c  
272 - ) =  
273 - c +=< ' '.  
274 -  
275 - Reading an atom from the input:  
276 -  
277 -define Atom  
278 - read_atom  
279 - =  
280 - if *unput_atoms is  
281 - {  
282 - [ ] then  
283 - if next_char is  
284 - {  
285 - failure then end_of_input,  
286 - success(c) then  
287 - if is_blank(c) then read_atom else // skip blanks  
288 - if is_token_char(c) then read_token([c], is_token_char) else  
289 - if c = '\"' then read_quoted_string([]) else  
290 - if c = '=' then equals else  
291 - if c = ':' then colon else  
292 - if c = ';' then semi_colon else  
293 - error  
294 - },  
295 - [h . t] then  
296 - unput_atoms <- t; h  
297 - }.  
298 -  
299 -define Atom  
300 - read_value  
301 - =  
302 - if *unput_atoms is  
303 - {  
304 - [ ] then  
305 - if next_char is  
306 - {  
307 - failure then end_of_input,  
308 - success(c) then  
309 - if is_blank(c) then read_value else // skip blanks  
310 - if is_value_char(c) then read_token([c], is_value_char) else  
311 - if c = '\"' then read_quoted_string([]) else  
312 - if c = ';' then semi_colon else  
313 - error  
314 - },  
315 - [h . t] then  
316 - unput_atoms <- t; h  
317 - }.  
318 -  
319 - Reading an attribute-value pair.  
320 -  
321 -type AttrVal:  
322 - comment(String),  
323 - domain(String),  
324 - max_age(String),  
325 - path(String),  
326 - secure,  
327 - version(String).  
328 -  
329 -define String  
330 - read_eq_value  
331 - =  
332 - with e = read_atom,  
333 - if e is equals then  
334 - (  
335 - with a = read_atom,  
336 - if a is token(n) then n else  
337 - if a is quoted_string(s) then s else  
338 - unput_atom(a); ""  
339 - )  
340 - else unput_atom(e); "".  
341 -  
342 -  
343 -define Maybe(AttrVal)  
344 - read_attr_val  
345 - =  
346 - if read_atom is semi_colon then  
347 - with a = read_atom,  
348 - if a is  
349 - {  
350 - end_of_input then failure,  
351 - error then failure,  
352 - comment then success(comment(read_eq_value)),  
353 - domain then success(domain(read_eq_value)),  
354 - max_age then success(max_age(read_eq_value)),  
355 - path then success(path(read_eq_value)),  
356 - secure then success(secure),  
357 - version then success(version(read_eq_value)),  
358 - token(_) then unput_atom(a); failure,  
359 - quoted_string(_) then unput_atom(a); failure,  
360 - equals then unput_atom(a); failure,  
361 - colon then unput_atom(a); failure,  
362 - semi_colon then unput_atom(a); failure,  
363 - }  
364 - else failure.  
365 -  
366 -  
367 - Getting attributes from a List(AttrVal).  
368 -  
369 -define Maybe(String)  
370 - get_comment  
371 - (  
372 - List(AttrVal) l  
373 - ) =  
374 - if l is  
375 - {  
376 - [ ] then failure,  
377 - [h . t] then if h is comment(c)  
378 - then success(c)  
379 - else get_comment(t)  
380 - }.  
381 -  
382 -define Maybe(String)  
383 - get_domain  
384 - (  
385 - List(AttrVal) l  
386 - ) =  
387 - if l is  
388 - {  
389 - [ ] then failure,  
390 - [h . t] then if h is domain(s)  
391 - then success(s)  
392 - else get_domain(t)  
393 - }.  
394 -  
395 -define Int  
396 - get_validity  
397 - (  
398 - List(AttrVal) l  
399 - ) =  
400 - if l is  
401 - {  
402 - [ ] then 0,  
403 - [h . t] then if h is max_age(a)  
404 - then if decimal_scan(a) is  
405 - {  
406 - failure then 0,  
407 - success(n) then n+now  
408 - }  
409 - else get_validity(t)  
410 - }.  
411 -  
412 -define Maybe(String)  
413 - get_path  
414 - (  
415 - List(AttrVal) l  
416 - ) =  
417 - if l is  
418 - {  
419 - [ ] then failure,  
420 - [h . t] then if h is path(p)  
421 - then success(p)  
422 - else get_path(t)  
423 - }.  
424 -  
425 -define Bool  
426 - get_secure  
427 - (  
428 - List(AttrVal) l  
429 - ) =  
430 - if l is  
431 - {  
432 - [ ] then false,  
433 - [h . t] then if h is secure  
434 - then true  
435 - else get_secure(t)  
436 - }.  
437 -  
438 -define Int  
439 - get_version  
440 - (  
441 - List(AttrVal) l  
442 - ) =  
443 - if l is  
444 - {  
445 - [ ] then 0,  
446 - [h . t] then if h is version(v)  
447 - then if decimal_scan(v) is  
448 - {  
449 - failure then 0,  
450 - success(n) then n  
451 - }  
452 - else get_version(t)  
453 - }.  
454 -  
455 -  
456 - Reading a cookie:  
457 -  
458 -variable String server_name = "".  
459 -  
460 -define Maybe(Cookie)  
461 - read_cookie_n_e_v  
462 - (  
463 - String name,  
464 - String value,  
465 - List(AttrVal) so_far  
466 - ) =  
467 - if read_attr_val is  
468 - {  
469 - failure then  
470 - success(cookie(  
471 - *server_name,  
472 - name,  
473 - value,  
474 - get_comment(so_far),  
475 - get_domain(so_far),  
476 - get_validity(so_far),  
477 - get_path(so_far),  
478 - get_secure(so_far),  
479 - get_version(so_far)  
480 - )),  
481 -  
482 - success(av) then read_cookie_n_e_v(name,value,[av . so_far])  
483 - }.  
484 -  
485 -define Maybe(Cookie)  
486 - read_cookie_n_e  
487 - (  
488 - String name  
489 - ) =  
490 - with a = read_value,  
491 -  
492 - if a is token(value) then read_cookie_n_e_v(name,value,[]) else  
493 - if a is quoted_string(value) then read_cookie_n_e_v(name,value,[]) else  
494 - unput_atom(a); failure.  
495 -  
496 -define Maybe(Cookie)  
497 - read_cookie_n  
498 - (  
499 - String name  
500 - ) =  
501 - with a = read_atom,  
502 - if a is equals  
503 - then read_cookie_n_e(name)  
504 - else unput_atom(a); failure.  
505 -  
506 -  
507 -define Maybe(Cookie)  
508 - read_cookie  
509 - =  
510 - with a = read_atom,  
511 - if a is token(name)  
512 - then read_cookie_n(name)  
513 - else unput_atom(a); failure.  
514 -  
515 -  
516 -define List(Cookie)  
517 - read_cookies  
518 - (  
519 - List(Cookie) so_far  
520 - ) =  
521 - if read_cookie is  
522 - {  
523 - failure then so_far,  
524 - success(c) then read_cookies([c . so_far])  
525 - }.  
526 -  
527 -  
528 -define List(Cookie)  
529 - get_cookies  
530 - (  
531 - String svn,  
532 - HTTP_header h  
533 - ) =  
534 - if h is http_header(n,v) then  
535 - if to_lower(n) = "set-cookie"  
536 - then (  
537 - unput_atoms <- [];  
538 - input <- v;  
539 - index <- 0;  
540 - server_name <- svn;  
541 - read_cookies([])  
542 - )  
543 - else [].  
544 -  
545 -public define List(Cookie)  
546 - get_cookies  
547 - (  
548 - String server_name,  
549 - List(HTTP_header) headers  
550 - ) =  
551 - if headers is  
552 - {  
553 - [ ] then [ ],  
554 - [h . t] then  
555 - append(get_cookies(server_name,h),get_cookies(server_name,t))  
556 - }.  
557 -  
558 -define List(Cookie)  
559 - server_get_cookies  
560 - (  
561 - HTTP_header h  
562 - ) =  
563 - if h is http_header(n,v) then  
564 - if to_lower(n) = "cookie"  
565 - then (  
566 - unput_atoms <- [];  
567 - input <- v;  
568 - index <- 0;  
569 - server_name <- "";  
570 - read_cookies([])  
571 - )  
572 - else [].  
573 -  
574 -public define List(Cookie)  
575 - server_get_cookies  
576 - (  
577 - // String server_name,  
578 - List(HTTP_header) headers  
579 - ) =  
580 - if headers is  
581 - {  
582 - [ ] then [ ],  
583 - [h . t] then  
584 - append(server_get_cookies(h), server_get_cookies(t))  
585 - }.  
586 -  
587 -public define Maybe(Cookie)  
588 - find_cookie  
589 - (  
590 - String name,  
591 - List(Cookie) cookies  
592 - )  
593 - =  
594 - if cookies is  
595 - {  
596 - [] then failure,  
597 - [h . t] then  
598 - if h is cookie(s, n, v, _, _, _, _, _, _) then  
599 - if name = n then success(h)  
600 - else find_cookie(name, t)  
601 - }.  
602 -  
603 -public define String  
604 - get_cookie_value  
605 - (  
606 - String name,  
607 - List(Cookie) cookies  
608 - )  
609 - =  
610 - if find_cookie(name, cookies) is  
611 - {  
612 - failure then "",  
613 - success(c) then if c is cookie(_, _, v, _, _, _, _, _, _) then v  
614 - }.  
615 -  
616 - *** Reformating cookies. **************************************************************  
617 -  
618 - Cookies should be resent reformated according to the following grammar (copy-pasted  
619 - from RFC 2109):  
620 -  
621 - cookie = "Cookie:" cookie-version  
622 - 1*((";" | ",") cookie-value)  
623 - cookie-value = NAME "=" VALUE [";" path] [";" domain]  
624 - cookie-version = "$Version" "=" value  
625 - NAME = attr  
626 - VALUE = value  
627 - path = "$Path" "=" value  
628 - domain = "$Domain" "=" value  
629 -  
630 -  
631 -define HTTP_header  
632 - reformat_cookie  
633 - (  
634 - Cookie c  
635 - ) =  
636 - if c is cookie(sn,n,v,mbc,mbd,vld,mbp,sec,ver) then  
637 - http_header("Cookie",  
638 - "$Version=" + to_decimal(ver) +  
639 - ";" + n + "=\"" + v + "\"" +  
640 - if mbp is  
641 - {  
642 - failure then "",  
643 - success(p) then ";$Path=\"" + p + "\""  
644 - } +  
645 - if mbd is  
646 - {  
647 - failure then "",  
648 - success(d) then ";$Domain=\"" + d + "\""  
649 - }  
650 - ).  
651 -  
652 -  
653 - According to RFC 2109, a cookie may be sent to a server if:  
654 -  
655 - (1) server name in the cookie is the name of the server,  
656 - (2) if 'Path' attribute is present, its value must match the URI,  
657 - (3) the cookie is still valid (validity = 0 means indefinitely valid).  
658 -  
659 - define Bool  
660 - path_match  
661 - (  
662 - Maybe(String) cookie_path,  
663 - String uri  
664 - ) =  
665 - if cookie_path is  
666 - {  
667 - failure then true,  
668 - success(p) then  
669 -  
670 - }.  
671 -  
672 -  
673 - Checking if the path matches:  
674 -  
675 -  
676 -define Bool  
677 - path_match  
678 - (  
679 - Maybe(String) mbp,  
680 - String uri  
681 - ) =  
682 - true.  
683 -  
684 -  
685 - The next function verifies if a cookie satisfies the rules.  
686 -  
687 -define Bool  
688 - may_resend_cookie  
689 - (  
690 - String server_name,  
691 - String uri,  
692 - Cookie c  
693 - ) =  
694 - if c is cookie(sn,n,v,mbc,mbd,vld,mbp,sec,ver) then  
695 - if sn = server_name  
696 - then (  
697 - if path_match(mbp,uri)  
698 - then (  
699 - if vld = 0 then true else vld > now  
700 - )  
701 - else false  
702 - )  
703 - else false.  
704 -  
705 -  
706 - The next function reformat all cookies which satisfy the 'resend' rules.  
707 -  
708 -public define List(HTTP_header)  
709 - reformat_cookies  
710 - (  
711 - String server_name,  
712 - String uri,  
713 - List(Cookie) cookies  
714 - ) =  
715 - if cookies is  
716 - {  
717 - [ ] then [ ],  
718 - [h . t] then  
719 - if may_resend_cookie(server_name,uri,h)  
720 - then [reformat_cookie(h) . reformat_cookies(server_name,uri,t)]  
721 - else reformat_cookies(server_name,uri,t)  
722 - }.  
723 -  
724 -  
725 -  
726 -  
727 -  
728 - See test_cookies.anubis for a test of this program.  
729 -  
730 -  
731 -  
732 -  
733 -  
734 -  
735 -  
736 -  
737 - 1 +
  2 + *Project* The Anubis Project
  3 +
  4 + *Title* Managing Cookies.
  5 +
  6 + *Copyright* Copyright (c) Alain Prouté 2001.
  7 +
  8 +
  9 + *Author* Alain Prouté
  10 +
  11 +
  12 +
  13 +
  14 +read tools/basis.anubis
  15 +read system/string.anubis
  16 +read CXM_common.anubis
  17 +read CXM_http_get_common.anubis
  18 +
  19 +
  20 + *Overview*
  21 + Cookies are defined in RFC 2109. Here is the corresponding Anubis type:
  22 +
  23 + Each cookie has an server name (the name of the server who constructed the cookie), a
  24 + name, a value, and several attributes.
  25 +
  26 +public type Cookie:
  27 + cookie(String server_name, // of the server who sent the cookie
  28 + String name, // of the cookie
  29 + String value, // of the cookie
  30 + // attributes:
  31 + Maybe(String) comment, // cookies may have human readable comments
  32 + Maybe(String) domain, // domain name as sent by the server
  33 + Int validity, // cookie still valid if this is > now
  34 + Maybe(String) path, // server path for which the cookie is valid
  35 + Bool secure, // if true, do not send this cookie over an insecure link
  36 + Int version). // Cookie version (normally 1: rfc 2109)
  37 +
  38 +
  39 + Cookies are sent by servers through 'Set-Cookie' HTTP headers. The function
  40 + 'get_cookies' retrieves a list of cookies from a list of HTTP headers.
  41 +
  42 +public define List(Cookie)
  43 + get_cookies
  44 + (
  45 + String server_name, // name of server who sent the cookies
  46 + List(HTTP_header) headers // HTTP headers sent by this server
  47 + ).
  48 +
  49 +public define Maybe(Cookie)
  50 + find_cookie
  51 + (
  52 + String name,
  53 + List(Cookie) cookies
  54 + ).
  55 +
  56 +
  57 +
  58 +
  59 +
  60 + Normally, this function is used on the list of HTTP headers returned by either
  61 + 'http_get' of 'https_get'.
  62 +
  63 +
  64 + Before they can be sent back to their origin server, cookies must be reformated, in
  65 + order to produce 'Cookie' HTTP headers:
  66 +
  67 +public define List(HTTP_header)
  68 + reformat_cookies
  69 + (
  70 + String server_name,
  71 + String uri,
  72 + List(Cookie) cookies
  73 + ).
  74 +
  75 + The result of 'reformat_cookies' may be appended to the list of headers given as
  76 + argument to 'http_get' or to 'https_get'.
  77 +
  78 + In the meantime, you may examine and maybe discard cookies, you may 'save' them into a
  79 + file, and 'retrieve' them later.
  80 +
  81 +
  82 +
  83 +
  84 + ------- That all for the public part. -------------------------------------------------
  85 +
  86 +
  87 + Here is the syntax of a 'Set-Cookie' header (according to RFC 2109):
  88 +
  89 + set-cookie = "Set-Cookie:" cookies
  90 + cookies = 1#cookie
  91 + cookie = NAME "=" VALUE *(";" cookie-av)
  92 + NAME = token
  93 + VALUE = value
  94 + value = token | quoted-string
  95 + cookie-av = "Comment" "=" value
  96 + | "Domain" "=" value
  97 + | "Max-Age" "=" value
  98 + | "Path" "=" value
  99 + | "Secure"
  100 + | "Version" "=" 1*DIGIT
  101 +
  102 +
  103 + According to RFC 2616 (obsolating RFC 2068) defining HTTP 1.1, 'control characters' are
  104 + 0 to 31 and DEL (127). A 'separator' is one of:
  105 +
  106 + ( ) < > @ , ; : \ " / [ ] ? = { } 32(space) and 9(tab) "
  107 +
  108 + Now, a token is a non empty sequence of ASCII characters (0 to 127), but not including
  109 + any control character or any separator. As a consequence, characters admissible in a
  110 + 'RFC 2616 token' are:
  111 +
  112 + 33 !
  113 + 35 to 39 # $ & '
  114 + 42 43 * +
  115 + 45 46 - .
  116 + 48 to 57 0 ... 9
  117 + 65 to 90 A ... Z
  118 + 94 to 122 ^ _ ` a ... z
  119 + 124 126 | ~
  120 +
  121 +define Bool
  122 + is_token_char
  123 + (
  124 + Word8 c
  125 + ) =
  126 + if c +< 33 then false else
  127 + if c +< 34 then true else
  128 + if c +< 35 then false else
  129 + if c +< 40 then true else
  130 + if c +< 42 then false else
  131 + if c +< 44 then true else
  132 + if c +< 45 then false else
  133 + if c +< 47 then true else
  134 + if c +< 48 then false else
  135 + if c +< 58 then true else
  136 + if c +< 65 then false else
  137 + if c +< 91 then true else
  138 + if c +< 94 then false else
  139 + if c +< 123 then true else
  140 + if c +< 124 then false else
  141 + if c = 124 then true else
  142 + c = 126.
  143 +
  144 + value char are token added of =, (, )
  145 +
  146 +define Bool
  147 + is_value_char
  148 + (
  149 + Word8 c
  150 + ) =
  151 + if c +< 33 then false else
  152 + if c +< 34 then true else
  153 + if c +< 35 then false else
  154 + if c +< 44 then true else
  155 + if c +< 45 then false else
  156 + if c +< 47 then true else
  157 + if c +< 48 then false else
  158 + if c +< 58 then true else
  159 + if c +< 61 then false else
  160 + if c +< 62 then true else
  161 + if c +< 65 then false else
  162 + if c +< 91 then true else
  163 + if c +< 94 then false else
  164 + if c +< 123 then true else
  165 + if c +< 124 then false else
  166 + if c = 124 then true else
  167 + c = 126.
  168 +
  169 + From the grammar, it is clear that atomic entities (called 'tokens' by YACC) are:
  170 +
  171 + - tokens (in the sens of RFC 2616) some of which have to be recognized as keywords
  172 + - quoted strings
  173 + - equal sign
  174 + - colon
  175 + - semi-colon
  176 +
  177 + Hence, the following type:
  178 +
  179 +public type Atom:
  180 + end_of_input,
  181 + error,
  182 + comment,
  183 + domain,
  184 + max_age,
  185 + path,
  186 + secure,
  187 + version,
  188 + token(String),
  189 + quoted_string(String),
  190 + equals,
  191 + colon,
  192 + semi_colon.
  193 +
  194 +
  195 +variable List(Atom) unput_atoms = [].
  196 +
  197 +define One
  198 + unput_atom
  199 + (
  200 + Atom a
  201 + ) =
  202 + unput_atoms <- [a . *unput_atoms].
  203 +
  204 +define Atom
  205 + recognize_keyword
  206 + (
  207 + String s
  208 + ) =
  209 + with l = to_lower(s),
  210 + if l = "comment" then comment else
  211 + if l = "domain" then domain else
  212 + if l = "max-age" then max_age else
  213 + if l = "path" then path else
  214 + if l = "secure" then secure else
  215 + if l = "version" then version else
  216 + token(s).
  217 +
  218 +
  219 +variable String input = "". From which cookies will be read.
  220 +variable Int index = 0. Current position within 'input'.
  221 +
  222 +define Maybe(Word8)
  223 + next_char
  224 + =
  225 + if nth(*index,*input) is
  226 + {
  227 + failure then failure,
  228 + success(c) then
  229 + index <- *index+1;
  230 + success(c)
  231 + }.
  232 +
  233 +define One
  234 + unput_char
  235 + =
  236 + index <- *index-1.
  237 +
  238 +define Atom
  239 + read_token
  240 + (
  241 + List(Word8) so_far, // contains at least 1 character
  242 + (Word8) -> Bool is_valid_char
  243 + ) =
  244 + if next_char is
  245 + {
  246 + failure then recognize_keyword(implode(reverse(so_far))),
  247 + success(c) then
  248 + if is_valid_char(c)
  249 + then read_token([c . so_far], is_valid_char)
  250 + else unput_char; recognize_keyword(implode(reverse(so_far)))
  251 + }.
  252 +
  253 +define Atom
  254 + read_quoted_string
  255 + (
  256 + List(Word8) so_far
  257 + ) =
  258 + if next_char is
  259 + {
  260 + failure then quoted_string(implode(reverse(so_far))),
  261 + success(c) then
  262 + if c = '\"'
  263 + then quoted_string(implode(reverse(so_far)))
  264 + else read_quoted_string([c . so_far])
  265 + }.
  266 +
  267 +define Bool
  268 + is_blank
  269 + (
  270 + Word8 c
  271 + ) =
  272 + c +=< ' '.
  273 +
  274 + Reading an atom from the input:
  275 +
  276 +define Atom
  277 + read_atom
  278 + =
  279 + if *unput_atoms is
  280 + {
  281 + [ ] then
  282 + if next_char is
  283 + {
  284 + failure then end_of_input,
  285 + success(c) then
  286 + if is_blank(c) then read_atom else // skip blanks
  287 + if is_token_char(c) then read_token([c], is_token_char) else
  288 + if c = '\"' then read_quoted_string([]) else
  289 + if c = '=' then equals else
  290 + if c = ':' then colon else
  291 + if c = ';' then semi_colon else
  292 + error
  293 + },
  294 + [h . t] then
  295 + unput_atoms <- t; h
  296 + }.
  297 +
  298 +define Atom
  299 + read_value
  300 + =
  301 + if *unput_atoms is
  302 + {
  303 + [ ] then
  304 + if next_char is
  305 + {
  306 + failure then end_of_input,
  307 + success(c) then
  308 + if is_blank(c) then read_value else // skip blanks
  309 + if is_value_char(c) then read_token([c], is_value_char) else
  310 + if c = '\"' then read_quoted_string([]) else
  311 + if c = ';' then semi_colon else
  312 + error
  313 + },
  314 + [h . t] then
  315 + unput_atoms <- t; h
  316 + }.
  317 +
  318 + Reading an attribute-value pair.
  319 +
  320 +type AttrVal:
  321 + comment(String),
  322 + domain(String),
  323 + max_age(String),
  324 + path(String),
  325 + secure,
  326 + version(String).
  327 +
  328 +define String
  329 + read_eq_value
  330 + =
  331 + with e = read_atom,
  332 + if e is equals then
  333 + (
  334 + with a = read_atom,
  335 + if a is token(n) then n else
  336 + if a is quoted_string(s) then s else
  337 + unput_atom(a); ""
  338 + )
  339 + else unput_atom(e); "".
  340 +
  341 +
  342 +define Maybe(AttrVal)
  343 + read_attr_val
  344 + =
  345 + if read_atom is semi_colon then
  346 + with a = read_atom,
  347 + if a is
  348 + {
  349 + end_of_input then failure,
  350 + error then failure,
  351 + comment then success(comment(read_eq_value)),
  352 + domain then success(domain(read_eq_value)),
  353 + max_age then success(max_age(read_eq_value)),
  354 + path then success(path(read_eq_value)),
  355 + secure then success(secure),
  356 + version then success(version(read_eq_value)),
  357 + token(_) then unput_atom(a); failure,
  358 + quoted_string(_) then unput_atom(a); failure,
  359 + equals then unput_atom(a); failure,
  360 + colon then unput_atom(a); failure,
  361 + semi_colon then unput_atom(a); failure,
  362 + }
  363 + else failure.
  364 +
  365 +
  366 + Getting attributes from a List(AttrVal).
  367 +
  368 +define Maybe(String)
  369 + get_comment
  370 + (
  371 + List(AttrVal) l
  372 + ) =
  373 + if l is
  374 + {
  375 + [ ] then failure,
  376 + [h . t] then if h is comment(c)
  377 + then success(c)
  378 + else get_comment(t)
  379 + }.
  380 +
  381 +define Maybe(String)
  382 + get_domain
  383 + (
  384 + List(AttrVal) l
  385 + ) =
  386 + if l is
  387 + {
  388 + [ ] then failure,
  389 + [h . t] then if h is domain(s)
  390 + then success(s)
  391 + else get_domain(t)
  392 + }.
  393 +
  394 +define Int
  395 + get_validity
  396 + (
  397 + List(AttrVal) l
  398 + ) =
  399 + if l is
  400 + {
  401 + [ ] then 0,
  402 + [h . t] then if h is max_age(a)
  403 + then if decimal_scan(a) is
  404 + {
  405 + failure then 0,
  406 + success(n) then n+now
  407 + }
  408 + else get_validity(t)
  409 + }.
  410 +
  411 +define Maybe(String)
  412 + get_path
  413 + (
  414 + List(AttrVal) l
  415 + ) =
  416 + if l is
  417 + {
  418 + [ ] then failure,
  419 + [h . t] then if h is path(p)
  420 + then success(p)
  421 + else get_path(t)
  422 + }.
  423 +
  424 +define Bool
  425 + get_secure
  426 + (
  427 + List(AttrVal) l
  428 + ) =
  429 + if l is
  430 + {
  431 + [ ] then false,
  432 + [h . t] then if h is secure
  433 + then true
  434 + else get_secure(t)
  435 + }.
  436 +
  437 +define Int
  438 + get_version
  439 + (
  440 + List(AttrVal) l
  441 + ) =
  442 + if l is
  443 + {
  444 + [ ] then 0,
  445 + [h . t] then if h is version(v)
  446 + then if decimal_scan(v) is
  447 + {
  448 + failure then 0,
  449 + success(n) then n
  450 + }
  451 + else get_version(t)
  452 + }.
  453 +
  454 +
  455 + Reading a cookie:
  456 +
  457 +variable String server_name = "".
  458 +
  459 +define Maybe(Cookie)
  460 + read_cookie_n_e_v
  461 + (
  462 + String name,
  463 + String value,
  464 + List(AttrVal) so_far
  465 + ) =
  466 + if read_attr_val is
  467 + {
  468 + failure then
  469 + success(cookie(
  470 + *server_name,
  471 + name,
  472 + value,
  473 + get_comment(so_far),
  474 + get_domain(so_far),
  475 + get_validity(so_far),
  476 + get_path(so_far),
  477 + get_secure(so_far),
  478 + get_version(so_far)
  479 + )),
  480 +
  481 + success(av) then read_cookie_n_e_v(name,value,[av . so_far])
  482 + }.
  483 +
  484 +define Maybe(Cookie)
  485 + read_cookie_n_e
  486 + (
  487 + String name
  488 + ) =
  489 + with a = read_value,
  490 +
  491 + if a is token(value) then read_cookie_n_e_v(name,value,[]) else
  492 + if a is quoted_string(value) then read_cookie_n_e_v(name,value,[]) else
  493 + unput_atom(a); failure.
  494 +
  495 +define Maybe(Cookie)
  496 + read_cookie_n
  497 + (
  498 + String name
  499 + ) =
  500 + with a = read_atom,
  501 + if a is equals
  502 + then read_cookie_n_e(name)
  503 + else unput_atom(a); failure.
  504 +
  505 +
  506 +define Maybe(Cookie)
  507 + read_cookie
  508 + =
  509 + with a = read_atom,
  510 + if a is token(name)
  511 + then read_cookie_n(name)
  512 + else unput_atom(a); failure.
  513 +
  514 +
  515 +define List(Cookie)
  516 + read_cookies
  517 + (
  518 + List(Cookie) so_far
  519 + ) =
  520 + if read_cookie is
  521 + {
  522 + failure then so_far,
  523 + success(c) then read_cookies([c . so_far])
  524 + }.
  525 +
  526 +
  527 +define List(Cookie)
  528 + get_cookies
  529 + (
  530 + String svn,
  531 + HTTP_header h
  532 + ) =
  533 + if h is http_header(n,v) then
  534 + if to_lower(n) = "set-cookie"
  535 + then (
  536 + unput_atoms <- [];
  537 + input <- v;
  538 + index <- 0;
  539 + server_name <- svn;
  540 + read_cookies([])
  541 + )
  542 + else [].
  543 +
  544 +public define List(Cookie)
  545 + get_cookies
  546 + (
  547 + String server_name,
  548 + List(HTTP_header) headers
  549 + ) =
  550 + if headers is
  551 + {
  552 + [ ] then [ ],
  553 + [h . t] then
  554 + append(get_cookies(server_name,h),get_cookies(server_name,t))
  555 + }.
  556 +
  557 +define List(Cookie)
  558 + server_get_cookies
  559 + (
  560 + HTTP_header h
  561 + ) =
  562 + if h is http_header(n,v) then
  563 + if to_lower(n) = "cookie"
  564 + then (
  565 + unput_atoms <- [];
  566 + input <- v;
  567 + index <- 0;
  568 + server_name <- "";
  569 + read_cookies([])
  570 + )
  571 + else [].
  572 +
  573 +public define List(Cookie)
  574 + server_get_cookies
  575 + (
  576 + // String server_name,
  577 + List(HTTP_header) headers
  578 + ) =
  579 + if headers is
  580 + {
  581 + [ ] then [ ],
  582 + [h . t] then
  583 + append(server_get_cookies(h), server_get_cookies(t))
  584 + }.
  585 +
  586 +public define Maybe(Cookie)
  587 + find_cookie
  588 + (
  589 + String name,
  590 + List(Cookie) cookies
  591 + )
  592 + =
  593 + if cookies is
  594 + {
  595 + [] then failure,
  596 + [h . t] then
  597 + if h is cookie(s, n, v, _, _, _, _, _, _) then
  598 + if name = n then success(h)
  599 + else find_cookie(name, t)
  600 + }.
  601 +
  602 +public define String
  603 + get_cookie_value
  604 + (
  605 + String name,
  606 + List(Cookie) cookies
  607 + )
  608 + =
  609 + if find_cookie(name, cookies) is
  610 + {
  611 + failure then "",
  612 + success(c) then if c is cookie(_, _, v, _, _, _, _, _, _) then v
  613 + }.
  614 +
  615 + *** Reformating cookies. **************************************************************
  616 +
  617 + Cookies should be resent reformated according to the following grammar (copy-pasted
  618 + from RFC 2109):
  619 +
  620 + cookie = "Cookie:" cookie-version
  621 + 1*((";" | ",") cookie-value)
  622 + cookie-value = NAME "=" VALUE [";" path] [";" domain]
  623 + cookie-version = "$Version" "=" value
  624 + NAME = attr
  625 + VALUE = value
  626 + path = "$Path" "=" value
  627 + domain = "$Domain" "=" value
  628 +
  629 +
  630 +define HTTP_header
  631 + reformat_cookie
  632 + (
  633 + Cookie c
  634 + ) =
  635 + if c is cookie(sn,n,v,mbc,mbd,vld,mbp,sec,ver) then
  636 + http_header("Cookie",
  637 + "$Version=" + to_decimal(ver) +
  638 + ";" + n + "=\"" + v + "\"" +
  639 + if mbp is
  640 + {
  641 + failure then "",
  642 + success(p) then ";$Path=\"" + p + "\""
  643 + } +
  644 + if mbd is
  645 + {
  646 + failure then "",
  647 + success(d) then ";$Domain=\"" + d + "\""
  648 + }
  649 + ).
  650 +
  651 +
  652 + According to RFC 2109, a cookie may be sent to a server if:
  653 +
  654 + (1) server name in the cookie is the name of the server,
  655 + (2) if 'Path' attribute is present, its value must match the URI,
  656 + (3) the cookie is still valid (validity = 0 means indefinitely valid).
  657 +
  658 + define Bool
  659 + path_match
  660 + (
  661 + Maybe(String) cookie_path,
  662 + String uri
  663 + ) =
  664 + if cookie_path is
  665 + {
  666 + failure then true,
  667 + success(p) then
  668 +
  669 + }.
  670 +
  671 +
  672 + Checking if the path matches:
  673 +
  674 +
  675 +define Bool
  676 + path_match
  677 + (
  678 + Maybe(String) mbp,
  679 + String uri
  680 + ) =
  681 + true.
  682 +
  683 +
  684 + The next function verifies if a cookie satisfies the rules.
  685 +
  686 +define Bool
  687 + may_resend_cookie
  688 + (
  689 + String server_name,
  690 + String uri,
  691 + Cookie c
  692 + ) =
  693 + if c is cookie(sn,n,v,mbc,mbd,vld,mbp,sec,ver) then
  694 + if sn = server_name
  695 + then (
  696 + if path_match(mbp,uri)
  697 + then (
  698 + if vld = 0 then true else vld > now
  699 + )
  700 + else false
  701 + )
  702 + else false.
  703 +
  704 +
  705 + The next function reformat all cookies which satisfy the 'resend' rules.
  706 +
  707 +public define List(HTTP_header)
  708 + reformat_cookies
  709 + (
  710 + String server_name,
  711 + String uri,
  712 + List(Cookie) cookies
  713 + ) =
  714 + if cookies is
  715 + {
  716 + [ ] then [ ],
  717 + [h . t] then
  718 + if may_resend_cookie(server_name,uri,h)
  719 + then [reformat_cookie(h) . reformat_cookies(server_name,uri,t)]
  720 + else reformat_cookies(server_name,uri,t)
  721 + }.
  722 +
  723 +
  724 +
  725 +
  726 +
  727 + See test_cookies.anubis for a test of this program.
  728 +
  729 +
  730 +
  731 +
  732 +
  733 +
  734 +
  735 +
  736 +
web/CXM_html.anubis deleted
1 -  
2 - *Project* The Anubis Project  
3 - *Title* Producing HTML/Javascript code.  
4 -  
5 - *Copyright* Copyright (c) Alain Prouté 2001.  
6 -  
7 -read tools/basis.anubis  
8 -read system/string.anubis  
9 -read tools/printable_tree.anubis  
10 -  
11 -  
12 - *** Managing Web Arguments.  
13 -  
14 - When a client submits a form, he sends informations to the server. This information is  
15 - transformed by the server into a list of data of type 'Web_arg'. This is the reason why  
16 - a 'web page' operation always has a unique argument of type 'List(Web_arg)'.  
17 -  
18 - The type 'Web_arg' is defined in 'web/common.anubis' as follows:  
19 -  
20 - public type Web_arg:  
21 - web_arg(String name,  
22 - String value),  
23 - upload (String name,  
24 - String value,  
25 - String temp_file_path).  
26 -  
27 -read CXM_common.anubis  
28 -  
29 - In other words, a 'web argument' is just a pair made of the name of the argument, and  
30 - the value of the argument, and both are character strings. 'upload' will be explained  
31 - later.  
32 -  
33 -  
34 - The next variable is a multipurpose counter (used to generate unique names).  
35 -  
36 -variable Int web_count = 0.  
37 -  
38 -define Int  
39 - new_web_count  
40 - =  
41 - web_count <- *web_count+1;  
42 - *web_count.  
43 -  
44 -  
45 - Names for Web colors.  
46 -  
47 -public type Web_color_name:  
48 - aliceblue,  
49 - antiquewhite1,  
50 - antiquewhite2,  
51 - antiquewhite3,  
52 - antiquewhite4,  
53 - aquamarine1,  
54 - aquamarine2,  
55 - aquamarine3,  
56 - aquamarine4,  
57 - azure1,  
58 - azure2,  
59 - azure3,  
60 - azure4,  
61 - yellow.  
62 -  
63 -  
64 - and so on ... (see below why I did not do more).  
65 -  
66 -  
67 -  
68 - Web colors.  
69 -  
70 -public type Web_color:  
71 - rgb(Word8,Word8,Word8), /* give the color by its components */  
72 - _(Web_color_name). /* or by its name */  
73 -  
74 -  
75 -  
76 -  
77 - The following produces '<meta>' tags, which are put in the head of the document.  
78 -  
79 -public type WebMeta:  
80 - keywords(List(String)),  
81 - refresh(String url, Int delay), // in seconds  
82 - meta(String name, String content),  
83 - http_equiv(String name, String content).  
84 -  
85 -  
86 -  
87 -  
88 -  
89 - *******************************************************  
90 - * Web items *  
91 - * (the many kinds of things one may put in a page) *  
92 - *******************************************************  
93 -  
94 -  
95 -public type Web_item:  
96 - [ ], /* empty (invisible) item */  
97 - ... this is a cross recursive type.  
98 -  
99 -  
100 -  
101 -  
102 - Options for web page body.  
103 -  
104 -public type LayerDisposition:  
105 - horizontal,  
106 - vertical.  
107 -  
108 -public type FollowPathCommand: // this type is used by the Web_body_option 'follow_path'.  
109 - pos(Int x, // x coordinate of position  
110 - Int y, // y coordinate of position  
111 - Int image_number, // the image to display at that position  
112 - Int delay). // wait that milliseconds before leaving this position  
113 -  
114 -public type Web_body_option:  
115 - background_color(Web_color), /* color for the background */  
116 - //  
117 - // 'psychedelic_background' produces a background color which is continuously changing.  
118 - // 'average' is the average luminosity of the color. 'amplitude' is the maximal variation  
119 - // the luminosity around the average. 'delay' is the number of milliseconds between two  
120 - // color changes. For example, you may try 'psychedelic_background(200,50,1000)', which  
121 - // produces a background whose color changes very slowly (this is not tiring) among rather  
122 - // light pastel colors.  
123 - //  
124 - psychedelic_background(Int average, /* average light (0 to 255) */  
125 - Int amplitude, /* amplitude of variation of light */  
126 - Int delay), /* in milliseconds */  
127 - background_image(String file_name), /* name of image file for the background */  
128 - //  
129 - // 'scrolling_layer' produces a layer above the page which is scrolling continuously either  
130 - // vertically or horizontally. The 'content' is indefinitly repeated.  
131 - //  
132 - scrolling_layer(LayerDisposition,  
133 - Int steps, /* number of pixels of each move */  
134 - Int margin, /* measured from left or top in pixels */  
135 - Int delay, /* milliseconds for one move */  
136 - Web_item content, /* content of layer (will be repeated) */  
137 - Int period), /* number of pixels between two instances of 'content' */  
138 - //  
139 - // 'bounce' shows its content above the page and let it move and bounce on the edges of a rectangle.  
140 - // The rectangle is determined by the last 4 arguments.  
141 - //  
142 - bounce(Web_item content,  
143 - Int left,  
144 - Int right,  
145 - Int top,  
146 - Int bottom),  
147 - //  
148 - // put something over the page in any position you want:  
149 - //  
150 - over(Web_item content,  
151 - Int left,  
152 - Int top),  
153 - //  
154 - // follow_path: let a changing image follow a path on the screen. This gadget shows  
155 - // an image following a polygonal path on the screen. The image may change at regular  
156 - // intervals, thus providing extra animation. The images are displayed in the order  
157 - // they are given in the first argument. When the last image has been displayed, the  
158 - // first image is displayed again, and so on. The path is a sequence of absolute positions  
159 - // on screen (actually in the browser's window or frame), which is followed in the  
160 - // order given in the 'path' argument. If 'loop' is true, the path is followed again and again.  
161 - // Otherwise, it is followed only once. If you want to make a closed loop, the last  
162 - // position must be the same as the first one. 'steps' is the number of pixels of distance  
163 - // between two successive positions of the image, and 'delay' the number of milliseconds  
164 - // between two successive positions. 'change_every' is the number of steps (a 'step' is  
165 - // passing from one position to the next one) after which the displayed image is replaced  
166 - // by the next image.  
167 - //  
168 - // Each position 'pos(x,y,i,d)' has 4 parameters. 'x' and 'y' are the coordinates of the  
169 - // position in the browser's window or frame. 'i' is the number of the image to display  
170 - // at this position (i.e. the rank of the image in the list 'filename'. The first one has  
171 - // rank 0). 'd' is the delay in milliseconds to wait before leaving that position.  
172 - //  
173 - follow_path(List(String) filenames, /* the changing images which follows the path */  
174 - Int change_every, /* number of steps betwen two changes */  
175 - List(FollowPathCommand) path, /* the polygonal path and commands */  
176 - Bool loop, /* if true do it repeatedly, otherwise only once */  
177 - Int steps, /* approximative distance (in pixels) between two  
178 - successive positions */  
179 - Int delay), /* milliseconds between two successive positions */  
180 -  
181 - load_image(String name), /* load an image (for next page), which is not displayed */  
182 - left_margin(Int), /* left margin for document */  
183 - top_margin(Int), /* top margin for document */  
184 - margin_width(Int),  
185 - margin_height(Int),  
186 - reload_frame(String name, /* name of target frame */  
187 - String url), /* url to load in this frame */  
188 - onload(String function_name). /* nom de la fonction javascript (sans les '()') */  
189 -  
190 -public type BodyOnload:  
191 - reload_frame(String name, String url).  
192 -  
193 -variable List(BodyOnload) body_onloads = [ ].  
194 -  
195 -define One  
196 - add_body_onload  
197 - (  
198 - BodyOnload item  
199 - ) =  
200 - body_onloads <- [item . *body_onloads].  
201 -  
202 -define Printable_tree  
203 - format  
204 - (  
205 - BodyOnload item  
206 - ) =  
207 - if item is  
208 - {  
209 - reload_frame(name,url) then (Printable_tree)  
210 - [ " window.open('",url,"','",name,"');" ]  
211 - }.  
212 -  
213 -define Printable_tree  
214 - format  
215 - (  
216 - List(BodyOnload) l  
217 - ) =  
218 - if l is  
219 - {  
220 - [ ] then (Printable_tree)[ ],  
221 - [h . t] then (Printable_tree)  
222 - [format(h) . format(t)]  
223 - }.  
224 -  
225 -  
226 ----- Body of a web page. --------------------------------------------  
227 -public type Page_body:  
228 - body(List(Web_body_option), /* list of body options */  
229 - Web_item content). /* the content of the page */  
230 -  
231 -  
232 -public type VFrame:  
233 - frame(Int height,  
234 - Printable_tree url,  
235 - String name).  
236 -  
237 ----- Web pages. -----------------------------------------------------  
238 -public type Web_page:  
239 - web_page(String title, /* title appearing on top of browser */  
240 - List(WebMeta) meta_tags,  
241 - Printable_tree head_scripts, /* scripts à placer dans la balise head */  
242 - Page_body body), /* body of page */  
243 - standard_frameset(String title,  
244 - List(WebMeta) meta_tags,  
245 - Int height, /* height of 'top menu' (pixels) */  
246 - Int width, /* width of 'left menu' (pixels) */  
247 - Printable_tree main). /* url for main */  
248 -  
249 - +---------+--------------------------+  
250 - | | ^ |  
251 - |<-width->| top height |  
252 - | | v |  
253 - | left +--------------------------+  
254 - | | |  
255 - | | main |  
256 - | | |  
257 - | | |  
258 - | | |  
259 - +---------+--------------------------+  
260 -  
261 - Note: top and left frames must be loaded through the Web_body_option 'reload_frame'.  
262 -  
263 -  
264 -public define Web_page  
265 - web_page  
266 - (  
267 - String title,  
268 - Page_body body  
269 - ) =  
270 - web_page(title,[],[], body).  
271 -  
272 -public define Web_page  
273 - web_page  
274 - (  
275 - String title,  
276 - List(WebMeta) meta_tags,  
277 - Page_body body  
278 - ) =  
279 -  
280 - web_page(title, meta_tags, [], body).  
281 -  
282 - public define Web_page  
283 -web_page  
284 - (  
285 - String title,  
286 - Printable_tree head_scripts,  
287 - Page_body body  
288 - ) =  
289 -  
290 - web_page(title, [], head_scripts, body).  
291 -  
292 -public define Web_page  
293 -standard_frameset  
294 - (  
295 - String title,  
296 - Int height,  
297 - Int width,  
298 - Printable_tree main  
299 - ) =  
300 -  
301 - standard_frameset(title, [], height, width, main).  
302 -  
303 -  
304 -variable Printable_tree scripts = [].  
305 -  
306 -define One  
307 - add_script  
308 - (  
309 - Printable_tree script  
310 - ) =  
311 - scripts <- [*scripts . script].  
312 -  
313 -  
314 -  
315 - ---- Non empty web items. -------------------------------------------  
316 -  
317 - We have already seen the empty web item. Together with the following one, it enables to  
318 - make (pseudo-)lists of web items, which will be presented one after the other (from  
319 - left to right) in the browser's window.  
320 -  
321 -public type Web_item:  
322 - [Web_item . Web_item],...  
323 -  
324 -  
325 - A web item may be a simple string or a simple integer:  
326 -  
327 -public type Web_item:  
328 - text(String),  
329 - text_pt(Printable_tree),  
330 - text_nowrap(String),  
331 - text_nowrap_pt(Printable_tree),  
332 - par(String),  
333 - preformated_text(String text),  
334 - integer(Int),  
335 - float(Float,Int),...  
336 -  
337 -  
338 - You may want to center a web item in a page. Just enclose it into  
339 - 'center(...)':  
340 -  
341 -public type Web_item:  
342 - center(Web_item),...  
343 -  
344 -  
345 - You may want to write characters of a given item with a big font:  
346 -  
347 -public type Web_item:  
348 - bigger(Int,Web_item),  
349 - smaller(Int,Web_item),  
350 - bold(Web_item),  
351 - italic(Web_item),  
352 - big(Web_item),  
353 - very_big(Web_item),...  
354 -  
355 -  
356 - Most of the previous are subsumed by 'style':  
357 -  
358 -public type WebStyle:  
359 - background_image(String file_name),  
360 - background_color(Web_color color),  
361 - background_transparent,  
362 - background_repeat_horizontal, // repeat the background image only horizontally  
363 - background_repeat_vertical,  
364 - background_no_repeat,  
365 - color(Web_color color),  
366 - float_to_left, // the web item will float to the left and text will wrap around  
367 - float_to_right,  
368 - font_family(String font_name), // "verdana" "helvetica" "times" etc...  
369 - font_size(Int size),  
370 - italic,  
371 - oblique,  
372 - small_capitals,  
373 - bold,  
374 - bolder,  
375 - lighter,  
376 - line_height(Int height),  
377 - text_center,  
378 - text_left,  
379 - text_right,  
380 - text_justify,  
381 - text_underline,  
382 - text_blink,  
383 - text_line_through,  
384 - width(Int n).  
385 -  
386 -  
387 -  
388 -public type Web_item:  
389 - style(List(WebStyle) styles, Web_item content),...  
390 -  
391 -  
392 -public type Web_item:  
393 - spacer(Int width, Int height),  
394 - image(String file_name), /* image */  
395 - image_d(String file_name, String description),  
396 - image_pt(Printable_tree file_name),  
397 - on_image(String file_name, Web_item content),  
398 - turning_images(NonEmptyList(String) filenames, Int millisecs),...  
399 -  
400 -  
401 -  
402 - A 'rollover' has the same role as a submit button or link, but it is prettier. It is  
403 - made of two images. The first one 'image_on' determines the aspect of the button when  
404 - the mouse cursor is on it. The other one 'image_off' determines the aspect of the  
405 - button when the mouse cursor is anywhere else. The two images should be of the same  
406 - size, otherwise bad effects may occur. The last operand 'description' is a small text  
407 - which describes the role of the button. It appears in a bubble in the browser's window.  
408 -  
409 -public type Web_item:  
410 - rollover(List(String) preload_images, // images to preload before the rollover is effective  
411 - String url, // URL with possible web arguments  
412 - String target,  
413 - String image_on, // file name of 'highlighted' image  
414 - String image_off, // file name of 'non highlighted' image  
415 - String description), // short behavior description  
416 - rollover(List(String) preload_images,  
417 - String url,  
418 - String target,  
419 - String image_on,  
420 - String image_off,  
421 - Int width,  
422 - Int height,  
423 - String description), ...  
424 -  
425 -  
426 - Mouse sensitive images are images with predefined zones which are clickable. When  
427 - clicking in a zone, the specified corresponding URL is loaded by the browser. If two  
428 - zones overlap, the first one (in the order they are defined) is selected.  
429 -  
430 - Zones are of 3 sorts: rectangles, circles and polygons. Point's coordinates are  
431 - specified as pairs of integers (of anonymous agglomeration type (Int,Int)). The  
432 - first coordinate counts pixels from the left of the image. The second coordinate counts  
433 - pixels from the top of the image. With polygons, you can construct zones which are  
434 - almost as complicated as you want. You may also construct a zone as the overlapping of  
435 - several zones with the same URL.  
436 -  
437 -public type Mouse_Sensitive_Zone:  
438 - rectangle  
439 - (  
440 - (Int,Int) left_top,  
441 - (Int,Int) right_bottom,  
442 - String url  
443 - ),  
444 - circle  
445 - (  
446 - (Int,Int) center,  
447 - Int radius,  
448 - String url  
449 - ),  
450 - polygon  
451 - (  
452 - List((Int,Int)) vertices,  
453 - String url  
454 - ).  
455 -  
456 -public type Web_item:  
457 - mouse_sensitive_image(String image_file_name, // the image itself  
458 - List(Mouse_Sensitive_Zone) zones),...  
459 -  
460 -  
461 - In project: mouse sensitive images, whose zones behave like submission buttons (to be  
462 - used within a form).  
463 -  
464 -  
465 -public type Web_item:  
466 - background_sound(String sound_file_name,  
467 - Bool loop),...  
468 -  
469 -  
470 -  
471 - *************************  
472 - * FORMS *  
473 - *************************  
474 -  
475 -  
476 - Use 'forms' in order to get informations back from the client. The constructor 'form'  
477 - take 2 arguments:  
478 -  
479 - - the name of the form, which must be the name of an Anubis web  
480 - page. Indeed, when the user will submit the form, this page will  
481 - be sent to him.  
482 - - the content of the form, which may be any web item, but which  
483 - normally (amongh other things) contains input fields and a  
484 - submit button.  
485 -  
486 -public type Web_item:  
487 - form(Printable_tree name,  
488 - Web_item content),  
489 - form_target(Printable_tree name,  
490 - Web_item content,  
491 - String target),  
492 - form(Printable_tree name,  
493 - String label_name,  
494 - Web_item content),  
495 - form_name(String form_name, // option name de form  
496 - Web_item content),...  
497 -  
498 - public define Web_item  
499 -form  
500 - (  
501 - Printable_tree name,  
502 - Web_item content  
503 - ) =  
504 -  
505 - form("", name, content).  
506 -  
507 - Within a form, you may put 'text input fields', that the client may  
508 - edit. The constructor 'text_input' has the following arguments:  
509 -  
510 - - name of input field. This will be the name of the correponding  
511 - web argument in the Anubis web page referred to by the form.  
512 - - size of field (as it appears on client screen),  
513 - - initial value of field (the text that appears in the field, when  
514 - the client downloads the page).  
515 -  
516 -  
517 - public type Text_Input_Option  
518 -  
519 -public type Web_item:  
520 - text_input(String name, /* text field to be documented by user */  
521 - Int size,  
522 - String initial_value),...  
523 -  
524 - public define Web_item  
525 - text_input  
526 - (  
527 - String name,  
528 - Int size,  
529 - String initial_value  
530 - ) =  
531 -  
532 -  
533 - text_input( (List(Text_Input_Option)) [], name, size, initial_value).  
534 -  
535 -  
536 -public type Web_item:  
537 - password_input(String name,  
538 - Int size),  
539 - text_area(String name,  
540 - Int columns,  
541 - Int rows,  
542 - String initial_text),  
543 - upload(String name, Int size),...  
544 -  
545 -  
546 -  
547 -  
548 -public type Web_item:  
549 - submit(String button_text), /* submit button with text on it */  
550 - submit_pt(Printable_tree button_text),  
551 - submit(String name, String text),  
552 - submit_close(String name, String text),  
553 - submit_pt2(String name, Printable_tree text),  
554 - image_submit(String name, String image_file_name),  
555 - image_submit(String name, String value, String image_file, Web_item content),  
556 - hl_image_submit(String action_name,  
557 - String value,  
558 - String image_name,  
559 - String image_file,  
560 - String hl_image_file),  
561 - text_submit(String name, String value, String text),  
562 - web_submit(String web_args, Web_item content),  
563 - button(String name, String text, String on_click_fonction, Int width, Int height),...  
564 -  
565 -  
566 -public type Web_item: /* mark the form with an information */  
567 - mark(String name, String value),  
568 - mark_pt(String name, Printable_tree value),...  
569 -  
570 -public type Web_item:  
571 - close_button, /* button that closes the window */  
572 - close_button(String image_file_name), ...  
573 -  
574 -  
575 -  
576 - *********************************  
577 - * LABELS *  
578 - *********************************  
579 -  
580 -  
581 - A 'label' is just a name that you may give to a position in a document. Use the  
582 - following invisible Web_item 'label' to this end. Now, you can also create links in  
583 - the same document, which, when clicked by the user, scroll the document, so that the  
584 - position whose name is the given label is shown just at the top of the browser's  
585 - window.  
586 -  
587 -public type Web_item:  
588 - label(String label_name), /* give a name to a position in the page */  
589 - go_to_label(String label_name, /* a link for jumping to a label */  
590 - Web_item content),...  
591 -  
592 -  
593 -  
594 - ********************************  
595 - * TABLES *  
596 - ********************************  
597 -  
598 -  
599 - A web item may be a table. A table is produced by the constructor  
600 - 'table' from the type 'Web_item'. This constructor takes 2  
601 - arguments:  
602 -  
603 - - a list of 'table options',  
604 - - a list of 'table rows'.  
605 -  
606 - Of course, you use as many options as you want, including  
607 - none (if you do not want any option, put the empty list '[ ]' as  
608 - this argument). Some options have precedence over others. For example  
609 - a background image will hide the background color.  
610 -  
611 - Table options are defined below:  
612 -  
613 -public type Table_option:  
614 -  
615 - /* use a color as a background for the table, if you want it to  
616 - be different from the background of the page */  
617 - background_color(Web_color),  
618 -  
619 - /* or use an image as the background of the table */  
620 - background_image(String file_name),  
621 -  
622 - /* draw a border line around the table (and around each cell in  
623 - the table). You may also specify a geometry (in pixels) for the  
624 - border. This makes the 'in relief' part of the border appear  
625 - more or less wide. You may also specify a color for the border. */  
626 - border,  
627 - nude, /* equivalent to 'border(0,0,0)' (below) */  
628 - border(Int, /* width of exterior (pixels) */  
629 - Int, /* width of top */  
630 - Int), /* width of interior */  
631 - border_color(Web_color),  
632 - absolute_width(Int).  
633 -  
634 -  
635 -  
636 - A 'table row' is made of a list of 'row options', and a list of  
637 - 'cells'. A 'cell' itself has a list of 'cell options', and a web item,  
638 - which is its content. We begin by the description of options.  
639 -  
640 -  
641 -public type Row_option:  
642 - /* following concerns the horizontal positions of items within the  
643 - cells of the row */  
644 - left,  
645 - h_center,  
646 - right,  
647 - /* the following concerns the vertical positions of items, within  
648 - the cells of the row */  
649 - top,  
650 - v_center,  
651 - bottom,  
652 - absolute_height(Int),  
653 - base_line,  
654 - /* set the background color of all cells in the row */  
655 - background_color(Web_color).  
656 -  
657 -  
658 -  
659 -public type Cell_option:  
660 - /* all row options are available for individual cells, and apply  
661 - here only to one cell. */  
662 - left,  
663 - h_center,  
664 - right,  
665 - top,  
666 - v_center,  
667 - bottom,  
668 - base_line,  
669 - background_color(Web_color),  
670 - /* you can set the width of the cell either absolutely (in pixels)  
671 - or as a percentage of the width of the table. */  
672 - background_image(String file_name),  
673 - absolute_width(Int),  
674 - relative_width(Int),  
675 - absolute_height(Int),  
676 - relative_height(Int),  
677 - /* a cell may span over several columns or rows in the table */  
678 - columns(Int),  
679 - rows(Int),  
680 - nowrap.  
681 -  
682 -  
683 -public type Cell:  
684 - cell(List(Cell_option),  
685 - Web_item).  
686 -  
687 -public type Table_row:  
688 - row(List(Row_option),  
689 - List(Cell)).  
690 -  
691 -public define Table_row row(Web_item i) = row([],[cell([],i)]).  
692 -public define Table_row row(Cell c) = row([],[c]).  
693 -public define Table_row row(List(Cell) l) = row([],l).  
694 -  
695 -  
696 -public type Web_item:  
697 - table(List(Table_option),  
698 - List(Table_row)),...  
699 -  
700 -public type Web_item:  
701 - list(List(Web_item)),...  
702 -  
703 -public type Web_item:  
704 - link(String name, Web_item),  
705 - link(String name, String target, Web_item),...  
706 -  
707 -public type Web_item:  
708 - link_for_download(String filename, Web_item),... // the filename is relative to the public directory  
709 -  
710 -public type Web_item:  
711 - mail_to(String addr, Web_item),...  
712 -  
713 -  
714 -public type Web_item:  
715 - select(String name,  
716 - Int size,  
717 - List(String) choices),  
718 - select(String name,  
719 - Int size,  
720 - List(String) choices,  
721 - String selected),  
722 - immediate_select(String name, // selection will immediately submit the form  
723 - Int size,  
724 - List(String) choices),...  
725 -  
726 -  
727 -public type Web_item:  
728 - radio_button (Printable_tree name, String value),  
729 - checked_radio_button (Printable_tree name, String value),  
730 - check_box (Printable_tree name, String value),  
731 - checked_box (Printable_tree name, String value),...  
732 -  
733 -  
734 -public type Web_item:  
735 - link_to_window(Printable_tree name, Web_item),  
736 - link_to_window(Printable_tree name, String window_name, Web_item),  
737 - link_to_window_with_ticket(String name,  
738 - String web_args,  
739 - String window_name,  
740 - Web_item content,  
741 - Int width,  
742 - Int height),  
743 - link_to_window_with_ticket_and_scroll  
744 - (String name,  
745 - String web_args,  
746 - String window_name,  
747 - Web_item content,  
748 - Int width,  
749 - Int height),  
750 - link_to_window_with_ticket_and_scroll  
751 - (String name,  
752 - String label_name,  
753 - String web_args,  
754 - String window_name,  
755 - Web_item content,  
756 - Int width,  
757 - Int height),  
758 - link_to_frame (Printable_tree name, String frame_name, Web_item).  
759 -  
760 -  
761 -  
762 ----- Formating operations (Anubis --> HTML/Javascript) ---------------------------  
763 -  
764 - Stupid operation formating a web color name.  
765 -  
766 -public define String  
767 - format  
768 - (  
769 - Web_color_name n  
770 - ) =  
771 - if n is  
772 - {  
773 - aliceblue then "aliceblue",  
774 - antiquewhite1 then "antiquewhite1",  
775 - antiquewhite2 then "antiquewhite2",  
776 - antiquewhite3 then "antiquewhite3",  
777 - antiquewhite4 then "antiquewhite4",  
778 - aquamarine1 then "aquamarine1",  
779 - aquamarine2 then "aquamarine2",  
780 - aquamarine3 then "aquamarine3",  
781 - aquamarine4 then "aquamarine4",  
782 - azure1 then "azure1",  
783 - azure2 then "azure2",  
784 - azure3 then "azure3",  
785 - azure4 then "azure4",  
786 - yellow then "yellow",  
787 - }.  
788 -  
789 - Anubis really needs some system of 'macros' to avoid this...  
790 -  
791 -  
792 - Formating a web color.  
793 -  
794 -public define String  
795 - format  
796 - (  
797 - Web_color wc  
798 - ) =  
799 - if wc is  
800 - {  
801 - rgb(r,g,b) then "\"#" + to_hexa(r) + to_hexa(g) + to_hexa(b) + "\"",  
802 - _(c) then format(c)  
803 - }.  
804 -  
805 -public define String  
806 - format_without_quotes  
807 - (  
808 - Web_color wc  
809 - ) =  
810 - if wc is  
811 - {  
812 - rgb(r,g,b) then "#" + to_hexa(r) + to_hexa(g) + to_hexa(b) + "",  
813 - _(c) then format(c)  
814 - }.  
815 -  
816 -define Printable_tree  
817 - format  
818 - (  
819 - Web_color c  
820 - ) = [(String)format(c)].  
821 -  
822 -public define String  
823 - format_without_sharp  
824 - (  
825 - Web_color wc  
826 - ) =  
827 - if wc is  
828 - {  
829 - rgb(r,g,b) then "" + to_hexa(r) + to_hexa(g) + to_hexa(b) + "",  
830 - _(c) then format(c)  
831 - }.  
832 -  
833 -define Printable_tree  
834 - format_without_sharp  
835 - (  
836 - Web_color c  
837 - ) = [(String)format_without_sharp(c)].  
838 -  
839 -  
840 - define Printable_tree  
841 - [Word32 x . Printable_tree t]  
842 - =  
843 - [to_Int(x) . t].  
844 -  
845 -define Printable_tree  
846 - psychedelic_bg  
847 - (  
848 - Int average,  
849 - Int amplitude,  
850 - Int delay  
851 - ) =  
852 - with ampl = if amplitude >= 120 then 120 else  
853 - if amplitude =< 1 then 1 else amplitude,  
854 - with aver = if average+ampl >= 254 then 254-ampl  
855 - else if average-ampl =< 1 then 1+ampl else average,  
856 - [ "<script>",  
857 - " var psy_t = 0;",  
858 - " function do_psy_bg() { psy_t += 0.05;",  
859 - " document.bgColor = '#' + ",  
860 - " (Math.round(",aver,"+",ampl,"*Math.cos(psy_t))).toString(16) + ",  
861 - " (Math.round(",aver,"+",ampl,"*Math.sin(psy_t))).toString(16) + ",  
862 - " (Math.round(",aver,"-",ampl,"*Math.cos(2*psy_t))).toString(16); ",  
863 - " setTimeout(\"do_psy_bg()\",",delay,"); }",  
864 - " setTimeout(\"do_psy_bg()\",1000);",  
865 - "</script>"].  
866 -  
867 -  
868 -define List(Web_body_option)  
869 - replace_background_init  
870 - (  
871 - List(Web_body_option) l,  
872 - Int average,  
873 - Int amplitude,  
874 - Int delay  
875 - ) =  
876 - if l is  
877 - {  
878 - [ ] then [ ],  
879 - [h . t] then  
880 - if h is background_color(_)  
881 - then [background_color(rgb(truncate_to_Word8(average+amplitude),  
882 - truncate_to_Word8(average),  
883 - truncate_to_Word8(average-amplitude)))  
884 - . replace_background_init(t,average,amplitude,delay)]  
885 - else [h . replace_background_init(t,average,amplitude,delay)]  
886 - }.  
887 -  
888 -define Maybe((Int,Int,Int))  
889 - get_psy  
890 - (  
891 - List(Web_body_option) l  
892 - ) =  
893 - if l is  
894 - {  
895 - [ ] then failure,  
896 - [h . t] then  
897 - if h is psychedelic_background(a,f,d)  
898 - then success((a,f,d))  
899 - else get_psy(t)  
900 - }.  
901 -  
902 -define List(Web_body_option)  
903 - prepare  
904 - (  
905 - List(Web_body_option) l  
906 - ) =  
907 - if get_psy(l) is  
908 - {  
909 - failure then l,  
910 - success(op) then if op is (a,f,d) then  
911 - replace_background_init(l,a,f,d)  
912 - }.  
913 -  
914 -  
915 -public define Printable_tree  
916 - format  
917 - (  
918 - String c_ticket,  
919 - String s_ticket,  
920 - Web_item i  
921 - ).  
922 -  
923 -define Printable_tree  
924 - move_layer_command  
925 - (  
926 - String property,  
927 - Int n,  
928 - Int num,  
929 - Int i,  
930 - Int period  
931 - ) =  
932 - if i >= num then [ ] else  
933 - [" if (document.layers)",  
934 - " { document.nslay",n,"_",i,".",property,"=layp",n,"+(",((i-1)),"); } ",  
935 - " else ",  
936 - " { ielay",n,"_",i,".style.",property,"=layp",n,"+(",(i-1)*period,"); } "  
937 - . move_layer_command(property,n,num,i+1,period)].  
938 -  
939 -define Printable_tree  
940 - format_layers  
941 - (  
942 - LayerDisposition disp,  
943 - Int margin,  
944 - Web_item content,  
945 - Int n,  
946 - Int num,  
947 - Int i,  
948 - Int period  
949 - ) =  
950 - if i >= num then [ ] else  
951 - ["<layer name=\"nslay",n,"_",i,"\" top=0",  
952 - " left=0", ">",  
953 - "<div id=ielay",n,"_",i," style=\"position:absolute;top:",  
954 - if disp is vertical then (i-1)*period else margin,  
955 - "px;left:",  
956 - if disp is vertical then margin else (i-1)*period,  
957 - "px\">",  
958 - format("","",content),  
959 - "</div></layer>" . format_layers(disp,margin,content,n,num,i+1,period)].  
960 -  
961 -define Printable_tree  
962 - s_layer  
963 - (  
964 - LayerDisposition disp,  
965 - Int steps,  
966 - Int margin,  
967 - Int delay,  
968 - Web_item content,  
969 - Int period,  
970 - Int num  
971 - ) =  
972 - with n = new_web_count,  
973 - [ "<script>",  
974 - " var layp",n," = 0;",  
975 - " function scroll_layer",n,"() {",  
976 - " layp",n,"+=(",steps,"); if (layp",n," ",if steps > 0 then ">" else "<",  
977 - "= ",if steps > 0 then period else 0,  
978 - ") layp",n," = ",if steps > 0 then 0 else period,";",  
979 - move_layer_command(if disp is vertical then "top" else "left",n,num,0,period),  
980 - " setTimeout(\"scroll_layer",n,"()\",",delay,");",  
981 - " }",  
982 - " setTimeout(\"scroll_layer",n,"()\",1000);",  
983 - "</script>",  
984 - format_layers(disp,margin,content,n,num,0,period),  
985 - ].  
986 -  
987 -  
988 -define Printable_tree  
989 - over  
990 - (  
991 - Web_item i,  
992 - Int left,  
993 - Int top  
994 - ) =  
995 - with n = new_web_count,  
996 - [ "<layer name=\"nslay",n,"\" top=",top," left=",left,">",  
997 - " <div id=\"ielay",n,"\" style=\"position:absolute;top=",top,"px;left=",left,"px\">",  
998 - format("","",i),  
999 - "</div></layer>"  
1000 - ].  
1001 -  
1002 -define Printable_tree  
1003 - bnce  
1004 - (  
1005 - Web_item i,  
1006 - Int left,  
1007 - Int right,  
1008 - Int top,  
1009 - Int bottom  
1010 - ) =  
1011 - with n = new_web_count,  
1012 - [ "<script>",  
1013 - " var bncx",n," = ",left,"; var bncy",n," = ",top,"; var bncdx",n," = 1; var bncdy",n," = 1;",  
1014 - " function do_bnc",n,"() {",  
1015 - " if (bncx",n," >= (",right,") || bncx",n," < ",left,") bncdx",n," = -bncdx",n,";",  
1016 - " if (bncy",n," >= (",bottom,") || bncy",n," < ",top,") bncdy",n," = -bncdy",n,";",  
1017 - " bncx",n," += bncdx",n,"; bncy",n," += bncdy",n,";",  
1018 - " if (document.layers)",  
1019 - " { document.nslay",n,".left = bncx",n,"; document.nslay",n,".top = bncy",n,"; } else",  
1020 - " { ielay",n,".style.left = bncx",n,"; ielay",n,".style.top = bncy",n,"; }",  
1021 - " setTimeout(\"do_bnc",n,"()\",40); }",  
1022 - " setTimeout(\"do_bnc",n,"()\",1000);",  
1023 - "</script>",  
1024 - "<layer name=\"nslay",n,"\" top=",top," left=",left,">",  
1025 - " <div id=\"ielay",n,"\" style=\"position:absolute;top=",top,"px;left=",left,"px\">",  
1026 - format("","",i),  
1027 - "</div></layer>"  
1028 - ].  
1029 -  
1030 -  
1031 -define Printable_tree  
1032 - folp_switch  
1033 - (  
1034 - Int n,  
1035 - Int i,  
1036 - List(FollowPathCommand) path,  
1037 - Bool loop,  
1038 - Int steps  
1039 - ) =  
1040 - if path is  
1041 - {  
1042 - [ ] then [ ],  
1043 - [p0 . t0] then if p0 is pos(x0,y0,i0,d0) then  
1044 - if t0 is  
1045 - {  
1046 - [ ] then if loop  
1047 - then [" default: ",  
1048 - " folpx",n,"=",x0,"; folpy",n,"=",y0,"; ",  
1049 - " folpseg",n,"=0; ",  
1050 - " folpwait",n,"=",d0,";",  
1051 - " if(document.layers)",  
1052 - " document.nslay",n,".document.folpim",n,".src=folpimages",n,"[",i0,"].src;",  
1053 - " else document.folpim",n,".src=folpimages",n,"[",i0,"].src;",  
1054 - " folpstpmax",n," = 0;",  
1055 - " folpstp",n,"=0;",  
1056 - " folpdx",n,"=0; ",  
1057 - " folpdy",n,"=0; ",  
1058 - " break;"]  
1059 - else [" default: folpend",n,"=1; break; "],  
1060 - [p1 . t1] then if p1 is pos(x1,y1,i1,d1) then  
1061 - [ " case ",i,": ",  
1062 - " folpx",n,"=",x0,"; folpy",n,"=",y0,"; ",  
1063 - " folpseg",n,"=",i+1,"; ",  
1064 - " folpwait",n,"=",d0,";",  
1065 - " if(document.layers)",  
1066 - " document.nslay",n,".document.folpim",n,".src=folpimages",n,"[",i0,"].src;",  
1067 - " else document.folpim",n,".src=folpimages",n,"[",i0,"].src;",  
1068 - " folpstpmax",n," = ",  
1069 - "Math.round(Math.sqrt(Math.pow(",x1,"-",x0,",2)+Math.pow(",y1,"-",y0,",2))/",steps,");",  
1070 - " folpstp",n,"=0;",  
1071 - " folpdx",n,"=((",x1,"-",x0,")/folpstpmax",n,"); ",  
1072 - " folpdy",n,"=((",y1,"-",y0,")/folpstpmax",n,"); ",  
1073 - " break; "  
1074 - . folp_switch(n,i+1,t0,loop,steps) ]  
1075 - }}.  
1076 -  
1077 -define Printable_tree  
1078 - set_folpimages  
1079 - (  
1080 - Int n,  
1081 - List(String) filenames,  
1082 - Int i,  
1083 - ) =  
1084 - if filenames is  
1085 - {  
1086 - [ ] then [ ],  
1087 - [h . t] then  
1088 - [ " folpimages",n,"[",i,"].src=\"",h,"\";"  
1089 - . set_folpimages(n,t,i+1)]  
1090 - }.  
1091 -  
1092 -define Printable_tree  
1093 - follow_path  
1094 - (  
1095 - List(String) filenames,  
1096 - Int change_every,  
1097 - List(FollowPathCommand) path,  
1098 - Bool loop,  
1099 - Int steps,  
1100 - Int delay  
1101 - ) =  
1102 - if filenames is  
1103 - {  
1104 - [ ] then (print("Error in usage of 'follow_path': 'filenames' must be non empty."); []),  
1105 - [im1 . other_ims] then  
1106 - if steps < 1 then (print("Error in usage of 'follow_path': 'step' must be >= 1."); [ ]) else  
1107 - if path is  
1108 - {  
1109 - [ ] then [ ],  
1110 - [p0 . t0] then if p0 is pos(x0,y0,i0,d0) then  
1111 - if t0 is  
1112 - {  
1113 - [ ] then  
1114 - (print("Error in usage of 'follow_path': 'path' must have at least 2 positions."); []),  
1115 - [p1 . t1] then if p1 is pos(x1,y1,i1,d1) then  
1116 - with n = new_web_count,  
1117 - [  
1118 - "<layer name=\"nslay",n,"\" top=",y0," left=",x0,">",  
1119 - "<div id=\"ielay",n,"\" style=\"position:absolute;top=",y0,"px;left=",x0,"px\">",  
1120 - "<img src=\"",im1,"\" name=\"folpim",n,"\" border=0>",  
1121 - "</div></layer>",  
1122 - "<script>",  
1123 - " var folpimages",n,"=new Array(",length(filenames),");",  
1124 - " var folpcurim",n,"=0;",  
1125 - " var folpx",n," = ",x0,";",  
1126 - " var folpy",n," = ",y0,";",  
1127 - " var folpseg",n," = 1;",  
1128 - " var folpstpmax",n," = ",  
1129 - "Math.round(Math.sqrt(Math.pow(",x1,"-",x0,",2)+Math.pow(",y1,"-",y0,",2))/",steps,");",  
1130 - " var folpdx",n,"=((",x1,"-",x0,")/folpstpmax",n,"); ",  
1131 - " var folpdy",n,"=((",y1,"-",y0,")/folpstpmax",n,"); ",  
1132 - " var folpstp",n," = 0;",  
1133 - " var folpchgcnt",n," = 0;",  
1134 - " var folpwait",n,"=",d0,";",  
1135 - " var folpend",n,"=0;",  
1136 - " for(var i = 0; i<",length(filenames),"; i++) {",  
1137 - " folpimages",n,"[i]=new Image(); }",  
1138 - set_folpimages(n,filenames,0),  
1139 - " function dofolp",n,"() {",  
1140 - " if (folpstp",n," >= folpstpmax",n,")",  
1141 - " { switch(folpseg",n,") {",  
1142 - folp_switch(n,0,path,loop,steps),  
1143 - " }} else { folpstp",n,"++; folpx",n," += folpdx",n,"; folpy",n," += folpdy",n,"; };",  
1144 - " if (document.layers)",  
1145 - " { document.nslay",n,".left=folpx",n,"; document.nslay",n,".top=folpy",n,"; } else",  
1146 - " { ielay",n,".style.left=folpx",n,"; ielay",n,".style.top=folpy",n,"; };",  
1147 -  
1148 - //--- change image if needed:  
1149 - if change_every = 0 then [ ] else  
1150 - if length(filenames) =< 1 then [ ] else  
1151 - [" if (folpchgcnt",n,"==",change_every,") ",  
1152 - " { ",  
1153 - " folpchgcnt",n,"=0;",  
1154 - " folpcurim",n,"++;",  
1155 - " if (folpcurim",n,"==",length(filenames),") folpcurim",n,"=0;",  
1156 - " if (document.layers) ",  
1157 - " document.nslay",n,".document.folpim",n,".src=folpimages",n,"[folpcurim",n,"].src;",  
1158 - " else document.folpim",n,".src=folpimages",n,"[folpcurim",n,"].src;",  
1159 - " }",  
1160 - " else { folpchgcnt",n,"++; };"],  
1161 -  
1162 - " if (!folpend",n,")",  
1163 - " if (folpstp",n,") setTimeout(\"dofolp",n,"()\",",delay,"); ",  
1164 - " else setTimeout(\"dofolp",n,"()\",",delay,"+folpwait",n,"); ",  
1165 - " }",  
1166 - " setTimeout(\"dofolp",n,"()\",1000);",  
1167 - "</script>",  
1168 - ]  
1169 - }}}.  
1170 -  
1171 -  
1172 -public type ImageToLoad:  
1173 - simple(String image_name),  
1174 - with_rollover(String image_name,  
1175 - String rollover_name).  
1176 -  
1177 -variable List(ImageToLoad) images_to_load = [].  
1178 -  
1179 -public define Printable_tree  
1180 - format  
1181 - (  
1182 - Web_body_option o  
1183 - ) =  
1184 - if o is  
1185 - {  
1186 - background_color(c) then [" bgcolor=" , (String)format(c)],  
1187 - psychedelic_background(a,f,d) then add_script(psychedelic_bg(a,f,d)); [ ],  
1188 - background_image(n) then [" background=", n],  
1189 - scrolling_layer(disp,st,m,t,c,p) then add_script(s_layer(disp,st,m,t,c,p,2000\p)); [ ],  
1190 - bounce(i,l,r,t,b) then add_script(bnce(i,l,r,t,b)); [ ],  
1191 - over(i,l,t) then add_script(over(i,l,t)); [ ],  
1192 - follow_path(li,ns,p,l,s,d) then add_script(follow_path(li,ns,p,l,s,d)); [ ],  
1193 - load_image(n) then images_to_load <- [simple(n) . *images_to_load]; [ ],  
1194 - left_margin(n) then [" leftmargin=", n],  
1195 - top_margin(n) then [" topmargin=", n],  
1196 - margin_width(n) then [" marginwidth=", n],  
1197 - margin_height(n) then [" marginheight=", n],  
1198 - reload_frame(n,url) then add_body_onload(reload_frame(n,url)); [ ],  
1199 - onload(n) then [" onLoad=\"", n, "()\""]  
1200 - }.  
1201 -  
1202 -define Printable_tree  
1203 - preload_list  
1204 - (  
1205 - List(ImageToLoad) images,  
1206 - Int n,  
1207 - ) =  
1208 - if images is  
1209 - {  
1210 - [ ] then [ ],  
1211 - [h . t] then  
1212 - [" preloaded_images[",n,"].src = '",image_name(h),"';",  
1213 - if h is  
1214 - {  
1215 - simple(_) then [],  
1216 - with_rollover(n1,r) then  
1217 - [" preloaded_images[",n1,"].onload = 'allow_rollover(\"",r,"\")';"]  
1218 - }  
1219 - . preload_list(t,n-1)]  
1220 - }.  
1221 -  
1222 -define Printable_tree  
1223 - load_image_script  
1224 - (  
1225 - List(ImageToLoad) images  
1226 - ) =  
1227 - if images is  
1228 - {  
1229 - [ ] then [ ],  
1230 - [_ . _] then  
1231 - [  
1232 - "<script>",  
1233 - " var preloaded_images = new Array(",length(images),");",  
1234 - " var pi_i = 0;",  
1235 - " for(pi_i = 0; pi_i < ",length(images),"; pi_i++) {",  
1236 - " preloaded_images[pi_i] = new Image(); }",  
1237 - " function preload_images() {",  
1238 - preload_list(images,length(images)-1),  
1239 - " }</script>"  
1240 - ]  
1241 - }.  
1242 -  
1243 -  
1244 -public define Printable_tree format(String c_ticket,  
1245 - String s_ticket,  
1246 - Web_item i).  
1247 -  
1248 -  
1249 -public define Printable_tree  
1250 - format(List(Table_option) l) =  
1251 - if l is  
1252 - {  
1253 - [ ] then [ ],  
1254 - [h . t] then [if h is  
1255 - {  
1256 - background_color(c) then [" bgcolor=", (String)format(c)],  
1257 - background_image(f) then [" background=",f],  
1258 - border then [" border"],  
1259 - nude then [" border=\"0\" cellspacing=\"0\" cellpadding=\"0\""],  
1260 - border(e,top,i) then [" border=",e," cellspacing=",top," cellpadding=",i],  
1261 - border_color(c) then [" bordercolor=", (String)format(c)],  
1262 - absolute_width(n) then [" width=",n]  
1263 - }, format(t)]  
1264 - }.  
1265 -  
1266 -  
1267 -  
1268 -public define Printable_tree  
1269 - format(List(Row_option) l) =  
1270 - if l is  
1271 - {  
1272 - [ ] then [ ],  
1273 - [first . others] then [if first is  
1274 - {  
1275 - left then [" align=left"],  
1276 - h_center then [" align=center"],  
1277 - right then [" align=right"],  
1278 - top then [" valign=top"],  
1279 - v_center then [" valign=center"],  
1280 - bottom then [" valign=bottom"],  
1281 - absolute_height(n) then [" height=\"",n,"\""],  
1282 - base_line then [" valign=baseline"],  
1283 - background_color(c) then [" bgcolor=",(String)format(c)]  
1284 - },  
1285 - format(others)]  
1286 - }.  
1287 -  
1288 -  
1289 -  
1290 -  
1291 -public define Int  
1292 - percentage(Int n) =  
1293 - if n < 0 then 0  
1294 - else if n > 100 then 100  
1295 - else n.  
1296 -  
1297 - public define Int percentage(Int n) = n.  
1298 -  
1299 -  
1300 -  
1301 -  
1302 -public define Printable_tree  
1303 - format  
1304 - (  
1305 - List(Web_body_option) l  
1306 - ) =  
1307 - if l is  
1308 - {  
1309 - [ ] then [ ],  
1310 - [h . t] then [format(h) . format(t)]  
1311 - }.  
1312 -  
1313 -  
1314 -  
1315 -  
1316 -public define Printable_tree  
1317 - format(List(Cell_option) l) =  
1318 - if l is  
1319 - {  
1320 - [ ] then [ ],  
1321 - [first . others] then  
1322 - [if first is  
1323 - {  
1324 - left then (Printable_tree)[" align=left"],  
1325 - h_center then (Printable_tree)[" align=center"],  
1326 - right then (Printable_tree)[" align=right"],  
1327 - top then (Printable_tree)[" valign=top"],  
1328 - v_center then (Printable_tree)[" valign=center"],  
1329 - bottom then (Printable_tree)[" valign=bottom"],  
1330 - base_line then (Printable_tree)[" valign=baseline"],  
1331 - background_color(c) then (Printable_tree)[" bgcolor=",(String)format(c)],  
1332 - background_image(n) then (Printable_tree)[" style=\"background: url(",n,")\""],  
1333 - absolute_width(w) then (Printable_tree)[" width=",w],  
1334 - relative_width(r) then (Printable_tree)[" width=",percentage(r),""],  
1335 - absolute_height(h) then (Printable_tree)[" height=",h],  
1336 - relative_height(r) then (Printable_tree)[" height=",percentage(r),""],  
1337 - columns(n) then (Printable_tree)[" colspan=",n],  
1338 - rows(n) then (Printable_tree)[" rowspan=",n],  
1339 - nowrap then (Printable_tree)[" nowrap"]  
1340 - }  
1341 - . format(others)]  
1342 - }.  
1343 -  
1344 -  
1345 -  
1346 -public define Printable_tree  
1347 - format  
1348 - (  
1349 - String c_ticket,  
1350 - String s_ticket,  
1351 - List(Cell) l  
1352 - ) =  
1353 - if l is  
1354 - {  
1355 - [ ] then [ ],  
1356 - [first . others] then  
1357 - [if first is cell(options,item) then  
1358 - ["<td",format(options),">",  
1359 - format(c_ticket,s_ticket,item),"</td>"],  
1360 - format(c_ticket,s_ticket,others)]  
1361 - }.  
1362 -  
1363 -  
1364 -public define Printable_tree  
1365 - format  
1366 - (  
1367 - String c_ticket,  
1368 - String s_ticket,  
1369 - List(Table_row) l  
1370 - ) =  
1371 - if l is  
1372 - {  
1373 - [ ] then [ ],  
1374 - [first_row . other_rows]  
1375 - then [if first_row is  
1376 - {  
1377 - row(options,cells) then  
1378 - [ "<tr",format(options),">",  
1379 - format(c_ticket,s_ticket,cells),"</tr>"]  
1380 - },  
1381 - format(c_ticket,s_ticket,other_rows)]  
1382 - }.  
1383 -  
1384 -  
1385 -public define Printable_tree  
1386 - format_choices  
1387 - (  
1388 - List(String) l  
1389 - ) =  
1390 - if l is  
1391 - {  
1392 - [ ] then [ ],  
1393 - [h . t] then ["<option>",h . format_choices(t)]  
1394 - }.  
1395 -  
1396 -  
1397 -public define Printable_tree  
1398 - format_choices  
1399 - (  
1400 - List(String) l,  
1401 - String selected  
1402 - ) =  
1403 - if l is  
1404 - {  
1405 - [ ] then [ ],  
1406 - [h . t] then if h = selected  
1407 - then ["<option selected>",h . format_choices(t)]  
1408 - else ["<option>",h . format_choices(t,selected)]  
1409 - }.  
1410 -  
1411 -  
1412 -public define Printable_tree  
1413 - format_list  
1414 - (  
1415 - String c_ticket,  
1416 - String s_ticket,  
1417 - List(Web_item) l  
1418 - ) =  
1419 - if l is  
1420 - {  
1421 - [ ] then [ ],  
1422 - [h . t] then ["<li>",format(c_ticket,s_ticket,h),  
1423 - format_list(c_ticket,s_ticket,t)]  
1424 - }.  
1425 -  
1426 -  
1427 -public define String  
1428 - format  
1429 - (  
1430 - WebStyle ws  
1431 - ) =  
1432 - if ws is  
1433 - {  
1434 - background_image(fn) then "background: url("+fn+")",  
1435 - background_color(c) then "background: "+format(c),  
1436 - background_transparent then "background: transparent",  
1437 - background_repeat_horizontal then "background: repeat-x",  
1438 - background_repeat_vertical then "background: repeat-y",  
1439 - background_no_repeat then "background: no-repeat",  
1440 - color(wc) then if wc is  
1441 - {  
1442 - rgb(r,g,b) then "color: rgb("+r+","+g+","+b+")",  
1443 - _(c) then "color: "+format(c)  
1444 - },  
1445 - float_to_left then "float: left",  
1446 - float_to_right then "float: right",  
1447 - font_family(n) then "font-family: "+n,  
1448 - font_size(n) then "font-size: "+to_decimal(n)+"pt",  
1449 - italic then "font-style: italic",  
1450 - oblique then "font-style: oblique",  
1451 - small_capitals then "font-variant: small-caps",  
1452 - bold then "font-weight: bold",  
1453 - bolder then "font-weight: bolder",  
1454 - lighter then "font-weight: lighter",  
1455 - line_height(h) then "line-height: "+to_decimal(h),  
1456 - text_center then "text-align: center",  
1457 - text_left then "text-align: left",  
1458 - text_right then "text-align: right",  
1459 - text_justify then "text-align: justify",  
1460 - text_underline then "text-decoration: underline",  
1461 - text_blink then "text-decoration: blink",  
1462 - text_line_through then "text-decoration: line-through",  
1463 - width(n) then "width: "+to_decimal(n),  
1464 - }.  
1465 -  
1466 -  
1467 -public define Printable_tree  
1468 - format  
1469 - (  
1470 - List(WebStyle) l  
1471 - ) =  
1472 - if l is  
1473 - {  
1474 - [ ] then [ ],  
1475 - [h . t] then  
1476 - if t is  
1477 - {  
1478 - [ ] then [format(h)],  
1479 - [_ . _] then [format(h), "; " . format(t)]  
1480 - }  
1481 - }.  
1482 -  
1483 -  
1484 -define Printable_tree  
1485 - format_polygon_coordinates  
1486 - (  
1487 - List((Int,Int)) vertices  
1488 - ) =  
1489 - if vertices is  
1490 - {  
1491 - [ ] then [ ],  
1492 - [h . t] then  
1493 - if h is (x,y) then  
1494 - if t is []  
1495 - then [ x,",",y ]  
1496 - else [ x,",",y,"," . format_polygon_coordinates(t)]  
1497 - }.  
1498 -  
1499 - define Printable_tree  
1500 - format  
1501 - (  
1502 - List(Mouse_Sensitive_Zone) zones  
1503 - ) =  
1504 - if zones is  
1505 - {  
1506 - [ ] then [ ],  
1507 - [h . t] then  
1508 - [  
1509 - if h is  
1510 - {  
1511 - rectangle(lt,rb,url) then  
1512 - if lt is (left,top) then  
1513 - if rb is (right,bottom) then  
1514 - [ "<area shape=rect coords=\"",left,",",top,",",right,",",bottom,  
1515 - "\" href=\"",url,"\">" ],  
1516 - circle(c,r,url) then  
1517 - if c is (x,y) then  
1518 - [ "<area shape=circle coords=\"",x,",",y,",",r,  
1519 - "\" href=\"",url,"\">" ],  
1520 - polygon(vs,url) then  
1521 - [ "<area shape=poly coords=\"",format_polygon_coordinates(vs),  
1522 - "\" href=\"",url,"\">" ]  
1523 - }  
1524 - . format(t)]  
1525 - }.  
1526 -  
1527 -define Printable_tree  
1528 - format  
1529 - (  
1530 - List(Mouse_Sensitive_Zone) zones,  
1531 - String c_ticket,  
1532 - String s_ticket,  
1533 - ) =  
1534 - if zones is  
1535 - {  
1536 - [ ] then [ ],  
1537 - [h . t] then  
1538 - [  
1539 - if h is  
1540 - {  
1541 - rectangle(lt,rb,url) then  
1542 - if lt is (left,top) then  
1543 - if rb is (right,bottom) then  
1544 - [ "<area shape=rect coords=\"",left,",",top,",",right,",",bottom,  
1545 - "\" href=\"", url + "&c_ticket=" + c_ticket + "&s_ticket=" + s_ticket,"\">" ],  
1546 - circle(c,r,url) then  
1547 - if c is (x,y) then  
1548 - [ "<area shape=circle coords=\"",x,",",y,",",r,  
1549 - "\" href=\"", url + "&c_ticket=" + c_ticket + "&s_ticket=" + s_ticket,"\">" ],  
1550 - polygon(vs,url) then  
1551 - [ "<area shape=poly coords=\"",format_polygon_coordinates(vs),  
1552 - "\" href=\"", url + "&c_ticket=" + c_ticket + "&s_ticket=" + s_ticket,"\">" ]  
1553 - }  
1554 - . format(t, c_ticket, s_ticket)]  
1555 - }.  
1556 -  
1557 -variable Int map_number = 0.  
1558 -  
1559 -define Printable_tree  
1560 - format_mouse_sensitive_image  
1561 - (  
1562 - String image_file_name,  
1563 - List(Mouse_Sensitive_Zone) zones,  
1564 - String c_ticket,  
1565 - String s_ticket,  
1566 - ) =  
1567 - map_number <- (*map_number)+1;  
1568 - [  
1569 - "<img src=\"",image_file_name,"\" usemap=\"#imsensmap",*map_number,"\" border=0>",  
1570 - "<map name=\"imsensmap",*map_number,"\">",  
1571 - format(zones, c_ticket, s_ticket),  
1572 - "</map>"  
1573 - ].  
1574 -  
1575 -  
1576 - Find an 'upload' in a web item.  
1577 -  
1578 -define Bool  
1579 - find_upload  
1580 - (  
1581 - Web_item i  
1582 - ).  
1583 -  
1584 -define Bool  
1585 - find_upload  
1586 - (  
1587 - List(Web_item) li  
1588 - ) =  
1589 - if li is  
1590 - {  
1591 - [ ] then false,  
1592 - [h . t] then if find_upload(h) then true else find_upload(t)  
1593 - }.  
1594 -  
1595 -define Bool  
1596 - find_upload  
1597 - (  
1598 - Cell c  
1599 - ) =  
1600 - if c is cell(lo,wi) then find_upload(wi).  
1601 -  
1602 -define Bool  
1603 - find_upload  
1604 - (  
1605 - List(Cell) lc  
1606 - ) =  
1607 - if lc is  
1608 - {  
1609 - [ ] then false,  
1610 - [h . t] then if find_upload(h) then true else find_upload(t)  
1611 - }.  
1612 -  
1613 -  
1614 -  
1615 -define Bool  
1616 - find_upload  
1617 - (  
1618 - Table_row tr  
1619 - ) =  
1620 - if tr is  
1621 - {  
1622 - row(lo,lc) then find_upload(lc)  
1623 - }.  
1624 -  
1625 -define Bool  
1626 - find_upload  
1627 - (  
1628 - List(Table_row) l  
1629 - ) =  
1630 - if l is  
1631 - {  
1632 - [ ] then false,  
1633 - [h . t] then  
1634 - if find_upload(h) then true else find_upload(t)  
1635 - }.  
1636 -  
1637 -define Bool  
1638 - find_upload  
1639 - (  
1640 - Web_item wi  
1641 - ) =  
1642 - if wi is  
1643 - {  
1644 - [ ] then (Bool)false,  
1645 - [a . b] then (Bool)if find_upload(a) then true else find_upload(b),  
1646 - text(_) then (Bool)false,  
1647 - text_pt(_) then (Bool)false,  
1648 - text_nowrap(_) then (Bool)false,  
1649 - text_nowrap_pt(_) then (Bool)false,  
1650 - par(_) then (Bool)false,  
1651 - preformated_text(_) then (Bool)false,  
1652 - integer(_) then (Bool)false,  
1653 - float(_,_) then (Bool)false,  
1654 - center(i) then (Bool)find_upload(i),  
1655 - bigger(n,i) then (Bool)find_upload(i),  
1656 - smaller(n,i) then (Bool)find_upload(i),  
1657 - bold(i) then (Bool)find_upload(i),  
1658 - italic(i) then (Bool)find_upload(i),  
1659 - big(i) then (Bool)find_upload(i),  
1660 - very_big(i) then (Bool)find_upload(i),  
1661 - style(_,i) then (Bool)find_upload(i),  
1662 - spacer(_,_) then (Bool)false,  
1663 - image(_) then (Bool)false,  
1664 - image_d(_,_) then (Bool)false,  
1665 - image_pt(_) then (Bool)false,  
1666 - on_image(_,_) then (Bool)false,  
1667 - turning_images(_,_) then (Bool)false,  
1668 - rollover(_,_,_,_,_,_) then (Bool)false,  
1669 - rollover(_,_,_,_,_,_,_,_) then (Bool)false,  
1670 - mouse_sensitive_image(_,_) then (Bool)false,  
1671 - background_sound(_,_) then (Bool)false,  
1672 - form(_,c) then (Bool)find_upload(c),  
1673 - form_target(_,c,_) then (Bool)find_upload(c),  
1674 - form(_,_,c) then (Bool)find_upload(c),  
1675 - form_name(_,c) then (Bool)find_upload(c),  
1676 - text_input(_,_,_) then (Bool)false,  
1677 - password_input(_,_) then (Bool)false,  
1678 - text_area(_,_,_,_) then (Bool)false,  
1679 - upload(_,_) then (Bool)true,  
1680 - submit(_) then (Bool)false,  
1681 - submit_pt(_) then (Bool)false,  
1682 - submit(_,_) then (Bool)false,  
1683 - submit_close(_,_) then (Bool)false,  
1684 - submit_pt2(_,_) then (Bool)false,  
1685 - image_submit(_,_) then (Bool)false,  
1686 - image_submit(_,_,_,_) then (Bool)false,  
1687 - hl_image_submit(_,_,_,_,_) then (Bool)false,  
1688 - text_submit(_,_,_) then (Bool)false,  
1689 - web_submit(_,c) then (Bool)find_upload(c),  
1690 - button(_,_,_,_,_) then (Bool)false,  
1691 - mark(_,_) then (Bool)false,  
1692 - mark_pt(_,_) then (Bool)false,  
1693 - close_button then (Bool)false,  
1694 - close_button(_) then (Bool)false,  
1695 - label(_) then (Bool)false,  
1696 - go_to_label(_,i) then (Bool)find_upload(i),  
1697 - table(_,rows) then (Bool)find_upload(rows),  
1698 - list(l) then (Bool)find_upload(l),  
1699 - link(_,i) then (Bool)find_upload(i),  
1700 - link(_,_,i) then (Bool)find_upload(i),  
1701 - link_for_download(n,i) then (Bool)find_upload(i),  
1702 - mail_to(_,i) then (Bool)find_upload(i),  
1703 - select(_,_,_) then (Bool)false,  
1704 - select(_,_,_,_) then (Bool)false,  
1705 - immediate_select(_,_,_) then (Bool)false,  
1706 - radio_button(_,_) then (Bool)false,  
1707 - checked_radio_button(_,_) then (Bool)false,  
1708 - check_box(_,_) then (Bool)false,  
1709 - checked_box(_,_) then (Bool)false,  
1710 - link_to_window(_,i) then (Bool)find_upload(i),  
1711 - link_to_window(_,_,i) then (Bool)find_upload(i),  
1712 - link_to_window_with_ticket(_,_,_,i,_,_) then (Bool)find_upload(i),  
1713 - link_to_window_with_ticket_and_scroll(_,_,_,i,_,_) then (Bool)find_upload(i),  
1714 - link_to_window_with_ticket_and_scroll(_,_,_,_,i,_,_) then (Bool)find_upload(i),  
1715 - link_to_frame(_,_,i) then (Bool)find_upload(i),  
1716 - }.  
1717 -  
1718 -  
1719 - The next function generates "enctype=multipart/form-data" or "", depending on the presence of  
1720 - an 'upload' in form-content.  
1721 -  
1722 -define String  
1723 - enctype  
1724 - (  
1725 - Web_item form_content  
1726 - ) =  
1727 - if find_upload(form_content)  
1728 - then "enctype=multipart/form-data"  
1729 - else "".  
1730 -  
1731 -define Printable_tree  
1732 - set_turning_images_sources  
1733 - (  
1734 - Int i,  
1735 - String name,  
1736 - List(String) filenames  
1737 - ) =  
1738 - if filenames is  
1739 - {  
1740 - [ ] then [ ],  
1741 - [h . t] then  
1742 - [" i",name,"[",i,"].src=\"",h,"\";"  
1743 - . set_turning_images_sources(i+1,name,t)]  
1744 - }.  
1745 -  
1746 -define Bool  
1747 - member  
1748 - (  
1749 - Word8 x,  
1750 - Printable_tree t  
1751 - ) =  
1752 - if t is  
1753 - {  
1754 - [] then false,  
1755 - str_pt(String _0,Printable_tree _1) then (member(x,_0) | member(x,_1)),  
1756 - ba_pt(ByteArray _0,Printable_tree _1) then (member(x,to_string(_0)) | member(x,_1)),  
1757 - int_pt(Int _0,Printable_tree _1) then (member(x,to_decimal(_0)) | member(x,_1)),  
1758 - pt_pt(Printable_tree _0,Printable_tree _1) then (member(x,_0) | member(x,_1))  
1759 - }.  
1760 -  
1761 -public define Printable_tree  
1762 - format  
1763 - (  
1764 - String c_ticket,  
1765 - String s_ticket,  
1766 - Web_item wi  
1767 - ) =  
1768 - if wi is  
1769 - {  
1770 - [ ] then (Printable_tree)[ ],  
1771 -  
1772 - [a . b] then (Printable_tree)[format(c_ticket,s_ticket,a), " ",  
1773 - format(c_ticket,s_ticket,b)],  
1774 -  
1775 - text(String s) then (Printable_tree)[s],  
1776 -  
1777 - text_pt(Printable_tree s) then (Printable_tree)s,  
1778 -  
1779 - text_nowrap(String s) then (Printable_tree)["<table><tr><td nowrap=\"nowrap>",s,"</td></tr></table>"],  
1780 -  
1781 - text_nowrap_pt(Printable_tree s) then  
1782 - (Printable_tree)["<table><tr><td \"nowrap\">",s,"</td></tr></table>"],  
1783 -  
1784 - par(s) then (Printable_tree)  
1785 - [ "<p align=\"justify\">", s, " </p>"],  
1786 -  
1787 - preformated_text(t) then (Printable_tree) ["<pre>",t,"</pre>"],  
1788 -  
1789 - integer(n) then (Printable_tree)[n],  
1790 -  
1791 - float(f,p) then (Printable_tree)[float_to_string(f,p)],  
1792 -  
1793 - center(item) then (Printable_tree)["<center>", format(c_ticket,s_ticket,item),"</center>"],  
1794 -  
1795 - bigger(n,item) then (Printable_tree)["<font size=\"+",n,"\">",format(c_ticket,s_ticket,item),"</font>"],  
1796 -  
1797 - smaller(n,item) then (Printable_tree)["<font size=\"-",n,"\">",format(c_ticket,s_ticket,item),"</font>"],  
1798 -  
1799 - bold(item) then  
1800 - (Printable_tree)["<font style=\"font-weight: bold\">",format(c_ticket,s_ticket,item),"</font>"],  
1801 -  
1802 - italic(item) then  
1803 - (Printable_tree)["<font style=\"font-style: italic\">",format(c_ticket,s_ticket,item),"</font>"],  
1804 -  
1805 - big(item) then (Printable_tree)["<font size=\"+1\">",format(c_ticket,s_ticket,item),"</font>"],  
1806 -  
1807 - very_big(item) then (Printable_tree)["<h1>",format(c_ticket,s_ticket,item),"</h1>"],  
1808 -  
1809 - style(l,i) then  
1810 - (Printable_tree)["<span style=\"", format(l), "\">", format(c_ticket,s_ticket,i), "</span>"],  
1811 -  
1812 - spacer(w,h) then (Printable_tree)  
1813 - ["<img src=\"spacer.gif\" width=\"",w,"\" height=\"",h,"\" border=\"0\">"],  
1814 -  
1815 - image(String f) then (Printable_tree)["<img src=\"", f,"\" border=\"0\">"],  
1816 -  
1817 - image_d(fn,desc) then (Printable_tree)["<img src=\"", fn,"\" alt=\"",desc,"\" border=\"0\">"],  
1818 -  
1819 - image_pt(Printable_tree l) then (Printable_tree)["<img src=\"", l,"\" border=\"0\">"],  
1820 -  
1821 - on_image(fn,x) then (Printable_tree)  
1822 - ["<span style=\"background: url(",fn,")\">",format(c_ticket,s_ticket,x),"</span>"],  
1823 -  
1824 - turning_images(fns,msec) then if fns is [im0 . imo] then  
1825 - with name = "trni"+to_decimal(new_web_count), n = 1+length(imo), (Printable_tree)  
1826 - ["<script>",  
1827 - "var i",name,"=new Array(",n,");",  
1828 - "var n",name,"=0;",  
1829 - "for(var i=0; i<",n,"; i++) {",  
1830 - " i",name,"[i]=new Image(); }",  
1831 - set_turning_images_sources(0,name,(List(String))[im0 . imo]),  
1832 -  
1833 - "function a",name,"() {",  
1834 - "if (i",name,"[(n",name,"+1)%",n,"].complete)",  
1835 - "{n",name,"=(n",name,"+1)%",n,";",  
1836 - "document.",name,".src=i",name,"[n",name,"].src; }",  
1837 - "setTimeout(\"a",name,"()\",",msec,"); }",  
1838 -  
1839 - "setTimeout(\"a",name,"()\",",msec,");",  
1840 - "</script>",  
1841 - "<img src=\"",im0,"\" name=\"",name,"\" border=\"0\">"],  
1842 -  
1843 - rollover(prlim,url,target,ion,ioff,descr) then (Printable_tree)  
1844 - (images_to_load <- [simple(ion) . *images_to_load];  
1845 - with name = "ron_"+to_decimal(new_web_count),  
1846 - ["<a target=\"",target,"\" href=\"",url,  
1847 - (if (c_ticket = "" & s_ticket = "") then "" else (if member('?',url) then "&" else "?")),  
1848 - (if c_ticket = "" then [ ] else ["c_ticket=",c_ticket]),  
1849 - (if s_ticket = "" then [ ] else ["&s_ticket=",s_ticket]),  
1850 - "\" onmouseout=\"",name,".src='",ioff,  
1851 - "';\" onmouseover=\"",name,".src='",ion,"';\"><img src=\"",ioff,  
1852 - "\" name=\"",name,"\" alt=\"",descr,"\" border=\"0\"></a>"]),  
1853 -  
1854 - rollover(prlim,url,target,ion,ioff,w,h,descr) then (Printable_tree)  
1855 - (images_to_load <- [simple(ion) . *images_to_load];  
1856 - with name = "ron_"+to_decimal(new_web_count),  
1857 - ["<a target=\"",target,"\" href=\"",url,  
1858 - (if (c_ticket = "" & s_ticket = "") then "" else (if member('?',url) then "&" else "?")),  
1859 - (if c_ticket = "" then [ ] else ["c_ticket=",c_ticket]),  
1860 - (if s_ticket = "" then [ ] else ["&s_ticket=",s_ticket]),  
1861 - "\" onmouseout=\"",name,".src='",ioff,  
1862 - "';\" onmouseover=\"",name,".src='",ion,"';\"><img src=\"",ioff,  
1863 - "\" name=\"",name,"\" width=\"",w,"\" height=\"",h,"\" alt=\"",descr,"\" border=\"0\"></a>"]),  
1864 -  
1865 - mouse_sensitive_image(fn,lz) then (Printable_tree)  
1866 - format_mouse_sensitive_image(fn,lz, c_ticket, s_ticket),  
1867 -  
1868 - background_sound(sfn,loop) then (Printable_tree)  
1869 - ["<embed src=\"", sfn, "\" autostart=\"true\" loop=\"", if loop then "true\">" else "false\">"],  
1870 -  
1871 - form(n,c) then (Printable_tree)  
1872 - ["<form ",enctype(c)," method=\"post\" action=\"", n, "\">",  
1873 - "<input type=\"hidden\" name=\"s_ticket\" value=\"",s_ticket,"\" />",  
1874 - "<input type=\"hidden\" name=\"c_ticket\" value=\"",c_ticket,"\" />",  
1875 - format(c_ticket,s_ticket,c),"</form>"],  
1876 -  
1877 - form_target(n,c,t) then (Printable_tree)  
1878 - ["<form ",enctype(c)," method=\"post\" action=\"", n, "\" target=\"", t, "\">",  
1879 - "<input type=\"hidden\" name=\"s_ticket\" value=\"",s_ticket,"\" />",  
1880 - "<input type=\"hidden\" name=\"c_ticket\" value=\"",c_ticket,"\" />",  
1881 - format(c_ticket,s_ticket,c),"</form>"],  
1882 -  
1883 - form(n,l,c) then (Printable_tree)  
1884 - ["<form ",enctype(c)," method=\"post\" action=\"", n,"#", l, "\">",  
1885 - "<input type=\"hidden\" name=\"s_ticket\" value=\"",s_ticket,"\" />",  
1886 - "<input type=\"hidden\" name=\"c_ticket\" value=\"",c_ticket,"\" />",  
1887 - format(c_ticket,s_ticket,c),"</form>"],  
1888 -  
1889 - form_name(fn,c) then (Printable_tree)  
1890 - [  
1891 - // "<form ",enctype(c), "\" name=\"", fn, "\">",  
1892 - "<form name=\"", fn, "\">",  
1893 - "<input type=\"hidden\" name=\"s_ticket\" value=\"",s_ticket,"\" />",  
1894 - "<input type=\"hidden\" name=\"c_ticket\" value=\"",c_ticket,"\" />",  
1895 - format(c_ticket,s_ticket,c),"</form>"],  
1896 -  
1897 - text_input(n,s,v) then (Printable_tree)  
1898 - ["&nbsp; <input type=\"text\" name=\"", n, "\" size=\"", s, "\" value=\"", v,"\" />"],  
1899 -  
1900 - password_input(n,s) then (Printable_tree)  
1901 - ["&nbsp; <input type=\"password\" name=\"", n, "\" size=\"", s,"\" />"],  
1902 -  
1903 - text_area(name,c,r,i) then (Printable_tree)  
1904 - ["<textarea name=\"",name,"\" cols=\"",c,"\" rows=\"",r,"\" wrap=\"physical\">",i,"</textarea>"],  
1905 -  
1906 - upload(n,size) then (Printable_tree)  
1907 - ["<input type=\"file\" size=\"",size,"\" multiple=\"multiple\" name=\"",n,"\" />"],  
1908 -  
1909 - submit(String t) then (Printable_tree)["<input type=\"submit\" value=\"",t,"\" />"],  
1910 -  
1911 - submit_pt(Printable_tree t) then (Printable_tree)["<input type=\"submit\" value=\"",t,"\" />"],  
1912 -  
1913 - submit(n, String t) then (Printable_tree)  
1914 - ["<input type=\"submit\" name=\"",n,"\" value=\"",t,"\">"],  
1915 -  
1916 - submit_close(n, String t) then (Printable_tree)  
1917 - ["<input type=\"submit\" name=\"",n,"\" value=\"",t,"\" onclick=\"window.top.close();\" />"],  
1918 -  
1919 - submit_pt2(n, Printable_tree t) then (Printable_tree)  
1920 - ["<input type=\"submit\" name=\"",n,"\" value=\"",t,"\" />"],  
1921 -  
1922 - image_submit(n,ifn) then (Printable_tree)  
1923 - ["<input type=\"image\" name=\"",n,"\" src=\"",ifn,"\" border=\"0\" />"],  
1924 -  
1925 - image_submit(n,v,ifn,c) then (Printable_tree)  
1926 - ["<img src=\"",ifn,"\" onmousedown=\"document.forms[0].action='",  
1927 - n,"=",v,"'; document.forms[0].submit();\" />"],  
1928 -  
1929 - hl_image_submit(n,v,in,ifn,hlifn) then (Printable_tree)  
1930 - ["<img src=\"",ifn,"\"",  
1931 - //" name=\"",in,"\"",  
1932 - " onmouseover=\"this.src='",hlifn,"'\"",  
1933 - " onmouseout=\"this.src='",ifn,"'\"",  
1934 - " onmousedown=\"document.forms[0].action='",  
1935 - n,"=",v,"'; document.forms[0].submit();\" />"],  
1936 -  
1937 - text_submit(n,v,t) then (Printable_tree)  
1938 - ["<a href=\"",n,  
1939 - (if (s_ticket = "" & c_ticket = "" & v = "") then "" else "?"),  
1940 - (if s_ticket = "" then [ ] else ["s_ticket=",s_ticket,"&"]),  
1941 - (if c_ticket = "" then [ ] else ["c_ticket=",c_ticket]),  
1942 - v,"\">",t,"</a>"],  
1943 -  
1944 -  
1945 - web_submit(wa,i) then (Printable_tree)  
1946 - ["<a href=\"javascript:document.forms[0].action='",  
1947 - wa,"'; document.forms[0].submit();\">",format(c_ticket,s_ticket,i),"</a>"],  
1948 -  
1949 - button(n, String t, String o, Int w, Int h) then (Printable_tree)  
1950 - ["<input type=button name=",n," value=\"",t,  
1951 - "\"style=\"width=",w, ";height=", h, "\" ", "\" onclick=\"", o, "\";\" />"],  
1952 -  
1953 - mark(n, String v) then (Printable_tree)  
1954 - ["<input type=\"hidden\" name=\"",n,"\" value=\"",v,"\" />"],  
1955 -  
1956 - mark_pt(n, Printable_tree v) then (Printable_tree)  
1957 - ["<input type=\"hidden\" name=\"",n,"\" value=\"",v,"\" />"],  
1958 -  
1959 - close_button then (Printable_tree)  
1960 - ["<form><input type=\"button\" value=\" Fermer \" onclick=\"window.top.close();\"></form>"],  
1961 -  
1962 - close_button(ifn) then (Printable_tree)  
1963 - ["<form><input type=\"image\" name=\"close\" src=\"", ifn,  
1964 - "\" onclick=\"window.top.close();\" /></form>"],  
1965 -  
1966 -/*  
1967 - close_button(ifn) then (Printable_tree)  
1968 - ["<form><input type=\"button\" name=\"close\" src=\"", ifn,  
1969 - "\" onclick=\"window.top.close();\"></form>"],  
1970 -*/  
1971 - label(name) then (Printable_tree)["<a name=\"",name,"\" />"],  
1972 -  
1973 - go_to_label(name,content) then (Printable_tree)["<a href=#",name,">",  
1974 - format(c_ticket,s_ticket,content),"</a>"],  
1975 -  
1976 - table(ops,rows) then (Printable_tree)["<table ",  
1977 - format(ops), ">",format(c_ticket,s_ticket,rows),"</table>"],  
1978 -  
1979 - list(l) then (Printable_tree)["<ul>",format_list(c_ticket,s_ticket,l),"</ul>"],  
1980 -  
1981 - link(name,i) then (Printable_tree)["<a href=\"",name,  
1982 - (if (c_ticket = "" & s_ticket = "") then "" else (if member('?',name) then "&" else "?")),  
1983 - (if c_ticket = "" then [ ] else ["c_ticket=",c_ticket]),  
1984 - (if s_ticket = "" then [ ] else [if c_ticket = "" then "" else "&","s_ticket=",s_ticket]),  
1985 - "\">",format(c_ticket,s_ticket,i),"</a>"],  
1986 -  
1987 - link(name,target,i) then (Printable_tree)["<a target=\"",target,"\" href=\"",name,  
1988 - (if (c_ticket = "" & s_ticket = "") then "" else (if member('?',name) then "&" else "?")),  
1989 - (if c_ticket = "" then [ ] else ["c_ticket=",c_ticket]),  
1990 - (if s_ticket = "" then [ ] else ["&s_ticket=",s_ticket]),  
1991 - "\">",format(c_ticket,s_ticket,i),"</a>"],  
1992 -  
1993 - link_for_download(fname,i) then (Printable_tree)  
1994 - ["<a href=\"",fname,"?download\">",format(c_ticket,s_ticket,i),"</a>"],  
1995 -  
1996 - mail_to(addr,i) then (Printable_tree)["<a href=\"mailto:",addr,"\">",  
1997 - format(c_ticket,s_ticket,i),"</a>"],  
1998 -  
1999 - select(name,size,choices) then (Printable_tree)  
2000 - ["<select name=\"",name,"\" size=\"",size,"\">",format_choices(choices),"</select>" ],  
2001 -  
2002 - select(name,size,choices,selected) then (Printable_tree)  
2003 - ["<select name=\"",name,"\" size=\"",size,"\">",format_choices(choices,selected),"</select>" ],  
2004 -  
2005 - immediate_select(name,size,choices) then (Printable_tree)  
2006 - ["<select name=\"",name,"\" size=\"",size,"\" onchange=\"submit();\">",  
2007 - format_choices(choices),"</select>" ],  
2008 -  
2009 - radio_button(n,v) then (Printable_tree)  
2010 - ["<input type=\"radio\" name=\"", n, "\" value=\"", v, "\" />"],  
2011 -  
2012 - checked_radio_button(n,v) then (Printable_tree)  
2013 - ["<input type=\"radio\" checked=\"checked\" name=\"", n, "\" value=\"", v, "\" />"],  
2014 -  
2015 - check_box(n,v) then (Printable_tree)  
2016 - ["<input type=\"checkbox\" name=\"", n, "\" value=\"", v, "\" />"],  
2017 -  
2018 - checked_box(n,v) then (Printable_tree)  
2019 - ["<input type=\"checkbox\" checked=\"checked\" name=\"", n, "\" value=\"", v, "\" />"],  
2020 -  
2021 - link_to_window(n,i) then (Printable_tree)  
2022 - ["<a href=\"javascript:void window.open('",n,"','default','resizable,scrollbars');\">",  
2023 - format(c_ticket,s_ticket,i),"</a>"],  
2024 -  
2025 - link_to_window(n,wn,i) then (Printable_tree)  
2026 - ["<a href=\"javascript:void window.open('",n,"','",wn,"','resizable,scrollbars');\" />",  
2027 - format(c_ticket,s_ticket,i),"</a>"],  
2028 -  
2029 - link_to_window_with_ticket(n,args,wn,i,w,h) then (Printable_tree)  
2030 - ["<a href=\"javascript:void window.open('",n,"?c_ticket=",c_ticket,  
2031 - (if s_ticket = "" then [ ] else ["&s_ticket=",s_ticket]),  
2032 - "&target=",wn,  
2033 - if args="" then "" else "&",  
2034 - args,"','",  
2035 - wn,"','width=",w,",height=",h,"');\">",  
2036 - format(c_ticket,s_ticket,i),"</a>"],  
2037 -  
2038 - link_to_window_with_ticket_and_scroll(n,args,wn,i,w,h) then (Printable_tree)  
2039 - ["<a href=\"javascript:void window.open('",n,"?c_ticket=",c_ticket,  
2040 - (if s_ticket = "" then [ ] else ["&s_ticket=",s_ticket]),  
2041 - "&target=",wn,  
2042 - if args="" then "" else "&",  
2043 - args,"','",  
2044 - wn,"','width=",w,",height=",h,", resizable,scrollbars');\">",  
2045 - format(c_ticket,s_ticket,i),"</a>"],  
2046 -  
2047 - link_to_window_with_ticket_and_scroll(n,lab,args,wn,i,w,h) then (Printable_tree)  
2048 - ["<a href=\"javascript:void window.open('",n,"?c_ticket=",c_ticket,  
2049 - (if s_ticket = "" then [ ] else ["&s_ticket=",s_ticket]),  
2050 - "&target=",wn,  
2051 - if args="" then "" else "&",  
2052 - args,"&#",lab, "','",  
2053 - wn,"','width=",w,",height=",h,", resizable,scrollbars');\">",  
2054 - format(c_ticket,s_ticket,i),"</a>"],  
2055 -  
2056 - link_to_frame(n,fn,i) then ["<a href=\"",n,  
2057 - (if member('?',n) then "&" else "?"),  
2058 - (if c_ticket = "" then [ ] else ["c_ticket=",c_ticket]),  
2059 - (if s_ticket = "" then [ ] else ["&s_ticket=",s_ticket]),  
2060 - "\" target=\"",fn,"\">",  
2061 - format(c_ticket,s_ticket,i),"</a>"]  
2062 -  
2063 - }.  
2064 -  
2065 -  
2066 -  
2067 -define Printable_tree  
2068 - add_tickets  
2069 - (  
2070 - String c_ticket,  
2071 - String s_ticket,  
2072 - String url  
2073 - ) =  
2074 - if member('?',url)  
2075 - then [url , "&c_ticket=", c_ticket, "&s_ticket=", s_ticket ]  
2076 - else [url , "?c_ticket=", c_ticket, "&s_ticket=", s_ticket ].  
2077 -  
2078 -define Printable_tree  
2079 - add_tickets  
2080 - (  
2081 - String c_ticket,  
2082 - String s_ticket,  
2083 - Printable_tree url  
2084 - ) =  
2085 - if member('?',url)  
2086 - then [url , "&c_ticket=", c_ticket, "&s_ticket=", s_ticket ]  
2087 - else [url , "?c_ticket=", c_ticket, "&s_ticket=", s_ticket ].  
2088 -  
2089 -  
2090 -define Printable_tree  
2091 - format  
2092 - (  
2093 - List(VFrame) frames,  
2094 - String c_ticket,  
2095 - String s_ticket  
2096 - ) =  
2097 - if frames is  
2098 - {  
2099 - [ ] then [ ],  
2100 - [h . t] then  
2101 - if h is frame(height,url,name) then  
2102 - [  
2103 - "<frame src=\"",add_tickets(c_ticket,s_ticket,url), "\" name=\"",name,"\" frameborder=\"no\" />"  
2104 - . format(t,c_ticket,s_ticket)]  
2105 - }.  
2106 -  
2107 -  
2108 -define String  
2109 - frame_size  
2110 - (  
2111 - Int s  
2112 - ) =  
2113 - if s =< 0 then "*" else to_decimal(s).  
2114 -  
2115 -define String  
2116 - frame_stack_rows  
2117 - (  
2118 - List(VFrame) frames  
2119 - ) =  
2120 - if frames is  
2121 - {  
2122 - [ ] then "",  
2123 - [h . t] then if h is frame (height,url,name) then  
2124 - frame_size(height)+  
2125 - if t is  
2126 - {  
2127 - [ ] then "",  
2128 - [_ . _] then ","  
2129 - }+frame_stack_rows(t)  
2130 - }.  
2131 -  
2132 -  
2133 -  
2134 - 'crlf' is defined in 'basis.anubis'.  
2135 -  
2136 -  
2137 -  
2138 -define Printable_tree  
2139 - standard_headers  
2140 - (  
2141 - Int size  
2142 - ) =  
2143 - [  
2144 - "HTTP/1.0 200 OK" + crlf +  
2145 - "Server: Anubis" + crlf +  
2146 - "Content-Type: text/html" + crlf +  
2147 - "Content-Length: "+to_decimal(size)+crlf+  
2148 - crlf  
2149 - ].  
2150 -  
2151 - define Printable_tree  
2152 - download_headers  
2153 - =  
2154 - [  
2155 - "HTTP/1.0 200 OK" + crlf +  
2156 - "Content-Type: application/octet-stream" + crlf +  
2157 - crlf  
2158 - ].  
2159 -  
2160 -define Printable_tree  
2161 - apache_headers  
2162 - =  
2163 - [  
2164 - "Content-type: text/html" + crlf +  
2165 - crlf  
2166 - ].  
2167 -  
2168 -  
2169 -define String  
2170 - empty_javascript_source  
2171 - =  
2172 - "javascript:'<html><head></head><body></body></html>';".  
2173 -  
2174 -public type HeaderSort:  
2175 - empty,  
2176 - anubis,  
2177 - apache.  
2178 -  
2179 -  
2180 -define Printable_tree  
2181 - format_keywords  
2182 - (  
2183 - List(String) l  
2184 - ) =  
2185 - if l is  
2186 - {  
2187 - [ ] then [ ],  
2188 - [h . t] then if t is [ ]  
2189 - then [h]  
2190 - else [h , ", " . format_keywords(t)]  
2191 - }.  
2192 -  
2193 -define Printable_tree  
2194 - format  
2195 - (  
2196 - WebMeta m  
2197 - ) =  
2198 - if m is  
2199 - {  
2200 - keywords(l) then ["<meta name=\"keywords\" content=\"",format_keywords(l),"\">"],  
2201 - refresh(url,delay) then ["<meta http-equiv=\"Refresh\" content=\"",delay,"; URL=",url,"\">"],  
2202 - meta(n,c) then ["<meta name=\"",n,"\" content=\"",c,"\">"],  
2203 - http_equiv(n,c) then ["<meta http-equiv=\"",n,"\" content=\"",c,"\">"]  
2204 - }.  
2205 -  
2206 -define Printable_tree  
2207 - format  
2208 - (  
2209 - List(WebMeta) metas  
2210 - ) =  
2211 - if metas is  
2212 - {  
2213 - [ ] then [ ],  
2214 - [h . t] then [format(h) . format(t)]  
2215 - }.  
2216 -  
2217 -  
2218 -  
2219 -  
2220 -  
2221 -  
2222 -public define Printable_tree  
2223 - format  
2224 - (  
2225 - HeaderSort hs,  
2226 - String c_ticket,  
2227 - String s_ticket,  
2228 - Web_page p  
2229 - ) =  
2230 - if p is  
2231 - {  
2232 - web_page(title,metas,head_scripts,body) then  
2233 - with fullpage =  
2234 - [  
2235 - "<html>",  
2236 - "<head>",  
2237 - "<title>", title, "</title>",  
2238 - format(metas),  
2239 - head_scripts,  
2240 - "</head>",  
2241 - "<body ",  
2242 - if body is body(options,item) then  
2243 - with b_options = format(prepare(options)),  
2244 - [b_options, " onload='body_onloads();",  
2245 - if *images_to_load is [] then "" else " preload_images();",  
2246 - "'>",  
2247 - load_image_script(*images_to_load),  
2248 - reverse(*scripts),  
2249 - "<script>",  
2250 - " function body_onloads() {",  
2251 - format(*body_onloads),  
2252 - "}</script>",  
2253 - format(c_ticket,s_ticket,item)  
2254 - ],  
2255 - "</body>",  
2256 - "</html>"  
2257 - ],  
2258 - [  
2259 - if hs is  
2260 - {  
2261 - empty then [ ],  
2262 - anubis then standard_headers(length(fullpage)),  
2263 - apache then apache_headers  
2264 - }  
2265 - . fullpage  
2266 - ],  
2267 - standard_frameset(title,metas,height,width,main) then  
2268 - with body =  
2269 - [  
2270 - "<html>",  
2271 - "<head>",  
2272 - "<title>", title, "</title>",  
2273 - format(metas),  
2274 - "</head>",  
2275 - "<frameset frameborder=\"no\" border=\"0\" framespacing=\"0\"",  
2276 - " marginwidth=\"0\" marginheight=\"0\" cols=\"",width,",*\" rows=\"*\">",  
2277 - " <frame src=\"",empty_javascript_source,"\" name=\"left\" frameborder=\"no\"",  
2278 - " marginwidth=\"0\" marginheight=\"0\" scrolling=\"no\">",  
2279 - " <frameset frameborder=\"no\" border=\"0\" framespacing=\"0\" rows=\"",height,",*\" cols=\"*\">",  
2280 - " <frame src=\"",empty_javascript_source,"\" name=\"top\"",  
2281 - " marginwidth=\"0\" marginheight=\"0\" frameborder=\"no\" scrolling=\"no\">",  
2282 - " <frame src =\"", add_tickets(c_ticket,s_ticket,main),  
2283 - "\" name=\"main\" marginwidth=\"0\" marginheight=\"0\" frameborder=\"no\">",  
2284 - " </frameset>",  
2285 - "</frameset>",  
2286 - "</html>"  
2287 - ],  
2288 - [  
2289 - if hs is  
2290 - {  
2291 - empty then [ ],  
2292 - anubis then standard_headers(length(body)),  
2293 - apache then apache_headers  
2294 - }  
2295 - . body  
2296 - ]  
2297 - }.  
2298 -  
2299 -  
2300 -public define Bool  
2301 - print  
2302 - (  
2303 - String c_ticket,  
2304 - String s_ticket,  
2305 - Web_page p  
2306 - ) =  
2307 - print(format(anubis,c_ticket,s_ticket,p)).  
2308 -  
2309 -public define Bool  
2310 - print_with_headers  
2311 - (  
2312 - HeaderSort hs,  
2313 - String c_ticket,  
2314 - String s_ticket,  
2315 - Web_page p  
2316 - ) =  
2317 - print(format(hs,c_ticket,s_ticket,p)).  
2318 -  
2319 -public define Bool  
2320 - print  
2321 - (  
2322 - Web_page p  
2323 - ) =  
2324 - print(format(anubis,"","",p)).  
2325 -  
2326 -  
2327 -  
2328 -  
2329 -public define Bool  
2330 - print_with_headers  
2331 - (  
2332 - HeaderSort hs,  
2333 - Web_page p  
2334 - ) =  
2335 - print(format(hs,"","",p)).  
2336 -  
2337 -  
2338 -public define Cell  
2339 - h_spacer  
2340 - (  
2341 - Int n  
2342 - ) =  
2343 - cell([absolute_width(n)],text("&nbsp;")).  
2344 -  
2345 -public define Cell  
2346 - v_spacer  
2347 - (  
2348 - Int n  
2349 - ) =  
2350 - cell([absolute_height(n)],text("&nbsp;")).  
2351 -  
2352 -public define Cell  
2353 - empty = cell([],text("&nbsp;")).  
2354 -  
2355 -  
2356 -  
2357 - *** Below is a simple gadget for counting visitors. It increments a counter (in a file)  
2358 - at each call. The argument is the name of the file (relative to the directory of the  
2359 - server), and the file is created automatically. It returns the number of the visitor.  
2360 -  
2361 -public define Int  
2362 - get_visitor_number  
2363 - (  
2364 - String counter_file_name,  
2365 - ) =  
2366 - protect  
2367 - if (RetrieveResult(Int))retrieve(counter_file_name) is  
2368 - {  
2369 - cannot_find_file then  
2370 - //  
2371 - // It's time to create the file.  
2372 - //  
2373 - if save((Int)1,counter_file_name) is  
2374 - {  
2375 - cannot_open_file then 0,  
2376 - write_error then 0,  
2377 - ok then 1  
2378 - },  
2379 - read_error then 0,  
2380 - type_error then 0,  
2381 - ok(n) then  
2382 - //  
2383 - // Increment the counter  
2384 - //  
2385 - if save(n+1,counter_file_name) is  
2386 - {  
2387 - cannot_open_file then n,  
2388 - write_error then n,  
2389 - ok then n+1  
2390 - }  
2391 - }.  
2392 -  
2393 -  
2394 -  
2395 -  
web/CXM_multihost_http_server.anubis
1 -  
2 - *Project* The Anubis Project  
3 -  
4 - *Title* A Multi Host HTTP/HTTPS Server  
5 -  
6 - *Copyright* Copyright (c) Anubis Team 2003-2007.  
7 -  
8 -  
9 - *Authors* Alain Prouté  
10 - David René  
11 - Cédric Ricard  
12 -  
13 -  
14 - *Revised* July 2007.  
15 -  
16 -  
17 -  
18 - *Overviews*  
19 - In this file a HTTP/HTTPS server is defined, which is able to handle multiple hosts  
20 - (virtual hosts). It answers HTTP/HTTPS requests, sends files (images or any other kind  
21 - of file), constructs HTML pages on the fly using informations received from the client  
22 - (when the URI ends by '.awp'), handles uploading of files and redirections. It is  
23 - multitasking by itself, and can handle any number of sites and clients simultaneously.  
24 - It should better be used in conjunction with 'making_a_web_site.anubis' to be found in  
25 - the same directory. If you use 'web/making_a_web_site.anubis', you don't need to read  
26 - this file.  
27 -  
28 -  
29 - ----------------------------------- Table of Contents ---------------------------------  
30 -  
31 - *** (1) Multihosting and redirections.  
32 - *** (2) The incompatibility between SSL and virtual hosts.  
33 - *** (3) HTTP headers and web arguments.  
34 - *** (4) Site descriptions.  
35 - *** (5) Protection against denial of service attacks.  
36 - *** (6) Starting your HTTP and HTTPS servers.  
37 - *** (7) Private download.  
38 - *** (8) About web argument names.  
39 - *** (9) A web dispatcher.  
40 - *** (10) HTTP Errors  
41 -  
42 - ---------------------------------------------------------------------------------------  
43 -  
44 -  
45 -  
46 -  
47 - *** (1) Multihosting and redirections.  
48 -  
49 - This HTTP/HTTPS server can handle several host (also called 'virtual hosts'), in other  
50 - words, you may have several sites on the same server, with the same IP address and same  
51 - port numbers, but distinct 'host names'.  
52 -  
53 - A HTTP request sent by a browser contains the following informations:  
54 -  
55 - - a 'host name',  
56 - - an URI (Uniform Resource Identifier),  
57 - - HTTP headers,  
58 - - web arguments (in the form 'name=value').  
59 -  
60 - Actually, the host name is just the value of the HTTP header whose name is 'Host'. The  
61 - host name indicates which site is requested. Hence, it is the primary information for  
62 - branching to the right site. If there is no 'Host' HTTP header in the request, the  
63 - request is denied.  
64 -  
65 - From now on, we may assume that the host is determined, and consequently that we are  
66 - concerned by only one site. Each site has his own directories on the server's  
67 - disk.  
68 -  
69 - Each site also has a list of 'redirections'. A redirection is a triplet, like this one:  
70 -  
71 - redirect("/", "www.our-business.com", "/homepage.awp")  
72 -  
73 - meaning that if the host is "www.our-business.com", and if the requested URI is "/",  
74 - then the URI to be served is "/homepage.awp". 'redirect' is a constructor of the type  
75 - 'Redirection' defined in 'web/common.anubis'.  
76 -  
77 - Now, an URI may end by ".awp" (meaning 'Anubis Web Page') or not. If it does, the  
78 - server understands that an HTML page must be constructed on the fly, and to that end it  
79 - calls the 'awp handler' of the site. Otherwise, the URI must end by a known extension,  
80 - like ".jpg", ".png", ".txt", etc... and represents a file path relative to the  
81 - 'public' directory of the site. If these conditions are satisfied, the file is sent to  
82 - the client. Known extensions are recorded in 'web/mime.anubis'.  
83 -  
84 -  
85 -  
86 -  
87 - *** (2) The incompatibility between SSL and virtual hosts.  
88 -  
89 - Handling virtual hosts makes a problem under SSL (i.e. when using HTTPS), which is due  
90 - to the fact that the guys at Netscape who designed SSL probably did not have the  
91 - question of virtual hosts in mind. Indeed, the SSL handshake is completed before the  
92 - server can know about the value of the 'Host' HTTP header, so that it cannot know which  
93 - server certificate must be sent to the client. This makes a problem, because the  
94 - browser will not accept a certificate whose common name does not correspond to the name  
95 - of the requested host. The user will have to accept the certificate manually, which is  
96 - not good for the security image of the site. This problem has at least two solutions  
97 - (as far as Anubis is concerned).  
98 -  
99 - Solution 1. Arrange so that the network interface on which the server is listening  
100 - has at least as many different IP addresses as you have virtual hosts. Such  
101 - supplementary IP addresses are called 'IP Aliases'. In this case, start one HTTPS  
102 - server for each virtual host, each one listening on a different address. For the time  
103 - being, this method is applicable under Anubis only if you start as many instances of  
104 - 'anbexec' as you have virtual hosts, because each instance of 'anbexec' can handle only  
105 - one server certificate. Of course, getting IP aliases is another problem to be solved  
106 - with your Internet provider.  
107 -  
108 - Solution 2. We propose a simple solution, using only one server certificate (hence  
109 - only one instance of 'anbexec'). Since, we have only one server certificate, we must  
110 - introduce a notion of 'main host', i.e. a host containing all other 'virtual  
111 - hosts'. The unique server certificate belong to the main host, so that only the main  
112 - host is identified by the client. The client must trust the main host and be confident  
113 - that the main host redirects him to the right virtual host. Actually, the process will  
114 - be transparent to the client, except that the client will see the name of the main host  
115 - instead of the name of the virtual host in the 'location' field of the browser.  
116 -  
117 - So, assume that the name of main host is 'www.securedhost.com', and that the names of  
118 - the virtual hosts are:  
119 -  
120 - actual name simplified name  
121 - -----------------------------------------------------  
122 - www.virtual1.com virtual1  
123 - www.virtual2.com virtual2  
124 - www.virtual3.com virtual3  
125 -  
126 - Then the (confidential) document '/doc/my_document.pdf' on 'www.virtual2.com' will have  
127 - the URL:  
128 -  
129 - https://www.securedhost.com/virtual2/doc/my_document.pdf  
130 -  
131 - In order to work transparently, this solution must combine HTTP and HTTPS. Indeed, the  
132 - vitual host must have a first page reachable under HTTP, through the URL:  
133 -  
134 - http://www.virtual2.com/  
135 -  
136 - The HTTP server will redirect this URL to the awp handler of virtual host 'virtual2'.  
137 - The handler of this virtual host is able to generate a first page containing the  
138 - following HTML meta:  
139 -  
140 - <meta http-equiv="Refresh" content="0;URL=https://www.securedhost.com/virtual2/">,  
141 -  
142 - so that the client is immediately redirected to the main host under HTTPS (hence  
143 - accepting tranparently the server certificate). The awp handler of 'virtual2' then  
144 - redirects this URL to the home page (maybe a login page) of 'virtual2'.  
145 -  
146 - See 'web/making_a_web_site.anubis' for the sequel of this story.  
147 -  
148 -  
149 -  
150 -  
151 -  
152 - *** (3) HTTP headers and web arguments.  
153 -  
154 - Each HTTP request which arrives on the server contains a request line followed by a  
155 - series of HTTP headers. Each HTTP header is a pair '(name,value)' assigning a value to  
156 - a name. The type 'HTTP_header' is defined in 'web/common.anubis'.  
157 -  
158 - The request may also have a 'body'. The body contains either 'web arguments' or  
159 - uploaded files (or both). The request line itself may also contain web arguments (in a  
160 - so-called 'query string'). Like HTTP headers, 'web arguments' are pairs  
161 - '(name,value)', but the difference is that these pairs are generated by the page within  
162 - which the client clicks, while HTTP headers are generated by the browser itself. The  
163 - type 'Web_arg' is defined in 'web/common.anubis'. It has two alternatives, one for  
164 - ordinary web arguments (pairs) and one for uploaded files.  
165 -  
166 -read CXM_common.anubis  
167 -read tools/basis.anubis  
168 -read tools/printable_tree.anubis  
169 -read system/string.anubis  
170 -read system/files.anubis  
171 -read system/lists.anubis  
172 -read web/mime.anubis  
173 -  
174 -  
175 -  
176 - *** (4) Site descriptions.  
177 -  
178 - The type HTTP_Info gathers informations comming along with the client's request. These  
179 - informations are rarely used for composing HTML pages. Nevertheless, they are at your  
180 - disposal.  
181 -  
182 -public type HTTP_Info:  
183 - http_info  
184 - (  
185 - Word32 ip_address, // IP address of the client  
186 - String hostname, // hostname requested by the client  
187 - String uri, // URI requested by the client  
188 - List(HTTP_header) http_headers, // HTTP headers sent by the client  
189 - Bool is_https,  
190 - One -> String generate_trust_ticket // may be used against denial of  
191 - // service attacks  
192 - ).  
193 -  
194 -  
195 -  
196 - Each site is described by a 'web site description', which is a datum of type  
197 - 'Web_Site_Description'.  
198 -  
199 -public type Web_Site_Description:  
200 - web_site_description(  
201 - List(String) common_names,  
202 - String site_directory,  
203 - Redirections redirections,  
204 - String charset,  
205 - List(String) journal_extensions,  
206 - List(String) journal_headers,  
207 - String authorization_secret,  
208 - List(MIME) known_mime_types,  
209 - (String host_name,  
210 - HTTP_Info http_info,  
211 - List(Web_arg) lwa,  
212 - Bool is_https) -> (//List(HTTP_header),  
213 - Printable_tree) awp_handler,  
214 - (HTTP_Info http_info,  
215 - List(Web_arg) lwa) -> One before_send_file  
216 - //Bool using_state_cookies,  
217 - ).  
218 -  
219 - The component 'common_names' is the list of names of the site, like for example  
220 - "www.our-business.com". The reason why we have a list of common names instead of a  
221 - single common name, is that it may be useful to have a common name like "192.168.0.1"  
222 - for testing.  
223 -  
224 - 'charset' is a string which will determine the character encoding to be used by the  
225 - browser. Typically, this string is one of: "UTF-8", "ISO-8859-1", "Windows-1252",  
226 - etc...  
227 -  
228 - 'journal_extensions' is the list of URI extensions for which you want a log in the  
229 - journal (and on the console). When a request arrives, and if the extension is a member  
230 - of this list, a message is printed into the journal of the site including the date, the  
231 - IP address of the client, the complete HTTP request line. The HTTP headers whose name  
232 - is a member of 'journal_headers' are also printed in the journal. A reasonable minimum  
233 - for these two components is:  
234 -  
235 - [".awp"] for journal_extensions  
236 - ["user-agent"] for journal_headers  
237 -  
238 - 'authorization_secret' is a string which should just be unguessable. You may choose  
239 - something like (but don't choose this one !):  
240 -  
241 - "Hg8kJe42gCML9jNH-74"  
242 -  
243 - i.e. a sequence of characters typed at random, long enough to be unguessable. This is  
244 - used by the 'private download' mecanism, which is discussed later in this file.  
245 -  
246 - The component 'awp_handler' is a function of type:  
247 -  
248 - (String host_name,  
249 - HTTP_Info http_info,  
250 - List(Web_arg) web_args,  
251 - Bool is_https) -> Printable_tree  
252 -  
253 - ('Printable_tree' is a substitute for 'String' and is defined in  
254 - 'tools/basis.anubis'). This function is the 'awp handler' for the site. When the URI  
255 - ends by ".awp", this function is called, and the result (an HTML page) is sent to the  
256 - client over the connection. The last operand to this function is a boolean which is  
257 - 'true' when the requests arrives through the HTTPS channel, and 'false' when it arrives  
258 - through the HTTP channel.  
259 -  
260 -  
261 -  
262 -  
263 -  
264 -  
265 -  
266 - *** (5) Protection against denial of service attacks.  
267 -  
268 - We need to protect our servers against 'denial of service' attacks. The attack may be  
269 - send automatically from machines which are infested by viruses. In that case, our  
270 - server is saturated of connections (all virtual machines at work), but nothing is  
271 - comming on the connections. In order to avoid this problem, we propose the following:  
272 -  
273 - (1) Limit the number of simultaneous connections (say to 100).  
274 - (2) Close a connection if the request is not complete after say 10 seconds.  
275 - (3) Close the connection if the request is bigger than a given size (normal requests  
276 - are small except when there are uploaded files.  
277 - (4) Close the connection during the sending of the answer if the client is waiting  
278 - too much.  
279 - (5) Record all IP addresses with which we have encountered one of the problems above.  
280 - (6) Immediately close the connections if the IP address is in our list.  
281 - (7) Remove an address from the list only after 5 minutes of inactivity of this  
282 - address.  
283 - (8) Maintain a list of reliable IP addresses.  
284 -  
285 - Of course, all the above are approximative solutions which may in some circumstances  
286 - become either cumbersome or also partially block the system. So, it is needed to have a  
287 - set of dynamically modifiable parameters in order to master the behavior of this  
288 - mecanism.  
289 -  
290 -  
291 - Each dubious IP address is recorded together with its last activity time.  
292 -  
293 -public type DubiousIP:  
294 - dubious_ip (Word32 address,  
295 - Int last_activity).  
296 -  
297 -  
298 -public type DenialOfService:  
299 - denial_of_service(Var(Int) max_connections,  
300 - Var(Int) request_line_delay, // seconds  
301 - Var(Int) headers_delay,  
302 - Var(Int) answer_delay,  
303 - Var(List(DubiousIP)) list_of_dubious,  
304 - Var(List(Word32)) reliable_addresses).  
305 -  
306 - The informations in this set of variables are stored serialized into the file  
307 - 'my_anubis/web_sites/dos_info'. If this file does not exist a set if variables with  
308 - default values is created. The values are saved on the disk each time they are  
309 - modified.  
310 -  
311 -public define DenialOfService load_denial_of_service_info.  
312 -  
313 -  
314 -  
315 - *** (6) Starting your HTTP and HTTPS servers.  
316 -  
317 - When your web site descriptions are ready, you can start a pair of servers (a HTTP  
318 - server and a HTTPS server) for serving your web sites. Notice that there are always  
319 - two servers, regardless of the number of web sites, and that each web sites normally  
320 - uses the two servers.  
321 -  
322 -  
323 -public define StartServerResult  
324 - start_http_server  
325 - (  
326 - Word32 ip_address,  
327 - Word32 http_port,  
328 - List(Web_Site_Description) web_sites,  
329 - DenialOfService dos  
330 - ).  
331 -  
332 -public define StartServerResult  
333 - start_https_server  
334 - (  
335 - Word32 ip_address,  
336 - Word32 https_port,  
337 - String certificate_common_name,  
338 - List(Web_Site_Description) web_sites,  
339 - DenialOfService dos  
340 - ).  
341 -  
342 - The first argument 'ip_address' is the IP address on which the servers listen. If you  
343 - put 0, the servers listen on all adresses of the machine (which is useful if the  
344 - machine has several network interfaces). Otherwise, use the function 'ip_address'  
345 - defined in 'tools/basis.anubis' for composing a particular IP address.  
346 -  
347 - The next arguments are the port numbers for HTTP and HTTPS. The usual values are 80 and  
348 - 443, but you may have reasons to choose other values.  
349 -  
350 - The next argument is the list of your web site descriptions. All the sites described in  
351 - this list will be accessible on the server.  
352 -  
353 - The argument 'dos' is a set of dynamic variables containing the informations for  
354 - protecting the servers against denial of service attacks.  
355 -  
356 -  
357 -  
358 -  
359 -  
360 -  
361 - *** (7) Private download.  
362 -  
363 - It may happen that you want to propose private files for download. This means that such  
364 - a file could be downloaded only by the authorized person, and should not be seen by any  
365 - other one. This feature can be used only under HTTPS, not under HTTP.  
366 -  
367 - The file may be located anywhere on the server. Hence, the file has a complete absolute  
368 - path, like for example:  
369 -  
370 - /home/georges/my_documents/my_text.pdf  
371 -  
372 - which has nothing to do with the directories of the web server. Now, you may also want  
373 - to show another path or simply just a name to the client, not the actual absolute path  
374 - above, which may need to remain secret. So for example, the same file may appear to the  
375 - client as:  
376 -  
377 - informations.pdf  
378 -  
379 - The page must provide a link with an authorization. The authorization is just a web  
380 - argument, whose name is "zauth". The value of this web argument is computed by hashing  
381 - some secret string (known only from the programmer of the web site) with the absolute  
382 - path of the file. The HTTPS request will have the form:  
383 -  
384 - GET /informations.pdf?zauth=d38161f5b4e87e2d46e06ff8b3e233be563794d1  
385 -  
386 - The server will search for a file named  
387 -  
388 - zd38161f5b4e87e2d46e06ff8b3e233be563794d1  
389 -  
390 - (i.e. "z" concatenated with the value of the authorization) in the subdirectory  
391 - 'private_download' of the site directory. This file contains the absolute path of the  
392 - file, i.e:  
393 -  
394 - /home/georges/my_documents/my_text.pdf  
395 -  
396 - At that point, the server may hash the secret string and the absolute path together, to  
397 - check if the client is authorized to download the file. If it is the case, it sends the  
398 - file (the MIME type is declared as 'application/octet-stream' if it is not recognized).  
399 - The file is sent under the visible name.  
400 -  
401 - The server creates automatically the subdirectory 'private_download/' within the 'site  
402 - directory' (for each web site) if it does not already exist. Files in this directory  
403 - are deleted when they become too old (for example, after 3 days of life).  
404 -  
405 - Here is the function for computing the value of the authorization, and for making the  
406 - authorization file in 'private_download'.  
407 -  
408 -public define String  
409 - make_authorization  
410 - (  
411 - String site_directory,  
412 - String authorization_secret, // known only by the programmer of the web site  
413 - String absolute_path // on server  
414 - ).  
415 -  
416 - See 'web/making_a_web_site.anubis' for the construction of the link for downloading.  
417 -  
418 -  
419 -  
420 -  
421 -  
422 -  
423 -  
424 -  
425 - *** (8) About web argument names.  
426 -  
427 - The server reserves the name "zauth" for the authorization in the private download  
428 - mecanism. Also, if the name of a web arguments begins by "p" (like 'password'), it does  
429 - not print the value of the web argument neither on the console or in the journal. A  
430 - good politics is to prefix all web arguments by letters distinct from 'p' and 'z'. This  
431 - method is used in 'web/making_a_web_site.anubis'. This will avoid clashes of names.  
432 -  
433 -  
434 -  
435 -  
436 -  
437 -  
438 - *** (9) A web dispatcher.  
439 -  
440 - For hosting several sites you may prefer another method which we now describe. We start  
441 - a HTTP server on port 80 (or on another port). This server is called the  
442 - ``dispatcher''. When a requests arrives, the dispatcher examines the ``host'' HTTP  
443 - header, so that it gets the name of the requested host. Then it sends to the client a  
444 - page like this one:  
445 -  
446 - <html>  
447 - <head>  
448 - <meta http-equiv="Refresh" content="0;URL=...">  
449 - </head>  
450 - <body>  
451 - </body>  
452 - </html>  
453 -  
454 - where the URL represented by '...' is the URL of the requested site. This URL may have  
455 - the same IP address as the dispatcher, except that the port number is different. It may  
456 - also have a different IP address.  
457 -  
458 - The dispatcher uses the file 'my_anubis/web_sites/dispatcher.info'. This file contains  
459 - a serialized datum of type 'List(DispatcherInfo)'.  
460 -  
461 -public type DispatcherInfo:  
462 - site(String common_name,  
463 - Word32 http_port).  
464 -  
465 - The dispatcher does not write into this file. It reads it when it starts, and rereads  
466 - it each time the date of last modification of the file changes, so that the dispatcher  
467 - always has up to date data. The file may be managed (written and updated) by another  
468 - program.  
469 -  
470 - So, for each site, the dispatcher knows the common name (needed to recognize the 'host'  
471 - HTTP header), and the pair (ip_address,port) used by the actual site for HTTP. The  
472 - dispatcher does not worry about HTTPS. HTTPS must be managed by the actual site.  
473 -  
474 - The dispatcher is started by:  
475 -  
476 -public define One  
477 - start_web_dispatcher  
478 - (  
479 - Word32 ip_address, // address for listening (typically 0)  
480 - Word32 port, // typically 80  
481 - DenialOfService dos  
482 - ).  
483 -  
484 - A command line tool for managing the file 'my_anubis/web_sites/dispatcher.info' is also  
485 - provided:  
486 -  
487 - global define One  
488 - manage_web_dispatcher  
489 - (  
490 - List(String) args  
491 - ).  
492 -  
493 -  
494 - *** (10) HTTP Errors  
495 -  
496 -public type HTTP_Status:  
497 - http_continue, // 100  
498 - http_switching_protocol, // 101  
499 -  
500 - http_ok, // 200  
501 - http_created, // 201  
502 - http_accepted, // 202  
503 - http_non_authoritative_info, // 203  
504 - http_no_content, // 204  
505 - http_reset_content, // 205  
506 - http_partial_content, // 206  
507 -  
508 - http_multiple_choices,  
509 - http_moved_permanently(String location), // 301  
510 - http_moved_temporarily(String location), // 302  
511 - http_see_other(String location), // 303  
512 - http_not_modified, // 304  
513 - http_use_proxy(String location), // 305  
514 - http_temporary_redirect(String location), // 307  
515 -  
516 - http_bad_request, // 400  
517 - http_unauthorized, // 401  
518 - http_payment_required, // 402  
519 - http_forbidden, // 403  
520 - http_not_found, // 404  
521 - http_method_not_allowed, // 405  
522 - http_not_acceptable, // 406  
523 - http_proxy_authentification_required, // 407  
524 - http_request_timeout, // 408  
525 - http_conflict, // 409  
526 - http_gone, // 410  
527 - http_length_required, // 411  
528 - http_precondition_failed, // 412  
529 - http_request_entity_too_large, // 413  
530 - http_request_uri_too_long, // 414  
531 - http_unsupported_media_type, // 415  
532 - http_request_range_unsatisfiable, // 416  
533 - http_expectation_failed, // 417  
534 -  
535 - http_internal_server_error, // 500  
536 - http_not_implemented, // 501  
537 - http_bad_gateway, // 502  
538 - http_service_unavailable, // 503  
539 - http_gateway_timeout, // 504  
540 - http_version_not_supported, // 505  
541 -  
542 - http_error(Int /*code*/, String /*message*/).  
543 -  
544 -public define (String, List(HTTP_header))  
545 - format  
546 - (  
547 - HTTP_Status status  
548 - ).  
549 -  
550 -  
551 -  
552 -  
553 -  
554 - --- That's all for the public part ! --------------------------------------------------  
555 -  
556 -define Maybe(String) get_host_header_value(List(HTTP_header) headers).  
557 -  
558 -define String  
559 - __utime_to_string  
560 - (  
561 - UTime t  
562 - ) =  
563 - to_decimal(t.seconds) + "." + zero_pad_n(6, t.microseconds ) + "s".  
564 -  
565 -  
566 -variable UTime t0 = utime(0,0).  
567 -variable UTime t1 = utime(0,0).  
568 -  
569 -define One  
570 - accumulate_t1  
571 - (  
572 - UTime start  
573 - ) =  
574 - with delta = (UTime)unow - start,  
575 - t1 <- delta + *t1;  
576 - unique.  
577 -  
578 -variable UTime t2 = utime(0,0).  
579 -  
580 -define One  
581 - accumulate_t2  
582 - (  
583 - UTime start  
584 - ) =  
585 - with delta = (UTime)unow - start,  
586 - t2 <- delta + *t2;  
587 - unique.  
588 -  
589 -  
590 -public define One  
591 - print_delta  
592 - (  
593 - String txt  
594 - ) =  
595 - println(__utime_to_string((UTime)unow - *t0) + " : " + txt).  
596 -  
597 -  
598 - ----------------------------------- Table of Contents ---------------------------------  
599 -  
600 - *** [1] Types which are private to this file.  
601 -  
602 - *** [2] Tools.  
603 - *** [2.1] Formating an error message.  
604 - *** [2.2] Converting IP addresses.  
605 - *** [2.3] Reading and unputting characters.  
606 - *** [2.4] Reading and discarding characters.  
607 - *** [2.5] Reading a character string.  
608 - *** [2.6] Padding integers with zeros.  
609 - *** [2.7] Converting web arguments to ASCII.  
610 - *** [2.8] Server description.  
611 -  
612 - *** [3] Managing the journal.  
613 - *** [3.1] Naming journal files.  
614 - *** [3.2] Formating HTTP headers.  
615 - *** [3.3] Formating web arguments.  
616 - *** [3.4] Formating the whole request.  
617 - *** [3.5] Putting it in the journal file (and on the console).  
618 -  
619 - *** [4] Reading the HTTP request.  
620 - *** [4.1] Skipping leading blanks.  
621 - *** [4.2] Reading a new line.  
622 - *** [4.3] Reading a 'word'.  
623 - *** [4.4] Separating the URI from the query string.  
624 - *** [4.5] Reading the web arguments.  
625 - *** [4.7] Reading the request line.  
626 - *** [4.8] Reading the HTTP headers.  
627 - *** [4.9] Getting the size of the request's body.  
628 - *** [4.10] Reading the body of the request.  
629 -  
630 - *** [5] Making the HTTP answer.  
631 - *** [5.1] Avoiding illegal URIs.  
632 - *** [5.2] Managing authorizations for downloading private files.  
633 - *** [5.3] Recognizing MIME types.  
634 - *** [5.4] Formating HTTP headers.  
635 - *** [5.5] Sending a file.  
636 - *** [5.6] Answering a www-url encoded request.  
637 - *** [5.7] Answering a multipart/form-data encoded request.  
638 - *** [5.7.1] Finding the boundary.  
639 - *** [5.7.2] Reading attributes from a multipart entity.  
640 - *** [5.7.3] Creating a temporary filename for an uploaded file.  
641 - *** [5.7.4] Saving an uploaded file under a temporary filename.  
642 - *** [5.7.5] Removing the path from a file name.  
643 - *** [5.7.6] Reading a multipart entity.  
644 - *** [5.8] Handling redirections.  
645 - *** [5.9] Answering both sorts of requests.  
646 -  
647 - *** [6] The HTTP/HTTPS servers.  
648 - *** [6.1] The HTTP request handler.  
649 - *** [6.2] Server's tasks.  
650 - *** [6.3] Starting the HTTP/HTTPS servers.  
651 -  
652 - *** [7] The web dispatcher.  
653 - *** [7.1] The dispatcher server.  
654 - *** [7.2] The dispatcher web site.  
655 - *** [7.3] Managing the info file.  
656 -  
657 - ---------------------------------------------------------------------------------------  
658 -  
659 -  
660 -  
661 -  
662 -read tools/basis.anubis  
663 -read tools/findstring.anubis  
664 -read tools/connections.anubis  
665 -  
666 -  
667 -  
668 -  
669 -  
670 - *** [1] Types which are private to this file.  
671 -  
672 - We use the following self-explanatory types.  
673 -  
674 -type Error:  
675 - cannot_read_from_connection,  
676 - not_get_or_post_request(String),  
677 - end_of_line_expected,  
678 - incorrect_content_length_value,  
679 - colon_expected,  
680 - timeout(Int).  
681 -  
682 -type HTTP_RequestType:  
683 - get,  
684 - post.  
685 -  
686 -type HTTP_RequestLine:  
687 - request_line (HTTP_RequestType type,  
688 - String uri,  
689 - List(Web_arg) query_string).  
690 -  
691 -type EncodingType:  
692 - www_url,  
693 - multipart_form_data.  
694 -  
695 -type BufferedConnection:  
696 - buffered_connection(Connection conn,  
697 - Var(ByteArray) buffer,  
698 - Var(Int) read_pos).  
699 -  
700 -  
701 -  
702 - *** [2] Tools.  
703 -  
704 - *** [2.1] Formating an error message.  
705 -  
706 - The next function formats an error message.  
707 -  
708 -define String  
709 - format  
710 - (  
711 - Error msg  
712 - ) =  
713 - if msg is  
714 - {  
715 - cannot_read_from_connection then  
716 - "Cannot read from connection.\n",  
717 - not_get_or_post_request(s) then  
718 - "The request did not begin by 'GET' or 'POST': "+s+".\n",  
719 - end_of_line_expected then  
720 - "End of line expected.\n",  
721 - incorrect_content_length_value then  
722 - "Incorrect value for HTTP header 'Content-Length'.\n",  
723 - colon_expected then  
724 - "':' was expected.\n",  
725 - timeout(n) then  
726 - //"time out: "+n+"\n"  
727 - //"time out.\n"  
728 - ""  
729 - }.  
730 -  
731 -  
732 -  
733 -  
734 -  
735 -  
736 - *** [2.2] Converting IP addresses.  
737 -  
738 - We need two conversion functions for IP addresses:  
739 -  
740 - (Word8,Word8,Word8,Word8) --> Word32 ip_address  
741 - Word32 --> String ip_addr_to_string  
742 -  
743 - These conversions are defined in 'tools/basis.anubis'.  
744 -  
745 -  
746 -  
747 -  
748 -  
749 -  
750 -  
751 -  
752 - *** [2.3] Reading and unputting characters.  
753 -  
754 - We need a mecanism for unputting several characters (actually at least 3). This is  
755 - because when reading the client connection, we must sometimes go ahead several  
756 - characters, and virtually put them back into the connection, so that they can be  
757 - reread. Of course, we do not send them back to the client. We store them in a list  
758 - (hold by the variable 'unput_chars'), and we manage this list, so that characters may  
759 - be virtually put back in the connection (this is called 'unputting').  
760 -  
761 -variable List(Word8) unput_chars = [].  
762 -  
763 - The most recently read one is the head of list. Fortunately, this variable is private  
764 - to this virtual machine (hence to this client).  
765 -  
766 -  
767 -define One  
768 - unput // unputting a character (add it in front of the list)  
769 - (  
770 - Word8 character  
771 - ) =  
772 - unput_chars <- (List(Word8))[character . *unput_chars].  
773 -  
774 -  
775 -  
776 -define One record_dubious_IP(Word32 addr,DenialOfService dos).  
777 -  
778 -variable Int sttm = 0. // contains the start time for this connection.  
779 -  
780 -define Result(Error,Word8)  
781 - record_dubious_connection  
782 - (  
783 - Connection conn,  
784 - Int dead_line,  
785 - DenialOfService dos,  
786 - ) =  
787 - if remote_IP_address_and_port(conn) is (addr,port) then  
788 - record_dubious_IP(addr,dos);  
789 - print("Recording IP address "+ip_addr_to_string(addr)+  
790 - " as dubious after "+(dead_line-*sttm)+" seconds. Total: "+  
791 - length(*list_of_dubious(dos))+"\n");  
792 - error(timeout(dead_line)).  
793 -  
794 -define String  
795 - pid  
796 - =  
797 - "[" + virtual_machine_id + "] ".  
798 -  
799 -  
800 -define One  
801 - put  
802 - (  
803 - ByteArray source,  
804 - ByteArray dest,  
805 - Int position,  
806 - Int i  
807 - ) =  
808 - if nth(i,source) is  
809 - {  
810 - failure then unique,  
811 - success(b) then if put(dest,position,b) is  
812 - {  
813 - failure then unique,  
814 - success(_) then put(source,dest,position+1,i+1)  
815 - }  
816 - }.  
817 -  
818 -define ReadResult  
819 - read_from_connexion  
820 - (  
821 - BufferedConnection connection,  
822 - Int size,  
823 - Int time_out,  
824 - ByteArray result_buffer,  
825 - Int position  
826 - ) =  
827 - //println(pid + "read_from_connexion(" + size + ")");  
828 -  
829 - if *connection.read_pos < length(*connection.buffer) then  
830 - //println(pid + " reading from buffer (size = " + length(*connection.buffer) + ", pos = " + *connection.read_pos);  
831 - //with t1_tmp = (UTime) unow,  
832 - with result = extract(*connection.buffer, *connection.read_pos, *connection.read_pos + size),  
833 - size_read = length(result),  
834 - put(result,result_buffer,position,0);  
835 - connection.read_pos <- *connection.read_pos + size_read;  
836 - //accumulate_t1(t1_tmp);  
837 - if size > size_read then  
838 - //println("Wanted " + size + ", read only " + size_read);  
839 -  
840 - terminal read_from_connexion(connection, size - size_read, time_out, result_buffer,position+size_read)  
841 -// {  
842 -// error then error,  
843 -// timeout then ok(result),  
844 -// ok(ba) then ok(result + ba)  
845 -// }  
846 - else  
847 - ok(result_buffer)  
848 - else  
849 - //if unow > dead_line then record_dubious_connection(connection,dead_line,dos) else  
850 - if read(connection.conn, 16384, time_out) is // the connection is closed after 10 minutes of inactivity  
851 - {  
852 - error then println(pid + "read failed)"); error,  
853 - timeout then timeout,  
854 - ok(ba) then  
855 -// println(pid + "ba = " + length(ba));  
856 - connection.buffer <- ba;  
857 - connection.read_pos <- 0;  
858 - //println(pid + "rb = " + length(*read_buffer));  
859 -  
860 - terminal read_from_connexion(connection, size, time_out, result_buffer,position)  
861 - }.  
862 -  
863 -define Result(Error,Word8)  
864 - next_char // reading a character (check the list first, and read on the connection  
865 - // only when the list is empty).  
866 - (  
867 - BufferedConnection connection,  
868 - Int dead_line,  
869 - DenialOfService dos  
870 - ) =  
871 - //with t2_tmp = (UTime) now,  
872 - if *unput_chars is  
873 - {  
874 - [ ] then  
875 - // ///////////////////  
876 - // Buffered reading  
877 - //if unow > dead_line then record_dubious_connection(connection,dead_line,dos) else  
878 - if nth(*connection.read_pos, *connection.buffer) is  
879 - {  
880 - failure then  
881 - if read_from_connexion(connection,1,600, constant_byte_array(1,0),0) is // the connection is closed after 10 minutes of inactivity  
882 - {  
883 - error then /*accumulate_t2(t2_tmp);*/ error(cannot_read_from_connection),  
884 - timeout then /*accumulate_t2(t2_tmp);*/ error(timeout(600)),  
885 - //record_dubious_connection(connection,dead_line,dos),  
886 - ok(ba) then if nth(0,ba) is  
887 - {  
888 - failure then /*accumulate_t2(t2_tmp);*/ error(cannot_read_from_connection),  
889 - success(c) then  
890 - //println("-" + pid + "read [" + implode([c]) + "]\t");  
891 - //accumulate_t2(t2_tmp);  
892 - ok(c)  
893 - }  
894 - },  
895 - success(c) then  
896 - connection.read_pos <- *connection.read_pos + 1;  
897 - //accumulate_t2(t2_tmp);  
898 - ok(c)  
899 - },  
900 -  
901 - // ///////////////////  
902 - // standard reading  
903 -// if read(connection.conn, 1, 600) is // the connection is closed after 10 minutes of inactivity  
904 -// {  
905 -// error then accumulate_t2(t2_tmp); println(pid + "read failed)"); error(cannot_read_from_connection),  
906 -// timeout then accumulate_t2(t2_tmp); error(timeout(600)),  
907 -// ok(ba) then if nth(0,ba) is  
908 -// {  
909 -// failure then accumulate_t2(t2_tmp); error(cannot_read_from_connection),  
910 -// success(c) then accumulate_t2(t2_tmp);  
911 -// ok(c)  
912 -// }  
913 -// },  
914 -  
915 - [h . t] then  
916 - unput_chars <- t; //accumulate_t2(t2_tmp);  
917 - ok(h)  
918 - }.  
919 -  
920 -  
921 -  
922 -  
923 -  
924 -  
925 - *** [2.4] Reading and discarding characters.  
926 -  
927 - The next function reads the specified number of bytes (this is the same as  
928 - 'characters') from the connection and discards them. This is used for discarding CR LF  
929 - just before the body of a request.  
930 -  
931 -define Result(Error,One)  
932 - read_and_ignore  
933 - (  
934 - BufferedConnection connection, // to client  
935 - Int dead_line,  
936 - Int number_of_characters, // number of characters to read and ignore  
937 - DenialOfService dos  
938 - ) =  
939 - if number_of_characters =< 0 then ok(unique) else  
940 - if next_char(connection, dead_line, dos) is  
941 - {  
942 - error(msg) then error(msg),  
943 - ok(c) then read_and_ignore(connection,dead_line,number_of_characters-1,dos)  
944 - }.  
945 -  
946 -  
947 -  
948 -  
949 -  
950 -  
951 -  
952 - *** [2.5] Reading a character string.  
953 -  
954 - Sometimes values of HTTP attributes or web args are presented in the form of double  
955 - quoted strings. The next function handles the reading of such things. The leading  
956 - double quote is already read in. We must read subsequent characters until the next non  
957 - backslashed double quote.  
958 -  
959 -define Result(Error,String)  
960 - read_string  
961 - (  
962 - BufferedConnection connection, // connection with the client  
963 - Int dead_line,  
964 - List(Word8) so_far, // characters read so far (in reverse order)  
965 - DenialOfService dos  
966 - ) =  
967 - if next_char(connection, dead_line,dos) is  
968 - {  
969 - error(msg) then error(msg),  
970 - ok(c) then  
971 - if c = '\\'  
972 - then if next_char(connection,dead_line,dos) is  
973 - {  
974 - error(msg) then error(msg),  
975 - ok(d) then  
976 - if d = '\"'  
977 - then read_string(connection,dead_line,['\"' . so_far],dos)  
978 - else read_string(connection,dead_line,[d, c . so_far],dos)  
979 - }  
980 - else if c = '\"'  
981 - then ok(implode(reverse(so_far)))  
982 - else read_string(connection,dead_line,[c . so_far],dos)  
983 - }.  
984 -  
985 -  
986 -  
987 -  
988 -  
989 -  
990 -  
991 -  
992 -  
993 -  
994 -  
995 -  
996 - *** [2.7] Converting web arguments to ASCII.  
997 -  
998 - The function 'web_to_ascii' gets a character string and replaces web encoding by normal  
999 - ASCII encoding. This amounts to replacing:  
1000 -  
1001 - + by blank  
1002 - %xx by the character whose ASCII code is xx in hexadecimal  
1003 -  
1004 - Note: We assume that '9' < 'A' (which is the case for ASCII code).  
1005 -  
1006 -  
1007 -  
1008 -define Word8  
1009 - web_decode  
1010 - (  
1011 - Word8 x1,  
1012 - Word8 x2  
1013 - ) =  
1014 - with n1 = if x1 +=< '9' then (x1 - '0') else if x1 +=< 'F' then (x1 - 'A' + 10) else (x1 - 'a' + 10),  
1015 - n2 = if x2 +=< '9' then (x2 - '0') else if x2 +=< 'F' then (x2 - 'A' + 10) else (x2 - 'a' + 10),  
1016 - (n1 << 4) + n2.  
1017 -  
1018 -  
1019 -  
1020 -define String  
1021 - web_to_ascii  
1022 - (  
1023 - String web_string,  
1024 - Int n, // current position in web_string  
1025 - List(Word8) so_far  
1026 - ) =  
1027 - if nth(n,web_string) is  
1028 - {  
1029 - failure then implode(reverse(so_far)),  
1030 - success(c) then  
1031 - if c = '+'  
1032 - then web_to_ascii(web_string,n+1,[' ' . so_far])  
1033 - else if c = '%'  
1034 - then if nth(n+1,web_string) is  
1035 - {  
1036 - failure then implode(reverse(so_far)),  
1037 - success(x1) then if nth(n+2,web_string) is  
1038 - {  
1039 - failure then implode(reverse(so_far)),  
1040 - success(x2) then web_to_ascii(web_string,n+3,[web_decode(x1,x2) . so_far])  
1041 - }  
1042 - }  
1043 - else web_to_ascii(web_string,n+1,[c . so_far])  
1044 - }.  
1045 -  
1046 -  
1047 -  
1048 -  
1049 -  
1050 -  
1051 -  
1052 -  
1053 - *** [3] Managing the journal.  
1054 -  
1055 - Concurrently working machines should not try to access the same file at the same  
1056 - time. This problem may be solved by using the 'protect' mecanism.  
1057 -  
1058 -  
1059 -  
1060 - *** [3.1] Naming journal files.  
1061 -  
1062 - Since journal messages are rather prolific, we should have at least one file per  
1063 - hour. Hence, the name of a journal file must be constructed from the current year,  
1064 - month, day and hour. For example, it may be:  
1065 -  
1066 - 2003_03_12_19  
1067 -  
1068 - (this is for the journal of 7 PM to 8 PM, 2003/mar/12).  
1069 -  
1070 -define String  
1071 - make_current_journal_file_name  
1072 - =  
1073 - if convert_time(now) is date_and_time(y,m,d,h,_,_,_,_,_) then  
1074 - to_decimal(y)+"_"+  
1075 - zero_pad_n(2,m)+"_"+  
1076 - zero_pad_n(2,d)+"_"+  
1077 - zero_pad_n(2,h).  
1078 -  
1079 -  
1080 -  
1081 -  
1082 -  
1083 -  
1084 -  
1085 - *** [3.2] Formating HTTP headers.  
1086 -  
1087 - HTTP headers may be shown on the console or written in the journal. The function below  
1088 - formats a list of HTTP headers.  
1089 -  
1090 -define String  
1091 - show_format  
1092 - (  
1093 - Web_Site_Description desc,  
1094 - List(HTTP_header) headers,  
1095 - ) =  
1096 - if headers is  
1097 - {  
1098 - [ ] then "",  
1099 - [h . t] then if h is http_header(name,value) then  
1100 - if member(journal_headers(desc),name)  
1101 - then " | "+name+": "+value+"\n"+show_format(desc,t)  
1102 - else show_format(desc,t)  
1103 - }.  
1104 -  
1105 -  
1106 -  
1107 -  
1108 -  
1109 -  
1110 - *** [3.3] Formating web arguments.  
1111 -  
1112 - The same thing for web arguments.  
1113 -  
1114 -define String  
1115 - show_format  
1116 - (  
1117 - List(Web_arg) lwa  
1118 - ) =  
1119 - if lwa is  
1120 - {  
1121 - [ ] then "",  
1122 - [h . t] then if h is  
1123 - {  
1124 - web_arg(n,v) then  
1125 - " | "+n+"="+(if nth(0,n) = success('p') then "<not shown>" else v)+"\n"+show_format(t),  
1126 - upload(n,fn,tfn) then  
1127 - " | "+n+"="+fn+" (uploaded as '"+tfn+"')\n"+show_format(t)  
1128 - }  
1129 - }.  
1130 -  
1131 -  
1132 -  
1133 -  
1134 -  
1135 -  
1136 - *** [3.4] Formating the whole request.  
1137 -  
1138 - It is cheap to transform month numbers into abbreviated month names. This enhances the  
1139 - readability of the journal.  
1140 -  
1141 -define String  
1142 - format_month  
1143 - (  
1144 - Int m  
1145 - ) =  
1146 - if m = 1 then "jan" else  
1147 - if m = 2 then "feb" else  
1148 - if m = 3 then "mar" else  
1149 - if m = 4 then "apr" else  
1150 - if m = 5 then "may" else  
1151 - if m = 6 then "jun" else  
1152 - if m = 7 then "jul" else  
1153 - if m = 8 then "aug" else  
1154 - if m = 9 then "sep" else  
1155 - if m = 10 then "oct" else  
1156 - if m = 11 then "nov" else  
1157 - if m = 12 then "dec" else  
1158 - "???".  
1159 -  
1160 -  
1161 - Below we format a whole HTTP request. This may give this (actually, it depends on how  
1162 - you defined the values of 'journal_headers' and 'journal_extensions'):  
1163 -  
1164 - [3] 2003/mar/10 10:06:57 from 123.456.123.456: /homepage.awp  
1165 - | host: www.the-best-one.com  
1166 - | user-agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.0.1) Gecko/20020823 Netscape/7.0  
1167 -  
1168 - The leading number between brackets is the number of the virtual machine which served  
1169 - the URI.  
1170 -  
1171 -define String  
1172 - format_request  
1173 - (  
1174 - Web_Site_Description desc,  
1175 - Connection client_connection,  
1176 - HTTP_RequestLine request_line,  
1177 - List(HTTP_header) headers,  
1178 - List(Web_arg) web_args  
1179 - ) =  
1180 - with dt = convert_time(now),  
1181 - if remote_IP_address_and_port(client_connection) is (addr,port) then  
1182 - to_decimal(year(dt))+"/"+format_month(month(dt))+"/"+zero_pad_n(2,day(dt))+" "+  
1183 - zero_pad_n(2,hour(dt))+":"+zero_pad_n(2,minute(dt))+":"+zero_pad_n(2,second(dt))+  
1184 - " from "+ip_addr_to_string(addr)+  
1185 - ": "+uri(request_line)+"\n"+  
1186 - show_format(desc,headers)+  
1187 - show_format(web_args).  
1188 -  
1189 -  
1190 -  
1191 -  
1192 -  
1193 -  
1194 -  
1195 - *** [3.5] Putting it in the journal file (and on the console).  
1196 -  
1197 - We must not forget to 'protect' this operation, so that the messages of two machines  
1198 - (working for the same site) will not be mixed together.  
1199 -  
1200 -define One  
1201 - log_journal_msg  
1202 - (  
1203 - Web_Site_Description desc,  
1204 - String msg,  
1205 - ) =  
1206 - with ba_msg = to_byte_array("["+virtual_machine_id+"] "+msg+"\n"),  
1207 - protect  
1208 - (  
1209 - if file(site_directory(desc)+"/journal/"+make_current_journal_file_name,append) is  
1210 - {  
1211 - failure then unique,  
1212 - success(journal_file) then  
1213 - forget(reliable_write(file(journal_file),ba_msg))  
1214 - };  
1215 - forget(reliable_write(file(stdout),ba_msg))  
1216 - ).  
1217 -  
1218 -  
1219 -  
1220 -  
1221 -  
1222 -  
1223 -  
1224 - *** [4] Reading the HTTP request.  
1225 -  
1226 -  
1227 - *** [4.1] Skipping leading blanks.  
1228 -  
1229 - One of the peculiarities of HTTP is that the characters 13 (carriage return) and 10  
1230 - (line feed) followed by either a space (32) or a tab (9), is considered as a blank not  
1231 - containing any new line. 'skip_http_blanks' must skip all blanks characters until the  
1232 - first non blank character, which should not be read in. Obviously, because of the above  
1233 - peculiarity, we need at least 3 characters of lookahead to do this. In other words, we  
1234 - must be able to unput at least 3 characters (hopefully we are).  
1235 -  
1236 - Strictly blanks characters are 'space' and 'tab'.  
1237 -  
1238 -define Bool  
1239 - is_strict_blank  
1240 - (  
1241 - Word8 c  
1242 - ) =  
1243 - if c = ' ' then true else c = '\t'.  
1244 -  
1245 -  
1246 - On the contrary, blanks include 13 and 10.  
1247 -  
1248 -define Bool  
1249 - is_blank  
1250 - (  
1251 - Word8 c  
1252 - ) =  
1253 - if c = ' ' then true else  
1254 - if c = '\t' then true else  
1255 - if c = 13 then true else  
1256 - c = 10.  
1257 -  
1258 -  
1259 - Skipping HTTP blanks.  
1260 -  
1261 -define Result(Error,One)  
1262 - skip_http_blanks  
1263 - (  
1264 - BufferedConnection connection,  
1265 - Int dead_line,  
1266 - DenialOfService dos  
1267 - ) =  
1268 - if next_char(connection,dead_line,dos) is  
1269 - {  
1270 - error(msg) then error(msg),  
1271 - ok(c) then  
1272 - if is_strict_blank(c)  
1273 - then skip_http_blanks(connection,dead_line,dos)  
1274 - else if c = 13  
1275 - then if next_char(connection,dead_line,dos) is  
1276 - {  
1277 - error(msg) then error(msg), // (unput(c); ok(unique)),  
1278 - ok(d) then  
1279 - if d = 10  
1280 - then if next_char(connection,dead_line,dos) is  
1281 - {  
1282 - error(msg) then error(msg), // (unput(d); unput(c); ok(unique)),  
1283 - ok(e) then  
1284 - if is_strict_blank(e)  
1285 - then skip_http_blanks(connection,dead_line,dos)  
1286 - else (unput(e); unput(d); unput(c); ok(unique))  
1287 - }  
1288 - else (unput(d); unput(c); ok(unique))  
1289 - }  
1290 - else (unput(c); ok(unique))  
1291 - }.  
1292 -  
1293 -  
1294 -  
1295 -  
1296 -  
1297 -  
1298 -  
1299 -  
1300 - *** [4.2] Reading a new line.  
1301 -  
1302 - Normally in HTTP a new line is the sequence 13 10 (carriage return line feed), not  
1303 - followed by a space or tabulator. If it is followed by a space or tabulator, the three  
1304 - characters are considered blanks, and no new line has been read. Before trying to read  
1305 - a new line, we first skip leading spaces and tabs. Then we try to read 13 and 10, and  
1306 - we read another character. if this character is space or tab, we consider we have read  
1307 - only blanks and we continue reading in order to find our new line. Otherwise, we unput  
1308 - this character (which may be for example the first character of the name of the next  
1309 - header), and answer that we have seen a new line.  
1310 -  
1311 - Warning: we must not use this function for reading the last pair (13,10) before the  
1312 - beginning of the body, because if the body is empty, there is no character to read  
1313 - after this pair, so that the server could wait for a character which will never  
1314 - come. This is the reason for 'read_and_ignore' above, which is used precisely for  
1315 - reading that last (13,10) pair.  
1316 -  
1317 -define Result(Error,One)  
1318 - read_new_line  
1319 - (  
1320 - BufferedConnection connection,  
1321 - Int dead_line,  
1322 - DenialOfService dos  
1323 - ) =  
1324 - if skip_http_blanks(connection,dead_line,dos) is  
1325 - {  
1326 - error(msg) then error(msg),  
1327 - ok(_) then  
1328 - if next_char(connection,dead_line,dos) is  
1329 - {  
1330 - error(msg) then error(msg),  
1331 - ok(c) then  
1332 - if c = 13  
1333 - then if next_char(connection,dead_line,dos) is  
1334 - {  
1335 - error(msg) then error(msg),  
1336 - ok(d) then  
1337 - if d = 10  
1338 - then ok(unique)  
1339 - else (unput(d);  
1340 - unput(c);  
1341 - error(end_of_line_expected))  
1342 - }  
1343 - else (unput(c);  
1344 - error(end_of_line_expected))  
1345 - }}.  
1346 -  
1347 -  
1348 -  
1349 -  
1350 -  
1351 -  
1352 -  
1353 -  
1354 - *** [4.3] Reading a 'word'.  
1355 -  
1356 - A 'word' is a sequence of characters which begins either by a double quote or not by a  
1357 - double quote. (However, any leading blanks are read in and ignored. This is  
1358 - accomplished by 'skip_http_blanks'.) If it begins by a double quote, it is read like a  
1359 - string, i.e. it ends at the next (non backslashed) double quote. Otherwise, it is  
1360 - right delimited by any character which may be considered as 'blank'. If the word is  
1361 - double quoted, the closing double quote is read in. On the contrary, if the word is not  
1362 - double quoted, the right delimiting blank character is not read in (it is 'unput' back  
1363 - into the connection), and may be read in again. This is needed because carriage return  
1364 - or line feed which are 'blank', also have a meaning in HTTP.  
1365 -  
1366 -define Result(Error,String)  
1367 - read_word_aux  
1368 - (  
1369 - BufferedConnection connection,  
1370 - Int dead_line,  
1371 - List(Word8) so_far,  
1372 - DenialOfService dos  
1373 - ) =  
1374 - if next_char(connection,dead_line,dos) is  
1375 - {  
1376 - error(msg) then error(msg),  
1377 - ok(c) then  
1378 - if is_blank(c)  
1379 - then (unput(c);  
1380 - ok(implode(reverse(so_far))))  
1381 - else read_word_aux(connection,dead_line,[c . so_far],dos)  
1382 - }.  
1383 -  
1384 -define Result(Error,String)  
1385 - read_word  
1386 - (  
1387 - BufferedConnection connection,  
1388 - Int dead_line,  
1389 - DenialOfService dos  
1390 - ) =  
1391 - if skip_http_blanks(connection,dead_line,dos) is  
1392 - {  
1393 - error(msg) then error(msg),  
1394 - ok(_) then  
1395 - if next_char(connection,dead_line,dos) is  
1396 - {  
1397 - error(msg) then error(msg),  
1398 - ok(c) then  
1399 - if c = '\"'  
1400 - then read_string(connection,dead_line,[],dos)  
1401 - else read_word_aux(connection,dead_line,[c],dos)  
1402 - }  
1403 - }.  
1404 -  
1405 -  
1406 -  
1407 -  
1408 -  
1409 -  
1410 -  
1411 -  
1412 - *** [4.4] Separating the URI from the query string.  
1413 -  
1414 - A 'query string' may be postfixed to the URI, just after a question mark. For example,  
1415 - the client may send the following request:  
1416 -  
1417 - GET /catalog.awp?item=3&color=blue  
1418 -  
1419 - We separate this into an URI: "/catalog.awp" and the string: "item=3&color=blue" which  
1420 - will be later transformed into the list:  
1421 -  
1422 - [web_arg("item","3"),web_arg("color","blue")]  
1423 -  
1424 -  
1425 -define (String,String)  
1426 - separate_uri_from_query_string  
1427 - (  
1428 - String uri_and_query_string,  
1429 - Int n  
1430 - ) =  
1431 - if nth(n,uri_and_query_string) is  
1432 - {  
1433 - failure then (uri_and_query_string,""),  
1434 - success(c) then  
1435 - if c = '?'  
1436 - then (substr(uri_and_query_string,0,n),  
1437 - substr(uri_and_query_string,n+1,length(uri_and_query_string)-(n+1)))  
1438 - else separate_uri_from_query_string(uri_and_query_string,n+1)  
1439 - }.  
1440 -  
1441 -  
1442 -  
1443 -  
1444 -  
1445 -  
1446 -  
1447 -  
1448 -  
1449 - *** [4.5] Reading the web arguments.  
1450 -  
1451 - HTTP/HTTPS requests are sent in one of two formats:  
1452 -  
1453 - (1) www-url encoded  
1454 - (2) multipart/form-data encoded  
1455 -  
1456 - The first one is the normal (historical) way of encoding. The second one is required  
1457 - for uploading files. A server which is supposed to accept upload of files must handle  
1458 - both formats. The first thing to do is to decide the format of the request. This is  
1459 - easily done by examining the HTTP headers. If we find the header:  
1460 -  
1461 - Content-Type: multipart/form-data  
1462 -  
1463 - the request is multipart/form-data encoded. Otherwise, it is 'www-url' encoded. We  
1464 - first consider 'www-url' encoded requests.  
1465 -  
1466 - For a 'www-url' encoded request, the web argument are either in the query string or in  
1467 - the body of the request, or both. The format is the same for both:  
1468 -  
1469 - name=value&name=value&...  
1470 -  
1471 - However, we may also have  
1472 -  
1473 - name  
1474 - name=  
1475 - name=&...  
1476 - name&...  
1477 -  
1478 - i.e. some parts may be missing. Hence, we must be careful.  
1479 -  
1480 - Furthermore, web arguments must be translated from web to ASCII when www-url encoded.  
1481 -  
1482 -define Bool  
1483 - is_ampersand_or_equal  
1484 - (  
1485 - Word8 c  
1486 - ) =  
1487 - if c = '&' then true else c = '='.  
1488 -  
1489 -  
1490 -  
1491 - The function 'read_name_or_value' reads the string 's' starting at position 'n' until  
1492 - either the end of the string or the first '&' or '='.  
1493 -  
1494 -define String  
1495 - read_name_or_value  
1496 - (  
1497 - String s,  
1498 - Int start,  
1499 - Int i  
1500 - ) =  
1501 - if nth(i,s) is  
1502 - {  
1503 - failure then substr(s,start,i - start),  
1504 - success(c) then  
1505 - if is_ampersand_or_equal(c)  
1506 - then substr(s,start,i-start) // the separator is not included  
1507 - else read_name_or_value(s,start,i+1)  
1508 - }.  
1509 -  
1510 -  
1511 -define List(Web_arg)  
1512 - read_www_url_encoded_web_args  
1513 - (  
1514 - String s,  
1515 - Int start,  
1516 - ) =  
1517 - with first = read_name_or_value(s,start,start),  
1518 - if first = ""  
1519 - then []  
1520 - else with i = start+length(first),  
1521 - if nth(i,s) is  
1522 - {  
1523 - failure then [web_arg(first,"")],  
1524 - success(c) then  
1525 - if c = '&'  
1526 - then [web_arg(first,"") . read_www_url_encoded_web_args(s,i+1)]  
1527 - else if c = '='  
1528 - then with second1 = read_name_or_value(s,i+1,i+1),  
1529 - // print("\""+second1+"\"\n");  
1530 - with second = web_to_ascii(second1,0,[]),  
1531 - [web_arg(first,second) . read_www_url_encoded_web_args(s,i+length(second1)+2)]  
1532 - else print("**** ALERT **** badly formatted argument [" + s + "]!!!\n");  
1533 - []  
1534 - }.  
1535 -  
1536 -  
1537 -  
1538 -  
1539 -  
1540 - *** [4.7] Reading the request line.  
1541 -  
1542 - 'read_request_line' reads three words and a new line from the connection. It tries to  
1543 - recognize "get" or "post" in the first word, separates the URI from the query string in  
1544 - the second word, transforms the query string into a list of 'Web_arg', and finally  
1545 - returns a datum of type 'HTTP_RequestLine' if no error arose.  
1546 -  
1547 -  
1548 -define Result(Error,HTTP_RequestType)  
1549 - identify_get_or_post  
1550 - (  
1551 - String s  
1552 - ) =  
1553 - with ls = to_lower(s),  
1554 - if ls = "get" then ok(get) else  
1555 - if ls = "post" then ok(post) else  
1556 - error(not_get_or_post_request(ls)).  
1557 -  
1558 -define Result(Error,HTTP_RequestLine)  
1559 - read_request_line  
1560 - (  
1561 - BufferedConnection connection,  
1562 - Int dead_line,  
1563 - DenialOfService dos  
1564 - ) =  
1565 - if read_word(connection,dead_line,dos) is  
1566 - {  
1567 - error(msg) then error(msg),  
1568 - ok(get_or_post) then if read_word(connection,dead_line,dos) is  
1569 - {  
1570 - error(msg) then error(msg),  
1571 - ok(uri_and_query_string) then if read_word(connection,dead_line,dos) is  
1572 - {  
1573 - error(msg) then error(msg),  
1574 - ok(http_version) then if read_new_line(connection,dead_line,dos) is  
1575 - {  
1576 - error(msg) then error(msg),  
1577 - ok(_) then if separate_uri_from_query_string(uri_and_query_string,0) is  
1578 - (uri,query_string) then if identify_get_or_post(get_or_post) is  
1579 - {  
1580 - error(msg) then error(msg),  
1581 - ok(request_type) then  
1582 - ok(request_line(request_type, web_to_ascii(uri, 0, []), read_www_url_encoded_web_args(query_string,0)))  
1583 - }  
1584 - }  
1585 - }  
1586 - }  
1587 - }.  
1588 -  
1589 -  
1590 -  
1591 -  
1592 -  
1593 -  
1594 -  
1595 - *** [4.8] Reading the HTTP headers.  
1596 -  
1597 - Each header is made of a name (containing only letters, the underscore, digits and the  
1598 - minus sign), a colon, a value, and a new line. The first empty line ends the headers.  
1599 -  
1600 -  
1601 - The next function tests characters acceptable in a header name.  
1602 -  
1603 -define Bool  
1604 - is_header_name_char  
1605 - (  
1606 - Word8 c  
1607 - ) =  
1608 - if ('a' +=< c & c +=< 'z') then true else  
1609 - if ('A' +=< c & c +=< 'Z') then true else  
1610 - if ('0' +=< c & c +=< '9') then true else  
1611 - if c = '-' then true else  
1612 - c = '_'.  
1613 -  
1614 -define Result(Error,String)  
1615 - read_header_name  
1616 - (  
1617 - BufferedConnection connection,  
1618 - Int dead_line,  
1619 - List(Word8) so_far,  
1620 - DenialOfService dos  
1621 - ) =  
1622 - if next_char(connection,dead_line,dos) is  
1623 - {  
1624 - error(msg) then error(msg),  
1625 - ok(c) then  
1626 - if is_header_name_char(c)  
1627 - then read_header_name(connection,dead_line,[to_lower(c) . so_far],dos)  
1628 - else unput(c); ok(implode(reverse(so_far)))  
1629 - }.  
1630 -  
1631 -define Result(Error,One)  
1632 - skip_colon  
1633 - (  
1634 - BufferedConnection connection,  
1635 - Int dead_line,  
1636 - DenialOfService dos  
1637 - ) =  
1638 - if skip_http_blanks(connection,dead_line,dos) is  
1639 - {  
1640 - error(msg) then error(msg),  
1641 - ok(_) then  
1642 - if next_char(connection,dead_line,dos) is  
1643 - {  
1644 - error(msg) then error(msg),  
1645 - ok(c) then  
1646 - if c = ':'  
1647 - then ok(unique)  
1648 - else error(colon_expected)  
1649 - }}.  
1650 -  
1651 -  
1652 -define Result(Error,String)  
1653 - read_header_value  
1654 - (  
1655 - BufferedConnection connection,  
1656 - Int dead_line,  
1657 - List(Word8) so_far,  
1658 - DenialOfService dos  
1659 - ) =  
1660 - if next_char(connection,dead_line,dos) is  
1661 - {  
1662 - error(msg) then error(msg),  
1663 - ok(c) then  
1664 - if c = 13  
1665 - then if next_char(connection,dead_line,dos) is  
1666 - {  
1667 - error(msg) then error(msg),  
1668 - ok(d) then  
1669 - if d = 10  
1670 - then if next_char(connection,dead_line,dos) is  
1671 - {  
1672 - error(msg) then error(msg),  
1673 - ok(e) then  
1674 - if is_strict_blank(e)  
1675 - then read_header_value(connection,dead_line,[e . so_far],dos)  
1676 - else (unput(e); ok(implode(reverse(so_far))))  
1677 - }  
1678 - else read_header_value(connection,dead_line,[d, c . so_far],dos)  
1679 - }  
1680 - else read_header_value(connection,dead_line,[c . so_far],dos)  
1681 - }.  
1682 -  
1683 -  
1684 - Reading a single header.  
1685 -  
1686 -define Result(Error,Maybe(HTTP_header))  
1687 - read_header  
1688 - (  
1689 - BufferedConnection connection,  
1690 - Int dead_line,  
1691 - DenialOfService dos  
1692 - ) =  
1693 - if read_header_name(connection,dead_line,[],dos) is  
1694 - {  
1695 - error(msg) then error(msg),  
1696 - ok(name) then  
1697 - if name = "" then  
1698 - if read_and_ignore(connection,dead_line,2,dos) /* 13 and 10 */ is  
1699 - {  
1700 - error(msg) then error(msg),  
1701 - ok(_) then // this is the blank line  
1702 - ok(failure) // end of headers  
1703 - }  
1704 - else if skip_colon(connection,dead_line,dos) is  
1705 - {  
1706 - error(msg) then error(msg),  
1707 - ok(_) then if skip_http_blanks(connection,dead_line,dos) is  
1708 - {  
1709 - error(msg) then error(msg),  
1710 - ok(_) then if read_header_value(connection,dead_line,[],dos) is  
1711 - {  
1712 - error(msg) then error(msg),  
1713 - ok(value) then  
1714 - ok(success(http_header(name,value)))  
1715 - }  
1716 - }  
1717 - }  
1718 - }.  
1719 -  
1720 -  
1721 -  
1722 - Reading all the headers.  
1723 -  
1724 -define Result(Error,List(HTTP_header))  
1725 - read_http_headers  
1726 - (  
1727 - BufferedConnection connection,  
1728 - Int dead_line,  
1729 - DenialOfService dos  
1730 - ) =  
1731 - if read_header(connection,dead_line,dos) is  
1732 - {  
1733 - error(msg) then error(msg),  
1734 - ok(mbh) then if mbh is  
1735 - {  
1736 - failure then ok([ ]),  
1737 - success(header) then  
1738 - if read_http_headers(connection,dead_line,dos) is  
1739 - {  
1740 - error(msg) then error(msg),  
1741 - ok(others) then ok([header . others])  
1742 - }  
1743 - }  
1744 - }.  
1745 -  
1746 -  
1747 -  
1748 -  
1749 -  
1750 -  
1751 -  
1752 - *** [4.9] Getting the size of the request's body.  
1753 -  
1754 - The size of the body of the request is given under the 'Content-Length' header. If this  
1755 - header is not present, the size is assumed to be zero.  
1756 -  
1757 -define Result(Error,Int)  
1758 - get_body_size  
1759 - (  
1760 - List(HTTP_header) headers  
1761 - ) =  
1762 - if headers is  
1763 - {  
1764 - [ ] then ok(0),  
1765 - [h . t] then if h is http_header(name,value) then  
1766 - if name = "content-length"  
1767 - then if decimal_scan(value) is  
1768 - {  
1769 - failure then error(incorrect_content_length_value),  
1770 - success(n) then ok(n)  
1771 - }  
1772 - else get_body_size(t)  
1773 - }.  
1774 -  
1775 -  
1776 -  
1777 -  
1778 -  
1779 -  
1780 -  
1781 -  
1782 -  
1783 -  
1784 - *** [4.10] Reading the body of the request.  
1785 -  
1786 - The body of the request may be very big (it contains uploaded files, if any). We read  
1787 - it using the primitive 'read', which returns the number of bytes read, which may be  
1788 - less than the number of bytes we wanted to read. This is not an error, but simply due  
1789 - to the fact the buffer associated with the connection in the Linux (or MS-Windows)  
1790 - kernel has a limited size. Hence, we must read bytes again until we have read the  
1791 - required number of bytes. However, if the number of bytes read is zero, the connection  
1792 - may be broken. In that case, we must not try to read indefinitely. On the contrary, we  
1793 - make at most 10 retries, with a small sleeping time between any two of them.  
1794 -  
1795 -define Result(Error,ByteArray)  
1796 - read_http_body  
1797 - (  
1798 - BufferedConnection connection,  
1799 - Int body_size,  
1800 - ByteArray so_far, // when calling this function, 'so_far' is the empty byte array  
1801 - Int retries // this function is called with retries = 10  
1802 - ) =  
1803 - if body_size = 0 then ok(constant_byte_array(0,0)) else  
1804 - if retries =< 0 then error(cannot_read_from_connection) else  
1805 - if read_from_connexion(connection,body_size,60,constant_byte_array(body_size,0),0) is  
1806 - {  
1807 - error then error(cannot_read_from_connection),  
1808 - timeout then error(timeout(60)),  
1809 - ok(new_bytes) then with  
1810 - ba = so_far + new_bytes, // contains all the bytes read so far  
1811 - nr = length(ba), // total read since the beginning  
1812 - nn = length(new_bytes), // number of bytes just read  
1813 - if nr < body_size // must read more bytes  
1814 - then if nn > 0 // if connection seems to work  
1815 - then read_http_body(connection,body_size,ba,1000) // continue reading  
1816 - else sleep(100); // otherwise, sleep 1/10 of second  
1817 - read_http_body(connection,body_size,ba, // and retry reading  
1818 - retries-1) // but no more than 10 times  
1819 - else ok(ba) // required number of bytes has been read  
1820 - }.  
1821 -  
1822 -  
1823 - Note: During sleeping, 'anbexec' runs other machines. Actually, calling 'sleep', even  
1824 - for one millisecond, is some way of giving up explicitly, so that other virtual  
1825 - machines may work.  
1826 -  
1827 -  
1828 -  
1829 -  
1830 -  
1831 -  
1832 -  
1833 -  
1834 -  
1835 -  
1836 -  
1837 -  
1838 - *** [5] Making the HTTP answer.  
1839 -  
1840 - At that point we have read the request line, the headers and the body of the  
1841 - request, and we must decide what to do.  
1842 -  
1843 - Actually, we can do one of the following:  
1844 -  
1845 - - send a file,  
1846 - - execute 'tickets_and_web_page' in case of an ".awp" URI.  
1847 -  
1848 - The uploaded file (which are in the body of the request) are saved into temporary files  
1849 - below.  
1850 -  
1851 -  
1852 -  
1853 -  
1854 -  
1855 - *** [5.1] Avoiding illegal URIs.  
1856 -  
1857 - For security reasons, we must avoid illegal URIs, for example those which may climb up  
1858 - in the file hierarchy. First we accept only few characters in URIs.  
1859 -  
1860 -define Bool  
1861 - is_legal_uri_char  
1862 - (  
1863 - Word8 c  
1864 - ) =  
1865 - if ('a' +=< c & c +=< 'z') then true else // accept 'a' to 'z'  
1866 - if ('A' +=< c & c +=< 'Z') then true else // accept 'A' to 'Z'  
1867 - if ('0' +=< c & c +=< '9') then true else // accept '0' to '9'  
1868 - if c = '.' then true else // accept '.' '-' '/' and '_'  
1869 - if c = '-' then true else  
1870 - if c = '/' then true else  
1871 - c = '_'.  
1872 -  
1873 - We do not accept ~ which is some way of climbing. Of course, we cannot disallow single  
1874 - dots, which are most often present in legal URIs, but we must avoid double dots ..  
1875 - which mean 'climb up'.  
1876 -  
1877 -define Bool  
1878 - is_illegal_uri  
1879 - (  
1880 - String uri,  
1881 - Int n  
1882 - ) =  
1883 - if nth(n,uri) is  
1884 - {  
1885 - failure then false,  
1886 - success(c) then  
1887 - if c = '.' // first dot  
1888 - then if nth(n+1,uri) is  
1889 - {  
1890 - failure then false,  
1891 - success(d) then  
1892 - if d = '.' // second dot  
1893 - then true  
1894 - else is_illegal_uri(uri,n+1)  
1895 - }  
1896 - else is_illegal_uri(uri,n+1)  
1897 - }.  
1898 -  
1899 -  
1900 -  
1901 -  
1902 -  
1903 -  
1904 - *** [5.2] Managing authorizations for downloading private files.  
1905 -  
1906 - Computing the authorization and making the authorization file (containing the absolute  
1907 - path of the file on the server).  
1908 -  
1909 -  
1910 -define String  
1911 - compute_authorization  
1912 - (  
1913 - String authorization_secret,  
1914 - String absolute_path  
1915 - ) =  
1916 - to_ascii(sha1((authorization_secret,  
1917 - absolute_path))).  
1918 -  
1919 -  
1920 -public define String  
1921 - make_authorization  
1922 - (  
1923 - String site_directory,  
1924 - String authorization_secret,  
1925 - String absolute_path  
1926 - ) =  
1927 - with private_download_dir = site_directory+"/private_download",  
1928 - auth = compute_authorization(authorization_secret,  
1929 - absolute_path),  
1930 - forget(save(absolute_path,  
1931 - private_download_dir+"/z"+auth));  
1932 - auth.  
1933 -  
1934 -  
1935 - The function 'send_file' defined below handles the recognition of authorizations.  
1936 -  
1937 -  
1938 -  
1939 -  
1940 -  
1941 - *** [5.3] Recognizing MIME types.  
1942 -  
1943 - The extension of the (redirected) URI must be either ".awp" or recognized as associated  
1944 - to a MIME type. Otherwise, the server will not send the file. This is for security, but  
1945 - also because, we must generate a 'Content-Type' header in the answer, with the right  
1946 - MIME type.  
1947 -  
1948 -define String  
1949 - get_uri_extension_aux  
1950 - (  
1951 - String uri,  
1952 - Int n // used for searching backwards  
1953 - ) =  
1954 - if nth(n,uri) is  
1955 - {  
1956 - failure then "",  
1957 - success(c) then  
1958 - if c = '.' then substr(uri,n,length(uri)-n)  
1959 - else if c = '/' then ""  
1960 - else get_uri_extension_aux(uri,n-1)  
1961 - }.  
1962 -  
1963 -public define String  
1964 - get_uri_extension  
1965 - (  
1966 - String uri  
1967 - ) =  
1968 - get_uri_extension_aux(uri,  
1969 - length(uri)-1). // search starts at the right end  
1970 -  
1971 -public define Bool  
1972 - contains_no_case  
1973 - (  
1974 - List(String) l,  
1975 - String val  
1976 - ) =  
1977 - if l is  
1978 - {  
1979 - [] then false,  
1980 - [h . t] then  
1981 - if insensitive_equal(h, val) then true  
1982 - else contains_no_case(t, val)  
1983 - }.  
1984 -  
1985 -  
1986 -define Maybe(MIME)  
1987 - recognize_mime_type_from_ext  
1988 - (  
1989 - String ext,  
1990 - List(MIME) l  
1991 - ) =  
1992 - if l is  
1993 - {  
1994 - [ ] then success(mime("application", "octet-stream", [])), // failure,  
1995 - [h . t] then if h is mime(type, subtype, extensions) then  
1996 - if contains_no_case(extensions, ext)  
1997 - then success(h)  
1998 - else recognize_mime_type_from_ext(ext,t)  
1999 - }.  
2000 -  
2001 -define Maybe(MIME)  
2002 - recognize_mime_type_from_uri  
2003 - (  
2004 - Web_Site_Description desc,  
2005 - String uri  
2006 - ) =  
2007 - recognize_mime_type_from_ext(get_uri_extension(uri),known_mime_types(desc)).  
2008 -  
2009 -  
2010 -  
2011 -  
2012 -  
2013 -  
2014 -  
2015 -  
2016 - *** [5.4] Formating HTTP headers.  
2017 -  
2018 - This is the formating for sending to the client (hence, it has nothing to do with the  
2019 - component 'journal_headers' in the web site description).  
2020 -  
2021 -public define Printable_tree  
2022 - format_headers  
2023 - (  
2024 - List(HTTP_header) headers  
2025 - ) =  
2026 - if headers is  
2027 - {  
2028 - [ ] then [ ],  
2029 - [h . t] then if h is http_header(name,value) then  
2030 - [name,": ",value,crlf . format_headers(t)]  
2031 - }.  
2032 -  
2033 -  
2034 -  
2035 -define String  
2036 - month_abrv  
2037 - (  
2038 - Date_and_Time d  
2039 - ) =  
2040 - if d.month = 1 then "Jan"  
2041 - else if d.month = 2 then "Feb"  
2042 - else if d.month = 3 then "Mar"  
2043 - else if d.month = 4 then "Apr"  
2044 - else if d.month = 5 then "May"  
2045 - else if d.month = 6 then "Jun"  
2046 - else if d.month = 7 then "Jul"  
2047 - else if d.month = 8 then "Aug"  
2048 - else if d.month = 9 then "Sep"  
2049 - else if d.month = 10 then "Oct"  
2050 - else if d.month = 11 then "Nov"  
2051 - else if d.month = 12 then "Dec"  
2052 - else  
2053 - println("Bad month value [" + d.month + "] on Date_and_Time");  
2054 - "XXX".  
2055 -  
2056 -define String  
2057 - weekday_abrv  
2058 - (  
2059 - Date_and_Time d  
2060 - ) =  
2061 - if d.week_day = 0 then "Sun"  
2062 - else if d.week_day = 1 then "Mon"  
2063 - else if d.week_day = 2 then "Tue"  
2064 - else if d.week_day = 3 then "Wed"  
2065 - else if d.week_day = 4 then "Thu"  
2066 - else if d.week_day = 5 then "Fri"  
2067 - else if d.week_day = 6 then "Sat"  
2068 - else  
2069 - println("Bad weekday value [" + d.week_day + "] on Date_and_Time");  
2070 - "XXX".  
2071 -  
2072 -/**  
2073 - * Format a date with the followin format : "Mon, 23 Jul 2007 11:33:43 GMT"  
2074 - * Currently, this function can't output a GMT time, but only local time.  
2075 - * So the final GMT is totally fake, but needed by protocol.  
2076 - */  
2077 -public define String  
2078 - format_http_date  
2079 - (  
2080 - Date_and_Time d  
2081 - ) =  
2082 - weekday_abrv(d) + ", " + zero_pad_n(2,day(d)) + " " + month_abrv(d) + " " + year(d)  
2083 - + " " + zero_pad_n(2,hour(d)) + ":" + zero_pad_n(2,minute(d)) + ":" + zero_pad_n(2,second(d)) + " GMT".  
2084 -  
2085 -/**  
2086 - * Same as previous format_http_date() function, but with seconds count from the UNIX epoch as input.  
2087 - */  
2088 -public define String  
2089 - format_http_date  
2090 - (  
2091 - Int date  
2092 - ) =  
2093 - format_http_date(convert_time(date)).  
2094 -  
2095 -  
2096 - *** [5.5] Sending a file.  
2097 -  
2098 - We send 2 headers 'Content-Type' and 'Content-Length'.  
2099 -  
2100 -define List(HTTP_header)  
2101 - headers_for_send_file  
2102 - (  
2103 - MIME mime_type,  
2104 - Int size,  
2105 - String etag,  
2106 - Maybe(FileTimes) mb_ftimes,  
2107 - ) =  
2108 - with headers = (List(HTTP_header))  
2109 - [  
2110 - http_header("Content-Type", to_String(mime_type)),  
2111 - http_header("Etag", etag),  
2112 - http_header("Content-Length",to_decimal(size)),  
2113 - ],  
2114 - if mb_ftimes is  
2115 - {  
2116 - failure then headers,  
2117 - success(ftimes) then [http_header("Last-Modified", format_http_date(to_Int(ftimes.last_modified))) . headers]  
2118 - }  
2119 - .  
2120 -  
2121 -  
2122 -  
2123 - Sending the body of the answer (i.e. the file itself).  
2124 -  
2125 -define One  
2126 - send_file_body  
2127 - (  
2128 - Web_Site_Description desc,  
2129 - Connection connection, // connection with the client  
2130 - Connection file, // file to be sent already opened  
2131 - Int size, // size of file  
2132 - Int sent, // bytes already sent  
2133 - String filename // name of file  
2134 - ) =  
2135 - if sent >= size then unique else  
2136 - if read(file,min(16384,size-sent),60) is  
2137 - {  
2138 - error then log_journal_msg(desc,"Cannot read from file '"+filename+"'.\n"),  
2139 - timeout then log_journal_msg(desc,"Cannot read from file timeoput'"+filename+"'.\n"),  
2140 - ok(ba) then  
2141 - with nr = length(ba), // get the number of bytes read  
2142 - if reliable_write(connection, ba) is  
2143 - {  
2144 - failure then log_journal_msg(desc,"Cannot write into connection delirering '"+filename+"' (sent="+sent+"; size="+size+"; current="+nr+").\n"),  
2145 - success(nw) then  
2146 - send_file_body(desc,connection,file,size,sent+nw,filename)  
2147 - }  
2148 - }.  
2149 -  
2150 -  
2151 -define String  
2152 - compute_etag  
2153 - (  
2154 - String filename,  
2155 - Maybe(FileTimes) mb_ftimes,  
2156 - Int size,  
2157 - ) =  
2158 - if mb_ftimes is  
2159 - {  
2160 - failure then println("Warning: no file times for '" + filename + "', etag won't be very accurate."); to_ascii(sha1((filename, size))),  
2161 - success(ftimes) then to_ascii(md5((filename, ftimes, size)))  
2162 - }.  
2163 -  
2164 -define Bool  
2165 - are_same_etag  
2166 - (  
2167 - Maybe(String) input_etag,  
2168 - String current_etag  
2169 - ) =  
2170 - if input_etag is  
2171 - {  
2172 - failure then false,  
2173 - success(etag) then etag = current_etag  
2174 - }.  
2175 -  
2176 - Sending the answer line, the headers and the body.  
2177 -  
2178 -define One  
2179 - send_file  
2180 - (  
2181 - Web_Site_Description desc,  
2182 - Connection connection,  
2183 - List(HTTP_header) input_headers,  
2184 - List(HTTP_header) headers,  
2185 - Int size,  
2186 - Connection file,  
2187 - String filename,  
2188 - String full_path,  
2189 - MIME mime_type,  
2190 - One -> One action_before_send_file  
2191 - ) =  
2192 - action_before_send_file(unique);  
2193 - with input_etag = http_header_value(input_headers, "If-None-Match"),  
2194 - mb_ftimes = get_file_times(full_path),  
2195 - current_etag = compute_etag(full_path, mb_ftimes, size),  
2196 - if are_same_etag(input_etag, current_etag) is  
2197 - {  
2198 - false then  
2199 - forget(reliable_write(connection,to_byte_array("HTTP/1.1 200 OK"+crlf)));  
2200 - forget(reliable_write(connection,[format_headers(headers + headers_for_send_file(mime_type, size, current_etag, mb_ftimes)) , crlf]));  
2201 - //forget(copy_file_to_Connection(file, connection, size)),  
2202 - send_file_body(desc,connection,file,size,0,filename),  
2203 - true then  
2204 - forget(reliable_write(connection,to_byte_array("HTTP/1.1 304 Not Modified"+crlf)));  
2205 - forget(reliable_write(connection,[format_headers([http_header("Etag", current_etag) . headers]) , crlf]))  
2206 - //send_file_body(desc,connection,file,size,0,filename)  
2207 - }.  
2208 -  
2209 -  
2210 -  
2211 - Checking if a connection is under SSL.  
2212 -  
2213 -define Bool  
2214 - is_SSL  
2215 - (  
2216 - Connection c  
2217 - ) =  
2218 - if c is  
2219 - {  
2220 - file_r(_) then false,  
2221 - file_w(_) then false,  
2222 - file_rw(_) then false,  
2223 - tcp(_) then false,  
2224 - ssl(_) then true  
2225 - }.  
2226 -  
2227 -  
2228 -  
2229 - Before opening and sending a file, we check the MIME type. It must be recognized,  
2230 - except if there is a valid authorization for private download.  
2231 -  
2232 -define One  
2233 - send_file  
2234 - (  
2235 - Web_Site_Description desc,  
2236 - Connection connection,  
2237 - String uri,  
2238 - List(HTTP_header) input_headers,  
2239 - List(HTTP_header) output_headers,  
2240 - Maybe(String) mbauthorization,  
2241 - One -> One action_before_send_file  
2242 - ) =  
2243 - if mbauthorization is  
2244 - {  
2245 - //--- file without authorization: take it from public ---  
2246 - failure then if recognize_mime_type_from_uri(desc,uri) is  
2247 - {  
2248 - failure then log_journal_msg(desc,"No MIME type found for '"+uri+"'.\n"),  
2249 - success(mime_type) then  
2250 - with path = site_directory(desc)+"/public"+uri,  
2251 - if (Maybe(RStream))file(path, read) is  
2252 - {  
2253 - failure then log_journal_msg(desc,"Cannot find file '"+path+"'.\n"),  
2254 - success(f) then with size = file_size(f),  
2255 - send_file(desc,  
2256 - connection,  
2257 - input_headers,  
2258 - output_headers,  
2259 - size,  
2260 - file(f),  
2261 - uri,  
2262 - path,  
2263 - mime_type,  
2264 - action_before_send_file)  
2265 - }  
2266 - },  
2267 -  
2268 - //--- file with authorization: apply 'private download' mecanism ---  
2269 - success(authorization) then  
2270 - with private_download_dir = site_directory(desc)+"/private_download",  
2271 - if (RetrieveResult(String))retrieve(private_download_dir+"/z"+authorization)  
2272 - is ok(absolute_path)  
2273 - then (  
2274 - with new_hash = compute_authorization(authorization_secret(desc),  
2275 - absolute_path),  
2276 - if (Maybe(RStream))connect to file absolute_path is  
2277 - {  
2278 - failure then log_journal_msg(desc,"Cannot find file '"+absolute_path+"'.\n"),  
2279 - success(f) then with size = file_size(f),  
2280 - mime_type = if recognize_mime_type_from_uri(desc,uri) is  
2281 - {  
2282 - failure then mime("application", "octet-stream", []),  
2283 - success(mime_type) then mime_type  
2284 - },  
2285 - send_file(desc,  
2286 - connection,  
2287 - input_headers,  
2288 - output_headers,  
2289 - size,  
2290 - file(f),  
2291 - uri,  
2292 - absolute_path,  
2293 - mime_type,  
2294 - action_before_send_file)  
2295 - }  
2296 - )  
2297 - else log_journal_msg(desc,"Cannot find or read authorization file.\n")  
2298 - }.  
2299 -  
2300 -  
2301 -  
2302 -  
2303 -  
2304 -  
2305 -  
2306 -  
2307 - *** [5.6] Answering a www-url encoded request.  
2308 -  
2309 - Standard headers are for answering ".awp" requests.  
2310 -  
2311 -public define List(HTTP_header)  
2312 - standard_headers  
2313 - =  
2314 - [  
2315 - http_header("Date", format_http_date(now)),  
2316 - http_header("Server", "Anubis Embedded Server v" + major_version_number + "." + minor_version_number),  
2317 - http_header("Connection", "close"),  
2318 - ].  
2319 -  
2320 -public define List(HTTP_header)  
2321 - standard_headers_for  
2322 - (  
2323 - String mime_type,  
2324 - Int answer_body_size,  
2325 - Maybe(String) mb_charset,  
2326 - ) =  
2327 - [  
2328 - http_header("Content-Type", mime_type + if mb_charset is success(charset) then "; charset="+charset else ""),  
2329 - http_header("Content-length", to_decimal(answer_body_size))  
2330 - ].  
2331 -  
2332 -public define List(HTTP_header)  
2333 - file_attached_header  
2334 - (  
2335 - String filename  
2336 - ) =  
2337 - [  
2338 - http_header("Content-Disposition", "attachment; filename=\"" + filename +"\"")  
2339 - ].  
2340 -  
2341 -  
2342 -define One  
2343 - www_url_answer  
2344 - (  
2345 - String host_name,  
2346 - Web_Site_Description desc,  
2347 - Connection connection, // with the client  
2348 - Word32 ip_addr, // of the client  
2349 - HTTP_RequestLine request_line,  
2350 - List(HTTP_header) headers,  
2351 - ByteArray body,  
2352 - One -> String generate_tt // trust ticket generation  
2353 - ) =  
2354 - with all_web_args = query_string(request_line) +  
2355 - read_www_url_encoded_web_args(to_string(body),0),  
2356 - uri = uri(request_line),  
2357 - ext = get_uri_extension(uri),  
2358 - http_inf = http_info(ip_addr, host_name, uri, headers, is_SSL(connection), generate_tt),  
2359 - (if member(journal_extensions(desc),ext)  
2360 - then log_journal_msg(desc,  
2361 - format_request(desc,connection,request_line,headers,all_web_args))  
2362 - else unique);  
2363 - if is_illegal_uri(uri,0)  
2364 - then log_journal_msg(desc,"Received illegal URI: "+uri+"\n")  
2365 - else (if (ext = ".awp" | ext = "")  
2366 - then (with answer_headers_body = awp_handler(desc)(host_name,  
2367 - http_inf,  
2368 - all_web_args,  
2369 - is_SSL(connection)),  
2370 - //print_delta("After page generation");  
2371 - forget(reliable_write(connection, answer_headers_body))  
2372 - //print_delta("After sending page")  
2373 - )  
2374 - else (send_file(desc,  
2375 - connection,  
2376 - uri,  
2377 - headers,  
2378 - standard_headers,  
2379 - if web_arg_value(all_web_args,"zauth") is  
2380 - {  
2381 - not_found then failure,  
2382 - found(v) then success(v)  
2383 - },  
2384 - (One u) |-> before_send_file(desc)(http_inf, all_web_args))  
2385 - //print_delta("After sending file")  
2386 - )).  
2387 -  
2388 -  
2389 -  
2390 -  
2391 -  
2392 -  
2393 -  
2394 - *** [5.7] Answering a multipart/form-data encoded request.  
2395 -  
2396 - In order to support upload of files, we must be able to read web arguments which are  
2397 - encoded in a multipart/form-data body. The first thing to do is to find the  
2398 - boundary. The boundary is a special string which delimits the various parts of the  
2399 - 'multipart' body. It is found within the value of the 'Content-Type' HTTP header, as  
2400 - the value of the 'boundary' attribute.  
2401 -  
2402 -  
2403 -  
2404 -  
2405 -  
2406 - *** [5.7.1] Finding the boundary.  
2407 -  
2408 - Hence, we just have to find the string 'boundary=' within the value of the  
2409 - 'Content-Type' header, and read the value of the boundary from there.  
2410 -  
2411 -define Bool  
2412 - delimits_boundary  
2413 - (  
2414 - Word8 c  
2415 - ) =  
2416 - if c = ' ' then true else  
2417 - if c = 13 then true else  
2418 - if c = 10 then true else  
2419 - if c = 0 then true else  
2420 - if c = ',' then true else  
2421 - c = ';'.  
2422 -  
2423 -  
2424 -define Maybe(String)  
2425 - get_boundary_value_3  
2426 - (  
2427 - String s,  
2428 - Int i,  
2429 - List(Word8) so_far  
2430 - ) =  
2431 - if nth(i,s) is  
2432 - {  
2433 - failure then success(implode(reverse(so_far))),  
2434 - success(c) then  
2435 - if delimits_boundary(c)  
2436 - then success(implode(reverse(so_far)))  
2437 - else get_boundary_value_3(s,i+1,[c . so_far])  
2438 - }.  
2439 -  
2440 -  
2441 -  
2442 -define Maybe(String)  
2443 - get_boundary_value_2  
2444 - (  
2445 - String s,  
2446 - Int i,  
2447 - ) =  
2448 - if nth(i,s) is  
2449 - {  
2450 - failure then failure,  
2451 - success(c) then  
2452 - if is_blank(c)  
2453 - then get_boundary_value_2(s,i+1)  
2454 - else get_boundary_value_3(s,i+1,[c])  
2455 - }.  
2456 -  
2457 -define Maybe(String)  
2458 - get_boundary_value_1  
2459 - (  
2460 - String s, // string into which we must find '= ...'  
2461 - Int i // position of start of search  
2462 - ) =  
2463 - if nth(i,s) is  
2464 - {  
2465 - failure then failure,  
2466 - success(c) then  
2467 - if is_blank(c)  
2468 - then get_boundary_value_1(s,i+1)  
2469 - else if c = '='  
2470 - then get_boundary_value_2(s,i+1)  
2471 - else failure  
2472 - }.  
2473 -  
2474 -  
2475 -define Maybe(String)  
2476 - get_boundary  
2477 - (  
2478 - String content_type_header_value  
2479 - ) =  
2480 - if find("boundary",content_type_header_value,0) is  
2481 - {  
2482 - failure then failure,  
2483 - success(n) then // 'boundary' has been found at position n  
2484 - get_boundary_value_1(content_type_header_value,n+8)  
2485 - }.  
2486 -  
2487 -define Maybe(String)  
2488 - get_boundary  
2489 - (  
2490 - List(HTTP_header) headers  
2491 - ) =  
2492 - if headers is  
2493 - {  
2494 - [ ] then failure,  
2495 - [h . t] then if h is http_header(name,value) then  
2496 - if name = "content-type"  
2497 - then get_boundary(value)  
2498 - else get_boundary(t)  
2499 - }.  
2500 -  
2501 -  
2502 -  
2503 -  
2504 -  
2505 -  
2506 -  
2507 -  
2508 - *** [5.7.2] Reading attributes from a multipart entity.  
2509 -  
2510 - Entities in a multipart/form-data body are separated by instances of the string:  
2511 -  
2512 - --bbbbb  
2513 -  
2514 - where bbbbb is the boundary computed above. Actually, the body has the form:  
2515 -  
2516 - --bbbbb  
2517 - <entity 1>  
2518 - --bbbbb  
2519 - <entity 2>  
2520 - --bbbbb  
2521 - ...  
2522 - --bbbbb  
2523 - <last entity>  
2524 - --bbbbb  
2525 -  
2526 -  
2527 - We have to extract an entity which is in the body between offsets 'start' and 'end'  
2528 - (computed when boundaries have been localized). The entity itself is made of two parts:  
2529 - headers and body. The body is separated from the headers by a blank line. This blank  
2530 - line (a double crlf) marks the beginning of the body of the entity. Within the headers  
2531 - of the entity, we look for a 'Content-Disposition' header, which should look like this:  
2532 -  
2533 - Content-Disposition: form-data; name="..."; filename="..." crlf  
2534 -  
2535 - We are just interested in the name and the file name. Hence we first search  
2536 - 'Content-Disposition', then we search 'name' and read the value, and we do the same for  
2537 - 'filename'.  
2538 -  
2539 - If the 'filename' attribute is not present, the web arg is an ordinary one, otherwise,  
2540 - it is an uploaded file.  
2541 -  
2542 -  
2543 - Below is a variant of 'find' (see 'tools/findstring.anubis'), with an extra 'end'  
2544 - argument.  
2545 -  
2546 -define Maybe(Int)  
2547 - find  
2548 - (  
2549 - String what,  
2550 - ByteArray where,  
2551 - Int start,  
2552 - Int end  
2553 - ) =  
2554 - if find(to_byte_array(what),where,start) is  
2555 - {  
2556 - failure then failure,  
2557 - success(n) then  
2558 - if n+length(what) >= end  
2559 - then failure  
2560 - else success(n)  
2561 - }.  
2562 -  
2563 -  
2564 -define String  
2565 - read_attribute_value  
2566 - (  
2567 - ByteArray where,  
2568 - Int start,  
2569 - Int end,  
2570 - List(Word8) so_far  
2571 - ) =  
2572 - if start >= end then implode(reverse(so_far)) else  
2573 - if nth(start,where) is  
2574 - {  
2575 - failure then implode(reverse(so_far)),  
2576 - success(c) then  
2577 - if c = '\"'  
2578 - then implode(reverse(so_far))  
2579 - else read_attribute_value(where,start+1,end,[c . so_far])  
2580 - }.  
2581 -  
2582 -define Maybe(String)  
2583 - find_attribute  
2584 - (  
2585 - String name,  
2586 - ByteArray where,  
2587 - Int start,  
2588 - Int end  
2589 - ) =  
2590 - with prefix = name+"=\"",  
2591 - if find(to_byte_array(prefix),where,start) is  
2592 - {  
2593 - failure then failure,  
2594 - success(n) then  
2595 - if n+length(prefix) >= end  
2596 - then failure  
2597 - else success(read_attribute_value(where,n+length(prefix),end,[]))  
2598 - }.  
2599 -  
2600 -  
2601 -  
2602 -define Maybe((String,Maybe(String)))  
2603 - find_name_and_filename  
2604 - (  
2605 - ByteArray body,  
2606 - Int start,  
2607 - Int end  
2608 - ) =  
2609 - if find(to_byte_array("Content-Disposition"),body,start) is  
2610 - {  
2611 - failure then failure,  
2612 - success(n) then  
2613 - if find_attribute("name",body,n+19,end) is  
2614 - {  
2615 - failure then failure,  
2616 - success(name_value) then if find_attribute("filename",body,n+19,end) is  
2617 - {  
2618 - failure then success((name_value,failure)),  
2619 - success(filename_value) then success((name_value,success(filename_value)))  
2620 - }  
2621 - }  
2622 - }.  
2623 -  
2624 -  
2625 -  
2626 -  
2627 -  
2628 -  
2629 -  
2630 -  
2631 -  
2632 -  
2633 - *** [5.7.3] Creating a temporary filename for an uploaded file.  
2634 -  
2635 -variable Int uploaded_file_count = 0.  
2636 -  
2637 - This variable is local to the virtual machine. Hence, its value is 0 each time a new  
2638 - requests arrives. Temporary uploaded files are stored in the directory represented by  
2639 - 'upload_temporary_directory'. The filenames have the form:  
2640 -  
2641 - _m_n  
2642 -  
2643 - where 'm' is the number of the virtual machine, and 'n' a number obtained by  
2644 - incrementing 'uploaded_file_count'. Notice that the program must do something with this  
2645 - file (move it to some directory/name), otherwise, it will probably be overwritten the  
2646 - next time the same machine works.  
2647 -  
2648 -  
2649 -  
2650 -  
2651 -  
2652 -  
2653 - *** [5.7.4] Saving an uploaded file under a temporary filename.  
2654 -  
2655 -define Maybe(String) // returns the temporary file name  
2656 - save_uploaded_file  
2657 - (  
2658 - Web_Site_Description desc,  
2659 - ByteArray body,  
2660 - Int start,  
2661 - Int end  
2662 - ) =  
2663 - uploaded_file_count <- 1 + *uploaded_file_count;  
2664 - with tfn = "_"+to_decimal(virtual_machine_id)+"_"+to_decimal(*uploaded_file_count),  
2665 - if (Maybe(WStream))connect to file site_directory(desc)+"/upload_temporary/"+tfn is  
2666 - {  
2667 - failure then failure,  
2668 - success(f) then  
2669 - if reliable_write(file(f),extract(body,start,end)) is  
2670 - {  
2671 - failure then failure,  
2672 - success(nw) then  
2673 - if nw = end - start  
2674 - then success(tfn)  
2675 - else failure  
2676 - }  
2677 - }.  
2678 -  
2679 -  
2680 -  
2681 -  
2682 -  
2683 -  
2684 -  
2685 -  
2686 - *** [5.7.5] Removing the path from a file name.  
2687 -  
2688 - When a file is uploaded, the browser sends the complete path of the file on the client  
2689 - machine as the file name. Actually, this is not quite normal. Nevertheless, we need to  
2690 - remove the path, and keep only the file name. This is achieved by 'remove_path' below.  
2691 -  
2692 -define Int  
2693 - file_name_begin  
2694 - (  
2695 - String full_name,  
2696 - Int i  
2697 - ) =  
2698 - if nth(i,full_name) is  
2699 - {  
2700 - failure then 0,  
2701 - success(c) then  
2702 - if c = '/' then i+1 else  
2703 - if c = '\\' then i+1 else  
2704 - file_name_begin(full_name,i-1)  
2705 - }.  
2706 -  
2707 -define String  
2708 - remove_path  
2709 - (  
2710 - String full_name  
2711 - ) =  
2712 - with l = length(full_name),  
2713 - b = file_name_begin(full_name,l-1),  
2714 - substr(full_name,b,l-b).  
2715 -  
2716 -  
2717 -  
2718 -  
2719 -  
2720 - *** [5.7.6] Reading a multipart entity.  
2721 -  
2722 -define Maybe(Web_arg)  
2723 - get_multipart_entity  
2724 - (  
2725 - Web_Site_Description desc,  
2726 - ByteArray body,  
2727 - Int start,  
2728 - Int end  
2729 - ) =  
2730 - if find(to_byte_array(crlf+crlf),body,start) is  
2731 - {  
2732 - failure then failure,  
2733 - success(k) then  
2734 - if k >= end // must be within this entity, not the next one  
2735 - then failure  
2736 - else if find_name_and_filename(body,start,k) is  
2737 - {  
2738 - failure then failure,  
2739 - success(n_mbfn) then if n_mbfn is (name,mbfn) then  
2740 - if mbfn is  
2741 - {  
2742 - failure then  
2743 - success(web_arg(name,to_string(extract(body,k+4,end-2)))),  
2744 - // we must substract 2 to end because of crlf just before the boundary  
2745 -  
2746 - success(fn) then  
2747 - if save_uploaded_file(desc,body,k+4,end-2) is  
2748 - {  
2749 - failure then failure,  
2750 - success(tfn) then  
2751 - success(upload(name,remove_path(fn),  
2752 - site_directory(desc)+"/upload_temporary/"+tfn))  
2753 -  
2754 - }  
2755 - }  
2756 - }  
2757 - }.  
2758 -  
2759 -  
2760 -  
2761 -define List(Web_arg)  
2762 - read_multipart_form_data_encoded_web_args  
2763 - (  
2764 - Web_Site_Description desc,  
2765 - ByteArray body,  
2766 - ByteArray __boundary,  
2767 - Int i,  
2768 - ) =  
2769 - if find(__boundary,body,i) is  
2770 - {  
2771 - failure then [ ],  
2772 - success(n) then  
2773 - if find(__boundary,body,n+length(__boundary)) is  
2774 - {  
2775 - failure then [ ],  
2776 - success(m) then  
2777 - if get_multipart_entity(desc,body,n+length(__boundary),m) is  
2778 - {  
2779 - failure then [ ],  
2780 - success(wa) then  
2781 - [wa . read_multipart_form_data_encoded_web_args(desc,body,__boundary,m)]  
2782 - }  
2783 - }  
2784 - }.  
2785 -  
2786 -  
2787 -  
2788 -define One  
2789 - multipart_form_data_answer  
2790 - (  
2791 - String host_name,  
2792 - Web_Site_Description desc,  
2793 - Connection connection,  
2794 - Word32 ip_addr,  
2795 - HTTP_RequestLine request_line,  
2796 - List(HTTP_header) headers,  
2797 - ByteArray body,  
2798 - One -> String generate_tt  
2799 - ) =  
2800 - if get_boundary(headers) is  
2801 - {  
2802 - failure then unique,  
2803 - success(boundary) then  
2804 - with all_web_args = query_string(request_line) +  
2805 - read_multipart_form_data_encoded_web_args(desc,  
2806 - body,  
2807 - to_byte_array("--"+boundary),  
2808 - 0),  
2809 - uri = uri(request_line),  
2810 - ext = get_uri_extension(uri),  
2811 - log_journal_msg(desc,  
2812 - format_request(desc,connection,request_line,headers,all_web_args));  
2813 - if is_illegal_uri(uri,0)  
2814 - then log_journal_msg(desc,"Received illegal URI: "+uri+"\n")  
2815 - else  
2816 - if (ext = ".awp" | ext = "") then  
2817 - (with answer_headers_body = awp_handler(desc)(host_name,  
2818 - http_info(ip_addr, host_name, uri, headers, is_SSL(connection), generate_tt),  
2819 - all_web_args,  
2820 - is_SSL(connection)),  
2821 - forget(reliable_write(connection, answer_headers_body)))  
2822 - else unique  
2823 - }.  
2824 -  
2825 -  
2826 -  
2827 -  
2828 -  
2829 -  
2830 -  
2831 -  
2832 - *** [5.8] Handling redirections.  
2833 -  
2834 - 'redirections' (of type 'List(Redirection)') contains redirection directives. Each one  
2835 - has the form:  
2836 -  
2837 - redirect(required_uri,required_host,corresponding_uri).  
2838 -  
2839 - The host required by the client may be found in the 'Host' HTTP header. The URI  
2840 - required by the client is given below as 'uri'. We just have to find the required host  
2841 - in the headers, and to find the corresponding redirection directive.  
2842 -  
2843 -  
2844 - In the next fonction, the required host and URI are known. We just have to search in  
2845 - the 'redirections' list.  
2846 -  
2847 -define String  
2848 - handle_redirection  
2849 - (  
2850 - String required_uri,  
2851 - String required_host,  
2852 - List(Redirection) redirections  
2853 - ) =  
2854 - if redirections is  
2855 - {  
2856 - [ ] then required_uri,  
2857 - [h . t] then if h is redirect(uri,host,target) then  
2858 - if host = required_host  
2859 - then if uri = required_uri  
2860 - then target  
2861 - else handle_redirection(required_uri,required_host,t)  
2862 - else handle_redirection(required_uri,required_host,t)  
2863 - }.  
2864 -  
2865 -  
2866 -  
2867 - The host name may be encumbered by a port number, like  
2868 -  
2869 - www.our-business.com:1607  
2870 -  
2871 - We must remove this port number, otherwise the host name may not be recognized.  
2872 -  
2873 -define String  
2874 - strip_port  
2875 - (  
2876 - String name,  
2877 - Int i  
2878 - ) =  
2879 - if nth(i,name) is  
2880 - {  
2881 - failure then name,  
2882 - success(c) then  
2883 - if c = ':'  
2884 - then substr(name,0,i)  
2885 - else strip_port(name,i+1)  
2886 - }.  
2887 -  
2888 -  
2889 -  
2890 -  
2891 -  
2892 - Finding the 'Host' header. No redirection is performed if this header is not found.  
2893 -  
2894 -define String  
2895 - handle_redirection // returns the redirected URI  
2896 - (  
2897 - Redirections redirections,  
2898 - String uri, // original URI  
2899 - List(HTTP_header) headers  
2900 - )=  
2901 - if get_host_header_value(headers) is  
2902 - {  
2903 - failure then uri,  
2904 - success(host) then  
2905 - if redirections is  
2906 - {  
2907 - redirection_list(l) then handle_redirection(uri, host, l)  
2908 - redirection_fn(f) then f(uri, host)  
2909 - }  
2910 - }.  
2911 -  
2912 -  
2913 -  
2914 -  
2915 -  
2916 -  
2917 - *** [5.9] Answering both sorts of requests.  
2918 -  
2919 - We must decide if the request is www-url encoded or multipart/form-data encoded. This  
2920 - is achieved through the header 'Content-Type'.  
2921 -  
2922 -define EncodingType  
2923 - get_encoding_type  
2924 - (  
2925 - List(HTTP_header) headers  
2926 - ) =  
2927 - if headers is  
2928 - {  
2929 - [ ] then www_url, // this is the default  
2930 - [h . t] then if h is http_header(name,value) then  
2931 - if name = "content-type"  
2932 - then if find("multipart/form-data",value,0) is  
2933 - {  
2934 - failure then www_url,  
2935 - success(_) then multipart_form_data  
2936 - }  
2937 - else get_encoding_type(t)  
2938 - }.  
2939 -  
2940 -  
2941 -  
2942 -define One  
2943 - send_answer  
2944 - (  
2945 - String host_name,  
2946 - Web_Site_Description desc,  
2947 - Connection connection,  
2948 - HTTP_RequestLine rqline,  
2949 - List(HTTP_header) headers,  
2950 - ByteArray body,  
2951 - One -> String generate_tt  
2952 - ) =  
2953 - if rqline is request_line(type,uri,qstring) then  
2954 - with rqline2 = request_line(type,handle_redirection(redirections(desc),uri,headers),qstring),  
2955 - if remote_IP_address_and_port(connection) is (ip_addr,_) then  
2956 - if get_encoding_type(headers) is  
2957 - {  
2958 - www_url then  
2959 - www_url_answer(host_name,desc,connection,ip_addr,rqline2,headers,body,generate_tt),  
2960 - multipart_form_data then  
2961 - multipart_form_data_answer(host_name,desc,connection,ip_addr,rqline2,headers,body,generate_tt)  
2962 - }.  
2963 -  
2964 -  
2965 -  
2966 -  
2967 -  
2968 -  
2969 -  
2970 - *** [6] The HTTP/HTTPS server.  
2971 -  
2972 - The command 'start_server' (declared in 'predefined.anubis') starts a virtual machine  
2973 - which opens a server TCP/IP connection, and which continuously listens to this  
2974 - connection. When a request arrives, this machine delegates the work of deciphering and  
2975 - answering the request to another virtual machine, and continues to listen. The job of  
2976 - the delegated machine is defined by the HTTP request handler below.  
2977 -  
2978 -  
2979 -  
2980 -  
2981 -  
2982 - *** [6.1] Determining the requested host.  
2983 -  
2984 - When a request arrives to one of our two servers, we must decide which site (host) is  
2985 - requested.  
2986 -  
2987 -define Maybe(String)  
2988 - get_host_header_value  
2989 - (  
2990 - List(HTTP_header) headers  
2991 - ) =  
2992 - if headers is  
2993 - {  
2994 - [ ] then failure,  
2995 - [h . t] then if h is http_header(name,value) then  
2996 - if name = "host"  
2997 - then success(strip_port(value,0))  
2998 - else get_host_header_value(t)  
2999 - }.  
3000 -  
3001 -define Maybe((String,Web_Site_Description))  
3002 - get_site  
3003 - (  
3004 - String requested_host,  
3005 - List(Web_Site_Description) sites  
3006 - ) =  
3007 - if sites is  
3008 - {  
3009 - [ ] then print("Requested host '"+requested_host+"' does not exist.\n"); failure,  
3010 - [site1 . others] then  
3011 - if site1 is web_site_description(common_names,_,_,_,_,_,_,_,_,_) then  
3012 - if member(common_names,requested_host)  
3013 - then success((requested_host,site1))  
3014 - else get_site(requested_host,others)  
3015 - }.  
3016 -  
3017 -  
3018 -define Maybe((String,Web_Site_Description))  
3019 - get_site  
3020 - (  
3021 - List(HTTP_header) headers,  
3022 - List(Web_Site_Description) sites  
3023 - ) =  
3024 - if get_host_header_value(headers) is  
3025 - {  
3026 - failure then print("No 'Host' HTTP header.\n"); failure,  
3027 - success(requested_host) then  
3028 - //here we treat the case with only one site. hence we accept any host request  
3029 - //print("*** there is " +length(sites) + " sites \n");  
3030 - if length(sites) = 1 then  
3031 - //with site = force_nth(0, sites),  
3032 - if sites is  
3033 - {  
3034 - [] then get_site(requested_host,sites),  
3035 - [site . t] then success((requested_host, site))  
3036 - }  
3037 - else  
3038 - get_site(requested_host,sites)  
3039 - }.  
3040 -  
3041 -  
3042 -  
3043 -  
3044 -  
3045 - *** [6.2] The HTTP request handler.  
3046 -  
3047 - Here is the HTTP/HTTPS handler. It is called at each new request in a separate virtual  
3048 - machine. It reads the headers of the HTTP request, determines the host, determines body  
3049 - size, reads the body of the HTTP request, and answers the request.  
3050 -  
3051 -  
3052 -  
3053 -define One -> String make_generate_trust_ticket(DenialOfService dos).  
3054 -  
3055 -define One  
3056 - http_https_handler  
3057 - (  
3058 - List(Web_Site_Description) sites,  
3059 - BufferedConnection connection,  
3060 - Bool is_https,  
3061 - DenialOfService dos  
3062 - ) =  
3063 - //t0 <- (UTime)unow;  
3064 - with start_time = (Int)now,  
3065 - sttm <- start_time;  
3066 - //println("Request time: " + format_http_date(start_time));  
3067 - if dos is denial_of_service(mc_v,rld_v,hd_v,ad_v,ld_v,ra_v) then  
3068 - if remote_IP_address_and_port(connection.conn) is (ip_addr,port) then  
3069 - if read_request_line(connection,start_time+*rld_v,dos) is  
3070 - {  
3071 - error(msg) then print(format(msg)),  
3072 - ok(request_line) then  
3073 - //print_delta("read_request_line");  
3074 - if read_http_headers(connection,start_time+*hd_v,dos) is  
3075 - {  
3076 - error(msg) then print(format(msg)),  
3077 - ok(headers) then //print_delta("read_http_headers");  
3078 - if get_site(headers,sites) is  
3079 - {  
3080 - failure then unique,  
3081 - success(p) then if p is (host_name,desc) then  
3082 - //print_delta("get_site");  
3083 - if get_body_size(headers) is  
3084 - {  
3085 - error(msg) then log_journal_msg(desc,format(msg)),  
3086 - ok(body_size) then  
3087 - //print_delta("get_body_size");  
3088 - if read_http_body(connection,body_size,constant_byte_array(0,0),1000) is  
3089 - {  
3090 - error(msg) then log_journal_msg(desc,format(msg)),  
3091 - ok(body) then  
3092 - //print_delta("before send_answer");  
3093 - send_answer(host_name, desc,connection.conn, request_line, headers, body,  
3094 - make_generate_trust_ticket(dos))  
3095 - //with duration = (UTime) unow - *t0,  
3096 - //println("Request duration: " + __utime_to_string(duration))  
3097 - //println("BufferRead duration: " + __utime_to_string(*t1));  
3098 - //println("next_char duration: " + __utime_to_string(*t2))  
3099 - }  
3100 - }  
3101 - }  
3102 - }  
3103 - }.  
3104 -  
3105 -  
3106 - Below are the two tools for constructing the handlers required by 'start_server' and  
3107 - 'start_ssl_server' (see 'predefined.anubis').  
3108 -  
3109 -define Bool is_dubious_IP(Word32 ip, DenialOfService dos).  
3110 -  
3111 -define Server -> ((RWStream) -> One)  
3112 - make_http_handler  
3113 - (  
3114 - List(Web_Site_Description) sites,  
3115 - DenialOfService dos  
3116 - ) =  
3117 - (Server server) |-> (RWStream conn) |->  
3118 - if remote_IP_address_and_port(conn) is (addr,_) then  
3119 - if is_dubious_IP(addr,dos)  
3120 - then print("Rejecting dubious IP address "+ip_addr_to_string(addr)+"\n")  
3121 - else  
3122 - with connection = buffered_connection(tcp(conn), var(constant_byte_array(0, 0)), var(0)),  
3123 - http_https_handler(sites, connection, false, dos).  
3124 -  
3125 -define Server -> (SSL_Connection -> One)  
3126 - make_https_handler  
3127 - (  
3128 - List(Web_Site_Description) sites,  
3129 - DenialOfService dos  
3130 - ) =  
3131 - (Server server) |-> (SSL_Connection conn) |->  
3132 - with connection = buffered_connection(ssl(conn), var(constant_byte_array(0, 0)), var(0)),  
3133 - http_https_handler(sites, connection, true, dos).  
3134 -  
3135 -  
3136 -  
3137 -  
3138 - *** [6.3] Server's tasks.  
3139 -  
3140 - Some tasks must be executed periodically, for example for cleaning up directories from  
3141 - short life time files.  
3142 -  
3143 - The next function removes from the given directory (and recursively from its  
3144 - subdirectories) all the files which are more than 10 minutes old.  
3145 -  
3146 -define One  
3147 - cleanup_directory_10mn  
3148 - (  
3149 - String dir // path of private download directory (or subdirectory) with trailing slash  
3150 - ) =  
3151 - forget(map((FileDescription fd) |-> if fd is  
3152 - {  
3153 - no_info(name) then forget(remove(dir+name)),  
3154 - file(name,_,_,d) then if to_Int(d)+600 < now then forget(remove(dir+name)) else unique,  
3155 - link(name,_,_,d) then if to_Int(d)+600 < now then forget(remove(dir+name)) else unique,  
3156 - directory(name,_,_) then cleanup_directory_10mn(dir+name+"/"),  
3157 - },  
3158 - directory_full_list(dir,"*","*","*"))).  
3159 -  
3160 -  
3161 -define One  
3162 - http_servers_tasks  
3163 - (  
3164 - List(Web_Site_Description) sites,  
3165 - List(Server) servers,  
3166 - Int period,  
3167 - Int next_time,  
3168 - ) =  
3169 - if mapand(is_down,servers)  
3170 - then unique  
3171 - else if now > next_time  
3172 - then  
3173 - (  
3174 - /*  
3175 - forget(map((Web_Site_Description wsd) |->  
3176 - cleanup_directory_10mn(site_directory(wsd)+"/private_download/"),  
3177 - sites));  
3178 - */  
3179 - http_servers_tasks(sites,servers,period,next_time+period)  
3180 - )  
3181 - else  
3182 - (  
3183 - sleep(1000);  
3184 - http_servers_tasks(sites,servers,period,next_time)  
3185 - ).  
3186 -  
3187 -  
3188 -public define One  
3189 - start_http_servers_tasks  
3190 - (  
3191 - List(Web_Site_Description) sites,  
3192 - List(Server) servers,  
3193 - Int period  
3194 - ) =  
3195 - delegate http_servers_tasks(sites,servers,period,now),  
3196 - unique.  
3197 -  
3198 -  
3199 -  
3200 -  
3201 - *** [6.4] Protection against 'denial of service' attacks.  
3202 -  
3203 -  
3204 - *** [6.4.1] Counting connections.  
3205 -  
3206 -define Bool // returns false if the counter cannot be incremented (too many connections)  
3207 - increment_connections_counter  
3208 - (  
3209 - Var(Int) counter  
3210 - ) =  
3211 - protect with n = *counter,  
3212 - if n >= 100  
3213 - then false  
3214 - else (counter <- (*counter)+1); true.  
3215 -  
3216 -define One  
3217 - decrement_connections_counter  
3218 - (  
3219 - Var(Int) counter  
3220 - ) =  
3221 - protect counter <- (*counter)-1.  
3222 -  
3223 -  
3224 -  
3225 -  
3226 -  
3227 - *** [6.4.2] Recording dubious IP addresses.  
3228 -  
3229 -  
3230 -define List(DubiousIP)  
3231 - record_dubious_IP  
3232 - (  
3233 - Word32 ip,  
3234 - List(DubiousIP) l  
3235 - ) =  
3236 - if l is  
3237 - {  
3238 - [ ] then [dubious_ip(ip,now)],  
3239 - [h . t] then if h is dubious_ip(addr,time) then  
3240 - if addr = ip  
3241 - then [dubious_ip(addr,now) . t]  
3242 - else [h . record_dubious_IP(ip,t)]  
3243 - }.  
3244 -  
3245 -  
3246 -define One  
3247 - record_dubious_IP  
3248 - (  
3249 - Word32 dubious_IP,  
3250 - Var(List(DubiousIP)) v  
3251 - ) =  
3252 - protect v <- record_dubious_IP(dubious_IP,*v).  
3253 -  
3254 -  
3255 -define One  
3256 - record_dubious_IP  
3257 - (  
3258 - Word32 addr,  
3259 - DenialOfService dos  
3260 - ) =  
3261 - record_dubious_IP(addr,list_of_dubious(dos)).  
3262 -  
3263 -  
3264 -public define DenialOfService  
3265 - load_denial_of_service_info  
3266 - =  
3267 - if (RetrieveResult(DenialOfService))retrieve(my_anubis_directory+"/web_sites/dos_info") is  
3268 - ok(dos) then dos else denial_of_service(  
3269 - var(100),  
3270 - var(1000),  
3271 - var(1500),  
3272 - var(2000),  
3273 - var([]),  
3274 - var([])).  
3275 -  
3276 -  
3277 -  
3278 -  
3279 - *** [6.4.3] Testing if an address is dubious.  
3280 -  
3281 -define Bool  
3282 - is_dubious_IP  
3283 - (  
3284 - Word32 ip,  
3285 - List(DubiousIP) l  
3286 - ) =  
3287 - if l is  
3288 - {  
3289 - [ ] then false,  
3290 - [h . t] then if h is dubious_ip(addr,time) then  
3291 - if ip = addr  
3292 - then true  
3293 - else is_dubious_IP(ip,t)  
3294 - }.  
3295 -  
3296 -  
3297 -define Bool  
3298 - is_dubious_IP  
3299 - (  
3300 - Word32 ip,  
3301 - DenialOfService dos  
3302 - ) =  
3303 - if dos is  
3304 - {  
3305 - denial_of_service(mc_v,rld_v,hd_v,ad_v,ld_v,ra_v) then  
3306 - if member(*ra_v,ip) then false else  
3307 - is_dubious_IP(ip,*ld_v)  
3308 - }.  
3309 -  
3310 -  
3311 -  
3312 -  
3313 - *** [6.4.4] Removing inactive dubious IP addresses.  
3314 -  
3315 -define List(DubiousIP)  
3316 - remove_inactive_dubious_IP  
3317 - (  
3318 - List(DubiousIP) l,  
3319 - Int ref_time,  
3320 - ) =  
3321 - if l is  
3322 - {  
3323 - [ ] then [ ],  
3324 - [h . t] then if h is dubious_ip(addr,time) then  
3325 - if time < ref_time  
3326 - then (print(ip_addr_to_string(addr)+" removed from dubious addresses list.\n");  
3327 - remove_inactive_dubious_IP(t,ref_time))  
3328 - else [h . remove_inactive_dubious_IP(t,ref_time)]  
3329 - }.  
3330 -  
3331 -define One  
3332 - remove_inactive_dubious_IP  
3333 - (  
3334 - Var(List(DubiousIP)) v  
3335 - ) =  
3336 - protect  
3337 - with ref_time = (Int)now - 600, // 10 minutes  
3338 - v <- remove_inactive_dubious_IP(*v,ref_time).  
3339 -  
3340 -  
3341 - The above function will be executed periodically by the servers's tasks machine.  
3342 -  
3343 -  
3344 -  
3345 - *** [6.4.5] Making the function for generating trust tickets.  
3346 -  
3347 -define One -> String  
3348 - make_generate_trust_ticket  
3349 - (  
3350 - DenialOfService dos  
3351 - ) =  
3352 - (One _) |-> "".  
3353 -  
3354 -  
3355 -  
3356 -  
3357 -  
3358 -  
3359 -  
3360 - *** [6.5] Starting the HTTP/HTTPS server.  
3361 -  
3362 -  
3363 - The next function creates the directories for all sites (if they don't already exist).  
3364 -  
3365 -define One  
3366 - create_directories  
3367 - (  
3368 - List(Web_Site_Description) sites  
3369 - ) =  
3370 - if sites is  
3371 - {  
3372 - [ ] then unique,  
3373 - [s1 . others] then  
3374 - with site_dir = site_directory(s1),  
3375 - forget(make_directory(site_dir+"/public",default_directory_mode));  
3376 - forget(make_directory(site_dir+"/upload_temporary",default_directory_mode));  
3377 - forget(make_directory(site_dir+"/private_download",default_directory_mode));  
3378 - forget(make_directory(site_dir+"/journal",default_directory_mode));  
3379 - create_directories(others)  
3380 - }.  
3381 -  
3382 -  
3383 -  
3384 -  
3385 -  
3386 - Below are the commands for starting an HTTP server and an HTTPS server.  
3387 -  
3388 -  
3389 -define StartServerResult  
3390 - start_http_server  
3391 - (  
3392 - Word32 ip_address,  
3393 - Word32 port,  
3394 - Server -> ((RWStream) -> One) handler,  
3395 - Int retries,  
3396 - DenialOfService dos  
3397 - ) =  
3398 - if start_server(ip_address,  
3399 - port,  
3400 - handler,  
3401 - identity) is ok(server)  
3402 - then print(" \r");  
3403 - ok(server)  
3404 - else print("Port "+port+": retry number "+retries+"\r");  
3405 - sleep(1000);  
3406 - start_http_server(ip_address,port,handler,retries+1,dos).  
3407 -  
3408 -public define StartServerResult  
3409 - start_http_server  
3410 - (  
3411 - Word32 ip_address,  
3412 - Word32 port,  
3413 - List(Web_Site_Description) sites,  
3414 - DenialOfService dos  
3415 - ) =  
3416 - create_directories(sites);  
3417 - start_http_server(ip_address,port,  
3418 - make_http_handler(sites,dos),  
3419 - 0,  
3420 - dos).  
3421 -  
3422 -  
3423 - For the HTTPS server, we have a problem which is due to the fact that 'anbexec' is not  
3424 - yet able to manipulate several SSL server certificates. 'anbexec' and  
3425 - 'predefined.anubis' must be changed. Sorry ! This will be done as soon as possible. The  
3426 - 'solution' for the time being is to provide the common name of the unique SSL server  
3427 - certificate.  
3428 -  
3429 -  
3430 -define StartServerResult  
3431 - start_https_server  
3432 - (  
3433 - Word32 ip_address,  
3434 - Word32 port,  
3435 - String certificate_common_name,  
3436 - Server -> (SSL_Connection -> One) handler,  
3437 - Int retries,  
3438 - DenialOfService dos  
3439 - ) =  
3440 - if start_ssl_server(ip_address,  
3441 - port,  
3442 - certificate_common_name,  
3443 - handler,  
3444 - identity) is ok(server)  
3445 - then print(" \r");  
3446 - ok(server)  
3447 - else print("Port "+port+": retry number "+retries+"\r");  
3448 - sleep(1000);  
3449 - start_https_server(ip_address,port,  
3450 - certificate_common_name,  
3451 - handler,retries+1,  
3452 - dos).  
3453 -  
3454 -  
3455 -public define StartServerResult  
3456 - start_https_server  
3457 - (  
3458 - Word32 ip_address,  
3459 - Word32 port,  
3460 - String certificate_common_name, // of SSL server certificate  
3461 - List(Web_Site_Description) sites,  
3462 - DenialOfService dos  
3463 - ) =  
3464 - create_directories(sites);  
3465 - start_https_server(ip_address,port,certificate_common_name,  
3466 - make_https_handler(sites,dos),  
3467 - 0,dos).  
3468 -  
3469 -  
3470 -  
3471 -  
3472 -  
3473 -  
3474 -  
3475 -  
3476 -  
3477 - *** [7] The web dispatcher.  
3478 -  
3479 -  
3480 - *** [7.1] The dispatcher server.  
3481 -  
3482 -define One  
3483 - send_dispatching_page  
3484 - (  
3485 - RWStream conn,  
3486 - String common_name,  
3487 - Word32 port  
3488 - ) =  
3489 - print("Dispatching '"+common_name+"' to port "+port+"\n");  
3490 - forget(reliable_write(conn,to_byte_array(  
3491 - "<html><head><meta http-equiv=\"Refresh\" content=\"0;URL="+  
3492 - "http://"+common_name+":"+port+"/"+  
3493 - "\"></head><body></body></html>"  
3494 - ))).  
3495 -  
3496 -  
3497 -  
3498 -define Maybe(DispatcherInfo)  
3499 - find_host  
3500 - (  
3501 - List(DispatcherInfo) l,  
3502 - String host  
3503 - ) =  
3504 - if l is  
3505 - {  
3506 - [ ] then failure,  
3507 - [h . t] then if h is site(name,port) then  
3508 - if name = host  
3509 - then success(h)  
3510 - else find_host(t,host)  
3511 - }.  
3512 -  
3513 -  
3514 -  
3515 -define Server -> ((RWStream) -> One)  
3516 - make_dispatcher_handler  
3517 - (  
3518 - Var(List(DispatcherInfo)) info_v,  
3519 - DenialOfService dos  
3520 - ) =  
3521 - (Server server) |-> (RWStream conn) |->  
3522 - with start_time = (Int)now,  
3523 - connection = buffered_connection(tcp(conn), var(constant_byte_array(0, 0)), var(0)),  
3524 - if read_request_line(connection, start_time+*request_line_delay(dos), dos) is  
3525 - {  
3526 - error(msg) then print(format(msg)),  
3527 - ok(request_line) then  
3528 - if read_http_headers(connection, start_time+*headers_delay(dos), dos) is  
3529 - {  
3530 - error(msg) then print(format(msg)),  
3531 - ok(headers) then if get_host_header_value(headers) is  
3532 - {  
3533 - failure then print("No 'HOST' HTTP header.\n"),  
3534 - success(host) then  
3535 - if find_host(*info_v,host) is  
3536 - {  
3537 - failure then print("Host: '"+host+"' not registered.\n"),  
3538 - success(s) then if s is site(common_name,ip_port) then  
3539 - send_dispatching_page(conn,common_name,ip_port)  
3540 - }  
3541 - }  
3542 - }  
3543 - }.  
3544 -  
3545 -  
3546 -define One  
3547 - dispatcher_update_error  
3548 - (  
3549 - String file_path  
3550 - ) =  
3551 - print("web_dispatcher: unable to reread file: '"+file_path+"'.\n").  
3552 -  
3553 -  
3554 -define Bool  
3555 - dispatcher_update_data  
3556 - (  
3557 - String info_file_path,  
3558 - Var(List(DispatcherInfo)) info_v,  
3559 - Var(Int) info_date_v  
3560 - ) =  
3561 - if directory_full_list(my_anubis_directory+"/web_sites","dispatcher.info","","") is  
3562 - {  
3563 - [ ] then false,  
3564 - [h . t] then if h is  
3565 - {  
3566 - no_info(n) then false,  
3567 - file(n,_,_,d) then if n = "dispatcher.info"  
3568 - then (info_date_v <- to_Int(d);  
3569 - if (RetrieveResult(List(DispatcherInfo)))retrieve(info_file_path) is  
3570 - {  
3571 - cannot_find_file then false,  
3572 - read_error then false,  
3573 - type_error then false,  
3574 - ok(info) then info_v <- info; true  
3575 - })  
3576 - else false,  
3577 - link(_,_,_,_) then false,  
3578 - directory(_,_,_) then false  
3579 - }  
3580 - }.  
3581 -  
3582 -  
3583 -  
3584 - The loop within which the dispatcher updates its data every 3 seconds:  
3585 -  
3586 -define One  
3587 - dispatcher_update_task  
3588 - (  
3589 - String info_file_path,  
3590 - Var(List(DispatcherInfo)) info_v,  
3591 - Var(Int) info_date_v  
3592 - ) =  
3593 - sleep(3000);  
3594 - (if dispatcher_update_data(info_file_path,info_v,info_date_v)  
3595 - then unique  
3596 - else dispatcher_update_error(info_file_path));  
3597 - dispatcher_update_task(info_file_path,info_v,info_date_v).  
3598 -  
3599 -  
3600 -public define One  
3601 - start_web_dispatcher  
3602 - (  
3603 - Word32 ip_address, // address for listening (typically 0: listen on all interfaces)  
3604 - Word32 http_port, // typically 80  
3605 - DenialOfService dos  
3606 - ) =  
3607 - with info_file_path = my_anubis_directory+"/web_sites/dispatcher.info",  
3608 - info_v = var((List(DispatcherInfo))[]),  
3609 - info_date_v = var((Int)0),  
3610 - if dispatcher_update_data(info_file_path,info_v,info_date_v)  
3611 - then if start_server(ip_address,  
3612 - http_port,  
3613 - make_dispatcher_handler(info_v,dos),  
3614 - (One u)|->u) is  
3615 - {  
3616 - cannot_create_the_socket then  
3617 - print("Cannot create the socket for HTTP server.\n"),  
3618 - cannot_bind_to_port then  
3619 - print("Cannot bind HTTP server to port "+http_port+".\n"),  
3620 - cannot_listen_on_port then  
3621 - print("HTTP server cannot listen on port "+http_port+".\n"),  
3622 - ok(http_server) then  
3623 - dispatcher_update_task(info_file_path,info_v,info_date_v)  
3624 - }  
3625 - else dispatcher_update_error(info_file_path).  
3626 -  
3627 -  
3628 -  
3629 - *** [7.2] The dispatcher web site.  
3630 -  
3631 - global define One  
3632 - web_dispatcher  
3633 - (  
3634 - List(String) args  
3635 - ) =  
3636 - start_web_dispatcher(0,80,load_denial_of_service_info).  
3637 -  
3638 -  
3639 -  
3640 -  
3641 -  
3642 -  
3643 - *** [7.3] Managing the info file.  
3644 -  
3645 -define Word32  
3646 - register_ip_address  
3647 - =  
3648 - if ip_address(prompt(" numerical IP address (for HTTP): ")) is  
3649 - {  
3650 - failure then print(" *** Error: incorrect IP address.\n");  
3651 - register_ip_address,  
3652 - success(n) then n  
3653 - }.  
3654 -  
3655 -  
3656 -define Word32  
3657 - register_ip_port  
3658 - =  
3659 - if decimal_scan(prompt(" IP port (for HTTP): ")) is  
3660 - {  
3661 - failure then print(" *** Error: incorrect IP port.\n");  
3662 - register_ip_port,  
3663 - success(p) then if (0 =< p & p =< 65535)  
3664 - then truncate_to_Word32(p)  
3665 - else print(" *** Error: IP port out of bounds.\n");  
3666 - register_ip_port  
3667 - }.  
3668 -  
3669 -  
3670 -define One  
3671 - register_new_site  
3672 - (  
3673 - Var(List(DispatcherInfo)) info_v  
3674 - ) =  
3675 - print("\n");  
3676 - print(" Registering a new site:\n");  
3677 - with name = prompt(" Site name: "),  
3678 - with addr = register_ip_address,  
3679 - with port = register_ip_port,  
3680 - (protect info_v <- [site(name,port) . *info_v]);  
3681 - print(" Site "+name+" at "+ip_addr_to_string(addr)+":"+port+" added\n (but not saved to disk).\n").  
3682 -  
3683 -  
3684 -define List(DispatcherInfo)  
3685 - find_sites  
3686 - (  
3687 - List(DispatcherInfo) l,  
3688 - String name  
3689 - ) =  
3690 - if l is  
3691 - {  
3692 - [ ] then [ ],  
3693 - [h . t] then if h is site(n,_) then  
3694 - if find(name,n,0) is  
3695 - {  
3696 - failure then find_sites(t,name),  
3697 - success(_) then [h . find_sites(t,name)]  
3698 - }  
3699 - }.  
3700 -  
3701 -  
3702 -define String  
3703 - pad  
3704 - (  
3705 - String s,  
3706 - Int l  
3707 - ) =  
3708 - if length(s) >= l  
3709 - then s  
3710 - else s+constant_string(l-length(s),' ').  
3711 -  
3712 -  
3713 -  
3714 -define One  
3715 - show_sites_1  
3716 - (  
3717 - List(DispatcherInfo) l,  
3718 - Int i  
3719 - ) =  
3720 - if l is  
3721 - {  
3722 - [ ] then unique,  
3723 - [h . t] then if h is site(name,port) then  
3724 - print(" ["+i+"] "+pad(name,40)+" "+" "+port+"\n");  
3725 - show_sites_1(t,i+1)  
3726 - }.  
3727 -  
3728 -  
3729 -define One  
3730 - show_sites  
3731 - (  
3732 - List(DispatcherInfo) l,  
3733 - Int i  
3734 - ) =  
3735 - print(" Name Port\n");  
3736 - print(" --------------------------------------------------------\n");  
3737 - show_sites_1(l,i).  
3738 -  
3739 -define List(DispatcherInfo)  
3740 - replace_info  
3741 - (  
3742 - List(DispatcherInfo) l,  
3743 - String site_name,  
3744 - Word32 new_port  
3745 - ) =  
3746 - if l is  
3747 - {  
3748 - [ ] then print("ALERT: Empty list into replace_info() [" + __FILE__ + "]\n"); [],  
3749 - [h . t] then if h is site(n,_) then  
3750 - if n = site_name  
3751 - then [site(n,new_port) . t]  
3752 - else [h . replace_info(t,site_name,new_port)]  
3753 - }.  
3754 -  
3755 -define List(DispatcherInfo)  
3756 - delete_info  
3757 - (  
3758 - List(DispatcherInfo) l,  
3759 - String site_name,  
3760 - ) =  
3761 - if l is  
3762 - {  
3763 - [ ] then print("ALERT: Empty list into delete_info() [" + __FILE__ + "]\n"); [],  
3764 - [h . t] then if h is site(n,_) then  
3765 - if n = site_name  
3766 - then t  
3767 - else [h . delete_info(t,site_name)]  
3768 - }.  
3769 -  
3770 -  
3771 -define One  
3772 - update_site  
3773 - (  
3774 - Var(List(DispatcherInfo)) info_v,  
3775 - String site_name,  
3776 - Word32 old_port  
3777 - ) =  
3778 - print("\n");  
3779 - print(" Updating site '"+site_name+"': (currently: "+old_port+")\n");  
3780 - with new_port = register_ip_port,  
3781 - answer = prompt(" Update '"+site_name+"' as: "+new_port+" [Y/N] ? "),  
3782 - if (answer = "Y" | answer = "y")  
3783 - then info_v <- replace_info(*info_v,site_name,new_port)  
3784 - else unique.  
3785 -  
3786 -  
3787 -  
3788 -define Bool  
3789 - compare  
3790 - (  
3791 - DispatcherInfo d1,  
3792 - DispatcherInfo d2  
3793 - ) =  
3794 - if d1 is site(n1,_) then  
3795 - if d2 is site(n2,_) then  
3796 - string_less(n1,n2).  
3797 -  
3798 -  
3799 -  
3800 -define One  
3801 - update_site  
3802 - (  
3803 - Var(List(DispatcherInfo)) info_v  
3804 - ) =  
3805 - print("\n");  
3806 - with prefix = prompt(" Search for site to update: "),  
3807 - if find_sites(*info_v,prefix) is  
3808 - {  
3809 - [ ] then print(" No site found.\n");  
3810 - update_site(info_v),  
3811 - [h . t] then  
3812 - show_sites(qsort([h . t],compare),1);  
3813 - with i1 = prompt(" Choose a site to update [1/.../"+(length(t)+1)+"]: "),  
3814 - if decimal_scan(i1) is  
3815 - {  
3816 - failure then print(" *** Error: site number not recognized.\n");  
3817 - update_site(info_v),  
3818 - success(ii1) then if nth(ii1-1,*info_v) is  
3819 - {  
3820 - failure then print(" *** Error: site number "+i1+" does not exist.\n");  
3821 - update_site(info_v),  
3822 - success(site_info) then if site_info is site(name,old_port) then  
3823 - update_site(info_v,name,old_port)  
3824 - }  
3825 - }  
3826 - }.  
3827 -  
3828 -  
3829 -define One  
3830 - delete_site  
3831 - (  
3832 - Var(List(DispatcherInfo)) info_v,  
3833 - String site_name,  
3834 - Word32 old_port  
3835 - ) =  
3836 - print("\n");  
3837 - print(" Deleting site '"+site_name+"': (currently: "+old_port+")\n");  
3838 - with answer = prompt(" Are you sure you want to delete site: '"+site_name+"' [Y/N] ? "),  
3839 - if (answer = "Y" | answer = "y")  
3840 - then info_v <- delete_info(*info_v,site_name)  
3841 - else print(" Site '"+site_name+"' not deleted.\n").  
3842 -  
3843 -  
3844 -define One  
3845 - delete_site  
3846 - (  
3847 - Var(List(DispatcherInfo)) info_v  
3848 - ) =  
3849 - print("\n");  
3850 - with prefix = prompt(" Search for site to delete: "),  
3851 - if find_sites(*info_v,prefix) is  
3852 - {  
3853 - [ ] then print(" No site found.\n");  
3854 - delete_site(info_v),  
3855 - [h . t] then  
3856 - show_sites(qsort([h . t],compare),1);  
3857 - with i1 = prompt(" Choose a site to delete [1/.../"+(length(t)+1)+"]: "),  
3858 - if decimal_scan(i1) is  
3859 - {  
3860 - failure then print(" *** Error: site number not recognized.\n");  
3861 - delete_site(info_v),  
3862 - success(ii1) then if nth(ii1-1,*info_v) is  
3863 - {  
3864 - failure then print(" *** Error: site number "+i1+" does not exist.\n");  
3865 - delete_site(info_v),  
3866 - success(site_info) then if site_info is site(name,old_port) then  
3867 - delete_site(info_v,name,old_port)  
3868 - }  
3869 - }  
3870 - }.  
3871 -  
3872 -  
3873 -define One  
3874 - manager  
3875 - (  
3876 - Var(List(DispatcherInfo)) info_v,  
3877 - String file_path  
3878 - ) =  
3879 - print("\n");  
3880 - print(" --- Welcome to the Web Dispatcher Manager ---\n");  
3881 - with l = length(*info_v),  
3882 - print(" "+l+" site"+(if l>1 then "s" else "")+" currently registred.\n");  
3883 - print(" [L] List registered sites.\n");  
3884 - print(" [R] Register a new site.\n");  
3885 - print(" [U] Update a registred site.\n");  
3886 - print(" [D] Delete a registred site.\n");  
3887 - with propose_write_v = var((Bool)true),  
3888 - action = prompt(" Choose an action [L/R/U/D]: "),  
3889 - (if (action = "L" | action = "l") then (show_sites(*info_v,1); propose_write_v <- false) else  
3890 - if (action = "R" | action = "r") then register_new_site(info_v) else  
3891 - if (action = "U" | action = "u") then update_site(info_v) else  
3892 - if (action = "D" | action = "d") then delete_site(info_v) else  
3893 - print("Action not recognized.\n"));  
3894 - print("\n");  
3895 - if *propose_write_v then  
3896 - with result = prompt(" Write modifications to data base [Y/N] ?"),  
3897 - if (result = "Y" | result = "y")  
3898 - then if save(*info_v,file_path) is  
3899 - {  
3900 - cannot_open_file then print(" File '"+file_path+"' not found.\n"),  
3901 - write_error then print(" Error while writing file '"+file_path+"'.\n"),  
3902 - ok then print(" Data base has been modified.\n")  
3903 - }  
3904 - else print(" Data base not modified.\n")  
3905 - else unique.  
3906 -  
3907 -  
3908 -  
3909 -global define One  
3910 - manage_web_dispatcher  
3911 - (  
3912 - List(String) args  
3913 - ) =  
3914 - with info_v = var((List(DispatcherInfo))[]),  
3915 - with file_path = my_anubis_directory+"/web_sites/dispatcher.info",  
3916 - if (RetrieveResult(List(DispatcherInfo)))retrieve(file_path) is  
3917 - {  
3918 - cannot_find_file then print("File '"+file_path+"' does not exist.\n");  
3919 - with answer = prompt("Create it [Y/N] ? "),  
3920 - if (answer = "Y" | answer = "y")  
3921 - then if save((List(DispatcherInfo))[],file_path) is  
3922 - {  
3923 - cannot_open_file then  
3924 - print("Cannot create file '"+file_path+"'.\n"),  
3925 - write_error then  
3926 - print("Error while creating file '"+file_path+"'.\n"),  
3927 - ok then manager(info_v,file_path)  
3928 - }  
3929 - else unique,  
3930 - read_error then print("Error while reading file '"+file_path+"'.\n"),  
3931 - type_error then print("File '"+file_path+"' is corrupted.\n"),  
3932 - ok(info) then info_v <- info;  
3933 - manager(info_v,file_path)  
3934 - }.  
3935 -  
3936 -  
3937 -  
3938 -  
3939 -  
3940 -public define (String, List(HTTP_header))  
3941 - format  
3942 - (  
3943 - HTTP_Status status  
3944 - ) =  
3945 - if status is  
3946 - {  
3947 - http_continue then ("100 Continue", []),  
3948 - http_switching_protocol then ("101 Switching Protocols", []),  
3949 -  
3950 - http_ok then ("200 OK", []),  
3951 - http_created then ("201 Created", []),  
3952 - http_accepted then ("202 Accepted", []),  
3953 - http_non_authoritative_info then ("203 Non-Authoritative Information", []),  
3954 - http_no_content then ("204 No Content", []),  
3955 - http_reset_content then ("205 Reset Content", []),  
3956 - http_partial_content then ("206 Partial Content", []),  
3957 -  
3958 - http_multiple_choices then ("300 Multiple Choices", []),  
3959 - http_moved_permanently(loc) then ("301 Moved Permanently", [http_header("Location", loc)]),  
3960 - http_moved_temporarily(loc) then ("302 Moved Temporarily", [http_header("Location", loc)]),  
3961 - http_see_other(loc) then ("303 See Other", [http_header("Location", loc)]),  
3962 - http_not_modified then ("304 Not Modified", []),  
3963 - http_use_proxy(loc) then ("305 Use Proxy", [http_header("Location", loc)]),  
3964 - http_temporary_redirect(loc) then ("307 Temporary Redirect", [http_header("Location", loc)]),  
3965 -  
3966 - http_bad_request then ("400 Bad Request", []),  
3967 - http_unauthorized then ("401 Unauthorized", []),  
3968 - http_payment_required then ("402 Payment Required", []),  
3969 - http_forbidden then ("403 Forbidden", []),  
3970 - http_not_found then ("404 Not Found", []),  
3971 - http_method_not_allowed then ("405 Method Not Allowed", []),  
3972 - http_not_acceptable then ("406 Not Acceptable", []),  
3973 - http_proxy_authentification_required then ("407 Proxy Authentication Required", []),  
3974 - http_request_timeout then ("408 Request Time-out", []),  
3975 - http_conflict then ("409 Conflict", []),  
3976 - http_gone then ("410 Gone", []),  
3977 - http_length_required then ("411 Length Required", []),  
3978 - http_precondition_failed then ("412 Precondition Failed", []),  
3979 - http_request_entity_too_large then ("413 Request Entity Too Large", []),  
3980 - http_request_uri_too_long then ("414 Request-URI Too Long", []),  
3981 - http_unsupported_media_type then ("415 Unsupported Media Type", []),  
3982 - http_request_range_unsatisfiable then ("416 Requested range unsatisfiable", []),  
3983 - http_expectation_failed then ("417 Expectation failed", []),  
3984 -  
3985 - http_internal_server_error then ("500 Internal Server Error", []),  
3986 - http_not_implemented then ("501 Not Implemented", []),  
3987 - http_bad_gateway then ("502 Bad Gateway", []),  
3988 - http_service_unavailable then ("503 Service Unavailable", []),  
3989 - http_gateway_timeout then ("504 Gateway Time-out", []),  
3990 - http_version_not_supported then ("505 HTTP Version not supported", []),  
3991 -  
3992 - http_error(code, message) then (abs_to_decimal(code) + " " + message, [])  
3993 - }.  
3994 -  
3995 - 1 +
  2 + *Project* The Anubis Project
  3 +
  4 + *Title* A Multi Host HTTP/HTTPS Server
  5 +
  6 + *Copyright* Copyright (c) Anubis Team 2003-2007.
  7 +
  8 +
  9 + *Authors* Alain Prouté
  10 + David René
  11 + Cédric Ricard
  12 +
  13 +
  14 + *Revised* July 2007.
  15 +
  16 +
  17 +
  18 + *Overviews*
  19 + In this file a HTTP/HTTPS server is defined, which is able to handle multiple hosts
  20 + (virtual hosts). It answers HTTP/HTTPS requests, sends files (images or any other kind
  21 + of file), constructs HTML pages on the fly using informations received from the client
  22 + (when the URI ends by '.awp'), handles uploading of files and redirections. It is
  23 + multitasking by itself, and can handle any number of sites and clients simultaneously.
  24 + It should better be used in conjunction with 'making_a_web_site.anubis' to be found in
  25 + the same directory. If you use 'web/making_a_web_site.anubis', you don't need to read
  26 + this file.
  27 +
  28 +
  29 + ----------------------------------- Table of Contents ---------------------------------
  30 +
  31 + *** (1) Multihosting and redirections.
  32 + *** (2) The incompatibility between SSL and virtual hosts.
  33 + *** (3) HTTP headers and web arguments.
  34 + *** (4) Site descriptions.
  35 + *** (5) Protection against denial of service attacks.
  36 + *** (6) Starting your HTTP and HTTPS servers.
  37 + *** (7) Private download.
  38 + *** (8) About web argument names.
  39 + *** (9) A web dispatcher.
  40 + *** (10) HTTP Errors
  41 +
  42 + ---------------------------------------------------------------------------------------
  43 +
  44 +
  45 +
  46 +
  47 + *** (1) Multihosting and redirections.
  48 +
  49 + This HTTP/HTTPS server can handle several host (also called 'virtual hosts'), in other
  50 + words, you may have several sites on the same server, with the same IP address and same
  51 + port numbers, but distinct 'host names'.
  52 +
  53 + A HTTP request sent by a browser contains the following informations:
  54 +
  55 + - a 'host name',
  56 + - an URI (Uniform Resource Identifier),
  57 + - HTTP headers,
  58 + - web arguments (in the form 'name=value').
  59 +
  60 + Actually, the host name is just the value of the HTTP header whose name is 'Host'. The
  61 + host name indicates which site is requested. Hence, it is the primary information for
  62 + branching to the right site. If there is no 'Host' HTTP header in the request, the
  63 + request is denied.
  64 +
  65 + From now on, we may assume that the host is determined, and consequently that we are
  66 + concerned by only one site. Each site has his own directories on the server's
  67 + disk.
  68 +
  69 + Each site also has a list of 'redirections'. A redirection is a triplet, like this one:
  70 +
  71 + redirect("/", "www.our-business.com", "/homepage.awp")
  72 +
  73 + meaning that if the host is "www.our-business.com", and if the requested URI is "/",
  74 + then the URI to be served is "/homepage.awp". 'redirect' is a constructor of the type
  75 + 'Redirection' defined in 'web/common.anubis'.
  76 +
  77 + Now, an URI may end by ".awp" (meaning 'Anubis Web Page') or not. If it does, the
  78 + server understands that an HTML page must be constructed on the fly, and to that end it
  79 + calls the 'awp handler' of the site. Otherwise, the URI must end by a known extension,
  80 + like ".jpg", ".png", ".txt", etc... and represents a file path relative to the
  81 + 'public' directory of the site. If these conditions are satisfied, the file is sent to
  82 + the client. Known extensions are recorded in 'web/mime.anubis'.
  83 +
  84 +
  85 +
  86 +
  87 + *** (2) The incompatibility between SSL and virtual hosts.
  88 +
  89 + Handling virtual hosts makes a problem under SSL (i.e. when using HTTPS), which is due
  90 + to the fact that the guys at Netscape who designed SSL probably did not have the
  91 + question of virtual hosts in mind. Indeed, the SSL handshake is completed before the
  92 + server can know about the value of the 'Host' HTTP header, so that it cannot know which
  93 + server certificate must be sent to the client. This makes a problem, because the
  94 + browser will not accept a certificate whose common name does not correspond to the name
  95 + of the requested host. The user will have to accept the certificate manually, which is
  96 + not good for the security image of the site. This problem has at least two solutions
  97 + (as far as Anubis is concerned).
  98 +
  99 + Solution 1. Arrange so that the network interface on which the server is listening
  100 + has at least as many different IP addresses as you have virtual hosts. Such
  101 + supplementary IP addresses are called 'IP Aliases'. In this case, start one HTTPS
  102 + server for each virtual host, each one listening on a different address. For the time
  103 + being, this method is applicable under Anubis only if you start as many instances of
  104 + 'anbexec' as you have virtual hosts, because each instance of 'anbexec' can handle only
  105 + one server certificate. Of course, getting IP aliases is another problem to be solved
  106 + with your Internet provider.
  107 +
  108 + Solution 2. We propose a simple solution, using only one server certificate (hence
  109 + only one instance of 'anbexec'). Since, we have only one server certificate, we must
  110 + introduce a notion of 'main host', i.e. a host containing all other 'virtual
  111 + hosts'. The unique server certificate belong to the main host, so that only the main
  112 + host is identified by the client. The client must trust the main host and be confident
  113 + that the main host redirects him to the right virtual host. Actually, the process will
  114 + be transparent to the client, except that the client will see the name of the main host
  115 + instead of the name of the virtual host in the 'location' field of the browser.
  116 +
  117 + So, assume that the name of main host is 'www.securedhost.com', and that the names of
  118 + the virtual hosts are:
  119 +
  120 + actual name simplified name
  121 + -----------------------------------------------------
  122 + www.virtual1.com virtual1
  123 + www.virtual2.com virtual2
  124 + www.virtual3.com virtual3
  125 +
  126 + Then the (confidential) document '/doc/my_document.pdf' on 'www.virtual2.com' will have
  127 + the URL:
  128 +
  129 + https://www.securedhost.com/virtual2/doc/my_document.pdf
  130 +
  131 + In order to work transparently, this solution must combine HTTP and HTTPS. Indeed, the
  132 + vitual host must have a first page reachable under HTTP, through the URL:
  133 +
  134 + http://www.virtual2.com/
  135 +
  136 + The HTTP server will redirect this URL to the awp handler of virtual host 'virtual2'.
  137 + The handler of this virtual host is able to generate a first page containing the
  138 + following HTML meta:
  139 +
  140 + <meta http-equiv="Refresh" content="0;URL=https://www.securedhost.com/virtual2/">,
  141 +
  142 + so that the client is immediately redirected to the main host under HTTPS (hence
  143 + accepting tranparently the server certificate). The awp handler of 'virtual2' then
  144 + redirects this URL to the home page (maybe a login page) of 'virtual2'.
  145 +
  146 + See 'web/making_a_web_site.anubis' for the sequel of this story.
  147 +
  148 +
  149 +
  150 +
  151 +
  152 + *** (3) HTTP headers and web arguments.
  153 +
  154 + Each HTTP request which arrives on the server contains a request line followed by a
  155 + series of HTTP headers. Each HTTP header is a pair '(name,value)' assigning a value to
  156 + a name. The type 'HTTP_header' is defined in 'web/common.anubis'.
  157 +
  158 + The request may also have a 'body'. The body contains either 'web arguments' or
  159 + uploaded files (or both). The request line itself may also contain web arguments (in a
  160 + so-called 'query string'). Like HTTP headers, 'web arguments' are pairs
  161 + '(name,value)', but the difference is that these pairs are generated by the page within
  162 + which the client clicks, while HTTP headers are generated by the browser itself. The
  163 + type 'Web_arg' is defined in 'web/common.anubis'. It has two alternatives, one for
  164 + ordinary web arguments (pairs) and one for uploaded files.
  165 +
  166 +read CXM_common.anubis
  167 +read tools/basis.anubis
  168 +read tools/printable_tree.anubis
  169 +read system/string.anubis
  170 +read system/files.anubis
  171 +read system/lists.anubis
  172 +read web/mime.anubis
  173 +
  174 +
  175 +
  176 + *** (4) Site descriptions.
  177 +
  178 + The type HTTP_Info gathers informations comming along with the client's request. These
  179 + informations are rarely used for composing HTML pages. Nevertheless, they are at your
  180 + disposal.
  181 +
  182 +public type HTTP_Info:
  183 + http_info
  184 + (
  185 + Word32 ip_address, // IP address of the client
  186 + String hostname, // hostname requested by the client
  187 + String uri, // URI requested by the client
  188 + List(HTTP_header) http_headers, // HTTP headers sent by the client
  189 + Bool is_https,
  190 + One -> String generate_trust_ticket // may be used against denial of
  191 + // service attacks
  192 + ).
  193 +
  194 +
  195 +
  196 + Each site is described by a 'web site description', which is a datum of type
  197 + 'Web_Site_Description'.
  198 +
  199 +public type Web_Site_Description:
  200 + web_site_description(
  201 + List(String) common_names,
  202 + String site_directory,
  203 + Redirections redirections,
  204 + String charset,
  205 + List(String) journal_extensions,
  206 + List(String) journal_headers,
  207 + String authorization_secret,
  208 + List(MIME) known_mime_types,
  209 + (String host_name,
  210 + HTTP_Info http_info,
  211 + List(Web_arg) lwa,
  212 + Bool is_https) -> (//List(HTTP_header),
  213 + Printable_tree) awp_handler,
  214 + (HTTP_Info http_info,
  215 + List(Web_arg) lwa) -> One before_send_file
  216 + //Bool using_state_cookies,
  217 + ).
  218 +
  219 + The component 'common_names' is the list of names of the site, like for example
  220 + "www.our-business.com". The reason why we have a list of common names instead of a
  221 + single common name, is that it may be useful to have a common name like "192.168.0.1"
  222 + for testing.
  223 +
  224 + 'charset' is a string which will determine the character encoding to be used by the
  225 + browser. Typically, this string is one of: "UTF-8", "ISO-8859-1", "Windows-1252",
  226 + etc...
  227 +
  228 + 'journal_extensions' is the list of URI extensions for which you want a log in the
  229 + journal (and on the console). When a request arrives, and if the extension is a member
  230 + of this list, a message is printed into the journal of the site including the date, the
  231 + IP address of the client, the complete HTTP request line. The HTTP headers whose name
  232 + is a member of 'journal_headers' are also printed in the journal. A reasonable minimum
  233 + for these two components is:
  234 +
  235 + [".awp"] for journal_extensions
  236 + ["user-agent"] for journal_headers
  237 +
  238 + 'authorization_secret' is a string which should just be unguessable. You may choose
  239 + something like (but don't choose this one !):
  240 +
  241 + "Hg8kJe42gCML9jNH-74"
  242 +
  243 + i.e. a sequence of characters typed at random, long enough to be unguessable. This is
  244 + used by the 'private download' mecanism, which is discussed later in this file.
  245 +
  246 + The component 'awp_handler' is a function of type:
  247 +
  248 + (String host_name,
  249 + HTTP_Info http_info,
  250 + List(Web_arg) web_args,
  251 + Bool is_https) -> Printable_tree
  252 +
  253 + ('Printable_tree' is a substitute for 'String' and is defined in
  254 + 'tools/basis.anubis'). This function is the 'awp handler' for the site. When the URI
  255 + ends by ".awp", this function is called, and the result (an HTML page) is sent to the
  256 + client over the connection. The last operand to this function is a boolean which is
  257 + 'true' when the requests arrives through the HTTPS channel, and 'false' when it arrives
  258 + through the HTTP channel.
  259 +
  260 +
  261 +
  262 +
  263 +
  264 +
  265 +
  266 + *** (5) Protection against denial of service attacks.
  267 +
  268 + We need to protect our servers against 'denial of service' attacks. The attack may be
  269 + send automatically from machines which are infested by viruses. In that case, our
  270 + server is saturated of connections (all virtual machines at work), but nothing is
  271 + comming on the connections. In order to avoid this problem, we propose the following:
  272 +
  273 + (1) Limit the number of simultaneous connections (say to 100).
  274 + (2) Close a connection if the request is not complete after say 10 seconds.
  275 + (3) Close the connection if the request is bigger than a given size (normal requests
  276 + are small except when there are uploaded files.
  277 + (4) Close the connection during the sending of the answer if the client is waiting
  278 + too much.
  279 + (5) Record all IP addresses with which we have encountered one of the problems above.
  280 + (6) Immediately close the connections if the IP address is in our list.
  281 + (7) Remove an address from the list only after 5 minutes of inactivity of this
  282 + address.
  283 + (8) Maintain a list of reliable IP addresses.
  284 +
  285 + Of course, all the above are approximative solutions which may in some circumstances
  286 + become either cumbersome or also partially block the system. So, it is needed to have a
  287 + set of dynamically modifiable parameters in order to master the behavior of this
  288 + mecanism.
  289 +
  290 +
  291 + Each dubious IP address is recorded together with its last activity time.
  292 +
  293 +public type DubiousIP:
  294 + dubious_ip (Word32 address,
  295 + Int last_activity).
  296 +
  297 +
  298 +public type DenialOfService:
  299 + denial_of_service(Var(Int) max_connections,
  300 + Var(Int) request_line_delay, // seconds
  301 + Var(Int) headers_delay,
  302 + Var(Int) answer_delay,
  303 + Var(List(DubiousIP)) list_of_dubious,
  304 + Var(List(Word32)) reliable_addresses).
  305 +
  306 + The informations in this set of variables are stored serialized into the file
  307 + 'my_anubis/web_sites/dos_info'. If this file does not exist a set if variables with
  308 + default values is created. The values are saved on the disk each time they are
  309 + modified.
  310 +
  311 +public define DenialOfService load_denial_of_service_info.
  312 +
  313 +
  314 +
  315 + *** (6) Starting your HTTP and HTTPS servers.
  316 +
  317 + When your web site descriptions are ready, you can start a pair of servers (a HTTP
  318 + server and a HTTPS server) for serving your web sites. Notice that there are always
  319 + two servers, regardless of the number of web sites, and that each web sites normally
  320 + uses the two servers.
  321 +
  322 +
  323 +public define StartServerResult
  324 + start_http_server
  325 + (
  326 + Word32 ip_address,
  327 + Word32 http_port,
  328 + List(Web_Site_Description) web_sites,
  329 + DenialOfService dos
  330 + ).
  331 +
  332 +public define StartServerResult
  333 + start_https_server
  334 + (
  335 + Word32 ip_address,
  336 + Word32 https_port,
  337 + String certificate_common_name,
  338 + List(Web_Site_Description) web_sites,
  339 + DenialOfService dos
  340 + ).
  341 +
  342 + The first argument 'ip_address' is the IP address on which the servers listen. If you
  343 + put 0, the servers listen on all adresses of the machine (which is useful if the
  344 + machine has several network interfaces). Otherwise, use the function 'ip_address'
  345 + defined in 'tools/basis.anubis' for composing a particular IP address.
  346 +
  347 + The next arguments are the port numbers for HTTP and HTTPS. The usual values are 80 and
  348 + 443, but you may have reasons to choose other values.
  349 +
  350 + The next argument is the list of your web site descriptions. All the sites described in
  351 + this list will be accessible on the server.
  352 +
  353 + The argument 'dos' is a set of dynamic variables containing the informations for
  354 + protecting the servers against denial of service attacks.
  355 +
  356 +
  357 +
  358 +
  359 +
  360 +
  361 + *** (7) Private download.
  362 +
  363 + It may happen that you want to propose private files for download. This means that such
  364 + a file could be downloaded only by the authorized person, and should not be seen by any
  365 + other one. This feature can be used only under HTTPS, not under HTTP.
  366 +
  367 + The file may be located anywhere on the server. Hence, the file has a complete absolute
  368 + path, like for example:
  369 +
  370 + /home/georges/my_documents/my_text.pdf
  371 +
  372 + which has nothing to do with the directories of the web server. Now, you may also want
  373 + to show another path or simply just a name to the client, not the actual absolute path
  374 + above, which may need to remain secret. So for example, the same file may appear to the
  375 + client as:
  376 +
  377 + informations.pdf
  378 +
  379 + The page must provide a link with an authorization. The authorization is just a web
  380 + argument, whose name is "zauth". The value of this web argument is computed by hashing
  381 + some secret string (known only from the programmer of the web site) with the absolute
  382 + path of the file. The HTTPS request will have the form:
  383 +
  384 + GET /informations.pdf?zauth=d38161f5b4e87e2d46e06ff8b3e233be563794d1
  385 +
  386 + The server will search for a file named
  387 +
  388 + zd38161f5b4e87e2d46e06ff8b3e233be563794d1
  389 +
  390 + (i.e. "z" concatenated with the value of the authorization) in the subdirectory
  391 + 'private_download' of the site directory. This file contains the absolute path of the
  392 + file, i.e:
  393 +
  394 + /home/georges/my_documents/my_text.pdf
  395 +
  396 + At that point, the server may hash the secret string and the absolute path together, to
  397 + check if the client is authorized to download the file. If it is the case, it sends the
  398 + file (the MIME type is declared as 'application/octet-stream' if it is not recognized).
  399 + The file is sent under the visible name.
  400 +
  401 + The server creates automatically the subdirectory 'private_download/' within the 'site
  402 + directory' (for each web site) if it does not already exist. Files in this directory
  403 + are deleted when they become too old (for example, after 3 days of life).
  404 +
  405 + Here is the function for computing the value of the authorization, and for making the
  406 + authorization file in 'private_download'.
  407 +
  408 +public define String
  409 + make_authorization
  410 + (
  411 + String site_directory,
  412 + String authorization_secret, // known only by the programmer of the web site
  413 + String absolute_path // on server
  414 + ).
  415 +
  416 + See 'web/making_a_web_site.anubis' for the construction of the link for downloading.
  417 +
  418 +
  419 +
  420 +
  421 +
  422 +
  423 +
  424 +
  425 + *** (8) About web argument names.
  426 +
  427 + The server reserves the name "zauth" for the authorization in the private download
  428 + mecanism. Also, if the name of a web arguments begins by "p" (like 'password'), it does
  429 + not print the value of the web argument neither on the console or in the journal. A
  430 + good politics is to prefix all web arguments by letters distinct from 'p' and 'z'. This
  431 + method is used in 'web/making_a_web_site.anubis'. This will avoid clashes of names.
  432 +
  433 +
  434 +
  435 +
  436 +
  437 +
  438 + *** (9) A web dispatcher.
  439 +
  440 + For hosting several sites you may prefer another method which we now describe. We start
  441 + a HTTP server on port 80 (or on another port). This server is called the
  442 + ``dispatcher''. When a requests arrives, the dispatcher examines the ``host'' HTTP
  443 + header, so that it gets the name of the requested host. Then it sends to the client a
  444 + page like this one:
  445 +
  446 + <html>
  447 + <head>
  448 + <meta http-equiv="Refresh" content="0;URL=...">
  449 + </head>
  450 + <body>
  451 + </body>
  452 + </html>
  453 +
  454 + where the URL represented by '...' is the URL of the requested site. This URL may have
  455 + the same IP address as the dispatcher, except that the port number is different. It may
  456 + also have a different IP address.
  457 +
  458 + The dispatcher uses the file 'my_anubis/web_sites/dispatcher.info'. This file contains
  459 + a serialized datum of type 'List(DispatcherInfo)'.
  460 +
  461 +public type DispatcherInfo:
  462 + site(String common_name,
  463 + Word32 http_port).
  464 +
  465 + The dispatcher does not write into this file. It reads it when it starts, and rereads
  466 + it each time the date of last modification of the file changes, so that the dispatcher
  467 + always has up to date data. The file may be managed (written and updated) by another
  468 + program.
  469 +
  470 + So, for each site, the dispatcher knows the common name (needed to recognize the 'host'
  471 + HTTP header), and the pair (ip_address,port) used by the actual site for HTTP. The
  472 + dispatcher does not worry about HTTPS. HTTPS must be managed by the actual site.
  473 +
  474 + The dispatcher is started by:
  475 +
  476 +public define One
  477 + start_web_dispatcher
  478 + (
  479 + Word32 ip_address, // address for listening (typically 0)
  480 + Word32 port, // typically 80
  481 + DenialOfService dos
  482 + ).
  483 +
  484 + A command line tool for managing the file 'my_anubis/web_sites/dispatcher.info' is also
  485 + provided:
  486 +
  487 + global define One
  488 + manage_web_dispatcher
  489 + (
  490 + List(String) args
  491 + ).
  492 +
  493 +
  494 + *** (10) HTTP Errors
  495 +
  496 +public type HTTP_Status:
  497 + http_continue, // 100
  498 + http_switching_protocol, // 101
  499 +
  500 + http_ok, // 200
  501 + http_created, // 201
  502 + http_accepted, // 202
  503 + http_non_authoritative_info, // 203
  504 + http_no_content, // 204
  505 + http_reset_content, // 205
  506 + http_partial_content, // 206
  507 +
  508 + http_multiple_choices,
  509 + http_moved_permanently(String location), // 301
  510 + http_moved_temporarily(String location), // 302
  511 + http_see_other(String location), // 303
  512 + http_not_modified, // 304
  513 + http_use_proxy(String location), // 305
  514 + http_temporary_redirect(String location), // 307
  515 +
  516 + http_bad_request, // 400
  517 + http_unauthorized, // 401
  518 + http_payment_required, // 402
  519 + http_forbidden, // 403
  520 + http_not_found, // 404
  521 + http_method_not_allowed, // 405
  522 + http_not_acceptable, // 406
  523 + http_proxy_authentification_required, // 407
  524 + http_request_timeout, // 408
  525 + http_conflict, // 409
  526 + http_gone, // 410
  527 + http_length_required, // 411
  528 + http_precondition_failed, // 412
  529 + http_request_entity_too_large, // 413
  530 + http_request_uri_too_long, // 414
  531 + http_unsupported_media_type, // 415
  532 + http_request_range_unsatisfiable, // 416
  533 + http_expectation_failed, // 417
  534 +
  535 + http_internal_server_error, // 500
  536 + http_not_implemented, // 501
  537 + http_bad_gateway, // 502
  538 + http_service_unavailable, // 503
  539 + http_gateway_timeout, // 504
  540 + http_version_not_supported, // 505
  541 +
  542 + http_error(Int /*code*/, String /*message*/).
  543 +
  544 +public define (String, List(HTTP_header))
  545 + format
  546 + (
  547 + HTTP_Status status
  548 + ).
  549 +
  550 +
  551 +
  552 +
  553 +
  554 + --- That's all for the public part ! --------------------------------------------------
  555 +
  556 +define Maybe(String) get_host_header_value(List(HTTP_header) headers).
  557 +
  558 +define String
  559 + __utime_to_string
  560 + (
  561 + UTime t
  562 + ) =
  563 + to_decimal(t.seconds) + "." + zero_pad_n(6, t.microseconds ) + "s".
  564 +
  565 +
  566 +variable UTime t0 = utime(0,0).
  567 +variable UTime t1 = utime(0,0).
  568 +
  569 +define One
  570 + accumulate_t1
  571 + (
  572 + UTime start
  573 + ) =
  574 + with delta = (UTime)unow - start,
  575 + t1 <- delta + *t1;
  576 + unique.
  577 +
  578 +variable UTime t2 = utime(0,0).
  579 +
  580 +define One
  581 + accumulate_t2
  582 + (
  583 + UTime start
  584 + ) =
  585 + with delta = (UTime)unow - start,
  586 + t2 <- delta + *t2;
  587 + unique.
  588 +
  589 +
  590 +public define One
  591 + print_delta
  592 + (
  593 + String txt
  594 + ) =
  595 + println(__utime_to_string((UTime)unow - *t0) + " : " + txt).
  596 +
  597 +
  598 + ----------------------------------- Table of Contents ---------------------------------
  599 +
  600 + *** [1] Types which are private to this file.
  601 +
  602 + *** [2] Tools.
  603 + *** [2.1] Formating an error message.
  604 + *** [2.2] Converting IP addresses.
  605 + *** [2.3] Reading and unputting characters.
  606 + *** [2.4] Reading and discarding characters.
  607 + *** [2.5] Reading a character string.
  608 + *** [2.6] Padding integers with zeros.
  609 + *** [2.7] Converting web arguments to ASCII.
  610 + *** [2.8] Server description.
  611 +
  612 + *** [3] Managing the journal.
  613 + *** [3.1] Naming journal files.
  614 + *** [3.2] Formating HTTP headers.
  615 + *** [3.3] Formating web arguments.
  616 + *** [3.4] Formating the whole request.
  617 + *** [3.5] Putting it in the journal file (and on the console).
  618 +
  619 + *** [4] Reading the HTTP request.
  620 + *** [4.1] Skipping leading blanks.
  621 + *** [4.2] Reading a new line.
  622 + *** [4.3] Reading a 'word'.
  623 + *** [4.4] Separating the URI from the query string.
  624 + *** [4.5] Reading the web arguments.
  625 + *** [4.7] Reading the request line.
  626 + *** [4.8] Reading the HTTP headers.
  627 + *** [4.9] Getting the size of the request's body.
  628 + *** [4.10] Reading the body of the request.
  629 +
  630 + *** [5] Making the HTTP answer.
  631 + *** [5.1] Avoiding illegal URIs.
  632 + *** [5.2] Managing authorizations for downloading private files.
  633 + *** [5.3] Recognizing MIME types.
  634 + *** [5.4] Formating HTTP headers.
  635 + *** [5.5] Sending a file.
  636 + *** [5.6] Answering a www-url encoded request.
  637 + *** [5.7] Answering a multipart/form-data encoded request.
  638 + *** [5.7.1] Finding the boundary.
  639 + *** [5.7.2] Reading attributes from a multipart entity.
  640 + *** [5.7.3] Creating a temporary filename for an uploaded file.
  641 + *** [5.7.4] Saving an uploaded file under a temporary filename.
  642 + *** [5.7.5] Removing the path from a file name.
  643 + *** [5.7.6] Reading a multipart entity.
  644 + *** [5.8] Handling redirections.
  645 + *** [5.9] Answering both sorts of requests.
  646 +
  647 + *** [6] The HTTP/HTTPS servers.
  648 + *** [6.1] The HTTP request handler.
  649 + *** [6.2] Server's tasks.
  650 + *** [6.3] Starting the HTTP/HTTPS servers.
  651 +
  652 + *** [7] The web dispatcher.
  653 + *** [7.1] The dispatcher server.
  654 + *** [7.2] The dispatcher web site.
  655 + *** [7.3] Managing the info file.
  656 +
  657 + ---------------------------------------------------------------------------------------
  658 +
  659 +
  660 +
  661 +
  662 +read tools/basis.anubis
  663 +read tools/findstring.anubis
  664 +read tools/connections.anubis
  665 +
  666 +
  667 +
  668 +
  669 +
  670 + *** [1] Types which are private to this file.
  671 +
  672 + We use the following self-explanatory types.
  673 +
  674 +type Error:
  675 + cannot_read_from_connection,
  676 + not_get_or_post_request(String),
  677 + end_of_line_expected,
  678 + incorrect_content_length_value,
  679 + colon_expected,
  680 + timeout(Int).
  681 +
  682 +type HTTP_RequestType:
  683 + get,
  684 + post.
  685 +
  686 +type HTTP_RequestLine:
  687 + request_line (HTTP_RequestType type,
  688 + String uri,
  689 + List(Web_arg) query_string).
  690 +
  691 +type EncodingType:
  692 + www_url,
  693 + multipart_form_data.
  694 +
  695 +type BufferedConnection:
  696 + buffered_connection(Connection conn,
  697 + Var(ByteArray) buffer,
  698 + Var(Int) read_pos).
  699 +
  700 +
  701 +
  702 + *** [2] Tools.
  703 +
  704 + *** [2.1] Formating an error message.
  705 +
  706 + The next function formats an error message.
  707 +
  708 +define String
  709 + format
  710 + (
  711 + Error msg
  712 + ) =
  713 + if msg is
  714 + {
  715 + cannot_read_from_connection then
  716 + "Cannot read from connection.\n",
  717 + not_get_or_post_request(s) then
  718 + "The request did not begin by 'GET' or 'POST': "+s+".\n",
  719 + end_of_line_expected then
  720 + "End of line expected.\n",
  721 + incorrect_content_length_value then
  722 + "Incorrect value for HTTP header 'Content-Length'.\n",
  723 + colon_expected then
  724 + "':' was expected.\n",
  725 + timeout(n) then
  726 + //"time out: "+n+"\n"
  727 + //"time out.\n"
  728 + ""
  729 + }.
  730 +
  731 +
  732 +
  733 +
  734 +
  735 +
  736 + *** [2.2] Converting IP addresses.
  737 +
  738 + We need two conversion functions for IP addresses:
  739 +
  740 + (Word8,Word8,Word8,Word8) --> Word32 ip_address
  741 + Word32 --> String ip_addr_to_string
  742 +
  743 + These conversions are defined in 'tools/basis.anubis'.
  744 +
  745 +
  746 +
  747 +
  748 +
  749 +
  750 +
  751 +
  752 + *** [2.3] Reading and unputting characters.
  753 +
  754 + We need a mecanism for unputting several characters (actually at least 3). This is
  755 + because when reading the client connection, we must sometimes go ahead several
  756 + characters, and virtually put them back into the connection, so that they can be
  757 + reread. Of course, we do not send them back to the client. We store them in a list
  758 + (hold by the variable 'unput_chars'), and we manage this list, so that characters may
  759 + be virtually put back in the connection (this is called 'unputting').
  760 +
  761 +variable List(Word8) unput_chars = [].
  762 +
  763 + The most recently read one is the head of list. Fortunately, this variable is private
  764 + to this virtual machine (hence to this client).
  765 +
  766 +
  767 +define One
  768 + unput // unputting a character (add it in front of the list)
  769 + (
  770 + Word8 character
  771 + ) =
  772 + unput_chars <- (List(Word8))[character . *unput_chars].
  773 +
  774 +
  775 +
  776 +define One record_dubious_IP(Word32 addr,DenialOfService dos).
  777 +
  778 +variable Int sttm = 0. // contains the start time for this connection.
  779 +
  780 +define Result(Error,Word8)
  781 + record_dubious_connection
  782 + (
  783 + Connection conn,
  784 + Int dead_line,
  785 + DenialOfService dos,
  786 + ) =
  787 + if remote_IP_address_and_port(conn) is (addr,port) then
  788 + record_dubious_IP(addr,dos);
  789 + print("Recording IP address "+ip_addr_to_string(addr)+
  790 + " as dubious after "+(dead_line-*sttm)+" seconds. Total: "+
  791 + length(*list_of_dubious(dos))+"\n");
  792 + error(timeout(dead_line)).
  793 +
  794 +define String
  795 + pid
  796 + =
  797 + "[" + virtual_machine_id + "] ".
  798 +
  799 +
  800 +define One
  801 + put
  802 + (
  803 + ByteArray source,
  804 + ByteArray dest,
  805 + Int position,
  806 + Int i
  807 + ) =
  808 + if nth(i,source) is
  809 + {
  810 + failure then unique,
  811 + success(b) then if put(dest,position,b) is
  812 + {
  813 + failure then unique,
  814 + success(_) then put(source,dest,position+1,i+1)
  815 + }
  816 + }.
  817 +
  818 +define ReadResult
  819 + read_from_connexion
  820 + (
  821 + BufferedConnection connection,
  822 + Int size,
  823 + Int time_out,
  824 + ByteArray result_buffer,
  825 + Int position
  826 + ) =
  827 + //println(pid + "read_from_connexion(" + size + ")");
  828 +
  829 + if *connection.read_pos < length(*connection.buffer) then
  830 + //println(pid + " reading from buffer (size = " + length(*connection.buffer) + ", pos = " + *connection.read_pos);
  831 + //with t1_tmp = (UTime) unow,
  832 + with result = extract(*connection.buffer, *connection.read_pos, *connection.read_pos + size),
  833 + size_read = length(result),
  834 + put(result,result_buffer,position,0);
  835 + connection.read_pos <- *connection.read_pos + size_read;
  836 + //accumulate_t1(t1_tmp);
  837 + if size > size_read then
  838 + //println("Wanted " + size + ", read only " + size_read);
  839 +
  840 + terminal read_from_connexion(connection, size - size_read, time_out, result_buffer,position+size_read)
  841 +// {
  842 +// error then error,
  843 +// timeout then ok(result),
  844 +// ok(ba) then ok(result + ba)
  845 +// }
  846 + else
  847 + ok(result_buffer)
  848 + else
  849 + //if unow > dead_line then record_dubious_connection(connection,dead_line,dos) else
  850 + if read(connection.conn, 16384, time_out) is // the connection is closed after 10 minutes of inactivity
  851 + {
  852 + error then println(pid + "read failed)"); error,
  853 + timeout then timeout,
  854 + ok(ba) then
  855 +// println(pid + "ba = " + length(ba));
  856 + connection.buffer <- ba;
  857 + connection.read_pos <- 0;
  858 + //println(pid + "rb = " + length(*read_buffer));
  859 +
  860 + terminal read_from_connexion(connection, size, time_out, result_buffer,position)
  861 + }.
  862 +
  863 +define Result(Error,Word8)
  864 + next_char // reading a character (check the list first, and read on the connection
  865 + // only when the list is empty).
  866 + (
  867 + BufferedConnection connection,
  868 + Int dead_line,
  869 + DenialOfService dos
  870 + ) =
  871 + //with t2_tmp = (UTime) now,
  872 + if *unput_chars is
  873 + {
  874 + [ ] then
  875 + // ///////////////////
  876 + // Buffered reading
  877 + //if unow > dead_line then record_dubious_connection(connection,dead_line,dos) else
  878 + if nth(*connection.read_pos, *connection.buffer) is
  879 + {
  880 + failure then
  881 + if read_from_connexion(connection,1,600, constant_byte_array(1,0),0) is // the connection is closed after 10 minutes of inactivity
  882 + {
  883 + error then /*accumulate_t2(t2_tmp);*/ error(cannot_read_from_connection),
  884 + timeout then /*accumulate_t2(t2_tmp);*/ error(timeout(600)),
  885 + //record_dubious_connection(connection,dead_line,dos),
  886 + ok(ba) then if nth(0,ba) is
  887 + {
  888 + failure then /*accumulate_t2(t2_tmp);*/ error(cannot_read_from_connection),
  889 + success(c) then
  890 + //println("-" + pid + "read [" + implode([c]) + "]\t");
  891 + //accumulate_t2(t2_tmp);
  892 + ok(c)
  893 + }
  894 + },
  895 + success(c) then
  896 + connection.read_pos <- *connection.read_pos + 1;
  897 + //accumulate_t2(t2_tmp);
  898 + ok(c)
  899 + },
  900 +
  901 + // ///////////////////
  902 + // standard reading
  903 +// if read(connection.conn, 1, 600) is // the connection is closed after 10 minutes of inactivity
  904 +// {
  905 +// error then accumulate_t2(t2_tmp); println(pid + "read failed)"); error(cannot_read_from_connection),
  906 +// timeout then accumulate_t2(t2_tmp); error(timeout(600)),
  907 +// ok(ba) then if nth(0,ba) is
  908 +// {
  909 +// failure then accumulate_t2(t2_tmp); error(cannot_read_from_connection),
  910 +// success(c) then accumulate_t2(t2_tmp);
  911 +// ok(c)
  912 +// }
  913 +// },
  914 +
  915 + [h . t] then
  916 + unput_chars <- t; //accumulate_t2(t2_tmp);
  917 + ok(h)
  918 + }.
  919 +
  920 +
  921 +
  922 +
  923 +
  924 +
  925 + *** [2.4] Reading and discarding characters.
  926 +
  927 + The next function reads the specified number of bytes (this is the same as
  928 + 'characters') from the connection and discards them. This is used for discarding CR LF
  929 + just before the body of a request.
  930 +
  931 +define Result(Error,One)
  932 + read_and_ignore
  933 + (
  934 + BufferedConnection connection, // to client
  935 + Int dead_line,
  936 + Int number_of_characters, // number of characters to read and ignore
  937 + DenialOfService dos
  938 + ) =
  939 + if number_of_characters =< 0 then ok(unique) else
  940 + if next_char(connection, dead_line, dos) is
  941 + {
  942 + error(msg) then error(msg),
  943 + ok(c) then read_and_ignore(connection,dead_line,number_of_characters-1,dos)
  944 + }.
  945 +
  946 +
  947 +
  948 +
  949 +
  950 +
  951 +
  952 + *** [2.5] Reading a character string.
  953 +
  954 + Sometimes values of HTTP attributes or web args are presented in the form of double
  955 + quoted strings. The next function handles the reading of such things. The leading
  956 + double quote is already read in. We must read subsequent characters until the next non
  957 + backslashed double quote.
  958 +
  959 +define Result(Error,String)
  960 + read_string
  961 + (
  962 + BufferedConnection connection, // connection with the client
  963 + Int dead_line,
  964 + List(Word8) so_far, // characters read so far (in reverse order)
  965 + DenialOfService dos
  966 + ) =
  967 + if next_char(connection, dead_line,dos) is
  968 + {
  969 + error(msg) then error(msg),
  970 + ok(c) then
  971 + if c = '\\'
  972 + then if next_char(connection,dead_line,dos) is
  973 + {
  974 + error(msg) then error(msg),
  975 + ok(d) then
  976 + if d = '\"'
  977 + then read_string(connection,dead_line,['\"' . so_far],dos)
  978 + else read_string(connection,dead_line,[d, c . so_far],dos)
  979 + }
  980 + else if c = '\"'
  981 + then ok(implode(reverse(so_far)))
  982 + else read_string(connection,dead_line,[c . so_far],dos)
  983 + }.
  984 +
  985 +
  986 +
  987 +
  988 +
  989 +
  990 +
  991 +
  992 +
  993 +
  994 +
  995 +
  996 + *** [2.7] Converting web arguments to ASCII.
  997 +
  998 + The function 'web_to_ascii' gets a character string and replaces web encoding by normal
  999 + ASCII encoding. This amounts to replacing:
  1000 +
  1001 + + by blank
  1002 + %xx by the character whose ASCII code is xx in hexadecimal
  1003 +
  1004 + Note: We assume that '9' < 'A' (which is the case for ASCII code).
  1005 +
  1006 +
  1007 +
  1008 +define Word8
  1009 + web_decode
  1010 + (
  1011 + Word8 x1,
  1012 + Word8 x2
  1013 + ) =
  1014 + with n1 = if x1 +=< '9' then (x1 - '0') else if x1 +=< 'F' then (x1 - 'A' + 10) else (x1 - 'a' + 10),
  1015 + n2 = if x2 +=< '9' then (x2 - '0') else if x2 +=< 'F' then (x2 - 'A' + 10) else (x2 - 'a' + 10),
  1016 + (n1 << 4) + n2.
  1017 +
  1018 +
  1019 +
  1020 +define String
  1021 + web_to_ascii
  1022 + (
  1023 + String web_string,
  1024 + Int n, // current position in web_string
  1025 + List(Word8) so_far
  1026 + ) =
  1027 + if nth(n,web_string) is
  1028 + {
  1029 + failure then implode(reverse(so_far)),
  1030 + success(c) then
  1031 + if c = '+'
  1032 + then web_to_ascii(web_string,n+1,[' ' . so_far])
  1033 + else if c = '%'
  1034 + then if nth(n+1,web_string) is
  1035 + {
  1036 + failure then implode(reverse(so_far)),
  1037 + success(x1) then if nth(n+2,web_string) is
  1038 + {
  1039 + failure then implode(reverse(so_far)),
  1040 + success(x2) then web_to_ascii(web_string,n+3,[web_decode(x1,x2) . so_far])
  1041 + }
  1042 + }
  1043 + else web_to_ascii(web_string,n+1,[c . so_far])
  1044 + }.
  1045 +
  1046 +
  1047 +
  1048 +
  1049 +
  1050 +
  1051 +
  1052 +
  1053 + *** [3] Managing the journal.
  1054 +
  1055 + Concurrently working machines should not try to access the same file at the same
  1056 + time. This problem may be solved by using the 'protect' mecanism.
  1057 +
  1058 +
  1059 +
  1060 + *** [3.1] Naming journal files.
  1061 +
  1062 + Since journal messages are rather prolific, we should have at least one file per
  1063 + hour. Hence, the name of a journal file must be constructed from the current year,
  1064 + month, day and hour. For example, it may be:
  1065 +
  1066 + 2003_03_12_19
  1067 +
  1068 + (this is for the journal of 7 PM to 8 PM, 2003/mar/12).
  1069 +
  1070 +define String
  1071 + make_current_journal_file_name
  1072 + =
  1073 + if convert_time(now) is date_and_time(y,m,d,h,_,_,_,_,_) then
  1074 + to_decimal(y)+"_"+
  1075 + zero_pad_n(2,m)+"_"+
  1076 + zero_pad_n(2,d)+"_"+
  1077 + zero_pad_n(2,h).
  1078 +
  1079 +
  1080 +
  1081 +
  1082 +
  1083 +
  1084 +
  1085 + *** [3.2] Formating HTTP headers.
  1086 +
  1087 + HTTP headers may be shown on the console or written in the journal. The function below
  1088 + formats a list of HTTP headers.
  1089 +
  1090 +define String
  1091 + show_format
  1092 + (
  1093 + Web_Site_Description desc,
  1094 + List(HTTP_header) headers,
  1095 + ) =
  1096 + if headers is
  1097 + {
  1098 + [ ] then "",
  1099 + [h . t] then if h is http_header(name,value) then
  1100 + if member(journal_headers(desc),name)
  1101 + then " | "+name+": "+value+"\n"+show_format(desc,t)
  1102 + else show_format(desc,t)
  1103 + }.
  1104 +
  1105 +
  1106 +
  1107 +
  1108 +
  1109 +
  1110 + *** [3.3] Formating web arguments.
  1111 +
  1112 + The same thing for web arguments.
  1113 +
  1114 +define String
  1115 + show_format
  1116 + (
  1117 + List(Web_arg) lwa
  1118 + ) =
  1119 + if lwa is
  1120 + {
  1121 + [ ] then "",
  1122 + [h . t] then if h is
  1123 + {
  1124 + web_arg(n,v) then
  1125 + " | "+n+"="+(if nth(0,n) = success('p') then "<not shown>" else v)+"\n"+show_format(t),
  1126 + upload(n,fn,tfn) then
  1127 + " | "+n+"="+fn+" (uploaded as '"+tfn+"')\n"+show_format(t)
  1128 + }
  1129 + }.
  1130 +
  1131 +
  1132 +
  1133 +
  1134 +
  1135 +
  1136 + *** [3.4] Formating the whole request.
  1137 +
  1138 + It is cheap to transform month numbers into abbreviated month names. This enhances the
  1139 + readability of the journal.
  1140 +
  1141 +define String
  1142 + format_month
  1143 + (
  1144 + Int m
  1145 + ) =
  1146 + if m = 1 then "jan" else
  1147 + if m = 2 then "feb" else
  1148 + if m = 3 then "mar" else
  1149 + if m = 4 then "apr" else
  1150 + if m = 5 then "may" else
  1151 + if m = 6 then "jun" else
  1152 + if m = 7 then "jul" else
  1153 + if m = 8 then "aug" else
  1154 + if m = 9 then "sep" else
  1155 + if m = 10 then "oct" else
  1156 + if m = 11 then "nov" else
  1157 + if m = 12 then "dec" else
  1158 + "???".
  1159 +
  1160 +
  1161 + Below we format a whole HTTP request. This may give this (actually, it depends on how
  1162 + you defined the values of 'journal_headers' and 'journal_extensions'):
  1163 +
  1164 + [3] 2003/mar/10 10:06:57 from 123.456.123.456: /homepage.awp
  1165 + | host: www.the-best-one.com
  1166 + | user-agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.0.1) Gecko/20020823 Netscape/7.0
  1167 +
  1168 + The leading number between brackets is the number of the virtual machine which served
  1169 + the URI.
  1170 +
  1171 +define String
  1172 + format_request
  1173 + (
  1174 + Web_Site_Description desc,
  1175 + Connection client_connection,
  1176 + HTTP_RequestLine request_line,
  1177 + List(HTTP_header) headers,
  1178 + List(Web_arg) web_args
  1179 + ) =
  1180 + with dt = convert_time(now),
  1181 + if remote_IP_address_and_port(client_connection) is (addr,port) then
  1182 + to_decimal(year(dt))+"/"+format_month(month(dt))+"/"+zero_pad_n(2,day(dt))+" "+
  1183 + zero_pad_n(2,hour(dt))+":"+zero_pad_n(2,minute(dt))+":"+zero_pad_n(2,second(dt))+
  1184 + " from "+ip_addr_to_string(addr)+
  1185 + ": "+uri(request_line)+"\n"+
  1186 + show_format(desc,headers)+
  1187 + show_format(web_args).
  1188 +
  1189 +
  1190 +
  1191 +
  1192 +
  1193 +
  1194 +
  1195 + *** [3.5] Putting it in the journal file (and on the console).
  1196 +
  1197 + We must not forget to 'protect' this operation, so that the messages of two machines
  1198 + (working for the same site) will not be mixed together.
  1199 +
  1200 +define One
  1201 + log_journal_msg
  1202 + (
  1203 + Web_Site_Description desc,
  1204 + String msg,
  1205 + ) =
  1206 + with ba_msg = to_byte_array("["+virtual_machine_id+"] "+msg+"\n"),
  1207 + protect
  1208 + (
  1209 + if file(site_directory(desc)+"/journal/"+make_current_journal_file_name,append) is
  1210 + {
  1211 + failure then unique,
  1212 + success(journal_file) then
  1213 + forget(reliable_write(file(journal_file),ba_msg))
  1214 + };
  1215 + forget(reliable_write(file(stdout),ba_msg))
  1216 + ).
  1217 +
  1218 +
  1219 +
  1220 +
  1221 +
  1222 +
  1223 +
  1224 + *** [4] Reading the HTTP request.
  1225 +
  1226 +
  1227 + *** [4.1] Skipping leading blanks.
  1228 +
  1229 + One of the peculiarities of HTTP is that the characters 13 (carriage return) and 10
  1230 + (line feed) followed by either a space (32) or a tab (9), is considered as a blank not
  1231 + containing any new line. 'skip_http_blanks' must skip all blanks characters until the
  1232 + first non blank character, which should not be read in. Obviously, because of the above
  1233 + peculiarity, we need at least 3 characters of lookahead to do this. In other words, we
  1234 + must be able to unput at least 3 characters (hopefully we are).
  1235 +
  1236 + Strictly blanks characters are 'space' and 'tab'.
  1237 +
  1238 +define Bool
  1239 + is_strict_blank
  1240 + (
  1241 + Word8 c
  1242 + ) =
  1243 + if c = ' ' then true else c = '\t'.
  1244 +
  1245 +
  1246 + On the contrary, blanks include 13 and 10.
  1247 +
  1248 +define Bool
  1249 + is_blank
  1250 + (
  1251 + Word8 c
  1252 + ) =
  1253 + if c = ' ' then true else
  1254 + if c = '\t' then true else
  1255 + if c = 13 then true else
  1256 + c = 10.
  1257 +
  1258 +
  1259 + Skipping HTTP blanks.
  1260 +
  1261 +define Result(Error,One)
  1262 + skip_http_blanks
  1263 + (
  1264 + BufferedConnection connection,
  1265 + Int dead_line,
  1266 + DenialOfService dos
  1267 + ) =
  1268 + if next_char(connection,dead_line,dos) is
  1269 + {
  1270 + error(msg) then error(msg),
  1271 + ok(c) then
  1272 + if is_strict_blank(c)
  1273 + then skip_http_blanks(connection,dead_line,dos)
  1274 + else if c = 13
  1275 + then if next_char(connection,dead_line,dos) is
  1276 + {
  1277 + error(msg) then error(msg), // (unput(c); ok(unique)),
  1278 + ok(d) then
  1279 + if d = 10
  1280 + then if next_char(connection,dead_line,dos) is
  1281 + {
  1282 + error(msg) then error(msg), // (unput(d); unput(c); ok(unique)),
  1283 + ok(e) then
  1284 + if is_strict_blank(e)
  1285 + then skip_http_blanks(connection,dead_line,dos)
  1286 + else (unput(e); unput(d); unput(c); ok(unique))
  1287 + }
  1288 + else (unput(d); unput(c); ok(unique))
  1289 + }
  1290 + else (unput(c); ok(unique))
  1291 + }.
  1292 +
  1293 +
  1294 +
  1295 +
  1296 +
  1297 +
  1298 +
  1299 +
  1300 + *** [4.2] Reading a new line.
  1301 +
  1302 + Normally in HTTP a new line is the sequence 13 10 (carriage return line feed), not
  1303 + followed by a space or tabulator. If it is followed by a space or tabulator, the three
  1304 + characters are considered blanks, and no new line has been read. Before trying to read
  1305 + a new line, we first skip leading spaces and tabs. Then we try to read 13 and 10, and
  1306 + we read another character. if this character is space or tab, we consider we have read
  1307 + only blanks and we continue reading in order to find our new line. Otherwise, we unput
  1308 + this character (which may be for example the first character of the name of the next
  1309 + header), and answer that we have seen a new line.
  1310 +
  1311 + Warning: we must not use this function for reading the last pair (13,10) before the
  1312 + beginning of the body, because if the body is empty, there is no character to read
  1313 + after this pair, so that the server could wait for a character which will never
  1314 + come. This is the reason for 'read_and_ignore' above, which is used precisely for
  1315 + reading that last (13,10) pair.
  1316 +
  1317 +define Result(Error,One)
  1318 + read_new_line
  1319 + (
  1320 + BufferedConnection connection,
  1321 + Int dead_line,
  1322 + DenialOfService dos
  1323 + ) =
  1324 + if skip_http_blanks(connection,dead_line,dos) is
  1325 + {
  1326 + error(msg) then error(msg),
  1327 + ok(_) then
  1328 + if next_char(connection,dead_line,dos) is
  1329 + {
  1330 + error(msg) then error(msg),
  1331 + ok(c) then
  1332 + if c = 13
  1333 + then if next_char(connection,dead_line,dos) is
  1334 + {
  1335 + error(msg) then error(msg),
  1336 + ok(d) then
  1337 + if d = 10
  1338 + then ok(unique)
  1339 + else (unput(d);
  1340 + unput(c);
  1341 + error(end_of_line_expected))
  1342 + }
  1343 + else (unput(c);
  1344 + error(end_of_line_expected))
  1345 + }}.
  1346 +
  1347 +
  1348 +
  1349 +
  1350 +
  1351 +
  1352 +
  1353 +
  1354 + *** [4.3] Reading a 'word'.
  1355 +
  1356 + A 'word' is a sequence of characters which begins either by a double quote or not by a
  1357 + double quote. (However, any leading blanks are read in and ignored. This is
  1358 + accomplished by 'skip_http_blanks'.) If it begins by a double quote, it is read like a
  1359 + string, i.e. it ends at the next (non backslashed) double quote. Otherwise, it is
  1360 + right delimited by any character which may be considered as 'blank'. If the word is
  1361 + double quoted, the closing double quote is read in. On the contrary, if the word is not
  1362 + double quoted, the right delimiting blank character is not read in (it is 'unput' back
  1363 + into the connection), and may be read in again. This is needed because carriage return
  1364 + or line feed which are 'blank', also have a meaning in HTTP.
  1365 +
  1366 +define Result(Error,String)
  1367 + read_word_aux
  1368 + (
  1369 + BufferedConnection connection,
  1370 + Int dead_line,
  1371 + List(Word8) so_far,
  1372 + DenialOfService dos
  1373 + ) =
  1374 + if next_char(connection,dead_line,dos) is
  1375 + {
  1376 + error(msg) then error(msg),
  1377 + ok(c) then
  1378 + if is_blank(c)
  1379 + then (unput(c);
  1380 + ok(implode(reverse(so_far))))
  1381 + else read_word_aux(connection,dead_line,[c . so_far],dos)
  1382 + }.
  1383 +
  1384 +define Result(Error,String)
  1385 + read_word
  1386 + (
  1387 + BufferedConnection connection,
  1388 + Int dead_line,
  1389 + DenialOfService dos
  1390 + ) =
  1391 + if skip_http_blanks(connection,dead_line,dos) is
  1392 + {
  1393 + error(msg) then error(msg),
  1394 + ok(_) then
  1395 + if next_char(connection,dead_line,dos) is
  1396 + {
  1397 + error(msg) then error(msg),
  1398 + ok(c) then
  1399 + if c = '\"'
  1400 + then read_string(connection,dead_line,[],dos)
  1401 + else read_word_aux(connection,dead_line,[c],dos)
  1402 + }
  1403 + }.
  1404 +
  1405 +
  1406 +
  1407 +
  1408 +
  1409 +
  1410 +
  1411 +
  1412 + *** [4.4] Separating the URI from the query string.
  1413 +
  1414 + A 'query string' may be postfixed to the URI, just after a question mark. For example,
  1415 + the client may send the following request:
  1416 +
  1417 + GET /catalog.awp?item=3&color=blue
  1418 +
  1419 + We separate this into an URI: "/catalog.awp" and the string: "item=3&color=blue" which
  1420 + will be later transformed into the list:
  1421 +
  1422 + [web_arg("item","3"),web_arg("color","blue")]
  1423 +
  1424 +
  1425 +define (String,String)
  1426 + separate_uri_from_query_string
  1427 + (
  1428 + String uri_and_query_string,
  1429 + Int n
  1430 + ) =
  1431 + if nth(n,uri_and_query_string) is
  1432 + {
  1433 + failure then (uri_and_query_string,""),
  1434 + success(c) then
  1435 + if c = '?'
  1436 + then (substr(uri_and_query_string,0,n),
  1437 + substr(uri_and_query_string,n+1,length(uri_and_query_string)-(n+1)))
  1438 + else separate_uri_from_query_string(uri_and_query_string,n+1)
  1439 + }.
  1440 +
  1441 +
  1442 +
  1443 +
  1444 +
  1445 +
  1446 +
  1447 +
  1448 +
  1449 + *** [4.5] Reading the web arguments.
  1450 +
  1451 + HTTP/HTTPS requests are sent in one of two formats:
  1452 +
  1453 + (1) www-url encoded
  1454 + (2) multipart/form-data encoded
  1455 +
  1456 + The first one is the normal (historical) way of encoding. The second one is required
  1457 + for uploading files. A server which is supposed to accept upload of files must handle
  1458 + both formats. The first thing to do is to decide the format of the request. This is
  1459 + easily done by examining the HTTP headers. If we find the header:
  1460 +
  1461 + Content-Type: multipart/form-data
  1462 +
  1463 + the request is multipart/form-data encoded. Otherwise, it is 'www-url' encoded. We
  1464 + first consider 'www-url' encoded requests.
  1465 +
  1466 + For a 'www-url' encoded request, the web argument are either in the query string or in
  1467 + the body of the request, or both. The format is the same for both:
  1468 +
  1469 + name=value&name=value&...
  1470 +
  1471 + However, we may also have
  1472 +
  1473 + name
  1474 + name=
  1475 + name=&...
  1476 + name&...
  1477 +
  1478 + i.e. some parts may be missing. Hence, we must be careful.
  1479 +
  1480 + Furthermore, web arguments must be translated from web to ASCII when www-url encoded.
  1481 +
  1482 +define Bool
  1483 + is_ampersand_or_equal
  1484 + (
  1485 + Word8 c
  1486 + ) =
  1487 + if c = '&' then true else c = '='.
  1488 +
  1489 +
  1490 +
  1491 + The function 'read_name_or_value' reads the string 's' starting at position 'n' until
  1492 + either the end of the string or the first '&' or '='.
  1493 +
  1494 +define String
  1495 + read_name_or_value
  1496 + (
  1497 + String s,
  1498 + Int start,
  1499 + Int i
  1500 + ) =
  1501 + if nth(i,s) is
  1502 + {
  1503 + failure then substr(s,start,i - start),
  1504 + success(c) then
  1505 + if is_ampersand_or_equal(c)
  1506 + then substr(s,start,i-start) // the separator is not included
  1507 + else read_name_or_value(s,start,i+1)
  1508 + }.
  1509 +
  1510 +
  1511 +define List(Web_arg)
  1512 + read_www_url_encoded_web_args
  1513 + (
  1514 + String s,
  1515 + Int start,
  1516 + ) =
  1517 + with first = read_name_or_value(s,start,start),
  1518 + if first = ""
  1519 + then []
  1520 + else with i = start+length(first),
  1521 + if nth(i,s) is
  1522 + {
  1523 + failure then [web_arg(first,"")],
  1524 + success(c) then
  1525 + if c = '&'
  1526 + then [web_arg(first,"") . read_www_url_encoded_web_args(s,i+1)]
  1527 + else if c = '='
  1528 + then with second1 = read_name_or_value(s,i+1,i+1),
  1529 + // print("\""+second1+"\"\n");
  1530 + with second = web_to_ascii(second1,0,[]),
  1531 + [web_arg(first,second) . read_www_url_encoded_web_args(s,i+length(second1)+2)]
  1532 + else print("**** ALERT **** badly formatted argument [" + s + "]!!!\n");
  1533 + []
  1534 + }.
  1535 +
  1536 +
  1537 +
  1538 +
  1539 +
  1540 + *** [4.7] Reading the request line.
  1541 +
  1542 + 'read_request_line' reads three words and a new line from the connection. It tries to
  1543 + recognize "get" or "post" in the first word, separates the URI from the query string in
  1544 + the second word, transforms the query string into a list of 'Web_arg', and finally
  1545 + returns a datum of type 'HTTP_RequestLine' if no error arose.
  1546 +
  1547 +
  1548 +define Result(Error,HTTP_RequestType)
  1549 + identify_get_or_post
  1550 + (
  1551 + String s
  1552 + ) =
  1553 + with ls = to_lower(s),
  1554 + if ls = "get" then ok(get) else
  1555 + if ls = "post" then ok(post) else
  1556 + error(not_get_or_post_request(ls)).
  1557 +
  1558 +define Result(Error,HTTP_RequestLine)
  1559 + read_request_line
  1560 + (
  1561 + BufferedConnection connection,
  1562 + Int dead_line,
  1563 + DenialOfService dos
  1564 + ) =
  1565 + if read_word(connection,dead_line,dos) is
  1566 + {
  1567 + error(msg) then error(msg),
  1568 + ok(get_or_post) then if read_word(connection,dead_line,dos) is
  1569 + {
  1570 + error(msg) then error(msg),
  1571 + ok(uri_and_query_string) then if read_word(connection,dead_line,dos) is
  1572 + {
  1573 + error(msg) then error(msg),
  1574 + ok(http_version) then if read_new_line(connection,dead_line,dos) is
  1575 + {
  1576 + error(msg) then error(msg),
  1577 + ok(_) then if separate_uri_from_query_string(uri_and_query_string,0) is
  1578 + (uri,query_string) then if identify_get_or_post(get_or_post) is
  1579 + {
  1580 + error(msg) then error(msg),
  1581 + ok(request_type) then
  1582 + ok(request_line(request_type, web_to_ascii(uri, 0, []), read_www_url_encoded_web_args(query_string,0)))
  1583 + }
  1584 + }
  1585 + }
  1586 + }
  1587 + }.
  1588 +
  1589 +
  1590 +
  1591 +
  1592 +
  1593 +
  1594 +
  1595 + *** [4.8] Reading the HTTP headers.
  1596 +
  1597 + Each header is made of a name (containing only letters, the underscore, digits and the
  1598 + minus sign), a colon, a value, and a new line. The first empty line ends the headers.
  1599 +
  1600 +
  1601 + The next function tests characters acceptable in a header name.
  1602 +
  1603 +define Bool
  1604 + is_header_name_char
  1605 + (
  1606 + Word8 c
  1607 + ) =
  1608 + if ('a' +=< c & c +=< 'z') then true else
  1609 + if ('A' +=< c & c +=< 'Z') then true else
  1610 + if ('0' +=< c & c +=< '9') then true else
  1611 + if c = '-' then true else
  1612 + c = '_'.
  1613 +
  1614 +define Result(Error,String)
  1615 + read_header_name
  1616 + (
  1617 + BufferedConnection connection,
  1618 + Int dead_line,
  1619 + List(Word8) so_far,
  1620 + DenialOfService dos
  1621 + ) =
  1622 + if next_char(connection,dead_line,dos) is
  1623 + {
  1624 + error(msg) then error(msg),
  1625 + ok(c) then
  1626 + if is_header_name_char(c)
  1627 + then read_header_name(connection,dead_line,[to_lower(c) . so_far],dos)
  1628 + else unput(c); ok(implode(reverse(so_far)))
  1629 + }.
  1630 +
  1631 +define Result(Error,One)
  1632 + skip_colon
  1633 + (
  1634 + BufferedConnection connection,
  1635 + Int dead_line,
  1636 + DenialOfService dos
  1637 + ) =
  1638 + if skip_http_blanks(connection,dead_line,dos) is
  1639 + {
  1640 + error(msg) then error(msg),
  1641 + ok(_) then
  1642 + if next_char(connection,dead_line,dos) is
  1643 + {
  1644 + error(msg) then error(msg),
  1645 + ok(c) then
  1646 + if c = ':'
  1647 + then ok(unique)
  1648 + else error(colon_expected)
  1649 + }}.
  1650 +
  1651 +
  1652 +define Result(Error,String)
  1653 + read_header_value
  1654 + (
  1655 + BufferedConnection connection,
  1656 + Int dead_line,
  1657 + List(Word8) so_far,
  1658 + DenialOfService dos
  1659 + ) =
  1660 + if next_char(connection,dead_line,dos) is
  1661 + {
  1662 + error(msg) then error(msg),
  1663 + ok(c) then
  1664 + if c = 13
  1665 + then if next_char(connection,dead_line,dos) is
  1666 + {
  1667 + error(msg) then error(msg),
  1668 + ok(d) then
  1669 + if d = 10
  1670 + then if next_char(connection,dead_line,dos) is
  1671 + {
  1672 + error(msg) then error(msg),
  1673 + ok(e) then
  1674 + if is_strict_blank(e)
  1675 + then read_header_value(connection,dead_line,[e . so_far],dos)
  1676 + else (unput(e); ok(implode(reverse(so_far))))
  1677 + }
  1678 + else read_header_value(connection,dead_line,[d, c . so_far],dos)
  1679 + }
  1680 + else read_header_value(connection,dead_line,[c . so_far],dos)
  1681 + }.
  1682 +
  1683 +
  1684 + Reading a single header.
  1685 +
  1686 +define Result(Error,Maybe(HTTP_header))
  1687 + read_header
  1688 + (
  1689 + BufferedConnection connection,
  1690 + Int dead_line,
  1691 + DenialOfService dos
  1692 + ) =
  1693 + if read_header_name(connection,dead_line,[],dos) is
  1694 + {
  1695 + error(msg) then error(msg),
  1696 + ok(name) then
  1697 + if name = "" then
  1698 + if read_and_ignore(connection,dead_line,2,dos) /* 13 and 10 */ is
  1699 + {
  1700 + error(msg) then error(msg),
  1701 + ok(_) then // this is the blank line
  1702 + ok(failure) // end of headers
  1703 + }
  1704 + else if skip_colon(connection,dead_line,dos) is
  1705 + {
  1706 + error(msg) then error(msg),
  1707 + ok(_) then if skip_http_blanks(connection,dead_line,dos) is
  1708 + {
  1709 + error(msg) then error(msg),
  1710 + ok(_) then if read_header_value(connection,dead_line,[],dos) is
  1711 + {
  1712 + error(msg) then error(msg),
  1713 + ok(value) then
  1714 + ok(success(http_header(name,value)))
  1715 + }
  1716 + }
  1717 + }
  1718 + }.
  1719 +
  1720 +
  1721 +
  1722 + Reading all the headers.
  1723 +
  1724 +define Result(Error,List(HTTP_header))
  1725 + read_http_headers
  1726 + (
  1727 + BufferedConnection connection,
  1728 + Int dead_line,
  1729 + DenialOfService dos
  1730 + ) =
  1731 + if read_header(connection,dead_line,dos) is
  1732 + {
  1733 + error(msg) then error(msg),
  1734 + ok(mbh) then if mbh is
  1735 + {
  1736 + failure then ok([ ]),
  1737 + success(header) then
  1738 + if read_http_headers(connection,dead_line,dos) is
  1739 + {
  1740 + error(msg) then error(msg),
  1741 + ok(others) then ok([header . others])
  1742 + }
  1743 + }
  1744 + }.
  1745 +
  1746 +
  1747 +
  1748 +
  1749 +
  1750 +
  1751 +
  1752 + *** [4.9] Getting the size of the request's body.
  1753 +
  1754 + The size of the body of the request is given under the 'Content-Length' header. If this
  1755 + header is not present, the size is assumed to be zero.
  1756 +
  1757 +define Result(Error,Int)
  1758 + get_body_size
  1759 + (
  1760 + List(HTTP_header) headers
  1761 + ) =
  1762 + if headers is
  1763 + {
  1764 + [ ] then ok(0),
  1765 + [h . t] then if h is http_header(name,value) then
  1766 + if name = "content-length"
  1767 + then if decimal_scan(value) is
  1768 + {
  1769 + failure then error(incorrect_content_length_value),
  1770 + success(n) then ok(n)
  1771 + }
  1772 + else get_body_size(t)
  1773 + }.
  1774 +
  1775 +
  1776 +
  1777 +
  1778 +
  1779 +
  1780 +
  1781 +
  1782 +
  1783 +
  1784 + *** [4.10] Reading the body of the request.
  1785 +
  1786 + The body of the request may be very big (it contains uploaded files, if any). We read
  1787 + it using the primitive 'read', which returns the number of bytes read, which may be
  1788 + less than the number of bytes we wanted to read. This is not an error, but simply due
  1789 + to the fact the buffer associated with the connection in the Linux (or MS-Windows)
  1790 + kernel has a limited size. Hence, we must read bytes again until we have read the
  1791 + required number of bytes. However, if the number of bytes read is zero, the connection
  1792 + may be broken. In that case, we must not try to read indefinitely. On the contrary, we
  1793 + make at most 10 retries, with a small sleeping time between any two of them.
  1794 +
  1795 +define Result(Error,ByteArray)
  1796 + read_http_body
  1797 + (
  1798 + BufferedConnection connection,
  1799 + Int body_size,
  1800 + ByteArray so_far, // when calling this function, 'so_far' is the empty byte array
  1801 + Int retries // this function is called with retries = 10
  1802 + ) =
  1803 + if body_size = 0 then ok(constant_byte_array(0,0)) else
  1804 + if retries =< 0 then error(cannot_read_from_connection) else
  1805 + if read_from_connexion(connection,body_size,60,constant_byte_array(body_size,0),0) is
  1806 + {
  1807 + error then error(cannot_read_from_connection),
  1808 + timeout then error(timeout(60)),
  1809 + ok(new_bytes) then with
  1810 + ba = so_far + new_bytes, // contains all the bytes read so far
  1811 + nr = length(ba), // total read since the beginning
  1812 + nn = length(new_bytes), // number of bytes just read
  1813 + if nr < body_size // must read more bytes
  1814 + then if nn > 0 // if connection seems to work
  1815 + then read_http_body(connection,body_size,ba,1000) // continue reading
  1816 + else sleep(100); // otherwise, sleep 1/10 of second
  1817 + read_http_body(connection,body_size,ba, // and retry reading
  1818 + retries-1) // but no more than 10 times
  1819 + else ok(ba) // required number of bytes has been read
  1820 + }.
  1821 +
  1822 +
  1823 + Note: During sleeping, 'anbexec' runs other machines. Actually, calling 'sleep', even
  1824 + for one millisecond, is some way of giving up explicitly, so that other virtual
  1825 + machines may work.
  1826 +
  1827 +
  1828 +
  1829 +
  1830 +
  1831 +
  1832 +
  1833 +
  1834 +
  1835 +
  1836 +
  1837 +
  1838 + *** [5] Making the HTTP answer.
  1839 +
  1840 + At that point we have read the request line, the headers and the body of the
  1841 + request, and we must decide what to do.
  1842 +
  1843 + Actually, we can do one of the following:
  1844 +
  1845 + - send a file,
  1846 + - execute 'tickets_and_web_page' in case of an ".awp" URI.
  1847 +
  1848 + The uploaded file (which are in the body of the request) are saved into temporary files
  1849 + below.
  1850 +
  1851 +
  1852 +
  1853 +
  1854 +
  1855 + *** [5.1] Avoiding illegal URIs.
  1856 +
  1857 + For security reasons, we must avoid illegal URIs, for example those which may climb up
  1858 + in the file hierarchy. First we accept only few characters in URIs.
  1859 +
  1860 +define Bool
  1861 + is_legal_uri_char
  1862 + (
  1863 + Word8 c
  1864 + ) =
  1865 + if ('a' +=< c & c +=< 'z') then true else // accept 'a' to 'z'
  1866 + if ('A' +=< c & c +=< 'Z') then true else // accept 'A' to 'Z'
  1867 + if ('0' +=< c & c +=< '9') then true else // accept '0' to '9'
  1868 + if c = '.' then true else // accept '.' '-' '/' and '_'
  1869 + if c = '-' then true else
  1870 + if c = '/' then true else
  1871 + c = '_'.
  1872 +
  1873 + We do not accept ~ which is some way of climbing. Of course, we cannot disallow single
  1874 + dots, which are most often present in legal URIs, but we must avoid double dots ..
  1875 + which mean 'climb up'.
  1876 +
  1877 +define Bool
  1878 + is_illegal_uri
  1879 + (
  1880 + String uri,
  1881 + Int n
  1882 + ) =
  1883 + if nth(n,uri) is
  1884 + {
  1885 + failure then false,
  1886 + success(c) then
  1887 + if c = '.' // first dot
  1888 + then if nth(n+1,uri) is
  1889 + {
  1890 + failure then false,
  1891 + success(d) then
  1892 + if d = '.' // second dot
  1893 + then true
  1894 + else is_illegal_uri(uri,n+1)
  1895 + }
  1896 + else is_illegal_uri(uri,n+1)
  1897 + }.
  1898 +
  1899 +
  1900 +
  1901 +
  1902 +
  1903 +
  1904 + *** [5.2] Managing authorizations for downloading private files.
  1905 +
  1906 + Computing the authorization and making the authorization file (containing the absolute
  1907 + path of the file on the server).
  1908 +
  1909 +
  1910 +define String
  1911 + compute_authorization
  1912 + (
  1913 + String authorization_secret,
  1914 + String absolute_path
  1915 + ) =
  1916 + to_ascii(sha1((authorization_secret,
  1917 + absolute_path))).
  1918 +
  1919 +
  1920 +public define String
  1921 + make_authorization
  1922 + (
  1923 + String site_directory,
  1924 + String authorization_secret,
  1925 + String absolute_path
  1926 + ) =
  1927 + with private_download_dir = site_directory+"/private_download",
  1928 + auth = compute_authorization(authorization_secret,
  1929 + absolute_path),
  1930 + forget(save(absolute_path,
  1931 + private_download_dir+"/z"+auth));
  1932 + auth.
  1933 +
  1934 +
  1935 + The function 'send_file' defined below handles the recognition of authorizations.
  1936 +
  1937 +
  1938 +
  1939 +
  1940 +
  1941 + *** [5.3] Recognizing MIME types.
  1942 +
  1943 + The extension of the (redirected) URI must be either ".awp" or recognized as associated
  1944 + to a MIME type. Otherwise, the server will not send the file. This is for security, but
  1945 + also because, we must generate a 'Content-Type' header in the answer, with the right
  1946 + MIME type.
  1947 +
  1948 +define String
  1949 + get_uri_extension_aux
  1950 + (
  1951 + String uri,
  1952 + Int n // used for searching backwards
  1953 + ) =
  1954 + if nth(n,uri) is
  1955 + {
  1956 + failure then "",
  1957 + success(c) then
  1958 + if c = '.' then substr(uri,n,length(uri)-n)
  1959 + else if c = '/' then ""
  1960 + else get_uri_extension_aux(uri,n-1)
  1961 + }.
  1962 +
  1963 +public define String
  1964 + get_uri_extension
  1965 + (
  1966 + String uri
  1967 + ) =
  1968 + get_uri_extension_aux(uri,
  1969 + length(uri)-1). // search starts at the right end
  1970 +
  1971 +public define Bool
  1972 + contains_no_case
  1973 + (
  1974 + List(String) l,
  1975 + String val
  1976 + ) =
  1977 + if l is
  1978 + {
  1979 + [] then false,
  1980 + [h . t] then
  1981 + if insensitive_equal(h, val) then true
  1982 + else contains_no_case(t, val)
  1983 + }.
  1984 +
  1985 +
  1986 +define Maybe(MIME)
  1987 + recognize_mime_type_from_ext
  1988 + (
  1989 + String ext,
  1990 + List(MIME) l
  1991 + ) =
  1992 + if l is
  1993 + {
  1994 + [ ] then success(mime("application", "octet-stream", [])), // failure,
  1995 + [h . t] then if h is mime(type, subtype, extensions) then
  1996 + if contains_no_case(extensions, ext)
  1997 + then success(h)
  1998 + else recognize_mime_type_from_ext(ext,t)
  1999 + }.
  2000 +
  2001 +define Maybe(MIME)
  2002 + recognize_mime_type_from_uri
  2003 + (
  2004 + Web_Site_Description desc,
  2005 + String uri
  2006 + ) =
  2007 + recognize_mime_type_from_ext(get_uri_extension(uri),known_mime_types(desc)).
  2008 +
  2009 +
  2010 +
  2011 +
  2012 +
  2013 +
  2014 +
  2015 +
  2016 + *** [5.4] Formating HTTP headers.
  2017 +
  2018 + This is the formating for sending to the client (hence, it has nothing to do with the
  2019 + component 'journal_headers' in the web site description).
  2020 +
  2021 +public define Printable_tree
  2022 + format_headers
  2023 + (
  2024 + List(HTTP_header) headers
  2025 + ) =
  2026 + if headers is
  2027 + {
  2028 + [ ] then [ ],
  2029 + [h . t] then if h is http_header(name,value) then
  2030 + [name,": ",value,crlf . format_headers(t)]
  2031 + }.
  2032 +
  2033 +
  2034 +
  2035 +define String
  2036 + month_abrv
  2037 + (
  2038 + Date_and_Time d
  2039 + ) =
  2040 + if d.month = 1 then "Jan"
  2041 + else if d.month = 2 then "Feb"
  2042 + else if d.month = 3 then "Mar"
  2043 + else if d.month = 4 then "Apr"
  2044 + else if d.month = 5 then "May"
  2045 + else if d.month = 6 then "Jun"
  2046 + else if d.month = 7 then "Jul"
  2047 + else if d.month = 8 then "Aug"
  2048 + else if d.month = 9 then "Sep"
  2049 + else if d.month = 10 then "Oct"
  2050 + else if d.month = 11 then "Nov"
  2051 + else if d.month = 12 then "Dec"
  2052 + else
  2053 + println("Bad month value [" + d.month + "] on Date_and_Time");
  2054 + "XXX".
  2055 +
  2056 +define String
  2057 + weekday_abrv
  2058 + (
  2059 + Date_and_Time d
  2060 + ) =
  2061 + if d.week_day = 0 then "Sun"
  2062 + else if d.week_day = 1 then "Mon"
  2063 + else if d.week_day = 2 then "Tue"
  2064 + else if d.week_day = 3 then "Wed"
  2065 + else if d.week_day = 4 then "Thu"
  2066 + else if d.week_day = 5 then "Fri"
  2067 + else if d.week_day = 6 then "Sat"
  2068 + else
  2069 + println("Bad weekday value [" + d.week_day + "] on Date_and_Time");
  2070 + "XXX".
  2071 +
  2072 +/**
  2073 + * Format a date with the followin format : "Mon, 23 Jul 2007 11:33:43 GMT"
  2074 + * Currently, this function can't output a GMT time, but only local time.
  2075 + * So the final GMT is totally fake, but needed by protocol.
  2076 + */
  2077 +public define String
  2078 + format_http_date
  2079 + (
  2080 + Date_and_Time d
  2081 + ) =
  2082 + weekday_abrv(d) + ", " + zero_pad_n(2,day(d)) + " " + month_abrv(d) + " " + year(d)
  2083 + + " " + zero_pad_n(2,hour(d)) + ":" + zero_pad_n(2,minute(d)) + ":" + zero_pad_n(2,second(d)) + " GMT".
  2084 +
  2085 +/**
  2086 + * Same as previous format_http_date() function, but with seconds count from the UNIX epoch as input.
  2087 + */
  2088 +public define String
  2089 + format_http_date
  2090 + (
  2091 + Int date
  2092 + ) =
  2093 + format_http_date(convert_time(date)).
  2094 +
  2095 +
  2096 + *** [5.5] Sending a file.
  2097 +
  2098 + We send 2 headers 'Content-Type' and 'Content-Length'.
  2099 +
  2100 +define List(HTTP_header)
  2101 + headers_for_send_file
  2102 + (
  2103 + MIME mime_type,
  2104 + Int size,
  2105 + String etag,
  2106 + Maybe(FileTimes) mb_ftimes,
  2107 + ) =
  2108 + with headers = (List(HTTP_header))
  2109 + [
  2110 + http_header("Content-Type", to_String(mime_type)),
  2111 + http_header("Etag", etag),
  2112 + http_header("Content-Length",to_decimal(size)),
  2113 + ],
  2114 + if mb_ftimes is
  2115 + {
  2116 + failure then headers,
  2117 + success(ftimes) then [http_header("Last-Modified", format_http_date(to_Int(ftimes.last_modified))) . headers]
  2118 + }
  2119 + .
  2120 +
  2121 +
  2122 +
  2123 + Sending the body of the answer (i.e. the file itself).
  2124 +
  2125 +define One
  2126 + send_file_body
  2127 + (
  2128 + Web_Site_Description desc,
  2129 + Connection connection, // connection with the client
  2130 + Connection file, // file to be sent already opened
  2131 + Int size, // size of file
  2132 + Int sent, // bytes already sent
  2133 + String filename // name of file
  2134 + ) =
  2135 + if sent >= size then unique else
  2136 + if read(file,min(16384,size-sent),60) is
  2137 + {
  2138 + error then log_journal_msg(desc,"Cannot read from file '"+filename+"'.\n"),
  2139 + timeout then log_journal_msg(desc,"Cannot read from file timeoput'"+filename+"'.\n"),
  2140 + ok(ba) then
  2141 + with nr = length(ba), // get the number of bytes read
  2142 + if reliable_write(connection, ba) is
  2143 + {
  2144 + failure then log_journal_msg(desc,"Cannot write into connection delirering '"+filename+"' (sent="+sent+"; size="+size+"; current="+nr+").\n"),
  2145 + success(nw) then
  2146 + send_file_body(desc,connection,file,size,sent+nw,filename)
  2147 + }
  2148 + }.
  2149 +
  2150 +
  2151 +define String
  2152 + compute_etag
  2153 + (
  2154 + String filename,
  2155 + Maybe(FileTimes) mb_ftimes,
  2156 + Int size,
  2157 + ) =
  2158 + if mb_ftimes is
  2159 + {
  2160 + failure then println("Warning: no file times for '" + filename + "', etag won't be very accurate."); to_ascii(sha1((filename, size))),
  2161 + success(ftimes) then to_ascii(md5((filename, ftimes, size)))
  2162 + }.
  2163 +
  2164 +define Bool
  2165 + are_same_etag
  2166 + (
  2167 + Maybe(String) input_etag,
  2168 + String current_etag
  2169 + ) =
  2170 + if input_etag is
  2171 + {
  2172 + failure then false,
  2173 + success(etag) then etag = current_etag
  2174 + }.
  2175 +
  2176 + Sending the answer line, the headers and the body.
  2177 +
  2178 +define One
  2179 + send_file
  2180 + (
  2181 + Web_Site_Description desc,
  2182 + Connection connection,
  2183 + List(HTTP_header) input_headers,
  2184 + List(HTTP_header) headers,
  2185 + Int size,
  2186 + Connection file,
  2187 + String filename,
  2188 + String full_path,
  2189 + MIME mime_type,
  2190 + One -> One action_before_send_file
  2191 + ) =
  2192 + action_before_send_file(unique);
  2193 + with input_etag = http_header_value(input_headers, "If-None-Match"),
  2194 + mb_ftimes = get_file_times(full_path),
  2195 + current_etag = compute_etag(full_path, mb_ftimes, size),
  2196 + if are_same_etag(input_etag, current_etag) is
  2197 + {
  2198 + false then
  2199 + forget(reliable_write(connection,to_byte_array("HTTP/1.1 200 OK"+crlf)));
  2200 + forget(reliable_write(connection,[format_headers(headers + headers_for_send_file(mime_type, size, current_etag, mb_ftimes)) , crlf]));
  2201 + //forget(copy_file_to_Connection(file, connection, size)),
  2202 + send_file_body(desc,connection,file,size,0,filename),
  2203 + true then
  2204 + forget(reliable_write(connection,to_byte_array("HTTP/1.1 304 Not Modified"+crlf)));
  2205 + forget(reliable_write(connection,[format_headers([http_header("Etag", current_etag) . headers]) , crlf]))
  2206 + //send_file_body(desc,connection,file,size,0,filename)
  2207 + }.
  2208 +
  2209 +
  2210 +
  2211 + Checking if a connection is under SSL.
  2212 +
  2213 +define Bool
  2214 + is_SSL
  2215 + (
  2216 + Connection c
  2217 + ) =
  2218 + if c is
  2219 + {
  2220 + file_r(_) then false,
  2221 + file_w(_) then false,
  2222 + file_rw(_) then false,
  2223 + tcp(_) then false,
  2224 + ssl(_) then true
  2225 + }.
  2226 +
  2227 +
  2228 +
  2229 + Before opening and sending a file, we check the MIME type. It must be recognized,
  2230 + except if there is a valid authorization for private download.
  2231 +
  2232 +define One
  2233 + send_file
  2234 + (
  2235 + Web_Site_Description desc,
  2236 + Connection connection,
  2237 + String uri,
  2238 + List(HTTP_header) input_headers,
  2239 + List(HTTP_header) output_headers,
  2240 + Maybe(String) mbauthorization,
  2241 + One -> One action_before_send_file
  2242 + ) =
  2243 + if mbauthorization is
  2244 + {
  2245 + //--- file without authorization: take it from public ---
  2246 + failure then if recognize_mime_type_from_uri(desc,uri) is
  2247 + {
  2248 + failure then log_journal_msg(desc,"No MIME type found for '"+uri+"'.\n"),
  2249 + success(mime_type) then
  2250 + with path = site_directory(desc)+"/public"+uri,
  2251 + if (Maybe(RStream))file(path, read) is
  2252 + {
  2253 + failure then log_journal_msg(desc,"Cannot find file '"+path+"'.\n"),
  2254 + success(f) then with size = file_size(f),
  2255 + send_file(desc,
  2256 + connection,
  2257 + input_headers,
  2258 + output_headers,
  2259 + size,
  2260 + file(f),
  2261 + uri,
  2262 + path,
  2263 + mime_type,
  2264 + action_before_send_file)
  2265 + }
  2266 + },
  2267 +
  2268 + //--- file with authorization: apply 'private download' mecanism ---
  2269 + success(authorization) then
  2270 + with private_download_dir = site_directory(desc)+"/private_download",
  2271 + if (RetrieveResult(String))retrieve(private_download_dir+"/z"+authorization)
  2272 + is ok(absolute_path)
  2273 + then (
  2274 + with new_hash = compute_authorization(authorization_secret(desc),
  2275 + absolute_path),
  2276 + if (Maybe(RStream))file(absolute_path, read) is
  2277 + {
  2278 + failure then log_journal_msg(desc,"Cannot find file '"+absolute_path+"'.\n"),
  2279 + success(f) then with size = file_size(f),
  2280 + mime_type = if recognize_mime_type_from_uri(desc,uri) is
  2281 + {
  2282 + failure then mime("application", "octet-stream", []),
  2283 + success(mime_type) then mime_type
  2284 + },
  2285 + send_file(desc,
  2286 + connection,
  2287 + input_headers,
  2288 + output_headers,
  2289 + size,
  2290 + file(f),
  2291 + uri,
  2292 + absolute_path,
  2293 + mime_type,
  2294 + action_before_send_file)
  2295 + }
  2296 + )
  2297 + else log_journal_msg(desc,"Cannot find or read authorization file.\n")
  2298 + }.
  2299 +
  2300 +
  2301 +
  2302 +
  2303 +
  2304 +
  2305 +
  2306 +
  2307 + *** [5.6] Answering a www-url encoded request.
  2308 +
  2309 + Standard headers are for answering ".awp" requests.
  2310 +
  2311 +public define List(HTTP_header)
  2312 + standard_headers
  2313 + =
  2314 + [
  2315 + http_header("Date", format_http_date(now)),
  2316 + http_header("Server", "Anubis Embedded Server v" + major_version_number + "." + minor_version_number),
  2317 + http_header("Connection", "close"),
  2318 + ].
  2319 +
  2320 +public define List(HTTP_header)
  2321 + standard_headers_for
  2322 + (
  2323 + String mime_type,
  2324 + Int answer_body_size,
  2325 + Maybe(String) mb_charset,
  2326 + ) =
  2327 + [
  2328 + http_header("Content-Type", mime_type + if mb_charset is success(charset) then "; charset="+charset else ""),
  2329 + http_header("Content-length", to_decimal(answer_body_size))
  2330 + ].
  2331 +
  2332 +public define List(HTTP_header)
  2333 + file_attached_header
  2334 + (
  2335 + String filename
  2336 + ) =
  2337 + [
  2338 + http_header("Content-Disposition", "attachment; filename=\"" + filename +"\"")
  2339 + ].
  2340 +
  2341 +
  2342 +define One
  2343 + www_url_answer
  2344 + (
  2345 + String host_name,
  2346 + Web_Site_Description desc,
  2347 + Connection connection, // with the client
  2348 + Word32 ip_addr, // of the client
  2349 + HTTP_RequestLine request_line,
  2350 + List(HTTP_header) headers,
  2351 + ByteArray body,
  2352 + One -> String generate_tt // trust ticket generation
  2353 + ) =
  2354 + with all_web_args = query_string(request_line) +
  2355 + read_www_url_encoded_web_args(to_string(body),0),
  2356 + uri = uri(request_line),
  2357 + ext = get_uri_extension(uri),
  2358 + http_inf = http_info(ip_addr, host_name, uri, headers, is_SSL(connection), generate_tt),
  2359 + (if member(journal_extensions(desc),ext)
  2360 + then log_journal_msg(desc,
  2361 + format_request(desc,connection,request_line,headers,all_web_args))
  2362 + else unique);
  2363 + if is_illegal_uri(uri,0)
  2364 + then log_journal_msg(desc,"Received illegal URI: "+uri+"\n")
  2365 + else (if (ext = ".awp" | ext = "")
  2366 + then (with answer_headers_body = awp_handler(desc)(host_name,
  2367 + http_inf,
  2368 + all_web_args,
  2369 + is_SSL(connection)),
  2370 + //print_delta("After page generation");
  2371 + forget(reliable_write(connection, answer_headers_body))
  2372 + //print_delta("After sending page")
  2373 + )
  2374 + else (send_file(desc,
  2375 + connection,
  2376 + uri,
  2377 + headers,
  2378 + standard_headers,
  2379 + if web_arg_value(all_web_args,"zauth") is
  2380 + {
  2381 + not_found then failure,
  2382 + found(v) then success(v)
  2383 + },
  2384 + (One u) |-> before_send_file(desc)(http_inf, all_web_args))
  2385 + //print_delta("After sending file")
  2386 + )).
  2387 +
  2388 +
  2389 +
  2390 +
  2391 +
  2392 +
  2393 +
  2394 + *** [5.7] Answering a multipart/form-data encoded request.
  2395 +
  2396 + In order to support upload of files, we must be able to read web arguments which are
  2397 + encoded in a multipart/form-data body. The first thing to do is to find the
  2398 + boundary. The boundary is a special string which delimits the various parts of the
  2399 + 'multipart' body. It is found within the value of the 'Content-Type' HTTP header, as
  2400 + the value of the 'boundary' attribute.
  2401 +
  2402 +
  2403 +
  2404 +
  2405 +
  2406 + *** [5.7.1] Finding the boundary.
  2407 +
  2408 + Hence, we just have to find the string 'boundary=' within the value of the
  2409 + 'Content-Type' header, and read the value of the boundary from there.
  2410 +
  2411 +define Bool
  2412 + delimits_boundary
  2413 + (
  2414 + Word8 c
  2415 + ) =
  2416 + if c = ' ' then true else
  2417 + if c = 13 then true else
  2418 + if c = 10 then true else
  2419 + if c = 0 then true else
  2420 + if c = ',' then true else
  2421 + c = ';'.
  2422 +
  2423 +
  2424 +define Maybe(String)
  2425 + get_boundary_value_3
  2426 + (
  2427 + String s,
  2428 + Int i,
  2429 + List(Word8) so_far
  2430 + ) =
  2431 + if nth(i,s) is
  2432 + {
  2433 + failure then success(implode(reverse(so_far))),
  2434 + success(c) then
  2435 + if delimits_boundary(c)
  2436 + then success(implode(reverse(so_far)))
  2437 + else get_boundary_value_3(s,i+1,[c . so_far])
  2438 + }.
  2439 +
  2440 +
  2441 +
  2442 +define Maybe(String)
  2443 + get_boundary_value_2
  2444 + (
  2445 + String s,
  2446 + Int i,
  2447 + ) =
  2448 + if nth(i,s) is
  2449 + {
  2450 + failure then failure,
  2451 + success(c) then
  2452 + if is_blank(c)
  2453 + then get_boundary_value_2(s,i+1)
  2454 + else get_boundary_value_3(s,i+1,[c])
  2455 + }.
  2456 +
  2457 +define Maybe(String)
  2458 + get_boundary_value_1
  2459 + (
  2460 + String s, // string into which we must find '= ...'
  2461 + Int i // position of start of search
  2462 + ) =
  2463 + if nth(i,s) is
  2464 + {
  2465 + failure then failure,
  2466 + success(c) then
  2467 + if is_blank(c)
  2468 + then get_boundary_value_1(s,i+1)
  2469 + else if c = '='
  2470 + then get_boundary_value_2(s,i+1)
  2471 + else failure
  2472 + }.
  2473 +
  2474 +
  2475 +define Maybe(String)
  2476 + get_boundary
  2477 + (
  2478 + String content_type_header_value
  2479 + ) =
  2480 + if find("boundary",content_type_header_value,0) is
  2481 + {
  2482 + failure then failure,
  2483 + success(n) then // 'boundary' has been found at position n
  2484 + get_boundary_value_1(content_type_header_value,n+8)
  2485 + }.
  2486 +
  2487 +define Maybe(String)
  2488 + get_boundary
  2489 + (
  2490 + List(HTTP_header) headers
  2491 + ) =
  2492 + if headers is
  2493 + {
  2494 + [ ] then failure,
  2495 + [h . t] then if h is http_header(name,value) then
  2496 + if name = "content-type"
  2497 + then get_boundary(value)
  2498 + else get_boundary(t)
  2499 + }.
  2500 +
  2501 +
  2502 +
  2503 +
  2504 +
  2505 +
  2506 +
  2507 +
  2508 + *** [5.7.2] Reading attributes from a multipart entity.
  2509 +
  2510 + Entities in a multipart/form-data body are separated by instances of the string:
  2511 +
  2512 + --bbbbb
  2513 +
  2514 + where bbbbb is the boundary computed above. Actually, the body has the form:
  2515 +
  2516 + --bbbbb
  2517 + <entity 1>
  2518 + --bbbbb
  2519 + <entity 2>
  2520 + --bbbbb
  2521 + ...
  2522 + --bbbbb
  2523 + <last entity>
  2524 + --bbbbb
  2525 +
  2526 +
  2527 + We have to extract an entity which is in the body between offsets 'start' and 'end'
  2528 + (computed when boundaries have been localized). The entity itself is made of two parts:
  2529 + headers and body. The body is separated from the headers by a blank line. This blank
  2530 + line (a double crlf) marks the beginning of the body of the entity. Within the headers
  2531 + of the entity, we look for a 'Content-Disposition' header, which should look like this:
  2532 +
  2533 + Content-Disposition: form-data; name="..."; filename="..." crlf
  2534 +
  2535 + We are just interested in the name and the file name. Hence we first search
  2536 + 'Content-Disposition', then we search 'name' and read the value, and we do the same for
  2537 + 'filename'.
  2538 +
  2539 + If the 'filename' attribute is not present, the web arg is an ordinary one, otherwise,
  2540 + it is an uploaded file.
  2541 +
  2542 +
  2543 + Below is a variant of 'find' (see 'tools/findstring.anubis'), with an extra 'end'
  2544 + argument.
  2545 +
  2546 +define Maybe(Int)
  2547 + find
  2548 + (
  2549 + String what,
  2550 + ByteArray where,
  2551 + Int start,
  2552 + Int end
  2553 + ) =
  2554 + if find(to_byte_array(what),where,start) is
  2555 + {
  2556 + failure then failure,
  2557 + success(n) then
  2558 + if n+length(what) >= end
  2559 + then failure
  2560 + else success(n)
  2561 + }.
  2562 +
  2563 +
  2564 +define String
  2565 + read_attribute_value
  2566 + (
  2567 + ByteArray where,
  2568 + Int start,
  2569 + Int end,
  2570 + List(Word8) so_far
  2571 + ) =
  2572 + if start >= end then implode(reverse(so_far)) else
  2573 + if nth(start,where) is
  2574 + {
  2575 + failure then implode(reverse(so_far)),
  2576 + success(c) then
  2577 + if c = '\"'
  2578 + then implode(reverse(so_far))
  2579 + else read_attribute_value(where,start+1,end,[c . so_far])
  2580 + }.
  2581 +
  2582 +define Maybe(String)
  2583 + find_attribute
  2584 + (
  2585 + String name,
  2586 + ByteArray where,
  2587 + Int start,
  2588 + Int end
  2589 + ) =
  2590 + with prefix = name+"=\"",
  2591 + if find(to_byte_array(prefix),where,start) is
  2592 + {
  2593 + failure then failure,
  2594 + success(n) then
  2595 + if n+length(prefix) >= end
  2596 + then failure
  2597 + else success(read_attribute_value(where,n+length(prefix),end,[]))
  2598 + }.
  2599 +
  2600 +
  2601 +
  2602 +define Maybe((String,Maybe(String)))
  2603 + find_name_and_filename
  2604 + (
  2605 + ByteArray body,
  2606 + Int start,
  2607 + Int end
  2608 + ) =
  2609 + if find(to_byte_array("Content-Disposition"),body,start) is
  2610 + {
  2611 + failure then failure,
  2612 + success(n) then
  2613 + if find_attribute("name",body,n+19,end) is
  2614 + {
  2615 + failure then failure,
  2616 + success(name_value) then if find_attribute("filename",body,n+19,end) is
  2617 + {
  2618 + failure then success((name_value,failure)),
  2619 + success(filename_value) then success((name_value,success(filename_value)))
  2620 + }
  2621 + }
  2622 + }.
  2623 +
  2624 +
  2625 +
  2626 +
  2627 +
  2628 +
  2629 +
  2630 +
  2631 +
  2632 +
  2633 + *** [5.7.3] Creating a temporary filename for an uploaded file.
  2634 +
  2635 +variable Int uploaded_file_count = 0.
  2636 +
  2637 + This variable is local to the virtual machine. Hence, its value is 0 each time a new
  2638 + requests arrives. Temporary uploaded files are stored in the directory represented by
  2639 + 'upload_temporary_directory'. The filenames have the form:
  2640 +
  2641 + _m_n
  2642 +
  2643 + where 'm' is the number of the virtual machine, and 'n' a number obtained by
  2644 + incrementing 'uploaded_file_count'. Notice that the program must do something with this
  2645 + file (move it to some directory/name), otherwise, it will probably be overwritten the
  2646 + next time the same machine works.
  2647 +
  2648 +
  2649 +
  2650 +
  2651 +
  2652 +
  2653 + *** [5.7.4] Saving an uploaded file under a temporary filename.
  2654 +
  2655 +define Maybe(String) // returns the temporary file name
  2656 + save_uploaded_file
  2657 + (
  2658 + Web_Site_Description desc,
  2659 + ByteArray body,
  2660 + Int start,
  2661 + Int end
  2662 + ) =
  2663 + uploaded_file_count <- 1 + *uploaded_file_count;
  2664 + with tfn = "_"+to_decimal(virtual_machine_id)+"_"+to_decimal(*uploaded_file_count),
  2665 + if (Maybe(RWStream))file(site_directory(desc)+"/upload_temporary/"+tfn, new) is
  2666 + {
  2667 + failure then failure,
  2668 + success(f) then
  2669 + if reliable_write(file(f),extract(body,start,end)) is
  2670 + {
  2671 + failure then failure,
  2672 + success(nw) then
  2673 + if nw = end - start
  2674 + then success(tfn)
  2675 + else failure
  2676 + }
  2677 + }.
  2678 +
  2679 +
  2680 +
  2681 +
  2682 +
  2683 +
  2684 +
  2685 +
  2686 + *** [5.7.5] Removing the path from a file name.
  2687 +
  2688 + When a file is uploaded, the browser sends the complete path of the file on the client
  2689 + machine as the file name. Actually, this is not quite normal. Nevertheless, we need to
  2690 + remove the path, and keep only the file name. This is achieved by 'remove_path' below.
  2691 +
  2692 +define Int
  2693 + file_name_begin
  2694 + (
  2695 + String full_name,
  2696 + Int i
  2697 + ) =
  2698 + if nth(i,full_name) is
  2699 + {
  2700 + failure then 0,
  2701 + success(c) then
  2702 + if c = '/' then i+1 else
  2703 + if c = '\\' then i+1 else
  2704 + file_name_begin(full_name,i-1)
  2705 + }.
  2706 +
  2707 +define String
  2708 + remove_path
  2709 + (
  2710 + String full_name
  2711 + ) =
  2712 + with l = length(full_name),
  2713 + b = file_name_begin(full_name,l-1),
  2714 + substr(full_name,b,l-b).
  2715 +
  2716 +
  2717 +
  2718 +
  2719 +
  2720 + *** [5.7.6] Reading a multipart entity.
  2721 +
  2722 +define Maybe(Web_arg)
  2723 + get_multipart_entity
  2724 + (
  2725 + Web_Site_Description desc,
  2726 + ByteArray body,
  2727 + Int start,
  2728 + Int end
  2729 + ) =
  2730 + if find(to_byte_array(crlf+crlf),body,start) is
  2731 + {
  2732 + failure then failure,
  2733 + success(k) then
  2734 + if k >= end // must be within this entity, not the next one
  2735 + then failure
  2736 + else if find_name_and_filename(body,start,k) is
  2737 + {
  2738 + failure then failure,
  2739 + success(n_mbfn) then if n_mbfn is (name,mbfn) then
  2740 + if mbfn is
  2741 + {
  2742 + failure then
  2743 + success(web_arg(name,to_string(extract(body,k+4,end-2)))),
  2744 + // we must substract 2 to end because of crlf just before the boundary
  2745 +
  2746 + success(fn) then
  2747 + if save_uploaded_file(desc,body,k+4,end-2) is
  2748 + {
  2749 + failure then failure,
  2750 + success(tfn) then
  2751 + success(upload(name,remove_path(fn),
  2752 + site_directory(desc)+"/upload_temporary/"+tfn))
  2753 +
  2754 + }
  2755 + }
  2756 + }
  2757 + }.
  2758 +
  2759 +
  2760 +
  2761 +define List(Web_arg)
  2762 + read_multipart_form_data_encoded_web_args
  2763 + (
  2764 + Web_Site_Description desc,
  2765 + ByteArray body,
  2766 + ByteArray __boundary,
  2767 + Int i,
  2768 + ) =
  2769 + if find(__boundary,body,i) is
  2770 + {
  2771 + failure then [ ],
  2772 + success(n) then
  2773 + if find(__boundary,body,n+length(__boundary)) is
  2774 + {
  2775 + failure then [ ],
  2776 + success(m) then
  2777 + if get_multipart_entity(desc,body,n+length(__boundary),m) is
  2778 + {
  2779 + failure then [ ],
  2780 + success(wa) then
  2781 + [wa . read_multipart_form_data_encoded_web_args(desc,body,__boundary,m)]
  2782 + }
  2783 + }
  2784 + }.
  2785 +
  2786 +
  2787 +
  2788 +define One
  2789 + multipart_form_data_answer
  2790 + (
  2791 + String host_name,
  2792 + Web_Site_Description desc,
  2793 + Connection connection,
  2794 + Word32 ip_addr,
  2795 + HTTP_RequestLine request_line,
  2796 + List(HTTP_header) headers,
  2797 + ByteArray body,
  2798 + One -> String generate_tt
  2799 + ) =
  2800 + if get_boundary(headers) is
  2801 + {
  2802 + failure then unique,
  2803 + success(boundary) then
  2804 + with all_web_args = query_string(request_line) +
  2805 + read_multipart_form_data_encoded_web_args(desc,
  2806 + body,
  2807 + to_byte_array("--"+boundary),
  2808 + 0),
  2809 + uri = uri(request_line),
  2810 + ext = get_uri_extension(uri),
  2811 + log_journal_msg(desc,
  2812 + format_request(desc,connection,request_line,headers,all_web_args));
  2813 + if is_illegal_uri(uri,0)
  2814 + then log_journal_msg(desc,"Received illegal URI: "+uri+"\n")
  2815 + else
  2816 + if (ext = ".awp" | ext = "") then
  2817 + (with answer_headers_body = awp_handler(desc)(host_name,
  2818 + http_info(ip_addr, host_name, uri, headers, is_SSL(connection), generate_tt),
  2819 + all_web_args,
  2820 + is_SSL(connection)),
  2821 + forget(reliable_write(connection, answer_headers_body)))
  2822 + else unique
  2823 + }.
  2824 +
  2825 +
  2826 +
  2827 +
  2828 +
  2829 +
  2830 +
  2831 +
  2832 + *** [5.8] Handling redirections.
  2833 +
  2834 + 'redirections' (of type 'List(Redirection)') contains redirection directives. Each one
  2835 + has the form:
  2836 +
  2837 + redirect(required_uri,required_host,corresponding_uri).
  2838 +
  2839 + The host required by the client may be found in the 'Host' HTTP header. The URI
  2840 + required by the client is given below as 'uri'. We just have to find the required host
  2841 + in the headers, and to find the corresponding redirection directive.
  2842 +
  2843 +
  2844 + In the next fonction, the required host and URI are known. We just have to search in
  2845 + the 'redirections' list.
  2846 +
  2847 +define String
  2848 + handle_redirection
  2849 + (
  2850 + String required_uri,
  2851 + String required_host,
  2852 + List(Redirection) redirections
  2853 + ) =
  2854 + if redirections is
  2855 + {
  2856 + [ ] then required_uri,
  2857 + [h . t] then if h is redirect(uri,host,target) then
  2858 + if host = required_host
  2859 + then if uri = required_uri
  2860 + then target
  2861 + else handle_redirection(required_uri,required_host,t)
  2862 + else handle_redirection(required_uri,required_host,t)
  2863 + }.
  2864 +
  2865 +
  2866 +
  2867 + The host name may be encumbered by a port number, like
  2868 +
  2869 + www.our-business.com:1607
  2870 +
  2871 + We must remove this port number, otherwise the host name may not be recognized.
  2872 +
  2873 +define String
  2874 + strip_port
  2875 + (
  2876 + String name,
  2877 + Int i
  2878 + ) =
  2879 + if nth(i,name) is
  2880 + {
  2881 + failure then name,
  2882 + success(c) then
  2883 + if c = ':'
  2884 + then substr(name,0,i)
  2885 + else strip_port(name,i+1)
  2886 + }.
  2887 +
  2888 +
  2889 +
  2890 +
  2891 +
  2892 + Finding the 'Host' header. No redirection is performed if this header is not found.
  2893 +
  2894 +define String
  2895 + handle_redirection // returns the redirected URI
  2896 + (
  2897 + Redirections redirections,
  2898 + String uri, // original URI
  2899 + List(HTTP_header) headers
  2900 + )=
  2901 + if get_host_header_value(headers) is
  2902 + {
  2903 + failure then uri,
  2904 + success(host) then
  2905 + if redirections is
  2906 + {
  2907 + redirection_list(l) then handle_redirection(uri, host, l)
  2908 + redirection_fn(f) then f(uri, host)
  2909 + }
  2910 + }.
  2911 +
  2912 +
  2913 +
  2914 +
  2915 +
  2916 +
  2917 + *** [5.9] Answering both sorts of requests.
  2918 +
  2919 + We must decide if the request is www-url encoded or multipart/form-data encoded. This
  2920 + is achieved through the header 'Content-Type'.
  2921 +
  2922 +define EncodingType
  2923 + get_encoding_type
  2924 + (
  2925 + List(HTTP_header) headers
  2926 + ) =
  2927 + if headers is
  2928 + {
  2929 + [ ] then www_url, // this is the default
  2930 + [h . t] then if h is http_header(name,value) then
  2931 + if name = "content-type"
  2932 + then if find("multipart/form-data",value,0) is
  2933 + {
  2934 + failure then www_url,
  2935 + success(_) then multipart_form_data
  2936 + }
  2937 + else get_encoding_type(t)
  2938 + }.
  2939 +
  2940 +
  2941 +
  2942 +define One
  2943 + send_answer
  2944 + (
  2945 + String host_name,
  2946 + Web_Site_Description desc,
  2947 + Connection connection,
  2948 + HTTP_RequestLine rqline,
  2949 + List(HTTP_header) headers,
  2950 + ByteArray body,
  2951 + One -> String generate_tt
  2952 + ) =
  2953 + if rqline is request_line(type,uri,qstring) then
  2954 + with rqline2 = request_line(type,handle_redirection(redirections(desc),uri,headers),qstring),
  2955 + if remote_IP_address_and_port(connection) is (ip_addr,_) then
  2956 + if get_encoding_type(headers) is
  2957 + {
  2958 + www_url then
  2959 + www_url_answer(host_name,desc,connection,ip_addr,rqline2,headers,body,generate_tt),
  2960 + multipart_form_data then
  2961 + multipart_form_data_answer(host_name,desc,connection,ip_addr,rqline2,headers,body,generate_tt)
  2962 + }.
  2963 +
  2964 +
  2965 +
  2966 +
  2967 +
  2968 +
  2969 +
  2970 + *** [6] The HTTP/HTTPS server.
  2971 +
  2972 + The command 'start_server' (declared in 'predefined.anubis') starts a virtual machine
  2973 + which opens a server TCP/IP connection, and which continuously listens to this
  2974 + connection. When a request arrives, this machine delegates the work of deciphering and
  2975 + answering the request to another virtual machine, and continues to listen. The job of
  2976 + the delegated machine is defined by the HTTP request handler below.
  2977 +
  2978 +
  2979 +
  2980 +
  2981 +
  2982 + *** [6.1] Determining the requested host.
  2983 +
  2984 + When a request arrives to one of our two servers, we must decide which site (host) is
  2985 + requested.
  2986 +
  2987 +define Maybe(String)
  2988 + get_host_header_value
  2989 + (
  2990 + List(HTTP_header) headers
  2991 + ) =
  2992 + if headers is
  2993 + {
  2994 + [ ] then failure,
  2995 + [h . t] then if h is http_header(name,value) then
  2996 + if name = "host"
  2997 + then success(strip_port(value,0))
  2998 + else get_host_header_value(t)
  2999 + }.
  3000 +
  3001 +define Maybe((String,Web_Site_Description))
  3002 + get_site
  3003 + (
  3004 + String requested_host,
  3005 + List(Web_Site_Description) sites
  3006 + ) =
  3007 + if sites is
  3008 + {
  3009 + [ ] then print("Requested host '"+requested_host+"' does not exist.\n"); failure,
  3010 + [site1 . others] then
  3011 + if site1 is web_site_description(common_names,_,_,_,_,_,_,_,_,_) then
  3012 + if member(common_names,requested_host)
  3013 + then success((requested_host,site1))
  3014 + else get_site(requested_host,others)
  3015 + }.
  3016 +
  3017 +
  3018 +define Maybe((String,Web_Site_Description))
  3019 + get_site
  3020 + (
  3021 + List(HTTP_header) headers,
  3022 + List(Web_Site_Description) sites
  3023 + ) =
  3024 + if get_host_header_value(headers) is
  3025 + {
  3026 + failure then print("No 'Host' HTTP header.\n"); failure,
  3027 + success(requested_host) then
  3028 + //here we treat the case with only one site. hence we accept any host request
  3029 + //print("*** there is " +length(sites) + " sites \n");
  3030 + if length(sites) = 1 then
  3031 + //with site = force_nth(0, sites),
  3032 + if sites is
  3033 + {
  3034 + [] then get_site(requested_host,sites),
  3035 + [site . t] then success((requested_host, site))
  3036 + }
  3037 + else
  3038 + get_site(requested_host,sites)
  3039 + }.
  3040 +
  3041 +
  3042 +
  3043 +
  3044 +
  3045 + *** [6.2] The HTTP request handler.
  3046 +
  3047 + Here is the HTTP/HTTPS handler. It is called at each new request in a separate virtual
  3048 + machine. It reads the headers of the HTTP request, determines the host, determines body
  3049 + size, reads the body of the HTTP request, and answers the request.
  3050 +
  3051 +
  3052 +
  3053 +define One -> String make_generate_trust_ticket(DenialOfService dos).
  3054 +
  3055 +define One
  3056 + http_https_handler
  3057 + (
  3058 + List(Web_Site_Description) sites,
  3059 + BufferedConnection connection,
  3060 + Bool is_https,
  3061 + DenialOfService dos
  3062 + ) =
  3063 + //t0 <- (UTime)unow;
  3064 + with start_time = (Int)now,
  3065 + sttm <- start_time;
  3066 + //println("Request time: " + format_http_date(start_time));
  3067 + if dos is denial_of_service(mc_v,rld_v,hd_v,ad_v,ld_v,ra_v) then
  3068 + if remote_IP_address_and_port(connection.conn) is (ip_addr,port) then
  3069 + if read_request_line(connection,start_time+*rld_v,dos) is
  3070 + {
  3071 + error(msg) then print(format(msg)),
  3072 + ok(request_line) then
  3073 + //print_delta("read_request_line");
  3074 + if read_http_headers(connection,start_time+*hd_v,dos) is
  3075 + {
  3076 + error(msg) then print(format(msg)),
  3077 + ok(headers) then //print_delta("read_http_headers");
  3078 + if get_site(headers,sites) is
  3079 + {
  3080 + failure then unique,
  3081 + success(p) then if p is (host_name,desc) then
  3082 + //print_delta("get_site");
  3083 + if get_body_size(headers) is
  3084 + {
  3085 + error(msg) then log_journal_msg(desc,format(msg)),
  3086 + ok(body_size) then
  3087 + //print_delta("get_body_size");
  3088 + if read_http_body(connection,body_size,constant_byte_array(0,0),1000) is
  3089 + {
  3090 + error(msg) then log_journal_msg(desc,format(msg)),
  3091 + ok(body) then
  3092 + //print_delta("before send_answer");
  3093 + send_answer(host_name, desc,connection.conn, request_line, headers, body,
  3094 + make_generate_trust_ticket(dos))
  3095 + //with duration = (UTime) unow - *t0,
  3096 + //println("Request duration: " + __utime_to_string(duration))
  3097 + //println("BufferRead duration: " + __utime_to_string(*t1));
  3098 + //println("next_char duration: " + __utime_to_string(*t2))
  3099 + }
  3100 + }
  3101 + }
  3102 + }
  3103 + }.
  3104 +
  3105 +
  3106 + Below are the two tools for constructing the handlers required by 'start_server' and
  3107 + 'start_ssl_server' (see 'predefined.anubis').
  3108 +
  3109 +define Bool is_dubious_IP(Word32 ip, DenialOfService dos).
  3110 +
  3111 +define Server -> ((RWStream) -> One)
  3112 + make_http_handler
  3113 + (
  3114 + List(Web_Site_Description) sites,
  3115 + DenialOfService dos
  3116 + ) =
  3117 + (Server server) |-> (RWStream conn) |->
  3118 + if remote_IP_address_and_port(conn) is (addr,_) then
  3119 + if is_dubious_IP(addr,dos)
  3120 + then print("Rejecting dubious IP address "+ip_addr_to_string(addr)+"\n")
  3121 + else
  3122 + with connection = buffered_connection(tcp(conn), var(constant_byte_array(0, 0)), var(0)),
  3123 + http_https_handler(sites, connection, false, dos).
  3124 +
  3125 +define Server -> (SSL_Connection -> One)
  3126 + make_https_handler
  3127 + (
  3128 + List(Web_Site_Description) sites,
  3129 + DenialOfService dos
  3130 + ) =
  3131 + (Server server) |-> (SSL_Connection conn) |->
  3132 + with connection = buffered_connection(ssl(conn), var(constant_byte_array(0, 0)), var(0)),
  3133 + http_https_handler(sites, connection, true, dos).
  3134 +
  3135 +
  3136 +
  3137 +
  3138 + *** [6.3] Server's tasks.
  3139 +
  3140 + Some tasks must be executed periodically, for example for cleaning up directories from
  3141 + short life time files.
  3142 +
  3143 + The next function removes from the given directory (and recursively from its
  3144 + subdirectories) all the files which are more than 10 minutes old.
  3145 +
  3146 +define One
  3147 + cleanup_directory_10mn
  3148 + (
  3149 + String dir // path of private download directory (or subdirectory) with trailing slash
  3150 + ) =
  3151 + forget(map((FileDescription fd) |-> if fd is
  3152 + {
  3153 + no_info(name) then forget(remove(dir+name)),
  3154 + file(name,_,_,d) then if to_Int(d)+600 < now then forget(remove(dir+name)) else unique,
  3155 + link(name,_,_,d) then if to_Int(d)+600 < now then forget(remove(dir+name)) else unique,
  3156 + directory(name,_,_) then cleanup_directory_10mn(dir+name+"/"),
  3157 + },
  3158 + directory_full_list(dir,"*","*","*"))).
  3159 +
  3160 +
  3161 +define One
  3162 + http_servers_tasks
  3163 + (
  3164 + List(Web_Site_Description) sites,
  3165 + List(Server) servers,
  3166 + Int period,
  3167 + Int next_time,
  3168 + ) =
  3169 + if mapand(is_down,servers)
  3170 + then unique
  3171 + else if now > next_time
  3172 + then
  3173 + (
  3174 + /*
  3175 + forget(map((Web_Site_Description wsd) |->
  3176 + cleanup_directory_10mn(site_directory(wsd)+"/private_download/"),
  3177 + sites));
  3178 + */
  3179 + http_servers_tasks(sites,servers,period,next_time+period)
  3180 + )
  3181 + else
  3182 + (
  3183 + sleep(1000);
  3184 + http_servers_tasks(sites,servers,period,next_time)
  3185 + ).
  3186 +
  3187 +
  3188 +public define One
  3189 + start_http_servers_tasks
  3190 + (
  3191 + List(Web_Site_Description) sites,
  3192 + List(Server) servers,
  3193 + Int period
  3194 + ) =
  3195 + delegate http_servers_tasks(sites,servers,period,now),
  3196 + unique.
  3197 +
  3198 +
  3199 +
  3200 +
  3201 + *** [6.4] Protection against 'denial of service' attacks.
  3202 +
  3203 +
  3204 + *** [6.4.1] Counting connections.
  3205 +
  3206 +define Bool // returns false if the counter cannot be incremented (too many connections)
  3207 + increment_connections_counter
  3208 + (
  3209 + Var(Int) counter
  3210 + ) =
  3211 + protect with n = *counter,
  3212 + if n >= 100
  3213 + then false
  3214 + else (counter <- (*counter)+1); true.
  3215 +
  3216 +define One
  3217 + decrement_connections_counter
  3218 + (
  3219 + Var(Int) counter
  3220 + ) =
  3221 + protect counter <- (*counter)-1.
  3222 +
  3223 +
  3224 +
  3225 +
  3226 +
  3227 + *** [6.4.2] Recording dubious IP addresses.
  3228 +
  3229 +
  3230 +define List(DubiousIP)
  3231 + record_dubious_IP
  3232 + (
  3233 + Word32 ip,
  3234 + List(DubiousIP) l
  3235 + ) =
  3236 + if l is
  3237 + {
  3238 + [ ] then [dubious_ip(ip,now)],
  3239 + [h . t] then if h is dubious_ip(addr,time) then
  3240 + if addr = ip
  3241 + then [dubious_ip(addr,now) . t]
  3242 + else [h . record_dubious_IP(ip,t)]
  3243 + }.
  3244 +
  3245 +
  3246 +define One
  3247 + record_dubious_IP
  3248 + (
  3249 + Word32 dubious_IP,
  3250 + Var(List(DubiousIP)) v
  3251 + ) =
  3252 + protect v <- record_dubious_IP(dubious_IP,*v).
  3253 +
  3254 +
  3255 +define One
  3256 + record_dubious_IP
  3257 + (
  3258 + Word32 addr,
  3259 + DenialOfService dos
  3260 + ) =
  3261 + record_dubious_IP(addr,list_of_dubious(dos)).
  3262 +
  3263 +
  3264 +public define DenialOfService
  3265 + load_denial_of_service_info
  3266 + =
  3267 + if (RetrieveResult(DenialOfService))retrieve(my_anubis_directory+"/web_sites/dos_info") is
  3268 + ok(dos) then dos else denial_of_service(
  3269 + var(100),
  3270 + var(1000),
  3271 + var(1500),
  3272 + var(2000),
  3273 + var([]),
  3274 + var([])).
  3275 +
  3276 +
  3277 +
  3278 +
  3279 + *** [6.4.3] Testing if an address is dubious.
  3280 +
  3281 +define Bool
  3282 + is_dubious_IP
  3283 + (
  3284 + Word32 ip,
  3285 + List(DubiousIP) l
  3286 + ) =
  3287 + if l is
  3288 + {
  3289 + [ ] then false,
  3290 + [h . t] then if h is dubious_ip(addr,time) then
  3291 + if ip = addr
  3292 + then true
  3293 + else is_dubious_IP(ip,t)
  3294 + }.
  3295 +
  3296 +
  3297 +define Bool
  3298 + is_dubious_IP
  3299 + (
  3300 + Word32 ip,
  3301 + DenialOfService dos
  3302 + ) =
  3303 + if dos is
  3304 + {
  3305 + denial_of_service(mc_v,rld_v,hd_v,ad_v,ld_v,ra_v) then
  3306 + if member(*ra_v,ip) then false else
  3307 + is_dubious_IP(ip,*ld_v)
  3308 + }.
  3309 +
  3310 +
  3311 +
  3312 +
  3313 + *** [6.4.4] Removing inactive dubious IP addresses.
  3314 +
  3315 +define List(DubiousIP)
  3316 + remove_inactive_dubious_IP
  3317 + (
  3318 + List(DubiousIP) l,
  3319 + Int ref_time,
  3320 + ) =
  3321 + if l is
  3322 + {
  3323 + [ ] then [ ],
  3324 + [h . t] then if h is dubious_ip(addr,time) then
  3325 + if time < ref_time
  3326 + then (print(ip_addr_to_string(addr)+" removed from dubious addresses list.\n");
  3327 + remove_inactive_dubious_IP(t,ref_time))
  3328 + else [h . remove_inactive_dubious_IP(t,ref_time)]
  3329 + }.
  3330 +
  3331 +define One
  3332 + remove_inactive_dubious_IP
  3333 + (
  3334 + Var(List(DubiousIP)) v
  3335 + ) =
  3336 + protect
  3337 + with ref_time = (Int)now - 600, // 10 minutes
  3338 + v <- remove_inactive_dubious_IP(*v,ref_time).
  3339 +
  3340 +
  3341 + The above function will be executed periodically by the servers's tasks machine.
  3342 +
  3343 +
  3344 +
  3345 + *** [6.4.5] Making the function for generating trust tickets.
  3346 +
  3347 +define One -> String
  3348 + make_generate_trust_ticket
  3349 + (
  3350 + DenialOfService dos
  3351 + ) =
  3352 + (One _) |-> "".
  3353 +
  3354 +
  3355 +
  3356 +
  3357 +
  3358 +
  3359 +
  3360 + *** [6.5] Starting the HTTP/HTTPS server.
  3361 +
  3362 +
  3363 + The next function creates the directories for all sites (if they don't already exist).
  3364 +
  3365 +define One
  3366 + create_directories
  3367 + (
  3368 + List(Web_Site_Description) sites
  3369 + ) =
  3370 + if sites is
  3371 + {
  3372 + [ ] then unique,
  3373 + [s1 . others] then
  3374 + with site_dir = site_directory(s1),
  3375 + forget(make_directory(site_dir+"/public",default_directory_mode));
  3376 + forget(make_directory(site_dir+"/upload_temporary",default_directory_mode));
  3377 + forget(make_directory(site_dir+"/private_download",default_directory_mode));
  3378 + forget(make_directory(site_dir+"/journal",default_directory_mode));
  3379 + create_directories(others)
  3380 + }.
  3381 +
  3382 +
  3383 +
  3384 +
  3385 +
  3386 + Below are the commands for starting an HTTP server and an HTTPS server.
  3387 +
  3388 +
  3389 +define StartServerResult
  3390 + start_http_server
  3391 + (
  3392 + Word32 ip_address,
  3393 + Word32 port,
  3394 + Server -> ((RWStream) -> One) handler,
  3395 + Int retries,
  3396 + DenialOfService dos
  3397 + ) =
  3398 + if start_server(ip_address,
  3399 + port,
  3400 + handler,
  3401 + identity) is ok(server)
  3402 + then print(" \r");
  3403 + ok(server)
  3404 + else print("Port "+port+": retry number "+retries+"\r");
  3405 + sleep(1000);
  3406 + start_http_server(ip_address,port,handler,retries+1,dos).
  3407 +
  3408 +public define StartServerResult
  3409 + start_http_server
  3410 + (
  3411 + Word32 ip_address,
  3412 + Word32 port,
  3413 + List(Web_Site_Description) sites,
  3414 + DenialOfService dos
  3415 + ) =
  3416 + create_directories(sites);
  3417 + start_http_server(ip_address,port,
  3418 + make_http_handler(sites,dos),
  3419 + 0,
  3420 + dos).
  3421 +
  3422 +
  3423 + For the HTTPS server, we have a problem which is due to the fact that 'anbexec' is not
  3424 + yet able to manipulate several SSL server certificates. 'anbexec' and
  3425 + 'predefined.anubis' must be changed. Sorry ! This will be done as soon as possible. The
  3426 + 'solution' for the time being is to provide the common name of the unique SSL server
  3427 + certificate.
  3428 +
  3429 +
  3430 +define StartServerResult
  3431 + start_https_server
  3432 + (
  3433 + Word32 ip_address,
  3434 + Word32 port,
  3435 + String certificate_common_name,
  3436 + Server -> (SSL_Connection -> One) handler,
  3437 + Int retries,
  3438 + DenialOfService dos
  3439 + ) =
  3440 + if start_ssl_server(ip_address,
  3441 + port,
  3442 + certificate_common_name,
  3443 + handler,
  3444 + identity) is ok(server)
  3445 + then print(" \r");
  3446 + ok(server)
  3447 + else print("Port "+port+": retry number "+retries+"\r");
  3448 + sleep(1000);
  3449 + start_https_server(ip_address,port,
  3450 + certificate_common_name,
  3451 + handler,retries+1,
  3452 + dos).
  3453 +
  3454 +
  3455 +public define StartServerResult
  3456 + start_https_server
  3457 + (
  3458 + Word32 ip_address,
  3459 + Word32 port,
  3460 + String certificate_common_name, // of SSL server certificate
  3461 + List(Web_Site_Description) sites,
  3462 + DenialOfService dos
  3463 + ) =
  3464 + create_directories(sites);
  3465 + start_https_server(ip_address,port,certificate_common_name,
  3466 + make_https_handler(sites,dos),
  3467 + 0,dos).
  3468 +
  3469 +
  3470 +
  3471 +
  3472 +
  3473 +
  3474 +
  3475 +
  3476 +
  3477 + *** [7] The web dispatcher.
  3478 +
  3479 +
  3480 + *** [7.1] The dispatcher server.
  3481 +
  3482 +define One
  3483 + send_dispatching_page
  3484 + (
  3485 + RWStream conn,
  3486 + String common_name,
  3487 + Word32 port
  3488 + ) =
  3489 + print("Dispatching '"+common_name+"' to port "+port+"\n");
  3490 + forget(reliable_write(conn,to_byte_array(
  3491 + "<html><head><meta http-equiv=\"Refresh\" content=\"0;URL="+
  3492 + "http://"+common_name+":"+port+"/"+
  3493 + "\"></head><body></body></html>"
  3494 + ))).
  3495 +
  3496 +
  3497 +
  3498 +define Maybe(DispatcherInfo)
  3499 + find_host
  3500 + (
  3501 + List(DispatcherInfo) l,
  3502 + String host
  3503 + ) =
  3504 + if l is
  3505 + {
  3506 + [ ] then failure,
  3507 + [h . t] then if h is site(name,port) then
  3508 + if name = host
  3509 + then success(h)
  3510 + else find_host(t,host)
  3511 + }.
  3512 +
  3513 +
  3514 +
  3515 +define Server -> ((RWStream) -> One)
  3516 + make_dispatcher_handler
  3517 + (
  3518 + Var(List(DispatcherInfo)) info_v,
  3519 + DenialOfService dos
  3520 + ) =
  3521 + (Server server) |-> (RWStream conn) |->
  3522 + with start_time = (Int)now,
  3523 + connection = buffered_connection(tcp(conn), var(constant_byte_array(0, 0)), var(0)),
  3524 + if read_request_line(connection, start_time+*request_line_delay(dos), dos) is
  3525 + {
  3526 + error(msg) then print(format(msg)),
  3527 + ok(request_line) then
  3528 + if read_http_headers(connection, start_time+*headers_delay(dos), dos) is
  3529 + {
  3530 + error(msg) then print(format(msg)),
  3531 + ok(headers) then if get_host_header_value(headers) is
  3532 + {
  3533 + failure then print("No 'HOST' HTTP header.\n"),
  3534 + success(host) then
  3535 + if find_host(*info_v,host) is
  3536 + {
  3537 + failure then print("Host: '"+host+"' not registered.\n"),
  3538 + success(s) then if s is site(common_name,ip_port) then
  3539 + send_dispatching_page(conn,common_name,ip_port)
  3540 + }
  3541 + }
  3542 + }
  3543 + }.
  3544 +
  3545 +
  3546 +define One
  3547 + dispatcher_update_error
  3548 + (
  3549 + String file_path
  3550 + ) =
  3551 + print("web_dispatcher: unable to reread file: '"+file_path+"'.\n").
  3552 +
  3553 +
  3554 +define Bool
  3555 + dispatcher_update_data
  3556 + (
  3557 + String info_file_path,
  3558 + Var(List(DispatcherInfo)) info_v,
  3559 + Var(Int) info_date_v
  3560 + ) =
  3561 + if directory_full_list(my_anubis_directory+"/web_sites","dispatcher.info","","") is
  3562 + {
  3563 + [ ] then false,
  3564 + [h . t] then if h is
  3565 + {
  3566 + no_info(n) then false,
  3567 + file(n,_,_,d) then if n = "dispatcher.info"
  3568 + then (info_date_v <- to_Int(d);
  3569 + if (RetrieveResult(List(DispatcherInfo)))retrieve(info_file_path) is
  3570 + {
  3571 + cannot_find_file then false,
  3572 + read_error then false,
  3573 + type_error then false,
  3574 + ok(info) then info_v <- info; true
  3575 + })
  3576 + else false,
  3577 + link(_,_,_,_) then false,
  3578 + directory(_,_,_) then false
  3579 + }
  3580 + }.
  3581 +
  3582 +
  3583 +
  3584 + The loop within which the dispatcher updates its data every 3 seconds:
  3585 +
  3586 +define One
  3587 + dispatcher_update_task
  3588 + (
  3589 + String info_file_path,
  3590 + Var(List(DispatcherInfo)) info_v,
  3591 + Var(Int) info_date_v
  3592 + ) =
  3593 + sleep(3000);
  3594 + (if dispatcher_update_data(info_file_path,info_v,info_date_v)
  3595 + then unique
  3596 + else dispatcher_update_error(info_file_path));
  3597 + dispatcher_update_task(info_file_path,info_v,info_date_v).
  3598 +
  3599 +
  3600 +public define One
  3601 + start_web_dispatcher
  3602 + (
  3603 + Word32 ip_address, // address for listening (typically 0: listen on all interfaces)
  3604 + Word32 http_port, // typically 80
  3605 + DenialOfService dos
  3606 + ) =
  3607 + with info_file_path = my_anubis_directory+"/web_sites/dispatcher.info",
  3608 + info_v = var((List(DispatcherInfo))[]),
  3609 + info_date_v = var((Int)0),
  3610 + if dispatcher_update_data(info_file_path,info_v,info_date_v)
  3611 + then if start_server(ip_address,
  3612 + http_port,
  3613 + make_dispatcher_handler(info_v,dos),
  3614 + (One u)|->u) is
  3615 + {
  3616 + cannot_create_the_socket then
  3617 + print("Cannot create the socket for HTTP server.\n"),
  3618 + cannot_bind_to_port then
  3619 + print("Cannot bind HTTP server to port "+http_port+".\n"),
  3620 + cannot_listen_on_port then
  3621 + print("HTTP server cannot listen on port "+http_port+".\n"),
  3622 + ok(http_server) then
  3623 + dispatcher_update_task(info_file_path,info_v,info_date_v)
  3624 + }
  3625 + else dispatcher_update_error(info_file_path).
  3626 +
  3627 +
  3628 +
  3629 + *** [7.2] The dispatcher web site.
  3630 +
  3631 + global define One
  3632 + web_dispatcher
  3633 + (
  3634 + List(String) args
  3635 + ) =
  3636 + start_web_dispatcher(0,80,load_denial_of_service_info).
  3637 +
  3638 +
  3639 +
  3640 +
  3641 +
  3642 +
  3643 + *** [7.3] Managing the info file.
  3644 +
  3645 +define Word32
  3646 + register_ip_address
  3647 + =
  3648 + if ip_address(prompt(" numerical IP address (for HTTP): ")) is
  3649 + {
  3650 + failure then print(" *** Error: incorrect IP address.\n");
  3651 + register_ip_address,
  3652 + success(n) then n
  3653 + }.
  3654 +
  3655 +
  3656 +define Word32
  3657 + register_ip_port
  3658 + =
  3659 + if decimal_scan(prompt(" IP port (for HTTP): ")) is
  3660 + {
  3661 + failure then print(" *** Error: incorrect IP port.\n");
  3662 + register_ip_port,
  3663 + success(p) then if (0 =< p & p =< 65535)
  3664 + then truncate_to_Word32(p)
  3665 + else print(" *** Error: IP port out of bounds.\n");
  3666 + register_ip_port
  3667 + }.
  3668 +
  3669 +
  3670 +define One
  3671 + register_new_site
  3672 + (
  3673 + Var(List(DispatcherInfo)) info_v
  3674 + ) =
  3675 + print("\n");
  3676 + print(" Registering a new site:\n");
  3677 + with name = prompt(" Site name: "),
  3678 + with addr = register_ip_address,
  3679 + with port = register_ip_port,
  3680 + (protect info_v <- [site(name,port) . *info_v]);
  3681 + print(" Site "+name+" at "+ip_addr_to_string(addr)+":"+port+" added\n (but not saved to disk).\n").
  3682 +
  3683 +
  3684 +define List(DispatcherInfo)
  3685 + find_sites
  3686 + (
  3687 + List(DispatcherInfo) l,
  3688 + String name
  3689 + ) =
  3690 + if l is
  3691 + {
  3692 + [ ] then [ ],
  3693 + [h . t] then if h is site(n,_) then
  3694 + if find(name,n,0) is
  3695 + {
  3696 + failure then find_sites(t,name),
  3697 + success(_) then [h . find_sites(t,name)]
  3698 + }
  3699 + }.
  3700 +
  3701 +
  3702 +define String
  3703 + pad
  3704 + (
  3705 + String s,
  3706 + Int l
  3707 + ) =
  3708 + if length(s) >= l
  3709 + then s
  3710 + else s+constant_string(l-length(s),' ').
  3711 +
  3712 +
  3713 +
  3714 +define One
  3715 + show_sites_1
  3716 + (
  3717 + List(DispatcherInfo) l,
  3718 + Int i
  3719 + ) =
  3720 + if l is
  3721 + {
  3722 + [ ] then unique,
  3723 + [h . t] then if h is site(name,port) then
  3724 + print(" ["+i+"] "+pad(name,40)+" "+" "+port+"\n");
  3725 + show_sites_1(t,i+1)
  3726 + }.
  3727 +
  3728 +
  3729 +define One
  3730 + show_sites
  3731 + (
  3732 + List(DispatcherInfo) l,
  3733 + Int i
  3734 + ) =
  3735 + print(" Name Port\n");
  3736 + print(" --------------------------------------------------------\n");
  3737 + show_sites_1(l,i).
  3738 +
  3739 +define List(DispatcherInfo)
  3740 + replace_info
  3741 + (
  3742 + List(DispatcherInfo) l,
  3743 + String site_name,
  3744 + Word32 new_port
  3745 + ) =
  3746 + if l is
  3747 + {
  3748 + [ ] then print("ALERT: Empty list into replace_info() [" + __FILE__ + "]\n"); [],
  3749 + [h . t] then if h is site(n,_) then
  3750 + if n = site_name
  3751 + then [site(n,new_port) . t]
  3752 + else [h . replace_info(t,site_name,new_port)]
  3753 + }.
  3754 +
  3755 +define List(DispatcherInfo)
  3756 + delete_info
  3757 + (
  3758 + List(DispatcherInfo) l,
  3759 + String site_name,
  3760 + ) =
  3761 + if l is
  3762 + {
  3763 + [ ] then print("ALERT: Empty list into delete_info() [" + __FILE__ + "]\n"); [],
  3764 + [h . t] then if h is site(n,_) then
  3765 + if n = site_name
  3766 + then t
  3767 + else [h . delete_info(t,site_name)]
  3768 + }.
  3769 +
  3770 +
  3771 +define One
  3772 + update_site
  3773 + (
  3774 + Var(List(DispatcherInfo)) info_v,
  3775 + String site_name,
  3776 + Word32 old_port
  3777 + ) =
  3778 + print("\n");
  3779 + print(" Updating site '"+site_name+"': (currently: "+old_port+")\n");
  3780 + with new_port = register_ip_port,
  3781 + answer = prompt(" Update '"+site_name+"' as: "+new_port+" [Y/N] ? "),
  3782 + if (answer = "Y" | answer = "y")
  3783 + then info_v <- replace_info(*info_v,site_name,new_port)
  3784 + else unique.
  3785 +
  3786 +
  3787 +
  3788 +define Bool
  3789 + compare
  3790 + (
  3791 + DispatcherInfo d1,
  3792 + DispatcherInfo d2
  3793 + ) =
  3794 + if d1 is site(n1,_) then
  3795 + if d2 is site(n2,_) then
  3796 + string_less(n1,n2).
  3797 +
  3798 +
  3799 +
  3800 +define One
  3801 + update_site
  3802 + (
  3803 + Var(List(DispatcherInfo)) info_v
  3804 + ) =
  3805 + print("\n");
  3806 + with prefix = prompt(" Search for site to update: "),
  3807 + if find_sites(*info_v,prefix) is
  3808 + {
  3809 + [ ] then print(" No site found.\n");
  3810 + update_site(info_v),
  3811 + [h . t] then
  3812 + show_sites(qsort([h . t],compare),1);
  3813 + with i1 = prompt(" Choose a site to update [1/.../"+(length(t)+1)+"]: "),
  3814 + if decimal_scan(i1) is
  3815 + {
  3816 + failure then print(" *** Error: site number not recognized.\n");
  3817 + update_site(info_v),
  3818 + success(ii1) then if nth(ii1-1,*info_v) is
  3819 + {
  3820 + failure then print(" *** Error: site number "+i1+" does not exist.\n");
  3821 + update_site(info_v),
  3822 + success(site_info) then if site_info is site(name,old_port) then
  3823 + update_site(info_v,name,old_port)
  3824 + }
  3825 + }
  3826 + }.
  3827 +
  3828 +
  3829 +define One
  3830 + delete_site
  3831 + (
  3832 + Var(List(DispatcherInfo)) info_v,
  3833 + String site_name,
  3834 + Word32 old_port
  3835 + ) =
  3836 + print("\n");
  3837 + print(" Deleting site '"+site_name+"': (currently: "+old_port+")\n");
  3838 + with answer = prompt(" Are you sure you want to delete site: '"+site_name+"' [Y/N] ? "),
  3839 + if (answer = "Y" | answer = "y")
  3840 + then info_v <- delete_info(*info_v,site_name)
  3841 + else print(" Site '"+site_name+"' not deleted.\n").
  3842 +
  3843 +
  3844 +define One
  3845 + delete_site
  3846 + (
  3847 + Var(List(DispatcherInfo)) info_v
  3848 + ) =
  3849 + print("\n");
  3850 + with prefix = prompt(" Search for site to delete: "),
  3851 + if find_sites(*info_v,prefix) is
  3852 + {
  3853 + [ ] then print(" No site found.\n");
  3854 + delete_site(info_v),
  3855 + [h . t] then
  3856 + show_sites(qsort([h . t],compare),1);
  3857 + with i1 = prompt(" Choose a site to delete [1/.../"+(length(t)+1)+"]: "),
  3858 + if decimal_scan(i1) is
  3859 + {
  3860 + failure then print(" *** Error: site number not recognized.\n");
  3861 + delete_site(info_v),
  3862 + success(ii1) then if nth(ii1-1,*info_v) is
  3863 + {
  3864 + failure then print(" *** Error: site number "+i1+" does not exist.\n");
  3865 + delete_site(info_v),
  3866 + success(site_info) then if site_info is site(name,old_port) then
  3867 + delete_site(info_v,name,old_port)
  3868 + }
  3869 + }
  3870 + }.
  3871 +
  3872 +
  3873 +define One
  3874 + manager
  3875 + (
  3876 + Var(List(DispatcherInfo)) info_v,
  3877 + String file_path
  3878 + ) =
  3879 + print("\n");
  3880 + print(" --- Welcome to the Web Dispatcher Manager ---\n");
  3881 + with l = length(*info_v),
  3882 + print(" "+l+" site"+(if l>1 then "s" else "")+" currently registred.\n");
  3883 + print(" [L] List registered sites.\n");
  3884 + print(" [R] Register a new site.\n");
  3885 + print(" [U] Update a registred site.\n");
  3886 + print(" [D] Delete a registred site.\n");
  3887 + with propose_write_v = var((Bool)true),
  3888 + action = prompt(" Choose an action [L/R/U/D]: "),
  3889 + (if (action = "L" | action = "l") then (show_sites(*info_v,1); propose_write_v <- false) else
  3890 + if (action = "R" | action = "r") then register_new_site(info_v) else
  3891 + if (action = "U" | action = "u") then update_site(info_v) else
  3892 + if (action = "D" | action = "d") then delete_site(info_v) else
  3893 + print("Action not recognized.\n"));
  3894 + print("\n");
  3895 + if *propose_write_v then
  3896 + with result = prompt(" Write modifications to data base [Y/N] ?"),
  3897 + if (result = "Y" | result = "y")
  3898 + then if save(*info_v,file_path) is
  3899 + {
  3900 + cannot_open_file then print(" File '"+file_path+"' not found.\n"),
  3901 + write_error then print(" Error while writing file '"+file_path+"'.\n"),
  3902 + ok then print(" Data base has been modified.\n")
  3903 + }
  3904 + else print(" Data base not modified.\n")
  3905 + else unique.
  3906 +
  3907 +
  3908 +
  3909 +global define One
  3910 + manage_web_dispatcher
  3911 + (
  3912 + List(String) args
  3913 + ) =
  3914 + with info_v = var((List(DispatcherInfo))[]),
  3915 + with file_path = my_anubis_directory+"/web_sites/dispatcher.info",
  3916 + if (RetrieveResult(List(DispatcherInfo)))retrieve(file_path) is
  3917 + {
  3918 + cannot_find_file then print("File '"+file_path+"' does not exist.\n");
  3919 + with answer = prompt("Create it [Y/N] ? "),
  3920 + if (answer = "Y" | answer = "y")
  3921 + then if save((List(DispatcherInfo))[],file_path) is
  3922 + {
  3923 + cannot_open_file then
  3924 + print("Cannot create file '"+file_path+"'.\n"),
  3925 + write_error then
  3926 + print("Error while creating file '"+file_path+"'.\n"),
  3927 + ok then manager(info_v,file_path)
  3928 + }
  3929 + else unique,
  3930 + read_error then print("Error while reading file '"+file_path+"'.\n"),
  3931 + type_error then print("File '"+file_path+"' is corrupted.\n"),
  3932 + ok(info) then info_v <- info;
  3933 + manager(info_v,file_path)
  3934 + }.
  3935 +
  3936 +
  3937 +
  3938 +
  3939 +
  3940 +public define (String, List(HTTP_header))
  3941 + format
  3942 + (
  3943 + HTTP_Status status
  3944 + ) =
  3945 + if status is
  3946 + {
  3947 + http_continue then ("100 Continue", []),
  3948 + http_switching_protocol then ("101 Switching Protocols", []),
  3949 +
  3950 + http_ok then ("200 OK", []),
  3951 + http_created then ("201 Created", []),
  3952 + http_accepted then ("202 Accepted", []),
  3953 + http_non_authoritative_info then ("203 Non-Authoritative Information", []),
  3954 + http_no_content then ("204 No Content", []),
  3955 + http_reset_content then ("205 Reset Content", []),
  3956 + http_partial_content then ("206 Partial Content", []),
  3957 +
  3958 + http_multiple_choices then ("300 Multiple Choices", []),
  3959 + http_moved_permanently(loc) then ("301 Moved Permanently", [http_header("Location", loc)]),
  3960 + http_moved_temporarily(loc) then ("302 Moved Temporarily", [http_header("Location", loc)]),
  3961 + http_see_other(loc) then ("303 See Other", [http_header("Location", loc)]),
  3962 + http_not_modified then ("304 Not Modified", []),
  3963 + http_use_proxy(loc) then ("305 Use Proxy", [http_header("Location", loc)]),
  3964 + http_temporary_redirect(loc) then ("307 Temporary Redirect", [http_header("Location", loc)]),
  3965 +
  3966 + http_bad_request then ("400 Bad Request", []),
  3967 + http_unauthorized then ("401 Unauthorized", []),
  3968 + http_payment_required then ("402 Payment Required", []),
  3969 + http_forbidden then ("403 Forbidden", []),
  3970 + http_not_found then ("404 Not Found", []),
  3971 + http_method_not_allowed then ("405 Method Not Allowed", []),
  3972 + http_not_acceptable then ("406 Not Acceptable", []),
  3973 + http_proxy_authentification_required then ("407 Proxy Authentication Required", []),
  3974 + http_request_timeout then ("408 Request Time-out", []),
  3975 + http_conflict then ("409 Conflict", []),
  3976 + http_gone then ("410 Gone", []),
  3977 + http_length_required then ("411 Length Required", []),
  3978 + http_precondition_failed then ("412 Precondition Failed", []),
  3979 + http_request_entity_too_large then ("413 Request Entity Too Large", []),
  3980 + http_request_uri_too_long then ("414 Request-URI Too Long", []),
  3981 + http_unsupported_media_type then ("415 Unsupported Media Type", []),
  3982 + http_request_range_unsatisfiable then ("416 Requested range unsatisfiable", []),
  3983 + http_expectation_failed then ("417 Expectation failed", []),
  3984 +
  3985 + http_internal_server_error then ("500 Internal Server Error", []),
  3986 + http_not_implemented then ("501 Not Implemented", []),
  3987 + http_bad_gateway then ("502 Bad Gateway", []),
  3988 + http_service_unavailable then ("503 Service Unavailable", []),
  3989 + http_gateway_timeout then ("504 Gateway Time-out", []),
  3990 + http_version_not_supported then ("505 HTTP Version not supported", []),
  3991 +
  3992 + http_error(code, message) then (abs_to_decimal(code) + " " + message, [])
  3993 + }.
  3994 +
  3995 +