Commit 8adc07dfc2e7fd7e617e803991fbe0cfeb9d39b6

Authored by Cédric RICARD
1 parent df3ae302

Deleting some 'alert' calls

calexium_lib/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 -  
41 - ---------------------------------------------------------------------------------------  
42 -  
43 -  
44 -  
45 -  
46 - *** (1) Multihosting and redirections.  
47 -  
48 - This HTTP/HTTPS server can handle several host (also called 'virtual hosts'), in other  
49 - words, you may have several sites on the same server, with the same IP address and same  
50 - port numbers, but distinct 'host names'.  
51 -  
52 - A HTTP request sent by a browser contains the following informations:  
53 -  
54 - - a 'host name',  
55 - - an URI (Uniform Resource Identifier),  
56 - - HTTP headers,  
57 - - web arguments (in the form 'name=value').  
58 -  
59 - Actually, the host name is just the value of the HTTP header whose name is 'Host'. The  
60 - host name indicates which site is requested. Hence, it is the primary information for  
61 - branching to the right site. If there is no 'Host' HTTP header in the request, the  
62 - request is denied.  
63 -  
64 - From now on, we may assume that the host is determined, and consequently that we are  
65 - concerned by only one site. Each site has his own directories on the server's  
66 - disk.  
67 -  
68 - Each site also has a list of 'redirections'. A redirection is a triplet, like this one:  
69 -  
70 - redirect("/", "www.our-business.com", "/homepage.awp")  
71 -  
72 - meaning that if the host is "www.our-business.com", and if the requested URI is "/",  
73 - then the URI to be served is "/homepage.awp". 'redirect' is a constructor of the type  
74 - 'Redirection' defined in 'web/common.anubis'.  
75 -  
76 - Now, an URI may end by ".awp" (meaning 'Anubis Web Page') or not. If it does, the  
77 - server understands that an HTML page must be constructed on the fly, and to that end it  
78 - calls the 'awp handler' of the site. Otherwise, the URI must end by a known extension,  
79 - like ".jpg", ".png", ".txt", etc... and represents a file path relative to the  
80 - 'public' directory of the site. If these conditions are satisfied, the file is sent to  
81 - the client. Known extensions are recorded in 'web/mime.anubis'.  
82 -  
83 -  
84 -  
85 -  
86 - *** (2) The incompatibility between SSL and virtual hosts.  
87 -  
88 - Handling virtual hosts makes a problem under SSL (i.e. when using HTTPS), which is due  
89 - to the fact that the guys at Netscape who designed SSL probably did not have the  
90 - question of virtual hosts in mind. Indeed, the SSL handshake is completed before the  
91 - server can know about the value of the 'Host' HTTP header, so that it cannot know which  
92 - server certificate must be sent to the client. This makes a problem, because the  
93 - browser will not accept a certificate whose common name does not correspond to the name  
94 - of the requested host. The user will have to accept the certificate manually, which is  
95 - not good for the security image of the site. This problem has at least two solutions  
96 - (as far as Anubis is concerned).  
97 -  
98 - Solution 1. Arrange so that the network interface on which the server is listening  
99 - has at least as many different IP addresses as you have virtual hosts. Such  
100 - supplementary IP addresses are called 'IP Aliases'. In this case, start one HTTPS  
101 - server for each virtual host, each one listening on a different address. For the time  
102 - being, this method is applicable under Anubis only if you start as many instances of  
103 - 'anbexec' as you have virtual hosts, because each instance of 'anbexec' can handle only  
104 - one server certificate. Of course, getting IP aliases is another problem to be solved  
105 - with your Internet provider.  
106 -  
107 - Solution 2. We propose a simple solution, using only one server certificate (hence  
108 - only one instance of 'anbexec'). Since, we have only one server certificate, we must  
109 - introduce a notion of 'main host', i.e. a host containing all other 'virtual  
110 - hosts'. The unique server certificate belong to the main host, so that only the main  
111 - host is identified by the client. The client must trust the main host and be confident  
112 - that the main host redirects him to the right virtual host. Actually, the process will  
113 - be transparent to the client, except that the client will see the name of the main host  
114 - instead of the name of the virtual host in the 'location' field of the browser.  
115 -  
116 - So, assume that the name of main host is 'www.securedhost.com', and that the names of  
117 - the virtual hosts are:  
118 -  
119 - actual name simplified name  
120 - -----------------------------------------------------  
121 - www.virtual1.com virtual1  
122 - www.virtual2.com virtual2  
123 - www.virtual3.com virtual3  
124 -  
125 - Then the (confidential) document '/doc/my_document.pdf' on 'www.virtual2.com' will have  
126 - the URL:  
127 -  
128 - https://www.securedhost.com/virtual2/doc/my_document.pdf  
129 -  
130 - In order to work transparently, this solution must combine HTTP and HTTPS. Indeed, the  
131 - vitual host must have a first page reachable under HTTP, through the URL:  
132 -  
133 - http://www.virtual2.com/  
134 -  
135 - The HTTP server will redirect this URL to the awp handler of virtual host 'virtual2'.  
136 - The handler of this virtual host is able to generate a first page containing the  
137 - following HTML meta:  
138 -  
139 - <meta http-equiv="Refresh" content="0;URL=https://www.securedhost.com/virtual2/">,  
140 -  
141 - so that the client is immediately redirected to the main host under HTTPS (hence  
142 - accepting tranparently the server certificate). The awp handler of 'virtual2' then  
143 - redirects this URL to the home page (maybe a login page) of 'virtual2'.  
144 -  
145 - See 'web/making_a_web_site.anubis' for the sequel of this story.  
146 -  
147 -  
148 -  
149 -  
150 -  
151 - *** (3) HTTP headers and web arguments.  
152 -  
153 - Each HTTP request which arrives on the server contains a request line followed by a  
154 - series of HTTP headers. Each HTTP header is a pair '(name,value)' assigning a value to  
155 - a name. The type 'HTTP_header' is defined in 'web/common.anubis'.  
156 -  
157 - The request may also have a 'body'. The body contains either 'web arguments' or  
158 - uploaded files (or both). The request line itself may also contain web arguments (in a  
159 - so-called 'query string'). Like HTTP headers, 'web arguments' are pairs  
160 - '(name,value)', but the difference is that these pairs are generated by the page within  
161 - which the client clicks, while HTTP headers are generated by the browser itself. The  
162 - type 'Web_arg' is defined in 'web/common.anubis'. It has two alternatives, one for  
163 - ordinary web arguments (pairs) and one for uploaded files.  
164 -  
165 -read CXM_common.anubis  
166 -read tools/basis.anubis  
167 -read system/string.anubis  
168 -read CXM_mime.anubis  
169 -  
170 -  
171 -  
172 - *** (4) Site descriptions.  
173 -  
174 - The type HTTP_Info gathers informations comming along with the client's request. These  
175 - informations are rarely used for composing HTML pages. Nevertheless, they are at your  
176 - disposal.  
177 -  
178 -public type HTTP_Info:  
179 - http_info  
180 - (  
181 - Int32 ip_address, // IP address of the client  
182 - String uri, // URI requested by the client  
183 - List(HTTP_header) http_headers, // HTTP headers sent by the client  
184 - One -> String generate_trust_ticket // may be used against denial of  
185 - // service attacks  
186 - ).  
187 -  
188 -  
189 -  
190 - Each site is described by a 'web site description', which is a datum of type  
191 - 'Web_Site_Description'.  
192 -  
193 -public type Web_Site_Description:  
194 - web_site_description(  
195 - List(String) common_names,  
196 - String site_directory,  
197 - List(Redirection) redirections,  
198 - String charset,  
199 - List(String) journal_extensions,  
200 - List(String) journal_headers,  
201 - String authorization_secret,  
202 - List(MIME) known_mime_types,  
203 - (String host_name,  
204 - HTTP_Info http_info,  
205 - List(Web_arg) lwa,  
206 - Bool is_https) -> (List(HTTP_header),  
207 - Printable_tree) awp_handler,  
208 - (List(Web_arg) lwa) -> One before_send_file).  
209 -  
210 - The component 'common_names' is the list of names of the site, like for example  
211 - "www.our-business.com". The reason why we have a list of common names instead of a  
212 - single common name, is that it may be useful to have a common name like "192.168.0.1"  
213 - for testing.  
214 -  
215 - 'charset' is a string which will determine the character encoding to be used by the  
216 - browser. Typically, this string is one of: "UTF-8", "ISO-8859-1", "Windows-1252",  
217 - etc...  
218 -  
219 - 'journal_extensions' is the list of URI extensions for which you want a log in the  
220 - journal (and on the console). When a request arrives, and if the extension is a member  
221 - of this list, a message is printed into the journal of the site including the date, the  
222 - IP address of the client, the complete HTTP request line. The HTTP headers whose name  
223 - is a member of 'journal_headers' are also printed in the journal. A reasonable minimum  
224 - for these two components is:  
225 -  
226 - [".awp"] for journal_extensions  
227 - ["user-agent"] for journal_headers  
228 -  
229 - 'authorization_secret' is a string which should just be unguessable. You may choose  
230 - something like (but don't choose this one !):  
231 -  
232 - "Hg8kJe42gCML9jNH-74"  
233 -  
234 - i.e. a sequence of characters typed at random, long enough to be unguessable. This is  
235 - used by the 'private download' mecanism, which is discussed later in this file.  
236 -  
237 - The component 'awp_handler' is a function of type:  
238 -  
239 - (String host_name,  
240 - HTTP_Info http_info,  
241 - List(Web_arg) web_args,  
242 - Bool is_https) -> Printable_tree  
243 -  
244 - ('Printable_tree' is a substitute for 'String' and is defined in  
245 - 'tools/basis.anubis'). This function is the 'awp handler' for the site. When the URI  
246 - ends by ".awp", this function is called, and the result (an HTML page) is sent to the  
247 - client over the connection. The last operand to this function is a boolean which is  
248 - 'true' when the requests arrives through the HTTPS channel, and 'false' when it arrives  
249 - through the HTTP channel.  
250 -  
251 -  
252 -  
253 -  
254 -  
255 -  
256 -  
257 - *** (5) Protection against denial of service attacks.  
258 -  
259 - We need to protect our servers against 'denial of service' attacks. The attack may be  
260 - send automatically from machines which are infested by viruses. In that case, our  
261 - server is saturated of connections (all virtual machines at work), but nothing is  
262 - comming on the connections. In order to avoid this problem, we propose the following:  
263 -  
264 - (1) Limit the number of simultaneous connections (say to 100).  
265 - (2) Close a connection if the request is not complete after say 10 seconds.  
266 - (3) Close the connection if the request is bigger than a given size (normal requests  
267 - are small except when there are uploaded files.  
268 - (4) Close the connection during the sending of the answer if the client is waiting  
269 - too much.  
270 - (5) Record all IP addresses with which we have encountered one of the problems above.  
271 - (6) Immediately close the connections if the IP address is in our list.  
272 - (7) Remove an address from the list only after 5 minutes of inactivity of this  
273 - address.  
274 - (8) Maintain a list of reliable IP addresses.  
275 -  
276 - Of course, all the above are approximative solutions which may in some circumstances  
277 - become either cumbersome or also partially block the system. So, it is needed to have a  
278 - set of dynamically modifiable parameters in order to master the behavior of this  
279 - mecanism.  
280 -  
281 -  
282 - Each dubious IP address is recorded together with its last activity time.  
283 -  
284 -public type DubiousIP:  
285 - dubious_ip (Int32 address,  
286 - Int32 last_activity).  
287 -  
288 -  
289 -public type DenialOfService:  
290 - denial_of_service(Var(Int32) max_connections,  
291 - Var(Int32) request_line_delay, // seconds  
292 - Var(Int32) headers_delay,  
293 - Var(Int32) answer_delay,  
294 - Var(List(DubiousIP)) list_of_dubious,  
295 - Var(List(Int32)) reliable_addresses).  
296 -  
297 - The informations in this set of variables are stored serialized into the file  
298 - 'my_anubis/web_sites/dos_info'. If this file does not exist a set if variables with  
299 - default values is created. The values are saved on the disk each time they are  
300 - modified.  
301 -  
302 -public define DenialOfService load_denial_of_service_info.  
303 -  
304 -  
305 -  
306 - *** (6) Starting your HTTP and HTTPS servers.  
307 -  
308 - When your web site descriptions are ready, you can start a pair of servers (a HTTP  
309 - server and a HTTPS server) for serving your web sites. Notice that there are always  
310 - two servers, regardless of the number of web sites, and that each web sites normally  
311 - uses the two servers.  
312 -  
313 -  
314 -public define StartServerResult  
315 - start_http_server  
316 - (  
317 - Int32 ip_address,  
318 - Int32 http_port,  
319 - List(Web_Site_Description) web_sites,  
320 - DenialOfService dos  
321 - ).  
322 -  
323 -public define StartServerResult  
324 - start_https_server  
325 - (  
326 - Int32 ip_address,  
327 - Int32 https_port,  
328 - String certificate_common_name,  
329 - List(Web_Site_Description) web_sites,  
330 - DenialOfService dos  
331 - ).  
332 -  
333 - The first argument 'ip_address' is the IP address on which the servers listen. If you  
334 - put 0, the servers listen on all adresses of the machine (which is useful if the  
335 - machine has several network interfaces). Otherwise, use the function 'ip_address'  
336 - defined in 'tools/basis.anubis' for composing a particular IP address.  
337 -  
338 - The next arguments are the port numbers for HTTP and HTTPS. The usual values are 80 and  
339 - 443, but you may have reasons to choose other values.  
340 -  
341 - The next argument is the list of your web site descriptions. All the sites described in  
342 - this list will be accessible on the server.  
343 -  
344 - The argument 'dos' is a set of dynamic variables containing the informations for  
345 - protecting the servers against denial of service attacks.  
346 -  
347 -  
348 -  
349 -  
350 -  
351 -  
352 - *** (7) Private download.  
353 -  
354 - It may happen that you want to propose private files for download. This means that such  
355 - a file could be downloaded only by the authorized person, and should not be seen by any  
356 - other one. This feature can be used only under HTTPS, not under HTTP.  
357 -  
358 - The file may be located anywhere on the server. Hence, the file has a complete absolute  
359 - path, like for example:  
360 -  
361 - /home/georges/my_documents/my_text.pdf  
362 -  
363 - which has nothing to do with the directories of the web server. Now, you may also want  
364 - to show another path or simply just a name to the client, not the actual absolute path  
365 - above, which may need to remain secret. So for example, the same file may appear to the  
366 - client as:  
367 -  
368 - informations.pdf  
369 -  
370 - The page must provide a link with an authorization. The authorization is just a web  
371 - argument, whose name is "zauth". The value of this web argument is computed by hashing  
372 - some secret string (known only from the programmer of the web site) with the absolute  
373 - path of the file. The HTTPS request will have the form:  
374 -  
375 - GET /informations.pdf?zauth=d38161f5b4e87e2d46e06ff8b3e233be563794d1  
376 -  
377 - The server will search for a file named  
378 -  
379 - zd38161f5b4e87e2d46e06ff8b3e233be563794d1  
380 -  
381 - (i.e. "z" concatenated with the value of the authorization) in the subdirectory  
382 - 'private_download' of the site directory. This file contains the absolute path of the  
383 - file, i.e:  
384 -  
385 - /home/georges/my_documents/my_text.pdf  
386 -  
387 - At that point, the server may hash the secret string and the absolute path together, to  
388 - check if the client is authorized to download the file. If it is the case, it sends the  
389 - file (the MIME type is declared as 'application/octet-stream' if it is not recognized).  
390 - The file is sent under the visible name.  
391 -  
392 - The server creates automatically the subdirectory 'private_download/' within the 'site  
393 - directory' (for each web site) if it does not already exist. Files in this directory  
394 - are deleted when they become too old (for example, after 3 days of life).  
395 -  
396 - Here is the function for computing the value of the authorization, and for making the  
397 - authorization file in 'private_download'.  
398 -  
399 -public define String  
400 - make_authorization  
401 - (  
402 - String site_directory,  
403 - String authorization_secret, // known only by the programmer of the web site  
404 - String absolute_path // on server  
405 - ).  
406 -  
407 - See 'web/making_a_web_site.anubis' for the construction of the link for downloading.  
408 -  
409 -  
410 -  
411 -  
412 -  
413 -  
414 -  
415 -  
416 - *** (8) About web argument names.  
417 -  
418 - The server reserves the name "zauth" for the authorization in the private download  
419 - mecanism. Also, if the name of a web arguments begins by "p" (like 'password'), it does  
420 - not print the value of the web argument neither on the console or in the journal. A  
421 - good politics is to prefix all web arguments by letters distinct from 'p' and 'z'. This  
422 - method is used in 'web/making_a_web_site.anubis'. This will avoid clashes of names.  
423 -  
424 -  
425 -  
426 -  
427 -  
428 -  
429 - *** (9) A web dispatcher.  
430 -  
431 - For hosting several sites you may prefer another method which we now describe. We start  
432 - a HTTP server on port 80 (or on another port). This server is called the  
433 - ``dispatcher''. When a requests arrives, the dispatcher examines the ``host'' HTTP  
434 - header, so that it gets the name of the requested host. Then it sends to the client a  
435 - page like this one:  
436 -  
437 - <html>  
438 - <head>  
439 - <meta http-equiv="Refresh" content="0;URL=...">  
440 - </head>  
441 - <body>  
442 - </body>  
443 - </html>  
444 -  
445 - where the URL represented by '...' is the URL of the requested site. This URL may have  
446 - the same IP address as the dispatcher, except that the port number is different. It may  
447 - also have a different IP address.  
448 -  
449 - The dispatcher uses the file 'my_anubis/web_sites/dispatcher.info'. This file contains  
450 - a serialized datum of type 'List(DispatcherInfo)'.  
451 -  
452 -public type DispatcherInfo:  
453 - site(String common_name,  
454 - Int32 http_port).  
455 -  
456 - The dispatcher does not write into this file. It reads it when it starts, and rereads  
457 - it each time the date of last modification of the file changes, so that the dispatcher  
458 - always has up to date data. The file may be managed (written and updated) by another  
459 - program.  
460 -  
461 - So, for each site, the dispatcher knows the common name (needed to recognize the 'host'  
462 - HTTP header), and the pair (ip_address,port) used by the actual site for HTTP. The  
463 - dispatcher does not worry about HTTPS. HTTPS must be managed by the actual site.  
464 -  
465 - The dispatcher is started by:  
466 -  
467 -public define One  
468 - start_web_dispatcher  
469 - (  
470 - Int32 ip_address, // address for listening (typically 0)  
471 - Int32 port, // typically 80  
472 - DenialOfService dos  
473 - ).  
474 -  
475 - A command line tool for managing the file 'my_anubis/web_sites/dispatcher.info' is also  
476 - provided:  
477 -  
478 - global define One  
479 - manage_web_dispatcher  
480 - (  
481 - List(String) args  
482 - ).  
483 -  
484 -  
485 -  
486 -  
487 -  
488 -  
489 -  
490 - --- That's all for the public part ! --------------------------------------------------  
491 -  
492 -  
493 -  
494 -  
495 -  
496 -  
497 -  
498 - ----------------------------------- Table of Contents ---------------------------------  
499 -  
500 - *** [1] Types which are private to this file.  
501 -  
502 - *** [2] Tools.  
503 - *** [2.1] Formating an error message.  
504 - *** [2.2] Converting IP addresses.  
505 - *** [2.3] Reading and unputting characters.  
506 - *** [2.4] Reading and discarding characters.  
507 - *** [2.5] Reading a character string.  
508 - *** [2.6] Padding integers with zeros.  
509 - *** [2.7] Converting web arguments to ASCII.  
510 - *** [2.8] Server description.  
511 -  
512 - *** [3] Managing the journal.  
513 - *** [3.1] Naming journal files.  
514 - *** [3.2] Formating HTTP headers.  
515 - *** [3.3] Formating web arguments.  
516 - *** [3.4] Formating the whole request.  
517 - *** [3.5] Putting it in the journal file (and on the console).  
518 -  
519 - *** [4] Reading the HTTP request.  
520 - *** [4.1] Skipping leading blanks.  
521 - *** [4.2] Reading a new line.  
522 - *** [4.3] Reading a 'word'.  
523 - *** [4.4] Separating the URI from the query string.  
524 - *** [4.5] Reading the web arguments.  
525 - *** [4.7] Reading the request line.  
526 - *** [4.8] Reading the HTTP headers.  
527 - *** [4.9] Getting the size of the request's body.  
528 - *** [4.10] Reading the body of the request.  
529 -  
530 - *** [5] Making the HTTP answer.  
531 - *** [5.1] Avoiding illegal URIs.  
532 - *** [5.2] Managing authorizations for downloading private files.  
533 - *** [5.3] Recognizing MIME types.  
534 - *** [5.4] Formating HTTP headers.  
535 - *** [5.5] Sending a file.  
536 - *** [5.6] Answering a www-url encoded request.  
537 - *** [5.7] Answering a multipart/form-data encoded request.  
538 - *** [5.7.1] Finding the boundary.  
539 - *** [5.7.2] Reading attributes from a multipart entity.  
540 - *** [5.7.3] Creating a temporary filename for an uploaded file.  
541 - *** [5.7.4] Saving an uploaded file under a temporary filename.  
542 - *** [5.7.5] Removing the path from a file name.  
543 - *** [5.7.6] Reading a multipart entity.  
544 - *** [5.8] Handling redirections.  
545 - *** [5.9] Answering both sorts of requests.  
546 -  
547 - *** [6] The HTTP/HTTPS servers.  
548 - *** [6.1] The HTTP request handler.  
549 - *** [6.2] Server's tasks.  
550 - *** [6.3] Starting the HTTP/HTTPS servers.  
551 -  
552 - *** [7] The web dispatcher.  
553 - *** [7.1] The dispatcher server.  
554 - *** [7.2] The dispatcher web site.  
555 - *** [7.3] Managing the info file.  
556 -  
557 - ---------------------------------------------------------------------------------------  
558 -  
559 -  
560 -  
561 -  
562 -read tools/basis.anubis  
563 -read tools/findstring.anubis  
564 -read tools/connections.anubis  
565 -  
566 -  
567 -  
568 -  
569 -  
570 - *** [1] Types which are private to this file.  
571 -  
572 - We use the following self-explanatory types.  
573 -  
574 -type Error:  
575 - cannot_read_from_connection,  
576 - not_get_or_post_request(String),  
577 - end_of_line_expected,  
578 - incorrect_content_length_value,  
579 - colon_expected,  
580 - timeout(Int32).  
581 -  
582 -type HTTP_RequestType:  
583 - get,  
584 - post.  
585 -  
586 -type HTTP_RequestLine:  
587 - request_line (HTTP_RequestType type,  
588 - String uri,  
589 - List(Web_arg) query_string).  
590 -  
591 -type EncodingType:  
592 - www_url,  
593 - multipart_form_data.  
594 -  
595 -type BufferedConnection:  
596 - buffered_connection(Connection conn,  
597 - Var(ByteArray) buffer,  
598 - Var(Int32) read_pos).  
599 -  
600 -  
601 -  
602 - *** [2] Tools.  
603 -  
604 - *** [2.1] Formating an error message.  
605 -  
606 - The next function formats an error message.  
607 -  
608 -define String  
609 - format  
610 - (  
611 - Error msg  
612 - ) =  
613 - if msg is  
614 - {  
615 - cannot_read_from_connection then  
616 - "Cannot read from connection.\n",  
617 - not_get_or_post_request(s) then  
618 - "The request did not begin by 'GET' or 'POST': "+s+".\n",  
619 - end_of_line_expected then  
620 - "End of line expected.\n",  
621 - incorrect_content_length_value then  
622 - "Incorrect value for HTTP header 'Content-Length'.\n",  
623 - colon_expected then  
624 - "':' was expected.\n",  
625 - timeout(n) then  
626 - //"time out: "+n+"\n"  
627 - //"time out.\n"  
628 - ""  
629 - }.  
630 -  
631 -  
632 -  
633 -  
634 -  
635 -  
636 - *** [2.2] Converting IP addresses.  
637 -  
638 - We need two conversion functions for IP addresses:  
639 -  
640 - (Word8,Word8,Word8,Word8) --> Int32 ip_address  
641 - Int32 --> String ip_addr_to_string  
642 -  
643 - These conversions are defined in 'tools/basis.anubis'.  
644 -  
645 -  
646 -  
647 -  
648 -  
649 -  
650 -  
651 -  
652 - *** [2.3] Reading and unputting characters.  
653 -  
654 - We need a mecanism for unputting several characters (actually at least 3). This is  
655 - because when reading the client connection, we must sometimes go ahead several  
656 - characters, and virtually put them back into the connection, so that they can be  
657 - reread. Of course, we do not send them back to the client. We store them in a list  
658 - (hold by the variable 'unput_chars'), and we manage this list, so that characters may  
659 - be virtually put back in the connection (this is called 'unputting').  
660 -  
661 -variable List(Word8) unput_chars = [].  
662 -  
663 - The most recently read one is the head of list. Fortunately, this variable is private  
664 - to this virtual machine (hence to this client).  
665 -  
666 -  
667 -define One  
668 - unput // unputting a character (add it in front of the list)  
669 - (  
670 - Word8 character  
671 - ) =  
672 - unput_chars <- (List(Word8))[character . *unput_chars].  
673 -  
674 -  
675 -  
676 -define One record_dubious_IP(Int32 addr,DenialOfService dos).  
677 -  
678 -variable Int32 sttm = 0. // contains the start time for this connection.  
679 -  
680 -define Result(Error,Word8)  
681 - record_dubious_connection  
682 - (  
683 - Connection conn,  
684 - Int32 dead_line,  
685 - DenialOfService dos,  
686 - ) =  
687 - if remote_IP_address_and_port(conn) is (addr,port) then  
688 - record_dubious_IP(addr,dos);  
689 - print("Recording IP address "+ip_addr_to_string(addr)+  
690 - " as dubious after "+(dead_line-*sttm)+" seconds. Total: "+  
691 - length(*list_of_dubious(dos))+"\n");  
692 - error(timeout(dead_line)).  
693 -  
694 -define String  
695 - pid  
696 - =  
697 - "[" + virtual_machine_id + "] ".  
698 -  
699 -define ReadResult  
700 - read  
701 - (  
702 - BufferedConnection connection,  
703 - Int32 size,  
704 - Int32 time_out  
705 - ) =  
706 - //println(pid + "read(" + size + ")");  
707 -  
708 - if *connection.read_pos < length(*connection.buffer) then  
709 - //println(pid + " reading from buffer (size = " + length(*connection.buffer) + ", pos = " + *connection.read_pos);  
710 - with result = extract(*connection.buffer, *connection.read_pos, *connection.read_pos + size),  
711 - size_read = length(result),  
712 - connection.read_pos <- *connection.read_pos + size_read;  
713 - if size > size_read  
714 - then  
715 - //println("Wanted " + size + ", read only " + size_read);  
716 - if read(connection, size - size_read, time_out) is  
717 - {  
718 - error then error,  
719 - timeout then ok(result),  
720 - ok(ba) then ok(result + ba)  
721 - }  
722 - else ok(result)  
723 - else  
724 - //if now > dead_line then record_dubious_connection(connection,dead_line,dos) else  
725 - if read(connection.conn, 16384, time_out) is // the connection is closed after 10 minutes of inactivity  
726 - {  
727 - error then println(pid + "read failed)"); error,  
728 - timeout then timeout,  
729 - ok(ba) then  
730 -// println(pid + "ba = " + length(ba));  
731 - connection.buffer <- ba;  
732 - connection.read_pos <- 0;  
733 - //println(pid + "rb = " + length(*read_buffer));  
734 - read(connection, size, time_out)  
735 - }.  
736 -  
737 -  
738 -define Result(Error,Word8)  
739 - read_one_byte  
740 - (  
741 - BufferedConnection connection,  
742 - Int32 dead_line,  
743 - DenialOfService dos  
744 - ) =  
745 - //if now > dead_line then record_dubious_connection(connection,dead_line,dos) else  
746 - if read(connection,1,600) is // the connection is closed after 10 minutes of inactivity  
747 - {  
748 - error then error(cannot_read_from_connection),  
749 - timeout then error(timeout(600)),  
750 - //record_dubious_connection(connection,dead_line,dos),  
751 - ok(ba) then if nth(0,ba) is  
752 - {  
753 - failure then error(cannot_read_from_connection),  
754 - success(c) then  
755 -// println("-" + pid + "read [" + implode([c]) + "]\t");  
756 - ok(c)  
757 - }  
758 - }.  
759 -  
760 -  
761 -variable ByteArray read_buffer = constant_byte_array(0, 0).  
762 -variable Int32 read_offset = 0.  
763 -  
764 - define Result(Error,Word8)  
765 - read_one_byte  
766 - (  
767 - BufferedConnection connection,  
768 - Int32 dead_line,  
769 - DenialOfService dos  
770 - ) =  
771 - if *read_offset < length(*read_buffer) then  
772 - if nth(*read_offset, *read_buffer) is  
773 - {  
774 - failure then println(pid + "nth failed)"); error(cannot_read_from_connection),  
775 - success(c) then  
776 - println("-" + pid + "read [" + implode([c]) + "]\tat " + *read_offset);  
777 - read_offset <- *read_offset + 1;  
778 - ok(c)  
779 - }  
780 - else  
781 - //if now > dead_line then record_dubious_connection(connection,dead_line,dos) else  
782 - println(pid + "len = " + length(*read_buffer));  
783 - if read(connection, 16384, 600) is // the connection is closed after 10 minutes of inactivity  
784 - {  
785 - error then println(pid + "read failed)"); error(cannot_read_from_connection),  
786 - timeout then error(timeout(600)),  
787 - //record_dubious_connection(connection,dead_line,dos),  
788 - ok(ba) then  
789 - println(pid + "ba = " + length(ba));  
790 - read_buffer <- ba;  
791 - read_offset <- 0;  
792 - println(pid + "rb = " + length(*read_buffer));  
793 - read_one_byte(connection, dead_line, dos)  
794 - }.  
795 -  
796 -  
797 -define Result(Error,Word8)  
798 - next_char // reading a character (check the list first, and read on the connection  
799 - // only when the list is empty).  
800 - (  
801 - BufferedConnection connection,  
802 - Int32 dead_line,  
803 - DenialOfService dos  
804 - ) =  
805 - if *unput_chars is  
806 - {  
807 - [ ] then read_one_byte(connection,dead_line,dos),  
808 -  
809 - [h . t] then  
810 - unput_chars <- t;  
811 - ok(h)  
812 - }.  
813 -  
814 -  
815 -  
816 -  
817 -  
818 -  
819 -  
820 - *** [2.4] Reading and discarding characters.  
821 -  
822 - The next function reads the specified number of bytes (this is the same as  
823 - 'characters') from the connection and discards them. This is used for discarding CR LF  
824 - just before the body of a request.  
825 -  
826 -define Result(Error,One)  
827 - read_and_ignore  
828 - (  
829 - BufferedConnection connection, // to client  
830 - Int32 dead_line,  
831 - Int32 number_of_characters, // number of characters to read and ignore  
832 - DenialOfService dos  
833 - ) =  
834 - if number_of_characters =< 0 then ok(unique) else  
835 - if next_char(connection, dead_line, dos) is  
836 - {  
837 - error(msg) then error(msg),  
838 - ok(c) then read_and_ignore(connection,dead_line,number_of_characters-1,dos)  
839 - }.  
840 -  
841 -  
842 -  
843 -  
844 -  
845 -  
846 -  
847 - *** [2.5] Reading a character string.  
848 -  
849 - Sometimes values of HTTP attributes or web args are presented in the form of double  
850 - quoted strings. The next function handles the reading of such things. The leading  
851 - double quote is already read in. We must read subsequent characters until the next non  
852 - backslashed double quote.  
853 -  
854 -define Result(Error,String)  
855 - read_string  
856 - (  
857 - BufferedConnection connection, // connection with the client  
858 - Int32 dead_line,  
859 - List(Word8) so_far, // characters read so far (in reverse order)  
860 - DenialOfService dos  
861 - ) =  
862 - if next_char(connection, dead_line,dos) is  
863 - {  
864 - error(msg) then error(msg),  
865 - ok(c) then  
866 - if c = '\\'  
867 - then if next_char(connection,dead_line,dos) is  
868 - {  
869 - error(msg) then error(msg),  
870 - ok(d) then  
871 - if d = '\"'  
872 - then read_string(connection,dead_line,['\"' . so_far],dos)  
873 - else read_string(connection,dead_line,[d, c . so_far],dos)  
874 - }  
875 - else if c = '\"'  
876 - then ok(implode(reverse(so_far)))  
877 - else read_string(connection,dead_line,[c . so_far],dos)  
878 - }.  
879 -  
880 -  
881 -  
882 -  
883 -  
884 -  
885 -  
886 - *** [2.6] Padding integers with zeros.  
887 -  
888 - 'zero_pad_2' transforms an integer (which is assumed to be between 0 and 99) into a  
889 - string with exactly two digits. This is used for formating days, hours, minutes and  
890 - seconds.  
891 -  
892 -define String  
893 - zero_pad_2  
894 - (  
895 - Int32 n  
896 - ) =  
897 - with s = integer_to_string(n),  
898 - if length(s) < 2  
899 - then "0"+s  
900 - else s.  
901 -  
902 -  
903 -  
904 -  
905 -  
906 -  
907 -  
908 - *** [2.7] Converting web arguments to ASCII.  
909 -  
910 - The function 'web_to_ascii' gets a character string and replaces web encoding by normal  
911 - ASCII encoding. This amounts to replacing:  
912 -  
913 - + by blank  
914 - %xx by the character whose ASCII code is xx in hexadecimal  
915 -  
916 - Note: We assume that '9' < 'A' (which is the case for ASCII code).  
917 -  
918 -  
919 -  
920 -define Word8  
921 - web_decode  
922 - (  
923 - Word8 x1,  
924 - Word8 x2  
925 - ) =  
926 - with z1 = word8_to_int32(x1),  
927 - n1 = if z1 =< '9' then (z1 - '0') else (z1 - 'A' + 10),  
928 - z2 = word8_to_int32(x2),  
929 - n2 = if z2 =< '9' then (z2 - '0') else (z2 - 'A' + 10),  
930 - n = (n1 << 4) + n2,  
931 - truncate_to_word8(n).  
932 -  
933 -  
934 -  
935 -define String  
936 - web_to_ascii  
937 - (  
938 - String web_string,  
939 - Int32 n, // current position in web_string  
940 - List(Word8) so_far  
941 - ) =  
942 - if nth(n,web_string) is  
943 - {  
944 - failure then implode(reverse(so_far)),  
945 - success(c) then  
946 - if c = '+'  
947 - then web_to_ascii(web_string,n+1,[' ' . so_far])  
948 - else if c = '%'  
949 - then if nth(n+1,web_string) is  
950 - {  
951 - failure then implode(reverse(so_far)),  
952 - success(x1) then if nth(n+2,web_string) is  
953 - {  
954 - failure then implode(reverse(so_far)),  
955 - success(x2) then web_to_ascii(web_string,n+3,[web_decode(x1,x2) . so_far])  
956 - }  
957 - }  
958 - else web_to_ascii(web_string,n+1,[c . so_far])  
959 - }.  
960 -  
961 -  
962 -  
963 -  
964 -  
965 -  
966 -  
967 -  
968 - *** [3] Managing the journal.  
969 -  
970 - Concurrently working machines should not try to access the same file at the same  
971 - time. This problem may be solved by using the 'protect' mecanism.  
972 -  
973 -  
974 -  
975 - *** [3.1] Naming journal files.  
976 -  
977 - Since journal messages are rather prolific, we should have at least one file per  
978 - hour. Hence, the name of a journal file must be constructed from the current year,  
979 - month, day and hour. For example, it may be:  
980 -  
981 - 2003_03_12_19  
982 -  
983 - (this is for the journal of 7 PM to 8 PM, 2003/mar/12).  
984 -  
985 -define String  
986 - make_current_journal_file_name  
987 - =  
988 - if convert_time(now) is date_and_time(y,m,d,h,_,_,_,_,_) then  
989 - integer_to_string(y)+"_"+  
990 - zero_pad_2(m)+"_"+  
991 - zero_pad_2(d)+"_"+  
992 - zero_pad_2(h).  
993 -  
994 -  
995 -  
996 -  
997 -  
998 -  
999 -  
1000 - *** [3.2] Formating HTTP headers.  
1001 -  
1002 - HTTP headers may be shown on the console or written in the journal. The function below  
1003 - formats a list of HTTP headers.  
1004 -  
1005 -define String  
1006 - show_format  
1007 - (  
1008 - Web_Site_Description desc,  
1009 - List(HTTP_header) headers,  
1010 - ) =  
1011 - if headers is  
1012 - {  
1013 - [ ] then "",  
1014 - [h . t] then if h is http_header(name,value) then  
1015 - if member(journal_headers(desc),name)  
1016 - then " | "+name+": "+value+"\n"+show_format(desc,t)  
1017 - else show_format(desc,t)  
1018 - }.  
1019 -  
1020 -  
1021 -  
1022 -  
1023 -  
1024 -  
1025 - *** [3.3] Formating web arguments.  
1026 -  
1027 - The same thing for web arguments.  
1028 -  
1029 -define String  
1030 - show_format  
1031 - (  
1032 - List(Web_arg) lwa  
1033 - ) =  
1034 - if lwa is  
1035 - {  
1036 - [ ] then "",  
1037 - [h . t] then if h is  
1038 - {  
1039 - web_arg(n,v) then  
1040 - " | "+n+"="+(if nth(0,n) = success('p') then "<not shown>" else v)+"\n"+show_format(t),  
1041 - upload(n,fn,tfn) then  
1042 - " | "+n+"="+fn+" (uploaded as '"+tfn+"')\n"+show_format(t)  
1043 - }  
1044 - }.  
1045 -  
1046 -  
1047 -  
1048 -  
1049 -  
1050 -  
1051 - *** [3.4] Formating the whole request.  
1052 -  
1053 - It is cheap to transform month numbers into abbreviated month names. This enhances the  
1054 - readability of the journal.  
1055 -  
1056 -define String  
1057 - format_month  
1058 - (  
1059 - Int32 m  
1060 - ) =  
1061 - if m = 1 then "jan" else  
1062 - if m = 2 then "feb" else  
1063 - if m = 3 then "mar" else  
1064 - if m = 4 then "apr" else  
1065 - if m = 5 then "may" else  
1066 - if m = 6 then "jun" else  
1067 - if m = 7 then "jul" else  
1068 - if m = 8 then "aug" else  
1069 - if m = 9 then "sep" else  
1070 - if m = 10 then "oct" else  
1071 - if m = 11 then "nov" else  
1072 - if m = 12 then "dec" else  
1073 - "???".  
1074 -  
1075 -  
1076 - Below we format a whole HTTP request. This may give this (actually, it depends on how  
1077 - you defined the values of 'journal_headers' and 'journal_extensions'):  
1078 -  
1079 - [3] 2003/mar/10 10:06:57 from 123.456.123.456: /homepage.awp  
1080 - | host: www.the-best-one.com  
1081 - | user-agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.0.1) Gecko/20020823 Netscape/7.0  
1082 -  
1083 - The leading number between brackets is the number of the virtual machine which served  
1084 - the URI.  
1085 -  
1086 -define String  
1087 - format_request  
1088 - (  
1089 - Web_Site_Description desc,  
1090 - Connection client_connection,  
1091 - HTTP_RequestLine request_line,  
1092 - List(HTTP_header) headers,  
1093 - List(Web_arg) web_args  
1094 - ) =  
1095 - with dt = convert_time(now),  
1096 - if remote_IP_address_and_port(client_connection) is (addr,port) then  
1097 - integer_to_string(year(dt))+"/"+format_month(month(dt))+"/"+zero_pad_2(day(dt))+" "+  
1098 - zero_pad_2(hour(dt))+":"+zero_pad_2(minute(dt))+":"+zero_pad_2(second(dt))+  
1099 - " from "+ip_addr_to_string(addr)+  
1100 - ": "+uri(request_line)+"\n"+  
1101 - show_format(desc,headers)+  
1102 - show_format(web_args).  
1103 -  
1104 -  
1105 -  
1106 -  
1107 -  
1108 -  
1109 -  
1110 - *** [3.5] Putting it in the journal file (and on the console).  
1111 -  
1112 - We must not forget to 'protect' this operation, so that the messages of two machines  
1113 - (working for the same site) will not be mixed together.  
1114 -  
1115 -define One  
1116 - log_journal_msg  
1117 - (  
1118 - Web_Site_Description desc,  
1119 - String msg,  
1120 - ) =  
1121 - with msg = to_byte_array("["+virtual_machine_id+"] "+msg+"\n"),  
1122 - protect  
1123 - (  
1124 - if file(site_directory(desc)+"/journal/"+make_current_journal_file_name,append) is  
1125 - {  
1126 - failure then unique,  
1127 - success(journal_file) then  
1128 - forget(reliable_write(file(journal_file),msg))  
1129 - };  
1130 - forget(reliable_write(file(stdout),msg))  
1131 - ).  
1132 -  
1133 -  
1134 -  
1135 -  
1136 -  
1137 -  
1138 -  
1139 - *** [4] Reading the HTTP request.  
1140 -  
1141 -  
1142 - *** [4.1] Skipping leading blanks.  
1143 -  
1144 - One of the peculiarities of HTTP is that the characters 13 (carriage return) and 10  
1145 - (line feed) followed by either a space (32) or a tab (9), is considered as a blank not  
1146 - containing any new line. 'skip_http_blanks' must skip all blanks characters until the  
1147 - first non blank character, which should not be read in. Obviously, because of the above  
1148 - peculiarity, we need at least 3 characters of lookahead to do this. In other words, we  
1149 - must be able to unput at least 3 characters (hopefully we are).  
1150 -  
1151 - Strictly blanks characters are 'space' and 'tab'.  
1152 -  
1153 -define Bool  
1154 - is_strict_blank  
1155 - (  
1156 - Word8 c  
1157 - ) =  
1158 - if c = ' ' then true else c = '\t'.  
1159 -  
1160 -  
1161 - On the contrary, blanks include 13 and 10.  
1162 -  
1163 -define Bool  
1164 - is_blank  
1165 - (  
1166 - Word8 c  
1167 - ) =  
1168 - if c = ' ' then true else  
1169 - if c = '\t' then true else  
1170 - if c = 13 then true else  
1171 - c = 10.  
1172 -  
1173 -  
1174 - Skipping HTTP blanks.  
1175 -  
1176 -define Result(Error,One)  
1177 - skip_http_blanks  
1178 - (  
1179 - BufferedConnection connection,  
1180 - Int32 dead_line,  
1181 - DenialOfService dos  
1182 - ) =  
1183 - if next_char(connection,dead_line,dos) is  
1184 - {  
1185 - error(msg) then error(msg),  
1186 - ok(c) then  
1187 - if is_strict_blank(c)  
1188 - then skip_http_blanks(connection,dead_line,dos)  
1189 - else if c = 13  
1190 - then if next_char(connection,dead_line,dos) is  
1191 - {  
1192 - error(msg) then error(msg), // (unput(c); ok(unique)),  
1193 - ok(d) then  
1194 - if d = 10  
1195 - then if next_char(connection,dead_line,dos) is  
1196 - {  
1197 - error(msg) then error(msg), // (unput(d); unput(c); ok(unique)),  
1198 - ok(e) then  
1199 - if is_strict_blank(e)  
1200 - then skip_http_blanks(connection,dead_line,dos)  
1201 - else (unput(e); unput(d); unput(c); ok(unique))  
1202 - }  
1203 - else (unput(d); unput(c); ok(unique))  
1204 - }  
1205 - else (unput(c); ok(unique))  
1206 - }.  
1207 -  
1208 -  
1209 -  
1210 -  
1211 -  
1212 -  
1213 -  
1214 -  
1215 - *** [4.2] Reading a new line.  
1216 -  
1217 - Normally in HTTP a new line is the sequence 13 10 (carriage return line feed), not  
1218 - followed by a space or tabulator. If it is followed by a space or tabulator, the three  
1219 - characters are considered blanks, and no new line has been read. Before trying to read  
1220 - a new line, we first skip leading spaces and tabs. Then we try to read 13 and 10, and  
1221 - we read another character. if this character is space or tab, we consider we have read  
1222 - only blanks and we continue reading in order to find our new line. Otherwise, we unput  
1223 - this character (which may be for example the first character of the name of the next  
1224 - header), and answer that we have seen a new line.  
1225 -  
1226 - Warning: we must not use this function for reading the last pair (13,10) before the  
1227 - beginning of the body, because if the body is empty, there is no character to read  
1228 - after this pair, so that the server could wait for a character which will never  
1229 - come. This is the reason for 'read_and_ignore' above, which is used precisely for  
1230 - reading that last (13,10) pair.  
1231 -  
1232 -define Result(Error,One)  
1233 - read_new_line  
1234 - (  
1235 - BufferedConnection connection,  
1236 - Int32 dead_line,  
1237 - DenialOfService dos  
1238 - ) =  
1239 - if skip_http_blanks(connection,dead_line,dos) is  
1240 - {  
1241 - error(msg) then error(msg),  
1242 - ok(_) then  
1243 - if next_char(connection,dead_line,dos) is  
1244 - {  
1245 - error(msg) then error(msg),  
1246 - ok(c) then  
1247 - if c = 13  
1248 - then if next_char(connection,dead_line,dos) is  
1249 - {  
1250 - error(msg) then error(msg),  
1251 - ok(d) then  
1252 - if d = 10  
1253 - then ok(unique)  
1254 - else (unput(d);  
1255 - unput(c);  
1256 - error(end_of_line_expected))  
1257 - }  
1258 - else (unput(c);  
1259 - error(end_of_line_expected))  
1260 - }}.  
1261 -  
1262 -  
1263 -  
1264 -  
1265 -  
1266 -  
1267 -  
1268 -  
1269 - *** [4.3] Reading a 'word'.  
1270 -  
1271 - A 'word' is a sequence of characters which begins either by a double quote or not by a  
1272 - double quote. (However, any leading blanks are read in and ignored. This is  
1273 - accomplished by 'skip_http_blanks'.) If it begins by a double quote, it is read like a  
1274 - string, i.e. it ends at the next (non backslashed) double quote. Otherwise, it is  
1275 - right delimited by any character which may be considered as 'blank'. If the word is  
1276 - double quoted, the closing double quote is read in. On the contrary, if the word is not  
1277 - double quoted, the right delimiting blank character is not read in (it is 'unput' back  
1278 - into the connection), and may be read in again. This is needed because carriage return  
1279 - or line feed which are 'blank', also have a meaning in HTTP.  
1280 -  
1281 -define Result(Error,String)  
1282 - read_word_aux  
1283 - (  
1284 - BufferedConnection connection,  
1285 - Int32 dead_line,  
1286 - List(Word8) so_far,  
1287 - DenialOfService dos  
1288 - ) =  
1289 - if next_char(connection,dead_line,dos) is  
1290 - {  
1291 - error(msg) then error(msg),  
1292 - ok(c) then  
1293 - if is_blank(c)  
1294 - then (unput(c);  
1295 - ok(implode(reverse(so_far))))  
1296 - else read_word_aux(connection,dead_line,[c . so_far],dos)  
1297 - }.  
1298 -  
1299 -define Result(Error,String)  
1300 - read_word  
1301 - (  
1302 - BufferedConnection connection,  
1303 - Int32 dead_line,  
1304 - DenialOfService dos  
1305 - ) =  
1306 - if skip_http_blanks(connection,dead_line,dos) is  
1307 - {  
1308 - error(msg) then error(msg),  
1309 - ok(_) then  
1310 - if next_char(connection,dead_line,dos) is  
1311 - {  
1312 - error(msg) then error(msg),  
1313 - ok(c) then  
1314 - if c = '\"'  
1315 - then read_string(connection,dead_line,[],dos)  
1316 - else read_word_aux(connection,dead_line,[c],dos)  
1317 - }  
1318 - }.  
1319 -  
1320 -  
1321 -  
1322 -  
1323 -  
1324 -  
1325 -  
1326 -  
1327 - *** [4.4] Separating the URI from the query string.  
1328 -  
1329 - A 'query string' may be postfixed to the URI, just after a question mark. For example,  
1330 - the client may send the following request:  
1331 -  
1332 - GET /catalog.awp?item=3&color=blue  
1333 -  
1334 - We separate this into an URI: "/catalog.awp" and the string: "item=3&color=blue" which  
1335 - will be later transformed into the list:  
1336 -  
1337 - [web_arg("item","3"),web_arg("color","blue")]  
1338 -  
1339 -  
1340 -define (String,String)  
1341 - separate_uri_from_query_string  
1342 - (  
1343 - String uri_and_query_string,  
1344 - Int32 n  
1345 - ) =  
1346 - if nth(n,uri_and_query_string) is  
1347 - {  
1348 - failure then (uri_and_query_string,""),  
1349 - success(c) then  
1350 - if c = '?'  
1351 - then (substr(uri_and_query_string,0,n),  
1352 - substr(uri_and_query_string,n+1,length(uri_and_query_string)-(n+1)))  
1353 - else separate_uri_from_query_string(uri_and_query_string,n+1)  
1354 - }.  
1355 -  
1356 -  
1357 -  
1358 -  
1359 -  
1360 -  
1361 -  
1362 -  
1363 -  
1364 - *** [4.5] Reading the web arguments.  
1365 -  
1366 - HTTP/HTTPS requests are sent in one of two formats:  
1367 -  
1368 - (1) www-url encoded  
1369 - (2) multipart/form-data encoded  
1370 -  
1371 - The first one is the normal (historical) way of encoding. The second one is required  
1372 - for uploading files. A server which is supposed to accept upload of files must handle  
1373 - both formats. The first thing to do is to decide the format of the request. This is  
1374 - easily done by examining the HTTP headers. If we find the header:  
1375 -  
1376 - Content-Type: multipart/form-data  
1377 -  
1378 - the request is multipart/form-data encoded. Otherwise, it is 'www-url' encoded. We  
1379 - first consider 'www-url' encoded requests.  
1380 -  
1381 - For a 'www-url' encoded request, the web argument are either in the query string or in  
1382 - the body of the request, or both. The format is the same for both:  
1383 -  
1384 - name=value&name=value&...  
1385 -  
1386 - However, we may also have  
1387 -  
1388 - name  
1389 - name=  
1390 - name=&...  
1391 - name&...  
1392 -  
1393 - i.e. some parts may be missing. Hence, we must be careful.  
1394 -  
1395 - Furthermore, web arguments must be translated from web to ASCII when www-url encoded.  
1396 -  
1397 -define Bool  
1398 - is_ampersand_or_equal  
1399 - (  
1400 - Word8 c  
1401 - ) =  
1402 - if c = '&' then true else c = '='.  
1403 -  
1404 -  
1405 -  
1406 - The function 'read_name_or_value' reads the string 's' starting at position 'n' until  
1407 - either the end of the string or the first '&' or '='.  
1408 -  
1409 -define String  
1410 - read_name_or_value  
1411 - (  
1412 - String s,  
1413 - Int32 start,  
1414 - Int32 i  
1415 - ) =  
1416 - if nth(i,s) is  
1417 - {  
1418 - failure then substr(s,start,i - start),  
1419 - success(c) then  
1420 - if is_ampersand_or_equal(c)  
1421 - then substr(s,start,i-start) // the separator is not included  
1422 - else read_name_or_value(s,start,i+1)  
1423 - }.  
1424 -  
1425 -  
1426 -define List(Web_arg)  
1427 - read_www_url_encoded_web_args  
1428 - (  
1429 - String s,  
1430 - Int32 start,  
1431 - ) =  
1432 - with first = read_name_or_value(s,start,start),  
1433 - if first = ""  
1434 - then []  
1435 - else with i = start+length(first),  
1436 - if nth(i,s) is  
1437 - {  
1438 - failure then [web_arg(first,"")],  
1439 - success(c) then  
1440 - if c = '&'  
1441 - then [web_arg(first,"") . read_www_url_encoded_web_args(s,i+1)]  
1442 - else if c = '='  
1443 - then with second1 = read_name_or_value(s,i+1,i+1),  
1444 - // print("\""+second1+"\"\n");  
1445 - with second = web_to_ascii(second1,0,[]),  
1446 - [web_arg(first,second) . read_www_url_encoded_web_args(s,i+length(second1)+2)]  
1447 - else alert  
1448 - }.  
1449 -  
1450 -  
1451 -  
1452 -  
1453 -  
1454 - *** [4.7] Reading the request line.  
1455 -  
1456 - 'read_request_line' reads three words and a new line from the connection. It tries to  
1457 - recognize "get" or "post" in the first word, separates the URI from the query string in  
1458 - the second word, transforms the query string into a list of 'Web_arg', and finally  
1459 - returns a datum of type 'HTTP_RequestLine' if no error arose.  
1460 -  
1461 -  
1462 -define Result(Error,HTTP_RequestType)  
1463 - identify_get_or_post  
1464 - (  
1465 - String s  
1466 - ) =  
1467 - with s = to_lower(s),  
1468 - if s = "get" then ok(get) else  
1469 - if s = "post" then ok(post) else  
1470 - error(not_get_or_post_request(s)).  
1471 -  
1472 -define Result(Error,HTTP_RequestLine)  
1473 - read_request_line  
1474 - (  
1475 - BufferedConnection connection,  
1476 - Int32 dead_line,  
1477 - DenialOfService dos  
1478 - ) =  
1479 - if read_word(connection,dead_line,dos) is  
1480 - {  
1481 - error(msg) then error(msg),  
1482 - ok(get_or_post) then if read_word(connection,dead_line,dos) is  
1483 - {  
1484 - error(msg) then error(msg),  
1485 - ok(uri_and_query_string) then if read_word(connection,dead_line,dos) is  
1486 - {  
1487 - error(msg) then error(msg),  
1488 - ok(http_version) then if read_new_line(connection,dead_line,dos) is  
1489 - {  
1490 - error(msg) then error(msg),  
1491 - ok(_) then if separate_uri_from_query_string(uri_and_query_string,0) is  
1492 - (uri,query_string) then if identify_get_or_post(get_or_post) is  
1493 - {  
1494 - error(msg) then error(msg),  
1495 - ok(request_type) then  
1496 - ok(request_line(request_type,uri,read_www_url_encoded_web_args(query_string,0)))  
1497 - }  
1498 - }  
1499 - }  
1500 - }  
1501 - }.  
1502 -  
1503 -  
1504 -  
1505 -  
1506 -  
1507 -  
1508 -  
1509 - *** [4.8] Reading the HTTP headers.  
1510 -  
1511 - Each header is made of a name (containing only letters, the underscore, digits and the  
1512 - minus sign), a colon, a value, and a new line. The first empty line ends the headers.  
1513 -  
1514 -  
1515 - The next function tests characters acceptable in a header name.  
1516 -  
1517 -define Bool  
1518 - is_header_name_char  
1519 - (  
1520 - Word8 c  
1521 - ) =  
1522 - with n = word8_to_int32(c),  
1523 - if ('a' =< n & n =< 'z') then true else  
1524 - if ('A' =< n & n =< 'Z') then true else  
1525 - if ('0' =< n & n =< '9') then true else  
1526 - if c = '-' then true else  
1527 - c = '_'.  
1528 -  
1529 -define Result(Error,String)  
1530 - read_header_name  
1531 - (  
1532 - BufferedConnection connection,  
1533 - Int32 dead_line,  
1534 - List(Word8) so_far,  
1535 - DenialOfService dos  
1536 - ) =  
1537 - if next_char(connection,dead_line,dos) is  
1538 - {  
1539 - error(msg) then error(msg),  
1540 - ok(c) then  
1541 - if is_header_name_char(c)  
1542 - then read_header_name(connection,dead_line,[to_lower(c) . so_far],dos)  
1543 - else unput(c); ok(implode(reverse(so_far)))  
1544 - }.  
1545 -  
1546 -define Result(Error,One)  
1547 - skip_colon  
1548 - (  
1549 - BufferedConnection connection,  
1550 - Int32 dead_line,  
1551 - DenialOfService dos  
1552 - ) =  
1553 - if skip_http_blanks(connection,dead_line,dos) is  
1554 - {  
1555 - error(msg) then error(msg),  
1556 - ok(_) then  
1557 - if next_char(connection,dead_line,dos) is  
1558 - {  
1559 - error(msg) then error(msg),  
1560 - ok(c) then  
1561 - if c = ':'  
1562 - then ok(unique)  
1563 - else error(colon_expected)  
1564 - }}.  
1565 -  
1566 -  
1567 -define Result(Error,String)  
1568 - read_header_value  
1569 - (  
1570 - BufferedConnection connection,  
1571 - Int32 dead_line,  
1572 - List(Word8) so_far,  
1573 - DenialOfService dos  
1574 - ) =  
1575 - if next_char(connection,dead_line,dos) is  
1576 - {  
1577 - error(msg) then error(msg),  
1578 - ok(c) then  
1579 - if c = 13  
1580 - then if next_char(connection,dead_line,dos) is  
1581 - {  
1582 - error(msg) then error(msg),  
1583 - ok(d) then  
1584 - if d = 10  
1585 - then if next_char(connection,dead_line,dos) is  
1586 - {  
1587 - error(msg) then error(msg),  
1588 - ok(e) then  
1589 - if is_strict_blank(e)  
1590 - then read_header_value(connection,dead_line,[e . so_far],dos)  
1591 - else (unput(e); ok(implode(reverse(so_far))))  
1592 - }  
1593 - else read_header_value(connection,dead_line,[d, c . so_far],dos)  
1594 - }  
1595 - else read_header_value(connection,dead_line,[c . so_far],dos)  
1596 - }.  
1597 -  
1598 -  
1599 - Reading a single header.  
1600 -  
1601 -define Result(Error,Maybe(HTTP_header))  
1602 - read_header  
1603 - (  
1604 - BufferedConnection connection,  
1605 - Int32 dead_line,  
1606 - DenialOfService dos  
1607 - ) =  
1608 - if read_header_name(connection,dead_line,[],dos) is  
1609 - {  
1610 - error(msg) then error(msg),  
1611 - ok(name) then  
1612 - if name = "" then  
1613 - if read_and_ignore(connection,dead_line,2,dos) /* 13 and 10 */ is  
1614 - {  
1615 - error(msg) then error(msg),  
1616 - ok(_) then // this is the blank line  
1617 - ok(failure) // end of headers  
1618 - }  
1619 - else if skip_colon(connection,dead_line,dos) is  
1620 - {  
1621 - error(msg) then error(msg),  
1622 - ok(_) then if skip_http_blanks(connection,dead_line,dos) is  
1623 - {  
1624 - error(msg) then error(msg),  
1625 - ok(_) then if read_header_value(connection,dead_line,[],dos) is  
1626 - {  
1627 - error(msg) then error(msg),  
1628 - ok(value) then  
1629 - ok(success(http_header(name,value)))  
1630 - }  
1631 - }  
1632 - }  
1633 - }.  
1634 -  
1635 -  
1636 -  
1637 - Reading all the headers.  
1638 -  
1639 -define Result(Error,List(HTTP_header))  
1640 - read_http_headers  
1641 - (  
1642 - BufferedConnection connection,  
1643 - Int32 dead_line,  
1644 - DenialOfService dos  
1645 - ) =  
1646 - if read_header(connection,dead_line,dos) is  
1647 - {  
1648 - error(msg) then error(msg),  
1649 - ok(mbh) then if mbh is  
1650 - {  
1651 - failure then ok([ ]),  
1652 - success(header) then  
1653 - if read_http_headers(connection,dead_line,dos) is  
1654 - {  
1655 - error(msg) then error(msg),  
1656 - ok(others) then ok([header . others])  
1657 - }  
1658 - }  
1659 - }.  
1660 -  
1661 -  
1662 -  
1663 -  
1664 -  
1665 -  
1666 -  
1667 - *** [4.9] Getting the size of the request's body.  
1668 -  
1669 - The size of the body of the request is given under the 'Content-Length' header. If this  
1670 - header is not present, the size is assumed to be zero.  
1671 -  
1672 -define Result(Error,Int32)  
1673 - get_body_size  
1674 - (  
1675 - List(HTTP_header) headers  
1676 - ) =  
1677 - if headers is  
1678 - {  
1679 - [ ] then ok(0),  
1680 - [h . t] then if h is http_header(name,value) then  
1681 - if name = "content-length"  
1682 - then if string_to_integer(value) is  
1683 - {  
1684 - failure then error(incorrect_content_length_value),  
1685 - success(n) then ok(n)  
1686 - }  
1687 - else get_body_size(t)  
1688 - }.  
1689 -  
1690 -  
1691 -  
1692 -  
1693 -  
1694 -  
1695 -  
1696 -  
1697 -  
1698 -  
1699 - *** [4.10] Reading the body of the request.  
1700 -  
1701 - The body of the request may be very big (it contains uploaded files, if any). We read  
1702 - it using the primitive 'read', which returns the number of bytes read, which may be  
1703 - less than the number of bytes we wanted to read. This is not an error, but simply due  
1704 - to the fact the buffer associated with the connection in the Linux (or MS-Windows)  
1705 - kernel has a limited size. Hence, we must read bytes again until we have read the  
1706 - required number of bytes. However, if the number of bytes read is zero, the connection  
1707 - may be broken. In that case, we must not try to read indefinitely. On the contrary, we  
1708 - make at most 10 retries, with a small sleeping time between any two of them.  
1709 -  
1710 -define Result(Error,ByteArray)  
1711 - read_http_body  
1712 - (  
1713 - BufferedConnection connection,  
1714 - Int32 body_size,  
1715 - ByteArray so_far, // when calling this function, 'so_far' is the empty byte array  
1716 - Int32 retries // this function is called with retries = 10  
1717 - ) =  
1718 - if body_size = 0 then ok(constant_byte_array(0,0)) else  
1719 - if retries =< 0 then error(cannot_read_from_connection) else  
1720 - if read(connection,body_size,60) is  
1721 - {  
1722 - error then error(cannot_read_from_connection),  
1723 - timeout then error(timeout(60)),  
1724 - ok(new_bytes) then with  
1725 - ba = so_far + new_bytes, // contains all the bytes read so far  
1726 - nr = length(ba), // total read since the beginning  
1727 - nn = length(new_bytes), // number of bytes just read  
1728 - if nr < body_size // must read more bytes  
1729 - then if nn > 0 // if connection seems to work  
1730 - then read_http_body(connection,body_size,ba,1000) // continue reading  
1731 - else sleep(100); // otherwise, sleep 1/10 of second  
1732 - read_http_body(connection,body_size,ba, // and retry reading  
1733 - retries-1) // but no more than 10 times  
1734 - else ok(ba) // required number of bytes has been read  
1735 - }.  
1736 -  
1737 -  
1738 - Note: During sleeping, 'anbexec' runs other machines. Actually, calling 'sleep', even  
1739 - for one millisecond, is some way of giving up explicitly, so that other virtual  
1740 - machines may work.  
1741 -  
1742 -  
1743 -  
1744 -  
1745 -  
1746 -  
1747 -  
1748 -  
1749 -  
1750 -  
1751 -  
1752 -  
1753 - *** [5] Making the HTTP answer.  
1754 -  
1755 - At that point we have read the request line, the headers and the body of the  
1756 - request, and we must decide what to do.  
1757 -  
1758 - Actually, we can do one of the following:  
1759 -  
1760 - - send a file,  
1761 - - execute 'tickets_and_web_page' in case of an ".awp" URI.  
1762 -  
1763 - The uploaded file (which are in the body of the request) are saved into temporary files  
1764 - below.  
1765 -  
1766 -  
1767 -  
1768 -  
1769 -  
1770 - *** [5.1] Avoiding illegal URIs.  
1771 -  
1772 - For security reasons, we must avoid illegal URIs, for example those which may climb up  
1773 - in the file hierarchy. First we accept only few characters in URIs.  
1774 -  
1775 -define Bool  
1776 - is_legal_uri_char  
1777 - (  
1778 - Word8 c  
1779 - ) =  
1780 - with n = word8_to_int32(c),  
1781 - if ('a' =< n & n =< 'z') then true else // accept 'a' to 'z'  
1782 - if ('A' =< n & n =< 'Z') then true else // accept 'A' to 'Z'  
1783 - if ('0' =< n & n =< '9') then true else // accept '0' to '9'  
1784 - if c = '.' then true else // accept '.' '-' '/' and '_'  
1785 - if c = '-' then true else  
1786 - if c = '/' then true else  
1787 - c = '_'.  
1788 -  
1789 - We do not accept ~ which is some way of climbing. Of course, we cannot disallow single  
1790 - dots, which are most often present in legal URIs, but we must avoid double dots ..  
1791 - which mean 'climb up'.  
1792 -  
1793 -define Bool  
1794 - is_illegal_uri  
1795 - (  
1796 - String uri,  
1797 - Int32 n  
1798 - ) =  
1799 - if nth(n,uri) is  
1800 - {  
1801 - failure then false,  
1802 - success(c) then  
1803 - if c = '.' // first dot  
1804 - then if nth(n+1,uri) is  
1805 - {  
1806 - failure then false,  
1807 - success(d) then  
1808 - if d = '.' // second dot  
1809 - then true  
1810 - else is_illegal_uri(uri,n+1)  
1811 - }  
1812 - else is_illegal_uri(uri,n+1)  
1813 - }.  
1814 -  
1815 -  
1816 -  
1817 -  
1818 -  
1819 -  
1820 - *** [5.2] Managing authorizations for downloading private files.  
1821 -  
1822 - Computing the authorization and making the authorization file (containing the absolute  
1823 - path of the file on the server).  
1824 -  
1825 -  
1826 -define String  
1827 - compute_authorization  
1828 - (  
1829 - String authorization_secret,  
1830 - String absolute_path  
1831 - ) =  
1832 - to_ascii(sha1((authorization_secret,  
1833 - absolute_path))).  
1834 -  
1835 -  
1836 -public define String  
1837 - make_authorization  
1838 - (  
1839 - String site_directory,  
1840 - String authorization_secret,  
1841 - String absolute_path  
1842 - ) =  
1843 - with private_download_dir = site_directory+"/private_download",  
1844 - auth = compute_authorization(authorization_secret,  
1845 - absolute_path),  
1846 - forget(save(absolute_path,  
1847 - private_download_dir+"/z"+auth));  
1848 - auth.  
1849 -  
1850 -  
1851 - The function 'send_file' defined below handles the recognition of authorizations.  
1852 -  
1853 -  
1854 -  
1855 -  
1856 -  
1857 - *** [5.3] Recognizing MIME types.  
1858 -  
1859 - The extension of the (redirected) URI must be either ".awp" or recognized as associated  
1860 - to a MIME type. Otherwise, the server will not send the file. This is for security, but  
1861 - also because, we must generate a 'Content-Type' header in the answer, with the right  
1862 - MIME type.  
1863 -  
1864 -define String  
1865 - get_uri_extension_aux  
1866 - (  
1867 - String uri,  
1868 - Int32 n // used for searching backwards  
1869 - ) =  
1870 - if nth(n,uri) is  
1871 - {  
1872 - failure then "",  
1873 - success(c) then  
1874 - if c = '.' then substr(uri,n,length(uri)-n)  
1875 - else if c = '/' then ""  
1876 - else get_uri_extension_aux(uri,n-1)  
1877 - }.  
1878 -  
1879 -public define String  
1880 - get_uri_extension  
1881 - (  
1882 - String uri  
1883 - ) =  
1884 - get_uri_extension_aux(uri,  
1885 - length(uri)-1). // search starts at the right end  
1886 -  
1887 -  
1888 -  
1889 -define Maybe(String)  
1890 - recognize_mime_type_from_ext  
1891 - (  
1892 - String ext,  
1893 - List(MIME) l  
1894 - ) =  
1895 - if l is  
1896 - {  
1897 - [ ] then success("application/octet-stream"), // failure,  
1898 - [h . t] then if h is mime(mime_type,extension) then  
1899 - if ext = extension  
1900 - then success(mime_type)  
1901 - else recognize_mime_type_from_ext(ext,t)  
1902 - }.  
1903 -  
1904 -define Maybe(String)  
1905 - recognize_mime_type_from_uri  
1906 - (  
1907 - Web_Site_Description desc,  
1908 - String uri  
1909 - ) =  
1910 - recognize_mime_type_from_ext(get_uri_extension(uri),known_mime_types(desc)).  
1911 -  
1912 -  
1913 -  
1914 -  
1915 -  
1916 -  
1917 -  
1918 -  
1919 - *** [5.4] Formating HTTP headers.  
1920 -  
1921 - This is the formating for sending to the client (hence, it has nothing to do with the  
1922 - component 'journal_headers' in the web site description).  
1923 -  
1924 -define Printable_tree  
1925 - format_headers  
1926 - (  
1927 - List(HTTP_header) headers  
1928 - ) =  
1929 - if headers is  
1930 - {  
1931 - [ ] then [ ],  
1932 - [h . t] then if h is http_header(name,value) then  
1933 - [name,": ",value,crlf . format_headers(t)]  
1934 - }.  
1935 -  
1936 -  
1937 -  
1938 -define String  
1939 - month_abrv  
1940 - (  
1941 - Date_and_Time d  
1942 - ) =  
1943 - if d.month = 1 then "Jan"  
1944 - else if d.month = 2 then "Feb"  
1945 - else if d.month = 3 then "Mar"  
1946 - else if d.month = 4 then "Apr"  
1947 - else if d.month = 5 then "May"  
1948 - else if d.month = 6 then "Jun"  
1949 - else if d.month = 7 then "Jul"  
1950 - else if d.month = 8 then "Aug"  
1951 - else if d.month = 9 then "Sep"  
1952 - else if d.month = 10 then "Oct"  
1953 - else if d.month = 11 then "Nov"  
1954 - else if d.month = 12 then "Dec"  
1955 - else  
1956 - println("Bad month value [" + d.month + "] on Date_and_Time");  
1957 - "XXX".  
1958 -  
1959 -define String  
1960 - weekday_abrv  
1961 - (  
1962 - Date_and_Time d  
1963 - ) =  
1964 - if d.week_day = 0 then "Sun"  
1965 - else if d.week_day = 1 then "Mon"  
1966 - else if d.week_day = 2 then "Tue"  
1967 - else if d.week_day = 3 then "Wed"  
1968 - else if d.week_day = 4 then "Thu"  
1969 - else if d.week_day = 5 then "Fri"  
1970 - else if d.week_day = 6 then "Sat"  
1971 - else  
1972 - println("Bad weekday value [" + d.week_day + "] on Date_and_Time");  
1973 - "XXX".  
1974 -  
1975 -/**  
1976 - * Format a date with the followin format : "Mon, 23 Jul 2007 11:33:43 GMT"  
1977 - * Currently, this function can't output a GMT time, but only local time.  
1978 - * So the final GMT is totally fake, but needed by protocol.  
1979 - */  
1980 -public define String  
1981 - format_http_date  
1982 - (  
1983 - Date_and_Time d  
1984 - ) =  
1985 - weekday_abrv(d) + ", " + zero_pad_n(2,day(d)) + " " + month_abrv(d) + " " + year(d)  
1986 - + " " + zero_pad_n(2,hour(d)) + ":" + zero_pad_n(2,minute(d)) + ":" + zero_pad_n(2,second(d)) + " GMT".  
1987 -  
1988 -/**  
1989 - * Same as previous format_http_date() function, but with seconds count from the UNIX epoch as input.  
1990 - */  
1991 -public define String  
1992 - format_http_date  
1993 - (  
1994 - Int32 date  
1995 - ) =  
1996 - format_http_date(convert_time(date)).  
1997 -  
1998 -  
1999 - *** [5.5] Sending a file.  
2000 -  
2001 - We send 2 headers 'Content-Type' and 'Content-Length'.  
2002 -  
2003 -define List(HTTP_header)  
2004 - headers_for_send_file  
2005 - (  
2006 - String mime_type,  
2007 - Int32 size,  
2008 - String etag,  
2009 - Maybe(FileTimes) mb_ftimes,  
2010 - ) =  
2011 - with headers = (List(HTTP_header))  
2012 - [  
2013 - http_header("Content-Type",mime_type),  
2014 - http_header("Etag", etag),  
2015 - http_header("Content-Length",integer_to_string(size)),  
2016 - ],  
2017 - if mb_ftimes is  
2018 - {  
2019 - failure then headers,  
2020 - success(ftimes) then [http_header("Last-Modified", format_http_date(ftimes.last_modified)) . headers]  
2021 - }  
2022 - .  
2023 -  
2024 -  
2025 -  
2026 - Sending the body of the answer (i.e. the file itself).  
2027 -  
2028 -define One  
2029 - send_file_body  
2030 - (  
2031 - Web_Site_Description desc,  
2032 - Connection connection, // connection with the client  
2033 - Connection file, // file to be sent already opened  
2034 - Int32 size, // size of file  
2035 - Int32 sent, // bytes already sent  
2036 - String filename // name of file  
2037 - ) =  
2038 - if sent >= size then unique else  
2039 - if read(file,min(10000,size-sent),60) is  
2040 - {  
2041 - error then log_journal_msg(desc,"Cannot read from file '"+filename+"'.\n"),  
2042 - timeout then log_journal_msg(desc,"Cannot read from file timeoput'"+filename+"'.\n"),  
2043 - ok(ba) then  
2044 - with nr = length(ba), // get the number of bytes read  
2045 - if reliable_write(connection,ba) is  
2046 - {  
2047 - failure then log_journal_msg(desc,"Cannot write into connection.\n"),  
2048 - success(nw) then  
2049 - send_file_body(desc,connection,file,size,sent+nw,filename)  
2050 - }  
2051 - }.  
2052 -  
2053 -  
2054 -define String  
2055 - compute_etag  
2056 - (  
2057 - String filename,  
2058 - Maybe(FileTimes) mb_ftimes,  
2059 - Int32 size,  
2060 - ) =  
2061 - if mb_ftimes is  
2062 - {  
2063 - failure then println("Warning: no file times for '" + filename + "', etag won't be very accurate."); to_ascii(sha1((filename, size))),  
2064 - success(ftimes) then to_ascii(sha1((filename, ftimes, size)))  
2065 - }.  
2066 -  
2067 -define Bool  
2068 - are_same_etag  
2069 - (  
2070 - Maybe(String) input_etag,  
2071 - String current_etag  
2072 - ) =  
2073 - if input_etag is  
2074 - {  
2075 - failure then false,  
2076 - success(etag) then etag = current_etag  
2077 - }.  
2078 -  
2079 - Sending the answer line, the headers and the body.  
2080 -  
2081 -define One  
2082 - send_file  
2083 - (  
2084 - Web_Site_Description desc,  
2085 - Connection connection,  
2086 - List(HTTP_header) input_headers,  
2087 - List(HTTP_header) headers,  
2088 - Int32 size,  
2089 - Connection file,  
2090 - String filename,  
2091 - String full_path,  
2092 - String mime_type,  
2093 - One -> One action_before_send_file  
2094 - ) =  
2095 - action_before_send_file(unique);  
2096 - with input_etag = http_header_value(input_headers, "If-None-Match"),  
2097 - mb_ftimes = get_file_times(full_path),  
2098 - current_etag = compute_etag(full_path, mb_ftimes, size),  
2099 - if are_same_etag(input_etag, current_etag) is  
2100 - {  
2101 - false then  
2102 - forget(reliable_write(connection,to_byte_array("HTTP/1.1 200 OK"+crlf)));  
2103 - forget(reliable_write(connection,[format_headers(headers + headers_for_send_file(mime_type, size, current_etag, mb_ftimes)) , crlf]));  
2104 - send_file_body(desc,connection,file,size,0,filename),  
2105 - true then  
2106 - forget(reliable_write(connection,to_byte_array("HTTP/1.1 304 Not Modified"+crlf)));  
2107 - forget(reliable_write(connection,[format_headers([http_header("Etag", current_etag) . headers]) , crlf]))  
2108 - //send_file_body(desc,connection,file,size,0,filename)  
2109 - }.  
2110 -  
2111 -  
2112 -  
2113 - Checking if a connection is under SSL.  
2114 -  
2115 -define Bool  
2116 - is_SSL  
2117 - (  
2118 - Connection c  
2119 - ) =  
2120 - if c is  
2121 - {  
2122 - file_r(_) then false,  
2123 - file_w(_) then false,  
2124 - file_rw(_) then false,  
2125 - tcp(_) then false,  
2126 - ssl(_) then true  
2127 - }.  
2128 -  
2129 -  
2130 -  
2131 - Before opening and sending a file, we check the MIME type. It must be recognized,  
2132 - except if there is a valid authorization for private download.  
2133 -  
2134 -define One  
2135 - send_file  
2136 - (  
2137 - Web_Site_Description desc,  
2138 - Connection connection,  
2139 - String uri,  
2140 - List(HTTP_header) input_headers,  
2141 - List(HTTP_header) output_headers,  
2142 - Maybe(String) mbauthorization,  
2143 - One -> One action_before_send_file  
2144 - ) =  
2145 - if mbauthorization is  
2146 - {  
2147 - //--- file without authorization: take it from public ---  
2148 - failure then if recognize_mime_type_from_uri(desc,uri) is  
2149 - {  
2150 - failure then log_journal_msg(desc,"No MIME type found for '"+uri+"'.\n"),  
2151 - success(mime_type) then  
2152 - with path = site_directory(desc)+"/public"+uri,  
2153 - if (Maybe(RStream))connect to file path is  
2154 - {  
2155 - failure then log_journal_msg(desc,"Cannot find file '"+path+"'.\n"),  
2156 - success(f) then with size = file_size(f),  
2157 - send_file(desc,  
2158 - connection,  
2159 - input_headers,  
2160 - output_headers,  
2161 - size,  
2162 - file(f),  
2163 - uri,  
2164 - path,  
2165 - mime_type,  
2166 - action_before_send_file)  
2167 - }  
2168 - },  
2169 -  
2170 - //--- file with authorization: apply 'private download' mecanism ---  
2171 - success(authorization) then  
2172 - with private_download_dir = site_directory(desc)+"/private_download",  
2173 - if (RetrieveResult(String))retrieve(private_download_dir+"/z"+authorization)  
2174 - is ok(absolute_path)  
2175 - then (  
2176 - with new_hash = compute_authorization(authorization_secret(desc),  
2177 - absolute_path),  
2178 - if (Maybe(RStream))connect to file absolute_path is  
2179 - {  
2180 - failure then log_journal_msg(desc,"Cannot find file '"+absolute_path+"'.\n"),  
2181 - success(f) then with size = file_size(f),  
2182 - mime_type = if recognize_mime_type_from_uri(desc,uri) is  
2183 - {  
2184 - failure then "application/octet-stream"  
2185 - success(mime_type) then mime_type  
2186 - },  
2187 - send_file(desc,  
2188 - connection,  
2189 - input_headers,  
2190 - output_headers,  
2191 - size,  
2192 - file(f),  
2193 - uri,  
2194 - absolute_path,  
2195 - mime_type,  
2196 - action_before_send_file)  
2197 - }  
2198 - )  
2199 - else log_journal_msg(desc,"Cannot find or read authorization file.\n")  
2200 - }.  
2201 -  
2202 -  
2203 -  
2204 -  
2205 -  
2206 -  
2207 -  
2208 -  
2209 - *** [5.6] Answering a www-url encoded request.  
2210 -  
2211 - Standard headers are for answering ".awp" requests.  
2212 -  
2213 -define List(HTTP_header)  
2214 - standard_headers  
2215 - =  
2216 - [  
2217 - http_header("Date", format_http_date(now)),  
2218 - http_header("Server", "Anubis Embedded Server v" + major_version_number + "." + minor_version_number)  
2219 - ].  
2220 -  
2221 -define List(HTTP_header)  
2222 - standard_headers_for_html  
2223 - (  
2224 - Int32 answer_body_size,  
2225 - String charset  
2226 - ) =  
2227 - [  
2228 - //http_header("Content-Type","text/html"),  
2229 - http_header("Content-Type","text/html; charset="+charset),  
2230 - http_header("Content-length",integer_to_string(answer_body_size))  
2231 - ].  
2232 -  
2233 -  
2234 -define One  
2235 - www_url_answer  
2236 - (  
2237 - String host_name,  
2238 - Web_Site_Description desc,  
2239 - Connection connection, // with the client  
2240 - Int32 ip_addr, // of the client  
2241 - HTTP_RequestLine request_line,  
2242 - List(HTTP_header) headers,  
2243 - ByteArray body,  
2244 - One -> String generate_tt // trust ticket generation  
2245 - ) =  
2246 - with all_web_args = query_string(request_line) +  
2247 - read_www_url_encoded_web_args(to_string(body),0),  
2248 - uri = uri(request_line),  
2249 - ext = get_uri_extension(uri),  
2250 - (if member(journal_extensions(desc),ext)  
2251 - then log_journal_msg(desc,  
2252 - format_request(desc,connection,request_line,headers,all_web_args))  
2253 - else unique);  
2254 - if is_illegal_uri(uri,0)  
2255 - then log_journal_msg(desc,"Received illegal URI: "+uri+"\n")  
2256 - else (if (ext = ".awp" | ext = "")  
2257 - then (with answer_headers_body = awp_handler(desc)(host_name,  
2258 - http_info(ip_addr,uri,headers,generate_tt),  
2259 - all_web_args,  
2260 - is_SSL(connection)),  
2261 - if answer_headers_body is (additional_headers,answer_body) then  
2262 - forget(reliable_write(connection,  
2263 - [ "HTTP/1.1 200 OK", crlf,  
2264 - format_headers(standard_headers),  
2265 - format_headers(standard_headers_for_html(length(answer_body),charset(desc))),  
2266 - format_headers(additional_headers),  
2267 - crlf .  
2268 - answer_body])))  
2269 - else (send_file(desc,  
2270 - connection,  
2271 - uri,  
2272 - headers,  
2273 - standard_headers,  
2274 - if web_arg_value(all_web_args,"zauth") is  
2275 - {  
2276 - not_found then failure,  
2277 - found(v) then success(v)  
2278 - },  
2279 - (One u) |-> before_send_file(desc)(all_web_args)))).  
2280 -  
2281 -  
2282 -  
2283 -  
2284 -  
2285 -  
2286 -  
2287 - *** [5.7] Answering a multipart/form-data encoded request.  
2288 -  
2289 - In order to support upload of files, we must be able to read web arguments which are  
2290 - encoded in a multipart/form-data body. The first thing to do is to find the  
2291 - boundary. The boundary is a special string which delimits the various parts of the  
2292 - 'multipart' body. It is found within the value of the 'Content-Type' HTTP header, as  
2293 - the value of the 'boundary' attribute.  
2294 -  
2295 -  
2296 -  
2297 -  
2298 -  
2299 - *** [5.7.1] Finding the boundary.  
2300 -  
2301 - Hence, we just have to find the string 'boundary=' within the value of the  
2302 - 'Content-Type' header, and read the value of the boundary from there.  
2303 -  
2304 -define Bool  
2305 - delimits_boundary  
2306 - (  
2307 - Word8 c  
2308 - ) =  
2309 - if c = ' ' then true else  
2310 - if c = 13 then true else  
2311 - if c = 10 then true else  
2312 - if c = 0 then true else  
2313 - if c = ',' then true else  
2314 - c = ';'.  
2315 -  
2316 -  
2317 -define Maybe(String)  
2318 - get_boundary_value_3  
2319 - (  
2320 - String s,  
2321 - Int32 i,  
2322 - List(Word8) so_far  
2323 - ) =  
2324 - if nth(i,s) is  
2325 - {  
2326 - failure then success(implode(reverse(so_far))),  
2327 - success(c) then  
2328 - if delimits_boundary(c)  
2329 - then success(implode(reverse(so_far)))  
2330 - else get_boundary_value_3(s,i+1,[c . so_far])  
2331 - }.  
2332 -  
2333 -  
2334 -  
2335 -define Maybe(String)  
2336 - get_boundary_value_2  
2337 - (  
2338 - String s,  
2339 - Int32 i,  
2340 - ) =  
2341 - if nth(i,s) is  
2342 - {  
2343 - failure then failure,  
2344 - success(c) then  
2345 - if is_blank(c)  
2346 - then get_boundary_value_2(s,i+1)  
2347 - else get_boundary_value_3(s,i+1,[c])  
2348 - }.  
2349 -  
2350 -define Maybe(String)  
2351 - get_boundary_value_1  
2352 - (  
2353 - String s, // string into which we must find '= ...'  
2354 - Int32 i // position of start of search  
2355 - ) =  
2356 - if nth(i,s) is  
2357 - {  
2358 - failure then failure,  
2359 - success(c) then  
2360 - if is_blank(c)  
2361 - then get_boundary_value_1(s,i+1)  
2362 - else if c = '='  
2363 - then get_boundary_value_2(s,i+1)  
2364 - else failure  
2365 - }.  
2366 -  
2367 -  
2368 -define Maybe(String)  
2369 - get_boundary  
2370 - (  
2371 - String content_type_header_value  
2372 - ) =  
2373 - if find("boundary",content_type_header_value,0) is  
2374 - {  
2375 - failure then failure,  
2376 - success(n) then // 'boundary' has been found at position n  
2377 - get_boundary_value_1(content_type_header_value,n+8)  
2378 - }.  
2379 -  
2380 -define Maybe(String)  
2381 - get_boundary  
2382 - (  
2383 - List(HTTP_header) headers  
2384 - ) =  
2385 - if headers is  
2386 - {  
2387 - [ ] then failure,  
2388 - [h . t] then if h is http_header(name,value) then  
2389 - if name = "content-type"  
2390 - then get_boundary(value)  
2391 - else get_boundary(t)  
2392 - }.  
2393 -  
2394 -  
2395 -  
2396 -  
2397 -  
2398 -  
2399 -  
2400 -  
2401 - *** [5.7.2] Reading attributes from a multipart entity.  
2402 -  
2403 - Entities in a multipart/form-data body are separated by instances of the string:  
2404 -  
2405 - --bbbbb  
2406 -  
2407 - where bbbbb is the boundary computed above. Actually, the body has the form:  
2408 -  
2409 - --bbbbb  
2410 - <entity 1>  
2411 - --bbbbb  
2412 - <entity 2>  
2413 - --bbbbb  
2414 - ...  
2415 - --bbbbb  
2416 - <last entity>  
2417 - --bbbbb  
2418 -  
2419 -  
2420 - We have to extract an entity which is in the body between offsets 'start' and 'end'  
2421 - (computed when boundaries have been localized). The entity itself is made of two parts:  
2422 - headers and body. The body is separated from the headers by a blank line. This blank  
2423 - line (a double crlf) marks the beginning of the body of the entity. Within the headers  
2424 - of the entity, we look for a 'Content-Disposition' header, which should look like this:  
2425 -  
2426 - Content-Disposition: form-data; name="..."; filename="..." crlf  
2427 -  
2428 - We are just interested in the name and the file name. Hence we first search  
2429 - 'Content-Disposition', then we search 'name' and read the value, and we do the same for  
2430 - 'filename'.  
2431 -  
2432 - If the 'filename' attribute is not present, the web arg is an ordinary one, otherwise,  
2433 - it is an uploaded file.  
2434 -  
2435 -  
2436 - Below is a variant of 'find' (see 'tools/findstring.anubis'), with an extra 'end'  
2437 - argument.  
2438 -  
2439 -define Maybe(Int32)  
2440 - find  
2441 - (  
2442 - String what,  
2443 - ByteArray where,  
2444 - Int32 start,  
2445 - Int32 end  
2446 - ) =  
2447 - if find(to_byte_array(what),where,start) is  
2448 - {  
2449 - failure then failure,  
2450 - success(n) then  
2451 - if n+length(what) >= end  
2452 - then failure  
2453 - else success(n)  
2454 - }.  
2455 -  
2456 -  
2457 -define String  
2458 - read_attribute_value  
2459 - (  
2460 - ByteArray where,  
2461 - Int32 start,  
2462 - Int32 end,  
2463 - List(Word8) so_far  
2464 - ) =  
2465 - if start >= end then implode(reverse(so_far)) else  
2466 - if nth(start,where) is  
2467 - {  
2468 - failure then implode(reverse(so_far)),  
2469 - success(c) then  
2470 - if c = '\"'  
2471 - then implode(reverse(so_far))  
2472 - else read_attribute_value(where,start+1,end,[c . so_far])  
2473 - }.  
2474 -  
2475 -define Maybe(String)  
2476 - find_attribute  
2477 - (  
2478 - String name,  
2479 - ByteArray where,  
2480 - Int32 start,  
2481 - Int32 end  
2482 - ) =  
2483 - with name = name+"=\"",  
2484 - if find(to_byte_array(name),where,start) is  
2485 - {  
2486 - failure then failure,  
2487 - success(n) then  
2488 - if n+length(name) >= end  
2489 - then failure  
2490 - else success(read_attribute_value(where,n+length(name),end,[]))  
2491 - }.  
2492 -  
2493 -  
2494 -  
2495 -define Maybe((String,Maybe(String)))  
2496 - find_name_and_filename  
2497 - (  
2498 - ByteArray body,  
2499 - Int32 start,  
2500 - Int32 end  
2501 - ) =  
2502 - if find(to_byte_array("Content-Disposition"),body,start) is  
2503 - {  
2504 - failure then failure,  
2505 - success(n) then  
2506 - if find_attribute("name",body,n+19,end) is  
2507 - {  
2508 - failure then failure,  
2509 - success(name_value) then if find_attribute("filename",body,n+19,end) is  
2510 - {  
2511 - failure then success((name_value,failure)),  
2512 - success(filename_value) then success((name_value,success(filename_value)))  
2513 - }  
2514 - }  
2515 - }.  
2516 -  
2517 -  
2518 -  
2519 -  
2520 -  
2521 -  
2522 -  
2523 -  
2524 -  
2525 -  
2526 - *** [5.7.3] Creating a temporary filename for an uploaded file.  
2527 -  
2528 -variable Int32 uploaded_file_count = 0.  
2529 -  
2530 - This variable is local to the virtual machine. Hence, its value is 0 each time a new  
2531 - requests arrives. Temporary uploaded files are stored in the directory represented by  
2532 - 'upload_temporary_directory'. The filenames have the form:  
2533 -  
2534 - _m_n  
2535 -  
2536 - where 'm' is the number of the virtual machine, and 'n' a number obtained by  
2537 - incrementing 'uploaded_file_count'. Notice that the program must do something with this  
2538 - file (move it to some directory/name), otherwise, it will probably be overwritten the  
2539 - next time the same machine works.  
2540 -  
2541 -  
2542 -  
2543 -  
2544 -  
2545 -  
2546 - *** [5.7.4] Saving an uploaded file under a temporary filename.  
2547 -  
2548 -define Maybe(String) // returns the temporary file name  
2549 - save_uploaded_file  
2550 - (  
2551 - Web_Site_Description desc,  
2552 - ByteArray body,  
2553 - Int32 start,  
2554 - Int32 end  
2555 - ) =  
2556 - uploaded_file_count <- 1 + *uploaded_file_count;  
2557 - with tfn = "_"+integer_to_string(virtual_machine_id)+"_"+integer_to_string(*uploaded_file_count),  
2558 - if (Maybe(WStream))connect to file site_directory(desc)+"/upload_temporary/"+tfn is  
2559 - {  
2560 - failure then failure,  
2561 - success(f) then  
2562 - if reliable_write(file(f),extract(body,start,end)) is  
2563 - {  
2564 - failure then failure,  
2565 - success(nw) then  
2566 - if nw = end - start  
2567 - then success(tfn)  
2568 - else failure  
2569 - }  
2570 - }.  
2571 -  
2572 -  
2573 -  
2574 -  
2575 -  
2576 -  
2577 -  
2578 -  
2579 - *** [5.7.5] Removing the path from a file name.  
2580 -  
2581 - When a file is uploaded, the browser sends the complete path of the file on the client  
2582 - machine as the file name. Actually, this is not quite normal. Nevertheless, we need to  
2583 - remove the path, and keep only the file name. This is achieved by 'remove_path' below.  
2584 -  
2585 -define Int32  
2586 - file_name_begin  
2587 - (  
2588 - String full_name,  
2589 - Int32 i  
2590 - ) =  
2591 - if nth(i,full_name) is  
2592 - {  
2593 - failure then 0,  
2594 - success(c) then  
2595 - if c = '/' then i+1 else  
2596 - if c = '\\' then i+1 else  
2597 - file_name_begin(full_name,i-1)  
2598 - }.  
2599 -  
2600 -define String  
2601 - remove_path  
2602 - (  
2603 - String full_name  
2604 - ) =  
2605 - with l = length(full_name),  
2606 - b = file_name_begin(full_name,l-1),  
2607 - substr(full_name,b,l-b).  
2608 -  
2609 -  
2610 -  
2611 -  
2612 -  
2613 - *** [5.7.6] Reading a multipart entity.  
2614 -  
2615 -define Maybe(Web_arg)  
2616 - get_multipart_entity  
2617 - (  
2618 - Web_Site_Description desc,  
2619 - ByteArray body,  
2620 - Int32 start,  
2621 - Int32 end  
2622 - ) =  
2623 - if find(to_byte_array(crlf+crlf),body,start) is  
2624 - {  
2625 - failure then failure,  
2626 - success(k) then  
2627 - if k >= end // must be within this entity, not the next one  
2628 - then failure  
2629 - else if find_name_and_filename(body,start,k) is  
2630 - {  
2631 - failure then failure,  
2632 - success(n_mbfn) then if n_mbfn is (name,mbfn) then  
2633 - if mbfn is  
2634 - {  
2635 - failure then  
2636 - success(web_arg(name,to_string(extract(body,k+4,end-2)))),  
2637 - // we must substract 2 to end because of crlf just before the boundary  
2638 -  
2639 - success(fn) then  
2640 - if save_uploaded_file(desc,body,k+4,end-2) is  
2641 - {  
2642 - failure then failure,  
2643 - success(tfn) then  
2644 - success(upload(name,remove_path(fn),  
2645 - site_directory(desc)+"/upload_temporary/"+tfn))  
2646 -  
2647 - }  
2648 - }  
2649 - }  
2650 - }.  
2651 -  
2652 -  
2653 -  
2654 -define List(Web_arg)  
2655 - read_multipart_form_data_encoded_web_args  
2656 - (  
2657 - Web_Site_Description desc,  
2658 - ByteArray body,  
2659 - ByteArray __boundary,  
2660 - Int32 i,  
2661 - ) =  
2662 - if find(__boundary,body,i) is  
2663 - {  
2664 - failure then [ ],  
2665 - success(n) then  
2666 - if find(__boundary,body,n+length(__boundary)) is  
2667 - {  
2668 - failure then [ ],  
2669 - success(m) then  
2670 - if get_multipart_entity(desc,body,n+length(__boundary),m) is  
2671 - {  
2672 - failure then [ ],  
2673 - success(wa) then  
2674 - [wa . read_multipart_form_data_encoded_web_args(desc,body,__boundary,m)]  
2675 - }  
2676 - }  
2677 - }.  
2678 -  
2679 -  
2680 -  
2681 -define One  
2682 - multipart_form_data_answer  
2683 - (  
2684 - String host_name,  
2685 - Web_Site_Description desc,  
2686 - Connection connection,  
2687 - Int32 ip_addr,  
2688 - HTTP_RequestLine request_line,  
2689 - List(HTTP_header) headers,  
2690 - ByteArray body,  
2691 - One -> String generate_tt  
2692 - ) =  
2693 - if get_boundary(headers) is  
2694 - {  
2695 - failure then unique,  
2696 - success(boundary) then  
2697 - with all_web_args = query_string(request_line) +  
2698 - read_multipart_form_data_encoded_web_args(desc,  
2699 - body,  
2700 - to_byte_array("--"+boundary),  
2701 - 0),  
2702 - uri = uri(request_line),  
2703 - ext = get_uri_extension(uri),  
2704 - log_journal_msg(desc,  
2705 - format_request(desc,connection,request_line,headers,all_web_args));  
2706 - if is_illegal_uri(uri,0)  
2707 - then log_journal_msg(desc,"Received illegal URI: "+uri+"\n")  
2708 - else  
2709 - if (ext = ".awp" | ext = "") then  
2710 - (with answer_headers_body = awp_handler(desc)(host_name,  
2711 - http_info(ip_addr,uri,headers,generate_tt),  
2712 - all_web_args,  
2713 - is_SSL(connection)),  
2714 - if answer_headers_body is (additional_headers,answer_body) then  
2715 - forget(reliable_write(connection,  
2716 - [ "HTTP/1.1 200 OK",crlf,  
2717 - format_headers(standard_headers_for_html(length(answer_body),charset(desc))),  
2718 - format_headers(additional_headers),  
2719 - crlf .  
2720 - answer_body])))  
2721 - else unique  
2722 - }.  
2723 -  
2724 -  
2725 -  
2726 -  
2727 -  
2728 -  
2729 -  
2730 -  
2731 - *** [5.8] Handling redirections.  
2732 -  
2733 - 'redirections' (of type 'List(Redirection)') contains redirection directives. Each one  
2734 - has the form:  
2735 -  
2736 - redirect(required_uri,required_host,corresponding_uri).  
2737 -  
2738 - The host required by the client may be found in the 'Host' HTTP header. The URI  
2739 - required by the client is given below as 'uri'. We just have to find the required host  
2740 - in the headers, and to find the corresponding redirection directive.  
2741 -  
2742 -  
2743 - In the next fonction, the required host and URI are known. We just have to search in  
2744 - the 'redirections' list.  
2745 -  
2746 -define String  
2747 - handle_redirection  
2748 - (  
2749 - String required_uri,  
2750 - String required_host,  
2751 - List(Redirection) redirections  
2752 - ) =  
2753 - if redirections is  
2754 - {  
2755 - [ ] then required_uri,  
2756 - [h . t] then if h is redirect(uri,host,target) then  
2757 - if host = required_host  
2758 - then if uri = required_uri  
2759 - then target  
2760 - else handle_redirection(required_uri,required_host,t)  
2761 - else handle_redirection(required_uri,required_host,t)  
2762 - }.  
2763 -  
2764 -  
2765 -  
2766 - The host name may be encumbered by a port number, like  
2767 -  
2768 - www.our-business.com:1607  
2769 -  
2770 - We must remove this port number, otherwise the host name may not be recognized.  
2771 -  
2772 -define String  
2773 - strip_port  
2774 - (  
2775 - String name,  
2776 - Int32 i  
2777 - ) =  
2778 - if nth(i,name) is  
2779 - {  
2780 - failure then name,  
2781 - success(c) then  
2782 - if c = ':'  
2783 - then substr(name,0,i)  
2784 - else strip_port(name,i+1)  
2785 - }.  
2786 -  
2787 -  
2788 -  
2789 -  
2790 -  
2791 - Finding the 'Host' header. No redirection is performed if this header is not found.  
2792 -  
2793 -define String  
2794 - handle_redirection // returns the redirected URI  
2795 - (  
2796 - List(Redirection) redirections,  
2797 - String uri, // original URI  
2798 - List(HTTP_header) headers  
2799 - ) =  
2800 - if headers is  
2801 - {  
2802 - [ ] then uri,  
2803 - [h . t] then if h is http_header(name,value) then  
2804 - if name = "host"  
2805 - then handle_redirection(uri,strip_port(value,0),redirections)  
2806 - else handle_redirection(redirections,uri,t)  
2807 - }.  
2808 -  
2809 -  
2810 -  
2811 -  
2812 -  
2813 -  
2814 -  
2815 -  
2816 - *** [5.9] Answering both sorts of requests.  
2817 -  
2818 - We must decide if the request is www-url encoded or multipart/form-data encoded. This  
2819 - is achieved through the header 'Content-Type'.  
2820 -  
2821 -define EncodingType  
2822 - get_encoding_type  
2823 - (  
2824 - List(HTTP_header) headers  
2825 - ) =  
2826 - if headers is  
2827 - {  
2828 - [ ] then www_url, // this is the default  
2829 - [h . t] then if h is http_header(name,value) then  
2830 - if name = "content-type"  
2831 - then if find("multipart/form-data",value,0) is  
2832 - {  
2833 - failure then www_url,  
2834 - success(_) then multipart_form_data  
2835 - }  
2836 - else get_encoding_type(t)  
2837 - }.  
2838 -  
2839 -  
2840 -  
2841 -define One  
2842 - send_answer  
2843 - (  
2844 - String host_name,  
2845 - Web_Site_Description desc,  
2846 - Connection connection,  
2847 - HTTP_RequestLine rqline,  
2848 - List(HTTP_header) headers,  
2849 - ByteArray body,  
2850 - One -> String generate_tt  
2851 - ) =  
2852 - if rqline is request_line(type,uri,qstring) then  
2853 - with rqline = request_line(type,handle_redirection(redirections(desc),uri,headers),qstring),  
2854 - if remote_IP_address_and_port(connection) is (ip_addr,_) then  
2855 - if get_encoding_type(headers) is  
2856 - {  
2857 - www_url then  
2858 - www_url_answer(host_name,desc,connection,ip_addr,rqline,headers,body,generate_tt),  
2859 - multipart_form_data then  
2860 - multipart_form_data_answer(host_name,desc,connection,ip_addr,rqline,headers,body,generate_tt)  
2861 - }.  
2862 -  
2863 -  
2864 -  
2865 -  
2866 -  
2867 -  
2868 -  
2869 - *** [6] The HTTP/HTTPS server.  
2870 -  
2871 - The command 'start_server' (declared in 'predefined.anubis') starts a virtual machine  
2872 - which opens a server TCP/IP connection, and which continuously listens to this  
2873 - connection. When a request arrives, this machine delegates the work of deciphering and  
2874 - answering the request to another virtual machine, and continues to listen. The job of  
2875 - the delegated machine is defined by the HTTP request handler below.  
2876 -  
2877 -  
2878 -  
2879 -  
2880 -  
2881 - *** [6.1] Determining the requested host.  
2882 -  
2883 - When a request arrives to one of our two servers, we must decide which site (host) is  
2884 - requested.  
2885 -  
2886 -define Maybe(String)  
2887 - get_host_header_value  
2888 - (  
2889 - List(HTTP_header) headers  
2890 - ) =  
2891 - if headers is  
2892 - {  
2893 - [ ] then failure,  
2894 - [h . t] then if h is http_header(name,value) then  
2895 - if name = "host"  
2896 - then success(strip_port(value,0))  
2897 - else get_host_header_value(t)  
2898 - }.  
2899 -  
2900 -define Maybe((String,Web_Site_Description))  
2901 - get_site  
2902 - (  
2903 - String requested_host,  
2904 - List(Web_Site_Description) sites  
2905 - ) =  
2906 - if sites is  
2907 - {  
2908 - [ ] then print("Requested host '"+requested_host+"' does not exist.\n"); failure,  
2909 - [site1 . others] then  
2910 - if site1 is web_site_description(common_names,_,_,_,_,_,_,_,_,_) then  
2911 - if member(common_names,requested_host)  
2912 - then success((requested_host,site1))  
2913 - else get_site(requested_host,others)  
2914 - }.  
2915 -  
2916 -  
2917 -define Maybe((String,Web_Site_Description))  
2918 - get_site  
2919 - (  
2920 - List(HTTP_header) headers,  
2921 - List(Web_Site_Description) sites  
2922 - ) =  
2923 - if get_host_header_value(headers) is  
2924 - {  
2925 - failure then print("No 'Host' HTTP header.\n"); failure,  
2926 - success(requested_host) then  
2927 - //here we treat the case with only one site. hence we accept any host request  
2928 - //print("*** there is " +length(sites) + " sites \n");  
2929 - if length(sites) = 1 then  
2930 - with site = force_nth(0, sites),  
2931 - //print("ONE server OK\n");  
2932 - success((requested_host, site))  
2933 - else  
2934 - get_site(requested_host,sites)  
2935 - }.  
2936 -  
2937 -  
2938 -  
2939 -  
2940 -  
2941 - *** [6.2] The HTTP request handler.  
2942 -  
2943 - Here is the HTTP/HTTPS handler. It is called at each new request in a separate virtual  
2944 - machine. It reads the headers of the HTTP request, determines the host, determines body  
2945 - size, reads the body of the HTTP request, and answers the request.  
2946 -  
2947 -  
2948 -  
2949 -define One -> String make_generate_trust_ticket(DenialOfService dos).  
2950 -  
2951 -  
2952 -define One  
2953 - http_https_handler  
2954 - (  
2955 - List(Web_Site_Description) sites,  
2956 - BufferedConnection connection,  
2957 - Bool is_https,  
2958 - DenialOfService dos  
2959 - ) =  
2960 - with start_time = (Int32)now,  
2961 - sttm <- start_time;  
2962 - if dos is denial_of_service(mc_v,rld_v,hd_v,ad_v,ld_v,ra_v) then  
2963 - if remote_IP_address_and_port(connection.conn) is (ip_addr,port) then  
2964 - if read_request_line(connection,start_time+*rld_v,dos) is  
2965 - {  
2966 - error(msg) then print(format(msg)),  
2967 - ok(request_line) then  
2968 - if read_http_headers(connection,start_time+*hd_v,dos) is  
2969 - {  
2970 - error(msg) then print(format(msg)),  
2971 - ok(headers) then if get_site(headers,sites) is  
2972 - {  
2973 - failure then unique,  
2974 - success(p) then if p is (host_name,desc) then  
2975 - if get_body_size(headers) is  
2976 - {  
2977 - error(msg) then log_journal_msg(desc,format(msg)),  
2978 - ok(body_size) then  
2979 - if read_http_body(connection,body_size,constant_byte_array(0,0),1000) is  
2980 - {  
2981 - error(msg) then log_journal_msg(desc,format(msg)),  
2982 - ok(body) then  
2983 - send_answer(host_name, desc,connection.conn, request_line, headers, body,  
2984 - make_generate_trust_ticket(dos))  
2985 - }  
2986 - }  
2987 - }  
2988 - }  
2989 - }.  
2990 -  
2991 -  
2992 - Below are the two tools for constructing the handlers required by 'start_server' and  
2993 - 'start_ssl_server' (see 'predefined.anubis').  
2994 -  
2995 -define Bool is_dubious_IP(Int32 ip, DenialOfService dos).  
2996 -  
2997 -define Server -> ((RWStream) -> One)  
2998 - make_http_handler  
2999 - (  
3000 - List(Web_Site_Description) sites,  
3001 - DenialOfService dos  
3002 - ) =  
3003 - (Server server) |-> (RWStream conn) |->  
3004 - if remote_IP_address_and_port(conn) is (addr,_) then  
3005 - if is_dubious_IP(addr,dos)  
3006 - then print("Rejecting dubious IP address "+ip_addr_to_string(addr)+"\n")  
3007 - else  
3008 - with connection = buffered_connection(tcp(conn), var(constant_byte_array(0, 0)), var(0)),  
3009 - http_https_handler(sites, connection, false, dos).  
3010 -  
3011 -define Server -> (SSL_Connection -> One)  
3012 - make_https_handler  
3013 - (  
3014 - List(Web_Site_Description) sites,  
3015 - DenialOfService dos  
3016 - ) =  
3017 - (Server server) |-> (SSL_Connection conn) |->  
3018 - with connection = buffered_connection(ssl(conn), var(constant_byte_array(0, 0)), var(0)),  
3019 - http_https_handler(sites, connection, true, dos).  
3020 -  
3021 -  
3022 -  
3023 -  
3024 - *** [6.3] Server's tasks.  
3025 -  
3026 - Some tasks must be executed periodically, for example for cleaning up directories from  
3027 - short life time files.  
3028 -  
3029 - The next function removes from the given directory (and recursively from its  
3030 - subdirectories) all the files which are more than 10 minutes old.  
3031 -  
3032 -define One  
3033 - cleanup_directory_10mn  
3034 - (  
3035 - String dir // path of private download directory (or subdirectory) with trailing slash  
3036 - ) =  
3037 - forget(map((FileDescription fd) |-> if fd is  
3038 - {  
3039 - no_info(name) then forget(remove(dir+name)),  
3040 - file(name,_,_,d) then if d+600 < now then forget(remove(dir+name)) else unique,  
3041 - link(name,_,_,d) then if d+600 < now then forget(remove(dir+name)) else unique,  
3042 - directory(name,_,_) then cleanup_directory_10mn(dir+name+"/"),  
3043 - },  
3044 - directory_full_list(dir,"*","*","*"))).  
3045 -  
3046 -  
3047 -define One  
3048 - http_servers_tasks  
3049 - (  
3050 - List(Web_Site_Description) sites,  
3051 - List(Server) servers,  
3052 - Int32 period,  
3053 - Int32 next_time,  
3054 - ) =  
3055 - if mapand(is_down,servers)  
3056 - then unique  
3057 - else if now > next_time  
3058 - then  
3059 - (  
3060 - /*  
3061 - forget(map((Web_Site_Description wsd) |->  
3062 - cleanup_directory_10mn(site_directory(wsd)+"/private_download/"),  
3063 - sites));  
3064 - */  
3065 - http_servers_tasks(sites,servers,period,next_time+period)  
3066 - )  
3067 - else  
3068 - (  
3069 - sleep(1000);  
3070 - http_servers_tasks(sites,servers,period,next_time)  
3071 - ).  
3072 -  
3073 -  
3074 -public define One  
3075 - start_http_servers_tasks  
3076 - (  
3077 - List(Web_Site_Description) sites,  
3078 - List(Server) servers,  
3079 - Int32 period  
3080 - ) =  
3081 - delegate http_servers_tasks(sites,servers,period,now),  
3082 - unique.  
3083 -  
3084 -  
3085 -  
3086 -  
3087 - *** [6.4] Protection against 'denial of service' attacks.  
3088 -  
3089 -  
3090 - *** [6.4.1] Counting connections.  
3091 -  
3092 -define Bool // returns false if the counter cannot be incremented (too many connections)  
3093 - increment_connections_counter  
3094 - (  
3095 - Var(Int32) counter  
3096 - ) =  
3097 - protect with n = *counter,  
3098 - if n >= 100  
3099 - then false  
3100 - else (counter <- (*counter)+1); true.  
3101 -  
3102 -define One  
3103 - decrement_connections_counter  
3104 - (  
3105 - Var(Int32) counter  
3106 - ) =  
3107 - protect counter <- (*counter)-1.  
3108 -  
3109 -  
3110 -  
3111 -  
3112 -  
3113 - *** [6.4.2] Recording dubious IP addresses.  
3114 -  
3115 -  
3116 -define List(DubiousIP)  
3117 - record_dubious_IP  
3118 - (  
3119 - Int32 ip,  
3120 - List(DubiousIP) l  
3121 - ) =  
3122 - if l is  
3123 - {  
3124 - [ ] then [dubious_ip(ip,now)],  
3125 - [h . t] then if h is dubious_ip(addr,time) then  
3126 - if addr = ip  
3127 - then [dubious_ip(addr,now) . t]  
3128 - else [h . record_dubious_IP(ip,t)]  
3129 - }.  
3130 -  
3131 -  
3132 -define One  
3133 - record_dubious_IP  
3134 - (  
3135 - Int32 dubious_IP,  
3136 - Var(List(DubiousIP)) v  
3137 - ) =  
3138 - protect v <- record_dubious_IP(dubious_IP,*v).  
3139 -  
3140 -  
3141 -define One  
3142 - record_dubious_IP  
3143 - (  
3144 - Int32 addr,  
3145 - DenialOfService dos  
3146 - ) =  
3147 - record_dubious_IP(addr,list_of_dubious(dos)).  
3148 -  
3149 -  
3150 -public define DenialOfService  
3151 - load_denial_of_service_info  
3152 - =  
3153 - if (RetrieveResult(DenialOfService))retrieve(my_anubis_directory+"/web_sites/dos_info") is  
3154 - ok(dos) then dos else denial_of_service(  
3155 - var(100),  
3156 - var(1000),  
3157 - var(1500),  
3158 - var(2000),  
3159 - var([]),  
3160 - var([])).  
3161 -  
3162 -  
3163 -  
3164 -  
3165 - *** [6.4.3] Testing if an address is dubious.  
3166 -  
3167 -define Bool  
3168 - is_dubious_IP  
3169 - (  
3170 - Int32 ip,  
3171 - List(DubiousIP) l  
3172 - ) =  
3173 - if l is  
3174 - {  
3175 - [ ] then false,  
3176 - [h . t] then if h is dubious_ip(addr,time) then  
3177 - if ip = addr  
3178 - then true  
3179 - else is_dubious_IP(ip,t)  
3180 - }.  
3181 -  
3182 -  
3183 -define Bool  
3184 - is_dubious_IP  
3185 - (  
3186 - Int32 ip,  
3187 - DenialOfService dos  
3188 - ) =  
3189 - if dos is  
3190 - {  
3191 - denial_of_service(mc_v,rld_v,hd_v,ad_v,ld_v,ra_v) then  
3192 - if member(*ra_v,ip) then false else  
3193 - is_dubious_IP(ip,*ld_v)  
3194 - }.  
3195 -  
3196 -  
3197 -  
3198 -  
3199 - *** [6.4.4] Removing inactive dubious IP addresses.  
3200 -  
3201 -define List(DubiousIP)  
3202 - remove_inactive_dubious_IP  
3203 - (  
3204 - List(DubiousIP) l,  
3205 - Int32 ref_time,  
3206 - ) =  
3207 - if l is  
3208 - {  
3209 - [ ] then [ ],  
3210 - [h . t] then if h is dubious_ip(addr,time) then  
3211 - if time < ref_time  
3212 - then (print(ip_addr_to_string(addr)+" removed from dubious addresses list.\n");  
3213 - remove_inactive_dubious_IP(t,ref_time))  
3214 - else [h . remove_inactive_dubious_IP(t,ref_time)]  
3215 - }.  
3216 -  
3217 -define One  
3218 - remove_inactive_dubious_IP  
3219 - (  
3220 - Var(List(DubiousIP)) v  
3221 - ) =  
3222 - protect  
3223 - with ref_time = now - 600, // 10 minutes  
3224 - v <- remove_inactive_dubious_IP(*v,ref_time).  
3225 -  
3226 -  
3227 - The above function will be executed periodically by the servers's tasks machine.  
3228 -  
3229 -  
3230 -  
3231 - *** [6.4.5] Making the function for generating trust tickets.  
3232 -  
3233 -define One -> String  
3234 - make_generate_trust_ticket  
3235 - (  
3236 - DenialOfService dos  
3237 - ) =  
3238 - (One _) |-> "".  
3239 -  
3240 -  
3241 -  
3242 -  
3243 -  
3244 -  
3245 -  
3246 - *** [6.5] Starting the HTTP/HTTPS server.  
3247 -  
3248 -  
3249 - The next function creates the directories for all sites (if they don't already exist).  
3250 -  
3251 -define One  
3252 - create_directories  
3253 - (  
3254 - List(Web_Site_Description) sites  
3255 - ) =  
3256 - if sites is  
3257 - {  
3258 - [ ] then unique,  
3259 - [s1 . others] then  
3260 - with site_dir = site_directory(s1),  
3261 - forget(make_directory(site_dir+"/public",default_directory_mode));  
3262 - forget(make_directory(site_dir+"/upload_temporary",default_directory_mode));  
3263 - forget(make_directory(site_dir+"/private_download",default_directory_mode));  
3264 - forget(make_directory(site_dir+"/journal",default_directory_mode));  
3265 - create_directories(others)  
3266 - }.  
3267 -  
3268 -  
3269 -  
3270 -  
3271 -  
3272 - Below are the commands for starting an HTTP server and an HTTPS server.  
3273 -  
3274 -  
3275 -define StartServerResult  
3276 - start_http_server  
3277 - (  
3278 - Int32 ip_address,  
3279 - Int32 port,  
3280 - Server -> ((RWStream) -> One) handler,  
3281 - Int32 retries,  
3282 - DenialOfService dos  
3283 - ) =  
3284 - if start_server(ip_address,  
3285 - port,  
3286 - handler,  
3287 - identity) is ok(server)  
3288 - then print(" \r");  
3289 - ok(server)  
3290 - else print("Port "+port+": retry number "+retries+"\r");  
3291 - sleep(1000);  
3292 - start_http_server(ip_address,port,handler,retries+1,dos).  
3293 -  
3294 -public define StartServerResult  
3295 - start_http_server  
3296 - (  
3297 - Int32 ip_address,  
3298 - Int32 port,  
3299 - List(Web_Site_Description) sites,  
3300 - DenialOfService dos  
3301 - ) =  
3302 - create_directories(sites);  
3303 - start_http_server(ip_address,port,  
3304 - make_http_handler(sites,dos),  
3305 - 0,  
3306 - dos).  
3307 -  
3308 -  
3309 - For the HTTPS server, we have a problem which is due to the fact that 'anbexec' is not  
3310 - yet able to manipulate several SSL server certificates. 'anbexec' and  
3311 - 'predefined.anubis' must be changed. Sorry ! This will be done as soon as possible. The  
3312 - 'solution' for the time being is to provide the common name of the unique SSL server  
3313 - certificate.  
3314 -  
3315 -  
3316 -define StartServerResult  
3317 - start_https_server  
3318 - (  
3319 - Int32 ip_address,  
3320 - Int32 port,  
3321 - String certificate_common_name,  
3322 - Server -> (SSL_Connection -> One) handler,  
3323 - Int32 retries,  
3324 - DenialOfService dos  
3325 - ) =  
3326 - if start_ssl_server(ip_address,  
3327 - port,  
3328 - certificate_common_name,  
3329 - handler,  
3330 - identity) is ok(server)  
3331 - then print(" \r");  
3332 - ok(server)  
3333 - else print("Port "+port+": retry number "+retries+"\r");  
3334 - sleep(1000);  
3335 - start_https_server(ip_address,port,  
3336 - certificate_common_name,  
3337 - handler,retries+1,  
3338 - dos).  
3339 -  
3340 -  
3341 -public define StartServerResult  
3342 - start_https_server  
3343 - (  
3344 - Int32 ip_address,  
3345 - Int32 port,  
3346 - String certificate_common_name, // of SSL server certificate  
3347 - List(Web_Site_Description) sites,  
3348 - DenialOfService dos  
3349 - ) =  
3350 - create_directories(sites);  
3351 - start_https_server(ip_address,port,certificate_common_name,  
3352 - make_https_handler(sites,dos),  
3353 - 0,dos).  
3354 -  
3355 -  
3356 -  
3357 -  
3358 -  
3359 -  
3360 -  
3361 -  
3362 -  
3363 - *** [7] The web dispatcher.  
3364 -  
3365 -  
3366 - *** [7.1] The dispatcher server.  
3367 -  
3368 -define One  
3369 - send_dispatching_page  
3370 - (  
3371 - RWStream conn,  
3372 - String common_name,  
3373 - Int32 port  
3374 - ) =  
3375 - print("Dispatching '"+common_name+"' to port "+port+"\n");  
3376 - forget(reliable_write(conn,to_byte_array(  
3377 - "<html><head><meta http-equiv=\"Refresh\" content=\"0;URL="+  
3378 - "http://"+common_name+":"+port+"/"+  
3379 - "\"></head><body></body></html>"  
3380 - ))).  
3381 -  
3382 -  
3383 -  
3384 -define Maybe(DispatcherInfo)  
3385 - find_host  
3386 - (  
3387 - List(DispatcherInfo) l,  
3388 - String host  
3389 - ) =  
3390 - if l is  
3391 - {  
3392 - [ ] then failure,  
3393 - [h . t] then if h is site(name,port) then  
3394 - if name = host  
3395 - then success(h)  
3396 - else find_host(t,host)  
3397 - }.  
3398 -  
3399 -  
3400 -  
3401 -define Server -> ((RWStream) -> One)  
3402 - make_dispatcher_handler  
3403 - (  
3404 - Var(List(DispatcherInfo)) info_v,  
3405 - DenialOfService dos  
3406 - ) =  
3407 - (Server server) |-> (RWStream conn) |->  
3408 - with start_time = (Int32)now,  
3409 - connection = buffered_connection(tcp(conn), var(constant_byte_array(0, 0)), var(0)),  
3410 - if read_request_line(connection, start_time+*request_line_delay(dos), dos) is  
3411 - {  
3412 - error(msg) then print(format(msg)),  
3413 - ok(request_line) then  
3414 - if read_http_headers(connection, start_time+*headers_delay(dos), dos) is  
3415 - {  
3416 - error(msg) then print(format(msg)),  
3417 - ok(headers) then if get_host_header_value(headers) is  
3418 - {  
3419 - failure then print("No 'HOST' HTTP header.\n"),  
3420 - success(host) then  
3421 - if find_host(*info_v,host) is  
3422 - {  
3423 - failure then print("Host: '"+host+"' not registered.\n"),  
3424 - success(s) then if s is site(common_name,ip_port) then  
3425 - send_dispatching_page(conn,common_name,ip_port)  
3426 - }  
3427 - }  
3428 - }  
3429 - }.  
3430 -  
3431 -  
3432 -define One  
3433 - dispatcher_update_error  
3434 - (  
3435 - String file_path  
3436 - ) =  
3437 - print("web_dispatcher: unable to reread file: '"+file_path+"'.\n").  
3438 -  
3439 -  
3440 -define Bool  
3441 - dispatcher_update_data  
3442 - (  
3443 - String info_file_path,  
3444 - Var(List(DispatcherInfo)) info_v,  
3445 - Var(Int32) info_date_v  
3446 - ) =  
3447 - if directory_full_list(my_anubis_directory+"/web_sites","dispatcher.info","","") is  
3448 - {  
3449 - [ ] then false,  
3450 - [h . t] then if h is  
3451 - {  
3452 - no_info(n) then false,  
3453 - file(n,_,_,d) then if n = "dispatcher.info"  
3454 - then (info_date_v <- d;  
3455 - if (RetrieveResult(List(DispatcherInfo)))retrieve(info_file_path) is  
3456 - {  
3457 - cannot_find_file then false,  
3458 - read_error then false,  
3459 - type_error then false,  
3460 - ok(info) then info_v <- info; true  
3461 - })  
3462 - else false,  
3463 - link(_,_,_,_) then false,  
3464 - directory(_,_,_) then false  
3465 - }  
3466 - }.  
3467 -  
3468 -  
3469 -  
3470 - The loop within which the dispatcher updates its data every 3 seconds:  
3471 -  
3472 -define One  
3473 - dispatcher_update_task  
3474 - (  
3475 - String info_file_path,  
3476 - Var(List(DispatcherInfo)) info_v,  
3477 - Var(Int32) info_date_v  
3478 - ) =  
3479 - sleep(3000);  
3480 - (if dispatcher_update_data(info_file_path,info_v,info_date_v)  
3481 - then unique  
3482 - else dispatcher_update_error(info_file_path));  
3483 - dispatcher_update_task(info_file_path,info_v,info_date_v).  
3484 -  
3485 -  
3486 -public define One  
3487 - start_web_dispatcher  
3488 - (  
3489 - Int32 ip_address, // address for listening (typically 0: listen on all interfaces)  
3490 - Int32 http_port, // typically 80  
3491 - DenialOfService dos  
3492 - ) =  
3493 - with info_file_path = my_anubis_directory+"/web_sites/dispatcher.info",  
3494 - info_v = var((List(DispatcherInfo))[]),  
3495 - info_date_v = var((Int32)0),  
3496 - if dispatcher_update_data(info_file_path,info_v,info_date_v)  
3497 - then if start_server(ip_address,  
3498 - http_port,  
3499 - make_dispatcher_handler(info_v,dos),  
3500 - (One u)|->u) is  
3501 - {  
3502 - cannot_create_the_socket then  
3503 - print("Cannot create the socket for HTTP server.\n"),  
3504 - cannot_bind_to_port then  
3505 - print("Cannot bind HTTP server to port "+http_port+".\n"),  
3506 - cannot_listen_on_port then  
3507 - print("HTTP server cannot listen on port "+http_port+".\n"),  
3508 - ok(http_server) then  
3509 - dispatcher_update_task(info_file_path,info_v,info_date_v)  
3510 - }  
3511 - else dispatcher_update_error(info_file_path).  
3512 -  
3513 -  
3514 -  
3515 - *** [7.2] The dispatcher web site.  
3516 -  
3517 - global define One  
3518 - web_dispatcher  
3519 - (  
3520 - List(String) args  
3521 - ) =  
3522 - start_web_dispatcher(0,80,load_denial_of_service_info).  
3523 -  
3524 -  
3525 -  
3526 -  
3527 -  
3528 -  
3529 - *** [7.3] Managing the info file.  
3530 -  
3531 -define Int32  
3532 - register_ip_address  
3533 - =  
3534 - if ip_address(prompt(" numerical IP address (for HTTP): ")) is  
3535 - {  
3536 - failure then print(" *** Error: incorrect IP address.\n");  
3537 - register_ip_address,  
3538 - success(n) then n  
3539 - }.  
3540 -  
3541 -  
3542 -define Int32  
3543 - register_ip_port  
3544 - =  
3545 - if string_to_integer(prompt(" IP port (for HTTP): ")) is  
3546 - {  
3547 - failure then print(" *** Error: incorrect IP port.\n");  
3548 - register_ip_port,  
3549 - success(p) then if (0 =< p & p =< 65535)  
3550 - then p  
3551 - else print(" *** Error: IP port out of bounds.\n");  
3552 - register_ip_port  
3553 - }.  
3554 -  
3555 -  
3556 -define One  
3557 - register_new_site  
3558 - (  
3559 - Var(List(DispatcherInfo)) info_v  
3560 - ) =  
3561 - print("\n");  
3562 - print(" Registering a new site:\n");  
3563 - with name = prompt(" Site name: "),  
3564 - with addr = register_ip_address,  
3565 - with port = register_ip_port,  
3566 - (protect info_v <- [site(name,port) . *info_v]);  
3567 - print(" Site "+name+" at "+ip_addr_to_string(addr)+":"+port+" added\n (but not saved to disk).\n").  
3568 -  
3569 -  
3570 -define List(DispatcherInfo)  
3571 - find_sites  
3572 - (  
3573 - List(DispatcherInfo) l,  
3574 - String name  
3575 - ) =  
3576 - if l is  
3577 - {  
3578 - [ ] then [ ],  
3579 - [h . t] then if h is site(n,_) then  
3580 - if find(name,n,0) is  
3581 - {  
3582 - failure then find_sites(t,name),  
3583 - success(_) then [h . find_sites(t,name)]  
3584 - }  
3585 - }.  
3586 -  
3587 -  
3588 -define String  
3589 - pad  
3590 - (  
3591 - String s,  
3592 - Int32 l  
3593 - ) =  
3594 - if length(s) >= l  
3595 - then s  
3596 - else s+constant_string(l-length(s),' ').  
3597 -  
3598 -  
3599 -  
3600 -define One  
3601 - show_sites_1  
3602 - (  
3603 - List(DispatcherInfo) l,  
3604 - Int32 i  
3605 - ) =  
3606 - if l is  
3607 - {  
3608 - [ ] then unique,  
3609 - [h . t] then if h is site(name,port) then  
3610 - print(" ["+i+"] "+pad(name,40)+" "+" "+port+"\n");  
3611 - show_sites_1(t,i+1)  
3612 - }.  
3613 -  
3614 -  
3615 -define One  
3616 - show_sites  
3617 - (  
3618 - List(DispatcherInfo) l,  
3619 - Int32 i  
3620 - ) =  
3621 - print(" Name Port\n");  
3622 - print(" --------------------------------------------------------\n");  
3623 - show_sites_1(l,i).  
3624 -  
3625 -define List(DispatcherInfo)  
3626 - replace_info  
3627 - (  
3628 - List(DispatcherInfo) l,  
3629 - String site_name,  
3630 - Int32 new_port  
3631 - ) =  
3632 - if l is  
3633 - {  
3634 - [ ] then alert,  
3635 - [h . t] then if h is site(n,_) then  
3636 - if n = site_name  
3637 - then [site(n,new_port) . t]  
3638 - else [h . replace_info(t,site_name,new_port)]  
3639 - }.  
3640 -  
3641 -define List(DispatcherInfo)  
3642 - delete_info  
3643 - (  
3644 - List(DispatcherInfo) l,  
3645 - String site_name,  
3646 - ) =  
3647 - if l is  
3648 - {  
3649 - [ ] then alert,  
3650 - [h . t] then if h is site(n,_) then  
3651 - if n = site_name  
3652 - then t  
3653 - else [h . delete_info(t,site_name)]  
3654 - }.  
3655 -  
3656 -  
3657 -define One  
3658 - update_site  
3659 - (  
3660 - Var(List(DispatcherInfo)) info_v,  
3661 - String site_name,  
3662 - Int32 old_port  
3663 - ) =  
3664 - print("\n");  
3665 - print(" Updating site '"+site_name+"': (currently: "+old_port+")\n");  
3666 - with new_port = register_ip_port,  
3667 - answer = prompt(" Update '"+site_name+"' as: "+new_port+" [Y/N] ? "),  
3668 - if (answer = "Y" | answer = "y")  
3669 - then info_v <- replace_info(*info_v,site_name,new_port)  
3670 - else unique.  
3671 -  
3672 -  
3673 -  
3674 -define Bool  
3675 - compare  
3676 - (  
3677 - DispatcherInfo d1,  
3678 - DispatcherInfo d2  
3679 - ) =  
3680 - if d1 is site(n1,_) then  
3681 - if d2 is site(n2,_) then  
3682 - string_less(n1,n2).  
3683 -  
3684 -  
3685 -  
3686 -define One  
3687 - update_site  
3688 - (  
3689 - Var(List(DispatcherInfo)) info_v  
3690 - ) =  
3691 - print("\n");  
3692 - with prefix = prompt(" Search for site to update: "),  
3693 - if find_sites(*info_v,prefix) is  
3694 - {  
3695 - [ ] then print(" No site found.\n");  
3696 - update_site(info_v),  
3697 - [h . t] then  
3698 - show_sites(qsort([h . t],compare),1);  
3699 - with i1 = prompt(" Choose a site to update [1/.../"+(length(t)+1)+"]: "),  
3700 - if string_to_integer(i1) is  
3701 - {  
3702 - failure then print(" *** Error: site number not recognized.\n");  
3703 - update_site(info_v),  
3704 - success(ii1) then if nth(ii1-1,*info_v) is  
3705 - {  
3706 - failure then print(" *** Error: site number "+i1+" does not exist.\n");  
3707 - update_site(info_v),  
3708 - success(site_info) then if site_info is site(name,old_port) then  
3709 - update_site(info_v,name,old_port)  
3710 - }  
3711 - }  
3712 - }.  
3713 -  
3714 -  
3715 -define One  
3716 - delete_site  
3717 - (  
3718 - Var(List(DispatcherInfo)) info_v,  
3719 - String site_name,  
3720 - Int32 old_port  
3721 - ) =  
3722 - print("\n");  
3723 - print(" Deleting site '"+site_name+"': (currently: "+old_port+")\n");  
3724 - with answer = prompt(" Are you sure you want to delete site: '"+site_name+"' [Y/N] ? "),  
3725 - if (answer = "Y" | answer = "y")  
3726 - then info_v <- delete_info(*info_v,site_name)  
3727 - else print(" Site '"+site_name+"' not deleted.\n").  
3728 -  
3729 -  
3730 -define One  
3731 - delete_site  
3732 - (  
3733 - Var(List(DispatcherInfo)) info_v  
3734 - ) =  
3735 - print("\n");  
3736 - with prefix = prompt(" Search for site to delete: "),  
3737 - if find_sites(*info_v,prefix) is  
3738 - {  
3739 - [ ] then print(" No site found.\n");  
3740 - delete_site(info_v),  
3741 - [h . t] then  
3742 - show_sites(qsort([h . t],compare),1);  
3743 - with i1 = prompt(" Choose a site to delete [1/.../"+(length(t)+1)+"]: "),  
3744 - if string_to_integer(i1) is  
3745 - {  
3746 - failure then print(" *** Error: site number not recognized.\n");  
3747 - delete_site(info_v),  
3748 - success(ii1) then if nth(ii1-1,*info_v) is  
3749 - {  
3750 - failure then print(" *** Error: site number "+i1+" does not exist.\n");  
3751 - delete_site(info_v),  
3752 - success(site_info) then if site_info is site(name,old_port) then  
3753 - delete_site(info_v,name,old_port)  
3754 - }  
3755 - }  
3756 - }.  
3757 -  
3758 -  
3759 -define One  
3760 - manager  
3761 - (  
3762 - Var(List(DispatcherInfo)) info_v,  
3763 - String file_path  
3764 - ) =  
3765 - print("\n");  
3766 - print(" --- Welcome to the Web Dispatcher Manager ---\n");  
3767 - with l = length(*info_v),  
3768 - print(" "+l+" site"+(if l>1 then "s" else "")+" currently registred.\n");  
3769 - print(" [L] List registered sites.\n");  
3770 - print(" [R] Register a new site.\n");  
3771 - print(" [U] Update a registred site.\n");  
3772 - print(" [D] Delete a registred site.\n");  
3773 - with propose_write_v = var((Bool)true),  
3774 - action = prompt(" Choose an action [L/R/U/D]: "),  
3775 - (if (action = "L" | action = "l") then (show_sites(*info_v,1); propose_write_v <- false) else  
3776 - if (action = "R" | action = "r") then register_new_site(info_v) else  
3777 - if (action = "U" | action = "u") then update_site(info_v) else  
3778 - if (action = "D" | action = "d") then delete_site(info_v) else  
3779 - print("Action not recognized.\n"));  
3780 - print("\n");  
3781 - if *propose_write_v then  
3782 - with result = prompt(" Write modifications to data base [Y/N] ?"),  
3783 - if (result = "Y" | result = "y")  
3784 - then if save(*info_v,file_path) is  
3785 - {  
3786 - cannot_open_file then print(" File '"+file_path+"' not found.\n"),  
3787 - write_error then print(" Error while writing file '"+file_path+"'.\n"),  
3788 - ok then print(" Data base has been modified.\n")  
3789 - }  
3790 - else print(" Data base not modified.\n")  
3791 - else unique.  
3792 -  
3793 -  
3794 -  
3795 -global define One  
3796 - manage_web_dispatcher  
3797 - (  
3798 - List(String) args  
3799 - ) =  
3800 - with info_v = var((List(DispatcherInfo))[]),  
3801 - with file_path = my_anubis_directory+"/web_sites/dispatcher.info",  
3802 - if (RetrieveResult(List(DispatcherInfo)))retrieve(file_path) is  
3803 - {  
3804 - cannot_find_file then print("File '"+file_path+"' does not exist.\n");  
3805 - with answer = prompt("Create it [Y/N] ? "),  
3806 - if (answer = "Y" | answer = "y")  
3807 - then if save((List(DispatcherInfo))[],file_path) is  
3808 - {  
3809 - cannot_open_file then  
3810 - print("Cannot create file '"+file_path+"'.\n"),  
3811 - write_error then  
3812 - print("Error while creating file '"+file_path+"'.\n"),  
3813 - ok then manager(info_v,file_path)  
3814 - }  
3815 - else unique,  
3816 - read_error then print("Error while reading file '"+file_path+"'.\n"),  
3817 - type_error then print("File '"+file_path+"' is corrupted.\n"),  
3818 - ok(info) then info_v <- info;  
3819 - manager(info_v,file_path)  
3820 - }.  
3821 -  
3822 -  
3823 -  
3824 -  
3825 - 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 +
  41 + ---------------------------------------------------------------------------------------
  42 +
  43 +
  44 +
  45 +
  46 + *** (1) Multihosting and redirections.
  47 +
  48 + This HTTP/HTTPS server can handle several host (also called 'virtual hosts'), in other
  49 + words, you may have several sites on the same server, with the same IP address and same
  50 + port numbers, but distinct 'host names'.
  51 +
  52 + A HTTP request sent by a browser contains the following informations:
  53 +
  54 + - a 'host name',
  55 + - an URI (Uniform Resource Identifier),
  56 + - HTTP headers,
  57 + - web arguments (in the form 'name=value').
  58 +
  59 + Actually, the host name is just the value of the HTTP header whose name is 'Host'. The
  60 + host name indicates which site is requested. Hence, it is the primary information for
  61 + branching to the right site. If there is no 'Host' HTTP header in the request, the
  62 + request is denied.
  63 +
  64 + From now on, we may assume that the host is determined, and consequently that we are
  65 + concerned by only one site. Each site has his own directories on the server's
  66 + disk.
  67 +
  68 + Each site also has a list of 'redirections'. A redirection is a triplet, like this one:
  69 +
  70 + redirect("/", "www.our-business.com", "/homepage.awp")
  71 +
  72 + meaning that if the host is "www.our-business.com", and if the requested URI is "/",
  73 + then the URI to be served is "/homepage.awp". 'redirect' is a constructor of the type
  74 + 'Redirection' defined in 'web/common.anubis'.
  75 +
  76 + Now, an URI may end by ".awp" (meaning 'Anubis Web Page') or not. If it does, the
  77 + server understands that an HTML page must be constructed on the fly, and to that end it
  78 + calls the 'awp handler' of the site. Otherwise, the URI must end by a known extension,
  79 + like ".jpg", ".png", ".txt", etc... and represents a file path relative to the
  80 + 'public' directory of the site. If these conditions are satisfied, the file is sent to
  81 + the client. Known extensions are recorded in 'web/mime.anubis'.
  82 +
  83 +
  84 +
  85 +
  86 + *** (2) The incompatibility between SSL and virtual hosts.
  87 +
  88 + Handling virtual hosts makes a problem under SSL (i.e. when using HTTPS), which is due
  89 + to the fact that the guys at Netscape who designed SSL probably did not have the
  90 + question of virtual hosts in mind. Indeed, the SSL handshake is completed before the
  91 + server can know about the value of the 'Host' HTTP header, so that it cannot know which
  92 + server certificate must be sent to the client. This makes a problem, because the
  93 + browser will not accept a certificate whose common name does not correspond to the name
  94 + of the requested host. The user will have to accept the certificate manually, which is
  95 + not good for the security image of the site. This problem has at least two solutions
  96 + (as far as Anubis is concerned).
  97 +
  98 + Solution 1. Arrange so that the network interface on which the server is listening
  99 + has at least as many different IP addresses as you have virtual hosts. Such
  100 + supplementary IP addresses are called 'IP Aliases'. In this case, start one HTTPS
  101 + server for each virtual host, each one listening on a different address. For the time
  102 + being, this method is applicable under Anubis only if you start as many instances of
  103 + 'anbexec' as you have virtual hosts, because each instance of 'anbexec' can handle only
  104 + one server certificate. Of course, getting IP aliases is another problem to be solved
  105 + with your Internet provider.
  106 +
  107 + Solution 2. We propose a simple solution, using only one server certificate (hence
  108 + only one instance of 'anbexec'). Since, we have only one server certificate, we must
  109 + introduce a notion of 'main host', i.e. a host containing all other 'virtual
  110 + hosts'. The unique server certificate belong to the main host, so that only the main
  111 + host is identified by the client. The client must trust the main host and be confident
  112 + that the main host redirects him to the right virtual host. Actually, the process will
  113 + be transparent to the client, except that the client will see the name of the main host
  114 + instead of the name of the virtual host in the 'location' field of the browser.
  115 +
  116 + So, assume that the name of main host is 'www.securedhost.com', and that the names of
  117 + the virtual hosts are:
  118 +
  119 + actual name simplified name
  120 + -----------------------------------------------------
  121 + www.virtual1.com virtual1
  122 + www.virtual2.com virtual2
  123 + www.virtual3.com virtual3
  124 +
  125 + Then the (confidential) document '/doc/my_document.pdf' on 'www.virtual2.com' will have
  126 + the URL:
  127 +
  128 + https://www.securedhost.com/virtual2/doc/my_document.pdf
  129 +
  130 + In order to work transparently, this solution must combine HTTP and HTTPS. Indeed, the
  131 + vitual host must have a first page reachable under HTTP, through the URL:
  132 +
  133 + http://www.virtual2.com/
  134 +
  135 + The HTTP server will redirect this URL to the awp handler of virtual host 'virtual2'.
  136 + The handler of this virtual host is able to generate a first page containing the
  137 + following HTML meta:
  138 +
  139 + <meta http-equiv="Refresh" content="0;URL=https://www.securedhost.com/virtual2/">,
  140 +
  141 + so that the client is immediately redirected to the main host under HTTPS (hence
  142 + accepting tranparently the server certificate). The awp handler of 'virtual2' then
  143 + redirects this URL to the home page (maybe a login page) of 'virtual2'.
  144 +
  145 + See 'web/making_a_web_site.anubis' for the sequel of this story.
  146 +
  147 +
  148 +
  149 +
  150 +
  151 + *** (3) HTTP headers and web arguments.
  152 +
  153 + Each HTTP request which arrives on the server contains a request line followed by a
  154 + series of HTTP headers. Each HTTP header is a pair '(name,value)' assigning a value to
  155 + a name. The type 'HTTP_header' is defined in 'web/common.anubis'.
  156 +
  157 + The request may also have a 'body'. The body contains either 'web arguments' or
  158 + uploaded files (or both). The request line itself may also contain web arguments (in a
  159 + so-called 'query string'). Like HTTP headers, 'web arguments' are pairs
  160 + '(name,value)', but the difference is that these pairs are generated by the page within
  161 + which the client clicks, while HTTP headers are generated by the browser itself. The
  162 + type 'Web_arg' is defined in 'web/common.anubis'. It has two alternatives, one for
  163 + ordinary web arguments (pairs) and one for uploaded files.
  164 +
  165 +read CXM_common.anubis
  166 +read tools/basis.anubis
  167 +read system/string.anubis
  168 +read CXM_mime.anubis
  169 +
  170 +
  171 +
  172 + *** (4) Site descriptions.
  173 +
  174 + The type HTTP_Info gathers informations comming along with the client's request. These
  175 + informations are rarely used for composing HTML pages. Nevertheless, they are at your
  176 + disposal.
  177 +
  178 +public type HTTP_Info:
  179 + http_info
  180 + (
  181 + Int32 ip_address, // IP address of the client
  182 + String uri, // URI requested by the client
  183 + List(HTTP_header) http_headers, // HTTP headers sent by the client
  184 + One -> String generate_trust_ticket // may be used against denial of
  185 + // service attacks
  186 + ).
  187 +
  188 +
  189 +
  190 + Each site is described by a 'web site description', which is a datum of type
  191 + 'Web_Site_Description'.
  192 +
  193 +public type Web_Site_Description:
  194 + web_site_description(
  195 + List(String) common_names,
  196 + String site_directory,
  197 + List(Redirection) redirections,
  198 + String charset,
  199 + List(String) journal_extensions,
  200 + List(String) journal_headers,
  201 + String authorization_secret,
  202 + List(MIME) known_mime_types,
  203 + (String host_name,
  204 + HTTP_Info http_info,
  205 + List(Web_arg) lwa,
  206 + Bool is_https) -> (List(HTTP_header),
  207 + Printable_tree) awp_handler,
  208 + (List(Web_arg) lwa) -> One before_send_file).
  209 +
  210 + The component 'common_names' is the list of names of the site, like for example
  211 + "www.our-business.com". The reason why we have a list of common names instead of a
  212 + single common name, is that it may be useful to have a common name like "192.168.0.1"
  213 + for testing.
  214 +
  215 + 'charset' is a string which will determine the character encoding to be used by the
  216 + browser. Typically, this string is one of: "UTF-8", "ISO-8859-1", "Windows-1252",
  217 + etc...
  218 +
  219 + 'journal_extensions' is the list of URI extensions for which you want a log in the
  220 + journal (and on the console). When a request arrives, and if the extension is a member
  221 + of this list, a message is printed into the journal of the site including the date, the
  222 + IP address of the client, the complete HTTP request line. The HTTP headers whose name
  223 + is a member of 'journal_headers' are also printed in the journal. A reasonable minimum
  224 + for these two components is:
  225 +
  226 + [".awp"] for journal_extensions
  227 + ["user-agent"] for journal_headers
  228 +
  229 + 'authorization_secret' is a string which should just be unguessable. You may choose
  230 + something like (but don't choose this one !):
  231 +
  232 + "Hg8kJe42gCML9jNH-74"
  233 +
  234 + i.e. a sequence of characters typed at random, long enough to be unguessable. This is
  235 + used by the 'private download' mecanism, which is discussed later in this file.
  236 +
  237 + The component 'awp_handler' is a function of type:
  238 +
  239 + (String host_name,
  240 + HTTP_Info http_info,
  241 + List(Web_arg) web_args,
  242 + Bool is_https) -> Printable_tree
  243 +
  244 + ('Printable_tree' is a substitute for 'String' and is defined in
  245 + 'tools/basis.anubis'). This function is the 'awp handler' for the site. When the URI
  246 + ends by ".awp", this function is called, and the result (an HTML page) is sent to the
  247 + client over the connection. The last operand to this function is a boolean which is
  248 + 'true' when the requests arrives through the HTTPS channel, and 'false' when it arrives
  249 + through the HTTP channel.
  250 +
  251 +
  252 +
  253 +
  254 +
  255 +
  256 +
  257 + *** (5) Protection against denial of service attacks.
  258 +
  259 + We need to protect our servers against 'denial of service' attacks. The attack may be
  260 + send automatically from machines which are infested by viruses. In that case, our
  261 + server is saturated of connections (all virtual machines at work), but nothing is
  262 + comming on the connections. In order to avoid this problem, we propose the following:
  263 +
  264 + (1) Limit the number of simultaneous connections (say to 100).
  265 + (2) Close a connection if the request is not complete after say 10 seconds.
  266 + (3) Close the connection if the request is bigger than a given size (normal requests
  267 + are small except when there are uploaded files.
  268 + (4) Close the connection during the sending of the answer if the client is waiting
  269 + too much.
  270 + (5) Record all IP addresses with which we have encountered one of the problems above.
  271 + (6) Immediately close the connections if the IP address is in our list.
  272 + (7) Remove an address from the list only after 5 minutes of inactivity of this
  273 + address.
  274 + (8) Maintain a list of reliable IP addresses.
  275 +
  276 + Of course, all the above are approximative solutions which may in some circumstances
  277 + become either cumbersome or also partially block the system. So, it is needed to have a
  278 + set of dynamically modifiable parameters in order to master the behavior of this
  279 + mecanism.
  280 +
  281 +
  282 + Each dubious IP address is recorded together with its last activity time.
  283 +
  284 +public type DubiousIP:
  285 + dubious_ip (Int32 address,
  286 + Int32 last_activity).
  287 +
  288 +
  289 +public type DenialOfService:
  290 + denial_of_service(Var(Int32) max_connections,
  291 + Var(Int32) request_line_delay, // seconds
  292 + Var(Int32) headers_delay,
  293 + Var(Int32) answer_delay,
  294 + Var(List(DubiousIP)) list_of_dubious,
  295 + Var(List(Int32)) reliable_addresses).
  296 +
  297 + The informations in this set of variables are stored serialized into the file
  298 + 'my_anubis/web_sites/dos_info'. If this file does not exist a set if variables with
  299 + default values is created. The values are saved on the disk each time they are
  300 + modified.
  301 +
  302 +public define DenialOfService load_denial_of_service_info.
  303 +
  304 +
  305 +
  306 + *** (6) Starting your HTTP and HTTPS servers.
  307 +
  308 + When your web site descriptions are ready, you can start a pair of servers (a HTTP
  309 + server and a HTTPS server) for serving your web sites. Notice that there are always
  310 + two servers, regardless of the number of web sites, and that each web sites normally
  311 + uses the two servers.
  312 +
  313 +
  314 +public define StartServerResult
  315 + start_http_server
  316 + (
  317 + Int32 ip_address,
  318 + Int32 http_port,
  319 + List(Web_Site_Description) web_sites,
  320 + DenialOfService dos
  321 + ).
  322 +
  323 +public define StartServerResult
  324 + start_https_server
  325 + (
  326 + Int32 ip_address,
  327 + Int32 https_port,
  328 + String certificate_common_name,
  329 + List(Web_Site_Description) web_sites,
  330 + DenialOfService dos
  331 + ).
  332 +
  333 + The first argument 'ip_address' is the IP address on which the servers listen. If you
  334 + put 0, the servers listen on all adresses of the machine (which is useful if the
  335 + machine has several network interfaces). Otherwise, use the function 'ip_address'
  336 + defined in 'tools/basis.anubis' for composing a particular IP address.
  337 +
  338 + The next arguments are the port numbers for HTTP and HTTPS. The usual values are 80 and
  339 + 443, but you may have reasons to choose other values.
  340 +
  341 + The next argument is the list of your web site descriptions. All the sites described in
  342 + this list will be accessible on the server.
  343 +
  344 + The argument 'dos' is a set of dynamic variables containing the informations for
  345 + protecting the servers against denial of service attacks.
  346 +
  347 +
  348 +
  349 +
  350 +
  351 +
  352 + *** (7) Private download.
  353 +
  354 + It may happen that you want to propose private files for download. This means that such
  355 + a file could be downloaded only by the authorized person, and should not be seen by any
  356 + other one. This feature can be used only under HTTPS, not under HTTP.
  357 +
  358 + The file may be located anywhere on the server. Hence, the file has a complete absolute
  359 + path, like for example:
  360 +
  361 + /home/georges/my_documents/my_text.pdf
  362 +
  363 + which has nothing to do with the directories of the web server. Now, you may also want
  364 + to show another path or simply just a name to the client, not the actual absolute path
  365 + above, which may need to remain secret. So for example, the same file may appear to the
  366 + client as:
  367 +
  368 + informations.pdf
  369 +
  370 + The page must provide a link with an authorization. The authorization is just a web
  371 + argument, whose name is "zauth". The value of this web argument is computed by hashing
  372 + some secret string (known only from the programmer of the web site) with the absolute
  373 + path of the file. The HTTPS request will have the form:
  374 +
  375 + GET /informations.pdf?zauth=d38161f5b4e87e2d46e06ff8b3e233be563794d1
  376 +
  377 + The server will search for a file named
  378 +
  379 + zd38161f5b4e87e2d46e06ff8b3e233be563794d1
  380 +
  381 + (i.e. "z" concatenated with the value of the authorization) in the subdirectory
  382 + 'private_download' of the site directory. This file contains the absolute path of the
  383 + file, i.e:
  384 +
  385 + /home/georges/my_documents/my_text.pdf
  386 +
  387 + At that point, the server may hash the secret string and the absolute path together, to
  388 + check if the client is authorized to download the file. If it is the case, it sends the
  389 + file (the MIME type is declared as 'application/octet-stream' if it is not recognized).
  390 + The file is sent under the visible name.
  391 +
  392 + The server creates automatically the subdirectory 'private_download/' within the 'site
  393 + directory' (for each web site) if it does not already exist. Files in this directory
  394 + are deleted when they become too old (for example, after 3 days of life).
  395 +
  396 + Here is the function for computing the value of the authorization, and for making the
  397 + authorization file in 'private_download'.
  398 +
  399 +public define String
  400 + make_authorization
  401 + (
  402 + String site_directory,
  403 + String authorization_secret, // known only by the programmer of the web site
  404 + String absolute_path // on server
  405 + ).
  406 +
  407 + See 'web/making_a_web_site.anubis' for the construction of the link for downloading.
  408 +
  409 +
  410 +
  411 +
  412 +
  413 +
  414 +
  415 +
  416 + *** (8) About web argument names.
  417 +
  418 + The server reserves the name "zauth" for the authorization in the private download
  419 + mecanism. Also, if the name of a web arguments begins by "p" (like 'password'), it does
  420 + not print the value of the web argument neither on the console or in the journal. A
  421 + good politics is to prefix all web arguments by letters distinct from 'p' and 'z'. This
  422 + method is used in 'web/making_a_web_site.anubis'. This will avoid clashes of names.
  423 +
  424 +
  425 +
  426 +
  427 +
  428 +
  429 + *** (9) A web dispatcher.
  430 +
  431 + For hosting several sites you may prefer another method which we now describe. We start
  432 + a HTTP server on port 80 (or on another port). This server is called the
  433 + ``dispatcher''. When a requests arrives, the dispatcher examines the ``host'' HTTP
  434 + header, so that it gets the name of the requested host. Then it sends to the client a
  435 + page like this one:
  436 +
  437 + <html>
  438 + <head>
  439 + <meta http-equiv="Refresh" content="0;URL=...">
  440 + </head>
  441 + <body>
  442 + </body>
  443 + </html>
  444 +
  445 + where the URL represented by '...' is the URL of the requested site. This URL may have
  446 + the same IP address as the dispatcher, except that the port number is different. It may
  447 + also have a different IP address.
  448 +
  449 + The dispatcher uses the file 'my_anubis/web_sites/dispatcher.info'. This file contains
  450 + a serialized datum of type 'List(DispatcherInfo)'.
  451 +
  452 +public type DispatcherInfo:
  453 + site(String common_name,
  454 + Int32 http_port).
  455 +
  456 + The dispatcher does not write into this file. It reads it when it starts, and rereads
  457 + it each time the date of last modification of the file changes, so that the dispatcher
  458 + always has up to date data. The file may be managed (written and updated) by another
  459 + program.
  460 +
  461 + So, for each site, the dispatcher knows the common name (needed to recognize the 'host'
  462 + HTTP header), and the pair (ip_address,port) used by the actual site for HTTP. The
  463 + dispatcher does not worry about HTTPS. HTTPS must be managed by the actual site.
  464 +
  465 + The dispatcher is started by:
  466 +
  467 +public define One
  468 + start_web_dispatcher
  469 + (
  470 + Int32 ip_address, // address for listening (typically 0)
  471 + Int32 port, // typically 80
  472 + DenialOfService dos
  473 + ).
  474 +
  475 + A command line tool for managing the file 'my_anubis/web_sites/dispatcher.info' is also
  476 + provided:
  477 +
  478 + global define One
  479 + manage_web_dispatcher
  480 + (
  481 + List(String) args
  482 + ).
  483 +
  484 +
  485 +
  486 +
  487 +
  488 +
  489 +
  490 + --- That's all for the public part ! --------------------------------------------------
  491 +
  492 +
  493 +
  494 +
  495 +
  496 +
  497 +
  498 + ----------------------------------- Table of Contents ---------------------------------
  499 +
  500 + *** [1] Types which are private to this file.
  501 +
  502 + *** [2] Tools.
  503 + *** [2.1] Formating an error message.
  504 + *** [2.2] Converting IP addresses.
  505 + *** [2.3] Reading and unputting characters.
  506 + *** [2.4] Reading and discarding characters.
  507 + *** [2.5] Reading a character string.
  508 + *** [2.6] Padding integers with zeros.
  509 + *** [2.7] Converting web arguments to ASCII.
  510 + *** [2.8] Server description.
  511 +
  512 + *** [3] Managing the journal.
  513 + *** [3.1] Naming journal files.
  514 + *** [3.2] Formating HTTP headers.
  515 + *** [3.3] Formating web arguments.
  516 + *** [3.4] Formating the whole request.
  517 + *** [3.5] Putting it in the journal file (and on the console).
  518 +
  519 + *** [4] Reading the HTTP request.
  520 + *** [4.1] Skipping leading blanks.
  521 + *** [4.2] Reading a new line.
  522 + *** [4.3] Reading a 'word'.
  523 + *** [4.4] Separating the URI from the query string.
  524 + *** [4.5] Reading the web arguments.
  525 + *** [4.7] Reading the request line.
  526 + *** [4.8] Reading the HTTP headers.
  527 + *** [4.9] Getting the size of the request's body.
  528 + *** [4.10] Reading the body of the request.
  529 +
  530 + *** [5] Making the HTTP answer.
  531 + *** [5.1] Avoiding illegal URIs.
  532 + *** [5.2] Managing authorizations for downloading private files.
  533 + *** [5.3] Recognizing MIME types.
  534 + *** [5.4] Formating HTTP headers.
  535 + *** [5.5] Sending a file.
  536 + *** [5.6] Answering a www-url encoded request.
  537 + *** [5.7] Answering a multipart/form-data encoded request.
  538 + *** [5.7.1] Finding the boundary.
  539 + *** [5.7.2] Reading attributes from a multipart entity.
  540 + *** [5.7.3] Creating a temporary filename for an uploaded file.
  541 + *** [5.7.4] Saving an uploaded file under a temporary filename.
  542 + *** [5.7.5] Removing the path from a file name.
  543 + *** [5.7.6] Reading a multipart entity.
  544 + *** [5.8] Handling redirections.
  545 + *** [5.9] Answering both sorts of requests.
  546 +
  547 + *** [6] The HTTP/HTTPS servers.
  548 + *** [6.1] The HTTP request handler.
  549 + *** [6.2] Server's tasks.
  550 + *** [6.3] Starting the HTTP/HTTPS servers.
  551 +
  552 + *** [7] The web dispatcher.
  553 + *** [7.1] The dispatcher server.
  554 + *** [7.2] The dispatcher web site.
  555 + *** [7.3] Managing the info file.
  556 +
  557 + ---------------------------------------------------------------------------------------
  558 +
  559 +
  560 +
  561 +
  562 +read tools/basis.anubis
  563 +read tools/findstring.anubis
  564 +read tools/connections.anubis
  565 +
  566 +
  567 +
  568 +
  569 +
  570 + *** [1] Types which are private to this file.
  571 +
  572 + We use the following self-explanatory types.
  573 +
  574 +type Error:
  575 + cannot_read_from_connection,
  576 + not_get_or_post_request(String),
  577 + end_of_line_expected,
  578 + incorrect_content_length_value,
  579 + colon_expected,
  580 + timeout(Int32).
  581 +
  582 +type HTTP_RequestType:
  583 + get,
  584 + post.
  585 +
  586 +type HTTP_RequestLine:
  587 + request_line (HTTP_RequestType type,
  588 + String uri,
  589 + List(Web_arg) query_string).
  590 +
  591 +type EncodingType:
  592 + www_url,
  593 + multipart_form_data.
  594 +
  595 +type BufferedConnection:
  596 + buffered_connection(Connection conn,
  597 + Var(ByteArray) buffer,
  598 + Var(Int32) read_pos).
  599 +
  600 +
  601 +
  602 + *** [2] Tools.
  603 +
  604 + *** [2.1] Formating an error message.
  605 +
  606 + The next function formats an error message.
  607 +
  608 +define String
  609 + format
  610 + (
  611 + Error msg
  612 + ) =
  613 + if msg is
  614 + {
  615 + cannot_read_from_connection then
  616 + "Cannot read from connection.\n",
  617 + not_get_or_post_request(s) then
  618 + "The request did not begin by 'GET' or 'POST': "+s+".\n",
  619 + end_of_line_expected then
  620 + "End of line expected.\n",
  621 + incorrect_content_length_value then
  622 + "Incorrect value for HTTP header 'Content-Length'.\n",
  623 + colon_expected then
  624 + "':' was expected.\n",
  625 + timeout(n) then
  626 + //"time out: "+n+"\n"
  627 + //"time out.\n"
  628 + ""
  629 + }.
  630 +
  631 +
  632 +
  633 +
  634 +
  635 +
  636 + *** [2.2] Converting IP addresses.
  637 +
  638 + We need two conversion functions for IP addresses:
  639 +
  640 + (Word8,Word8,Word8,Word8) --> Int32 ip_address
  641 + Int32 --> String ip_addr_to_string
  642 +
  643 + These conversions are defined in 'tools/basis.anubis'.
  644 +
  645 +
  646 +
  647 +
  648 +
  649 +
  650 +
  651 +
  652 + *** [2.3] Reading and unputting characters.
  653 +
  654 + We need a mecanism for unputting several characters (actually at least 3). This is
  655 + because when reading the client connection, we must sometimes go ahead several
  656 + characters, and virtually put them back into the connection, so that they can be
  657 + reread. Of course, we do not send them back to the client. We store them in a list
  658 + (hold by the variable 'unput_chars'), and we manage this list, so that characters may
  659 + be virtually put back in the connection (this is called 'unputting').
  660 +
  661 +variable List(Word8) unput_chars = [].
  662 +
  663 + The most recently read one is the head of list. Fortunately, this variable is private
  664 + to this virtual machine (hence to this client).
  665 +
  666 +
  667 +define One
  668 + unput // unputting a character (add it in front of the list)
  669 + (
  670 + Word8 character
  671 + ) =
  672 + unput_chars <- (List(Word8))[character . *unput_chars].
  673 +
  674 +
  675 +
  676 +define One record_dubious_IP(Int32 addr,DenialOfService dos).
  677 +
  678 +variable Int32 sttm = 0. // contains the start time for this connection.
  679 +
  680 +define Result(Error,Word8)
  681 + record_dubious_connection
  682 + (
  683 + Connection conn,
  684 + Int32 dead_line,
  685 + DenialOfService dos,
  686 + ) =
  687 + if remote_IP_address_and_port(conn) is (addr,port) then
  688 + record_dubious_IP(addr,dos);
  689 + print("Recording IP address "+ip_addr_to_string(addr)+
  690 + " as dubious after "+(dead_line-*sttm)+" seconds. Total: "+
  691 + length(*list_of_dubious(dos))+"\n");
  692 + error(timeout(dead_line)).
  693 +
  694 +define String
  695 + pid
  696 + =
  697 + "[" + virtual_machine_id + "] ".
  698 +
  699 +define ReadResult
  700 + read
  701 + (
  702 + BufferedConnection connection,
  703 + Int32 size,
  704 + Int32 time_out
  705 + ) =
  706 + //println(pid + "read(" + size + ")");
  707 +
  708 + if *connection.read_pos < length(*connection.buffer) then
  709 + //println(pid + " reading from buffer (size = " + length(*connection.buffer) + ", pos = " + *connection.read_pos);
  710 + with result = extract(*connection.buffer, *connection.read_pos, *connection.read_pos + size),
  711 + size_read = length(result),
  712 + connection.read_pos <- *connection.read_pos + size_read;
  713 + if size > size_read
  714 + then
  715 + //println("Wanted " + size + ", read only " + size_read);
  716 + if read(connection, size - size_read, time_out) is
  717 + {
  718 + error then error,
  719 + timeout then ok(result),
  720 + ok(ba) then ok(result + ba)
  721 + }
  722 + else ok(result)
  723 + else
  724 + //if now > dead_line then record_dubious_connection(connection,dead_line,dos) else
  725 + if read(connection.conn, 16384, time_out) is // the connection is closed after 10 minutes of inactivity
  726 + {
  727 + error then println(pid + "read failed)"); error,
  728 + timeout then timeout,
  729 + ok(ba) then
  730 +// println(pid + "ba = " + length(ba));
  731 + connection.buffer <- ba;
  732 + connection.read_pos <- 0;
  733 + //println(pid + "rb = " + length(*read_buffer));
  734 + read(connection, size, time_out)
  735 + }.
  736 +
  737 +
  738 +define Result(Error,Word8)
  739 + read_one_byte
  740 + (
  741 + BufferedConnection connection,
  742 + Int32 dead_line,
  743 + DenialOfService dos
  744 + ) =
  745 + //if now > dead_line then record_dubious_connection(connection,dead_line,dos) else
  746 + if read(connection,1,600) is // the connection is closed after 10 minutes of inactivity
  747 + {
  748 + error then error(cannot_read_from_connection),
  749 + timeout then error(timeout(600)),
  750 + //record_dubious_connection(connection,dead_line,dos),
  751 + ok(ba) then if nth(0,ba) is
  752 + {
  753 + failure then error(cannot_read_from_connection),
  754 + success(c) then
  755 +// println("-" + pid + "read [" + implode([c]) + "]\t");
  756 + ok(c)
  757 + }
  758 + }.
  759 +
  760 +
  761 +variable ByteArray read_buffer = constant_byte_array(0, 0).
  762 +variable Int32 read_offset = 0.
  763 +
  764 + define Result(Error,Word8)
  765 + read_one_byte
  766 + (
  767 + BufferedConnection connection,
  768 + Int32 dead_line,
  769 + DenialOfService dos
  770 + ) =
  771 + if *read_offset < length(*read_buffer) then
  772 + if nth(*read_offset, *read_buffer) is
  773 + {
  774 + failure then println(pid + "nth failed)"); error(cannot_read_from_connection),
  775 + success(c) then
  776 + println("-" + pid + "read [" + implode([c]) + "]\tat " + *read_offset);
  777 + read_offset <- *read_offset + 1;
  778 + ok(c)
  779 + }
  780 + else
  781 + //if now > dead_line then record_dubious_connection(connection,dead_line,dos) else
  782 + println(pid + "len = " + length(*read_buffer));
  783 + if read(connection, 16384, 600) is // the connection is closed after 10 minutes of inactivity
  784 + {
  785 + error then println(pid + "read failed)"); error(cannot_read_from_connection),
  786 + timeout then error(timeout(600)),
  787 + //record_dubious_connection(connection,dead_line,dos),
  788 + ok(ba) then
  789 + println(pid + "ba = " + length(ba));
  790 + read_buffer <- ba;
  791 + read_offset <- 0;
  792 + println(pid + "rb = " + length(*read_buffer));
  793 + read_one_byte(connection, dead_line, dos)
  794 + }.
  795 +
  796 +
  797 +define Result(Error,Word8)
  798 + next_char // reading a character (check the list first, and read on the connection
  799 + // only when the list is empty).
  800 + (
  801 + BufferedConnection connection,
  802 + Int32 dead_line,
  803 + DenialOfService dos
  804 + ) =
  805 + if *unput_chars is
  806 + {
  807 + [ ] then read_one_byte(connection,dead_line,dos),
  808 +
  809 + [h . t] then
  810 + unput_chars <- t;
  811 + ok(h)
  812 + }.
  813 +
  814 +
  815 +
  816 +
  817 +
  818 +
  819 +
  820 + *** [2.4] Reading and discarding characters.
  821 +
  822 + The next function reads the specified number of bytes (this is the same as
  823 + 'characters') from the connection and discards them. This is used for discarding CR LF
  824 + just before the body of a request.
  825 +
  826 +define Result(Error,One)
  827 + read_and_ignore
  828 + (
  829 + BufferedConnection connection, // to client
  830 + Int32 dead_line,
  831 + Int32 number_of_characters, // number of characters to read and ignore
  832 + DenialOfService dos
  833 + ) =
  834 + if number_of_characters =< 0 then ok(unique) else
  835 + if next_char(connection, dead_line, dos) is
  836 + {
  837 + error(msg) then error(msg),
  838 + ok(c) then read_and_ignore(connection,dead_line,number_of_characters-1,dos)
  839 + }.
  840 +
  841 +
  842 +
  843 +
  844 +
  845 +
  846 +
  847 + *** [2.5] Reading a character string.
  848 +
  849 + Sometimes values of HTTP attributes or web args are presented in the form of double
  850 + quoted strings. The next function handles the reading of such things. The leading
  851 + double quote is already read in. We must read subsequent characters until the next non
  852 + backslashed double quote.
  853 +
  854 +define Result(Error,String)
  855 + read_string
  856 + (
  857 + BufferedConnection connection, // connection with the client
  858 + Int32 dead_line,
  859 + List(Word8) so_far, // characters read so far (in reverse order)
  860 + DenialOfService dos
  861 + ) =
  862 + if next_char(connection, dead_line,dos) is
  863 + {
  864 + error(msg) then error(msg),
  865 + ok(c) then
  866 + if c = '\\'
  867 + then if next_char(connection,dead_line,dos) is
  868 + {
  869 + error(msg) then error(msg),
  870 + ok(d) then
  871 + if d = '\"'
  872 + then read_string(connection,dead_line,['\"' . so_far],dos)
  873 + else read_string(connection,dead_line,[d, c . so_far],dos)
  874 + }
  875 + else if c = '\"'
  876 + then ok(implode(reverse(so_far)))
  877 + else read_string(connection,dead_line,[c . so_far],dos)
  878 + }.
  879 +
  880 +
  881 +
  882 +
  883 +
  884 +
  885 +
  886 + *** [2.6] Padding integers with zeros.
  887 +
  888 + 'zero_pad_2' transforms an integer (which is assumed to be between 0 and 99) into a
  889 + string with exactly two digits. This is used for formating days, hours, minutes and
  890 + seconds.
  891 +
  892 +define String
  893 + zero_pad_2
  894 + (
  895 + Int32 n
  896 + ) =
  897 + with s = integer_to_string(n),
  898 + if length(s) < 2
  899 + then "0"+s
  900 + else s.
  901 +
  902 +
  903 +
  904 +
  905 +
  906 +
  907 +
  908 + *** [2.7] Converting web arguments to ASCII.
  909 +
  910 + The function 'web_to_ascii' gets a character string and replaces web encoding by normal
  911 + ASCII encoding. This amounts to replacing:
  912 +
  913 + + by blank
  914 + %xx by the character whose ASCII code is xx in hexadecimal
  915 +
  916 + Note: We assume that '9' < 'A' (which is the case for ASCII code).
  917 +
  918 +
  919 +
  920 +define Word8
  921 + web_decode
  922 + (
  923 + Word8 x1,
  924 + Word8 x2
  925 + ) =
  926 + with z1 = word8_to_int32(x1),
  927 + n1 = if z1 =< '9' then (z1 - '0') else (z1 - 'A' + 10),
  928 + z2 = word8_to_int32(x2),
  929 + n2 = if z2 =< '9' then (z2 - '0') else (z2 - 'A' + 10),
  930 + n = (n1 << 4) + n2,
  931 + truncate_to_word8(n).
  932 +
  933 +
  934 +
  935 +define String
  936 + web_to_ascii
  937 + (
  938 + String web_string,
  939 + Int32 n, // current position in web_string
  940 + List(Word8) so_far
  941 + ) =
  942 + if nth(n,web_string) is
  943 + {
  944 + failure then implode(reverse(so_far)),
  945 + success(c) then
  946 + if c = '+'
  947 + then web_to_ascii(web_string,n+1,[' ' . so_far])
  948 + else if c = '%'
  949 + then if nth(n+1,web_string) is
  950 + {
  951 + failure then implode(reverse(so_far)),
  952 + success(x1) then if nth(n+2,web_string) is
  953 + {
  954 + failure then implode(reverse(so_far)),
  955 + success(x2) then web_to_ascii(web_string,n+3,[web_decode(x1,x2) . so_far])
  956 + }
  957 + }
  958 + else web_to_ascii(web_string,n+1,[c . so_far])
  959 + }.
  960 +
  961 +
  962 +
  963 +
  964 +
  965 +
  966 +
  967 +
  968 + *** [3] Managing the journal.
  969 +
  970 + Concurrently working machines should not try to access the same file at the same
  971 + time. This problem may be solved by using the 'protect' mecanism.
  972 +
  973 +
  974 +
  975 + *** [3.1] Naming journal files.
  976 +
  977 + Since journal messages are rather prolific, we should have at least one file per
  978 + hour. Hence, the name of a journal file must be constructed from the current year,
  979 + month, day and hour. For example, it may be:
  980 +
  981 + 2003_03_12_19
  982 +
  983 + (this is for the journal of 7 PM to 8 PM, 2003/mar/12).
  984 +
  985 +define String
  986 + make_current_journal_file_name
  987 + =
  988 + if convert_time(now) is date_and_time(y,m,d,h,_,_,_,_,_) then
  989 + integer_to_string(y)+"_"+
  990 + zero_pad_2(m)+"_"+
  991 + zero_pad_2(d)+"_"+
  992 + zero_pad_2(h).
  993 +
  994 +
  995 +
  996 +
  997 +
  998 +
  999 +
  1000 + *** [3.2] Formating HTTP headers.
  1001 +
  1002 + HTTP headers may be shown on the console or written in the journal. The function below
  1003 + formats a list of HTTP headers.
  1004 +
  1005 +define String
  1006 + show_format
  1007 + (
  1008 + Web_Site_Description desc,
  1009 + List(HTTP_header) headers,
  1010 + ) =
  1011 + if headers is
  1012 + {
  1013 + [ ] then "",
  1014 + [h . t] then if h is http_header(name,value) then
  1015 + if member(journal_headers(desc),name)
  1016 + then " | "+name+": "+value+"\n"+show_format(desc,t)
  1017 + else show_format(desc,t)
  1018 + }.
  1019 +
  1020 +
  1021 +
  1022 +
  1023 +
  1024 +
  1025 + *** [3.3] Formating web arguments.
  1026 +
  1027 + The same thing for web arguments.
  1028 +
  1029 +define String
  1030 + show_format
  1031 + (
  1032 + List(Web_arg) lwa
  1033 + ) =
  1034 + if lwa is
  1035 + {
  1036 + [ ] then "",
  1037 + [h . t] then if h is
  1038 + {
  1039 + web_arg(n,v) then
  1040 + " | "+n+"="+(if nth(0,n) = success('p') then "<not shown>" else v)+"\n"+show_format(t),
  1041 + upload(n,fn,tfn) then
  1042 + " | "+n+"="+fn+" (uploaded as '"+tfn+"')\n"+show_format(t)
  1043 + }
  1044 + }.
  1045 +
  1046 +
  1047 +
  1048 +
  1049 +
  1050 +
  1051 + *** [3.4] Formating the whole request.
  1052 +
  1053 + It is cheap to transform month numbers into abbreviated month names. This enhances the
  1054 + readability of the journal.
  1055 +
  1056 +define String
  1057 + format_month
  1058 + (
  1059 + Int32 m
  1060 + ) =
  1061 + if m = 1 then "jan" else
  1062 + if m = 2 then "feb" else
  1063 + if m = 3 then "mar" else
  1064 + if m = 4 then "apr" else
  1065 + if m = 5 then "may" else
  1066 + if m = 6 then "jun" else
  1067 + if m = 7 then "jul" else
  1068 + if m = 8 then "aug" else
  1069 + if m = 9 then "sep" else
  1070 + if m = 10 then "oct" else
  1071 + if m = 11 then "nov" else
  1072 + if m = 12 then "dec" else
  1073 + "???".
  1074 +
  1075 +
  1076 + Below we format a whole HTTP request. This may give this (actually, it depends on how
  1077 + you defined the values of 'journal_headers' and 'journal_extensions'):
  1078 +
  1079 + [3] 2003/mar/10 10:06:57 from 123.456.123.456: /homepage.awp
  1080 + | host: www.the-best-one.com
  1081 + | user-agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.0.1) Gecko/20020823 Netscape/7.0
  1082 +
  1083 + The leading number between brackets is the number of the virtual machine which served
  1084 + the URI.
  1085 +
  1086 +define String
  1087 + format_request
  1088 + (
  1089 + Web_Site_Description desc,
  1090 + Connection client_connection,
  1091 + HTTP_RequestLine request_line,
  1092 + List(HTTP_header) headers,
  1093 + List(Web_arg) web_args
  1094 + ) =
  1095 + with dt = convert_time(now),
  1096 + if remote_IP_address_and_port(client_connection) is (addr,port) then
  1097 + integer_to_string(year(dt))+"/"+format_month(month(dt))+"/"+zero_pad_2(day(dt))+" "+
  1098 + zero_pad_2(hour(dt))+":"+zero_pad_2(minute(dt))+":"+zero_pad_2(second(dt))+
  1099 + " from "+ip_addr_to_string(addr)+
  1100 + ": "+uri(request_line)+"\n"+
  1101 + show_format(desc,headers)+
  1102 + show_format(web_args).
  1103 +
  1104 +
  1105 +
  1106 +
  1107 +
  1108 +
  1109 +
  1110 + *** [3.5] Putting it in the journal file (and on the console).
  1111 +
  1112 + We must not forget to 'protect' this operation, so that the messages of two machines
  1113 + (working for the same site) will not be mixed together.
  1114 +
  1115 +define One
  1116 + log_journal_msg
  1117 + (
  1118 + Web_Site_Description desc,
  1119 + String msg,
  1120 + ) =
  1121 + with msg = to_byte_array("["+virtual_machine_id+"] "+msg+"\n"),
  1122 + protect
  1123 + (
  1124 + if file(site_directory(desc)+"/journal/"+make_current_journal_file_name,append) is
  1125 + {
  1126 + failure then unique,
  1127 + success(journal_file) then
  1128 + forget(reliable_write(file(journal_file),msg))
  1129 + };
  1130 + forget(reliable_write(file(stdout),msg))
  1131 + ).
  1132 +
  1133 +
  1134 +
  1135 +
  1136 +
  1137 +
  1138 +
  1139 + *** [4] Reading the HTTP request.
  1140 +
  1141 +
  1142 + *** [4.1] Skipping leading blanks.
  1143 +
  1144 + One of the peculiarities of HTTP is that the characters 13 (carriage return) and 10
  1145 + (line feed) followed by either a space (32) or a tab (9), is considered as a blank not
  1146 + containing any new line. 'skip_http_blanks' must skip all blanks characters until the
  1147 + first non blank character, which should not be read in. Obviously, because of the above
  1148 + peculiarity, we need at least 3 characters of lookahead to do this. In other words, we
  1149 + must be able to unput at least 3 characters (hopefully we are).
  1150 +
  1151 + Strictly blanks characters are 'space' and 'tab'.
  1152 +
  1153 +define Bool
  1154 + is_strict_blank
  1155 + (
  1156 + Word8 c
  1157 + ) =
  1158 + if c = ' ' then true else c = '\t'.
  1159 +
  1160 +
  1161 + On the contrary, blanks include 13 and 10.
  1162 +
  1163 +define Bool
  1164 + is_blank
  1165 + (
  1166 + Word8 c
  1167 + ) =
  1168 + if c = ' ' then true else
  1169 + if c = '\t' then true else
  1170 + if c = 13 then true else
  1171 + c = 10.
  1172 +
  1173 +
  1174 + Skipping HTTP blanks.
  1175 +
  1176 +define Result(Error,One)
  1177 + skip_http_blanks
  1178 + (
  1179 + BufferedConnection connection,
  1180 + Int32 dead_line,
  1181 + DenialOfService dos
  1182 + ) =
  1183 + if next_char(connection,dead_line,dos) is
  1184 + {
  1185 + error(msg) then error(msg),
  1186 + ok(c) then
  1187 + if is_strict_blank(c)
  1188 + then skip_http_blanks(connection,dead_line,dos)
  1189 + else if c = 13
  1190 + then if next_char(connection,dead_line,dos) is
  1191 + {
  1192 + error(msg) then error(msg), // (unput(c); ok(unique)),
  1193 + ok(d) then
  1194 + if d = 10
  1195 + then if next_char(connection,dead_line,dos) is
  1196 + {
  1197 + error(msg) then error(msg), // (unput(d); unput(c); ok(unique)),
  1198 + ok(e) then
  1199 + if is_strict_blank(e)
  1200 + then skip_http_blanks(connection,dead_line,dos)
  1201 + else (unput(e); unput(d); unput(c); ok(unique))
  1202 + }
  1203 + else (unput(d); unput(c); ok(unique))
  1204 + }
  1205 + else (unput(c); ok(unique))
  1206 + }.
  1207 +
  1208 +
  1209 +
  1210 +
  1211 +
  1212 +
  1213 +
  1214 +
  1215 + *** [4.2] Reading a new line.
  1216 +
  1217 + Normally in HTTP a new line is the sequence 13 10 (carriage return line feed), not
  1218 + followed by a space or tabulator. If it is followed by a space or tabulator, the three
  1219 + characters are considered blanks, and no new line has been read. Before trying to read
  1220 + a new line, we first skip leading spaces and tabs. Then we try to read 13 and 10, and
  1221 + we read another character. if this character is space or tab, we consider we have read
  1222 + only blanks and we continue reading in order to find our new line. Otherwise, we unput
  1223 + this character (which may be for example the first character of the name of the next
  1224 + header), and answer that we have seen a new line.
  1225 +
  1226 + Warning: we must not use this function for reading the last pair (13,10) before the
  1227 + beginning of the body, because if the body is empty, there is no character to read
  1228 + after this pair, so that the server could wait for a character which will never
  1229 + come. This is the reason for 'read_and_ignore' above, which is used precisely for
  1230 + reading that last (13,10) pair.
  1231 +
  1232 +define Result(Error,One)
  1233 + read_new_line
  1234 + (
  1235 + BufferedConnection connection,
  1236 + Int32 dead_line,
  1237 + DenialOfService dos
  1238 + ) =
  1239 + if skip_http_blanks(connection,dead_line,dos) is
  1240 + {
  1241 + error(msg) then error(msg),
  1242 + ok(_) then
  1243 + if next_char(connection,dead_line,dos) is
  1244 + {
  1245 + error(msg) then error(msg),
  1246 + ok(c) then
  1247 + if c = 13
  1248 + then if next_char(connection,dead_line,dos) is
  1249 + {
  1250 + error(msg) then error(msg),
  1251 + ok(d) then
  1252 + if d = 10
  1253 + then ok(unique)
  1254 + else (unput(d);
  1255 + unput(c);
  1256 + error(end_of_line_expected))
  1257 + }
  1258 + else (unput(c);
  1259 + error(end_of_line_expected))
  1260 + }}.
  1261 +
  1262 +
  1263 +
  1264 +
  1265 +
  1266 +
  1267 +
  1268 +
  1269 + *** [4.3] Reading a 'word'.
  1270 +
  1271 + A 'word' is a sequence of characters which begins either by a double quote or not by a
  1272 + double quote. (However, any leading blanks are read in and ignored. This is
  1273 + accomplished by 'skip_http_blanks'.) If it begins by a double quote, it is read like a
  1274 + string, i.e. it ends at the next (non backslashed) double quote. Otherwise, it is
  1275 + right delimited by any character which may be considered as 'blank'. If the word is
  1276 + double quoted, the closing double quote is read in. On the contrary, if the word is not
  1277 + double quoted, the right delimiting blank character is not read in (it is 'unput' back
  1278 + into the connection), and may be read in again. This is needed because carriage return
  1279 + or line feed which are 'blank', also have a meaning in HTTP.
  1280 +
  1281 +define Result(Error,String)
  1282 + read_word_aux
  1283 + (
  1284 + BufferedConnection connection,
  1285 + Int32 dead_line,
  1286 + List(Word8) so_far,
  1287 + DenialOfService dos
  1288 + ) =
  1289 + if next_char(connection,dead_line,dos) is
  1290 + {
  1291 + error(msg) then error(msg),
  1292 + ok(c) then
  1293 + if is_blank(c)
  1294 + then (unput(c);
  1295 + ok(implode(reverse(so_far))))
  1296 + else read_word_aux(connection,dead_line,[c . so_far],dos)
  1297 + }.
  1298 +
  1299 +define Result(Error,String)
  1300 + read_word
  1301 + (
  1302 + BufferedConnection connection,
  1303 + Int32 dead_line,
  1304 + DenialOfService dos
  1305 + ) =
  1306 + if skip_http_blanks(connection,dead_line,dos) is
  1307 + {
  1308 + error(msg) then error(msg),
  1309 + ok(_) then
  1310 + if next_char(connection,dead_line,dos) is
  1311 + {
  1312 + error(msg) then error(msg),
  1313 + ok(c) then
  1314 + if c = '\"'
  1315 + then read_string(connection,dead_line,[],dos)
  1316 + else read_word_aux(connection,dead_line,[c],dos)
  1317 + }
  1318 + }.
  1319 +
  1320 +
  1321 +
  1322 +
  1323 +
  1324 +
  1325 +
  1326 +
  1327 + *** [4.4] Separating the URI from the query string.
  1328 +
  1329 + A 'query string' may be postfixed to the URI, just after a question mark. For example,
  1330 + the client may send the following request:
  1331 +
  1332 + GET /catalog.awp?item=3&color=blue
  1333 +
  1334 + We separate this into an URI: "/catalog.awp" and the string: "item=3&color=blue" which
  1335 + will be later transformed into the list:
  1336 +
  1337 + [web_arg("item","3"),web_arg("color","blue")]
  1338 +
  1339 +
  1340 +define (String,String)
  1341 + separate_uri_from_query_string
  1342 + (
  1343 + String uri_and_query_string,
  1344 + Int32 n
  1345 + ) =
  1346 + if nth(n,uri_and_query_string) is
  1347 + {
  1348 + failure then (uri_and_query_string,""),
  1349 + success(c) then
  1350 + if c = '?'
  1351 + then (substr(uri_and_query_string,0,n),
  1352 + substr(uri_and_query_string,n+1,length(uri_and_query_string)-(n+1)))
  1353 + else separate_uri_from_query_string(uri_and_query_string,n+1)
  1354 + }.
  1355 +
  1356 +
  1357 +
  1358 +
  1359 +
  1360 +
  1361 +
  1362 +
  1363 +
  1364 + *** [4.5] Reading the web arguments.
  1365 +
  1366 + HTTP/HTTPS requests are sent in one of two formats:
  1367 +
  1368 + (1) www-url encoded
  1369 + (2) multipart/form-data encoded
  1370 +
  1371 + The first one is the normal (historical) way of encoding. The second one is required
  1372 + for uploading files. A server which is supposed to accept upload of files must handle
  1373 + both formats. The first thing to do is to decide the format of the request. This is
  1374 + easily done by examining the HTTP headers. If we find the header:
  1375 +
  1376 + Content-Type: multipart/form-data
  1377 +
  1378 + the request is multipart/form-data encoded. Otherwise, it is 'www-url' encoded. We
  1379 + first consider 'www-url' encoded requests.
  1380 +
  1381 + For a 'www-url' encoded request, the web argument are either in the query string or in
  1382 + the body of the request, or both. The format is the same for both:
  1383 +
  1384 + name=value&name=value&...
  1385 +
  1386 + However, we may also have
  1387 +
  1388 + name
  1389 + name=
  1390 + name=&...
  1391 + name&...
  1392 +
  1393 + i.e. some parts may be missing. Hence, we must be careful.
  1394 +
  1395 + Furthermore, web arguments must be translated from web to ASCII when www-url encoded.
  1396 +
  1397 +define Bool
  1398 + is_ampersand_or_equal
  1399 + (
  1400 + Word8 c
  1401 + ) =
  1402 + if c = '&' then true else c = '='.
  1403 +
  1404 +
  1405 +
  1406 + The function 'read_name_or_value' reads the string 's' starting at position 'n' until
  1407 + either the end of the string or the first '&' or '='.
  1408 +
  1409 +define String
  1410 + read_name_or_value
  1411 + (
  1412 + String s,
  1413 + Int32 start,
  1414 + Int32 i
  1415 + ) =
  1416 + if nth(i,s) is
  1417 + {
  1418 + failure then substr(s,start,i - start),
  1419 + success(c) then
  1420 + if is_ampersand_or_equal(c)
  1421 + then substr(s,start,i-start) // the separator is not included
  1422 + else read_name_or_value(s,start,i+1)
  1423 + }.
  1424 +
  1425 +
  1426 +define List(Web_arg)
  1427 + read_www_url_encoded_web_args
  1428 + (
  1429 + String s,
  1430 + Int32 start,
  1431 + ) =
  1432 + with first = read_name_or_value(s,start,start),
  1433 + if first = ""
  1434 + then []
  1435 + else with i = start+length(first),
  1436 + if nth(i,s) is
  1437 + {
  1438 + failure then [web_arg(first,"")],
  1439 + success(c) then
  1440 + if c = '&'
  1441 + then [web_arg(first,"") . read_www_url_encoded_web_args(s,i+1)]
  1442 + else if c = '='
  1443 + then with second1 = read_name_or_value(s,i+1,i+1),
  1444 + // print("\""+second1+"\"\n");
  1445 + with second = web_to_ascii(second1,0,[]),
  1446 + [web_arg(first,second) . read_www_url_encoded_web_args(s,i+length(second1)+2)]
  1447 + else print("**** ALERT **** badly formatted argument [" + s + "]!!!\n");
  1448 + []
  1449 + }.
  1450 +
  1451 +
  1452 +
  1453 +
  1454 +
  1455 + *** [4.7] Reading the request line.
  1456 +
  1457 + 'read_request_line' reads three words and a new line from the connection. It tries to
  1458 + recognize "get" or "post" in the first word, separates the URI from the query string in
  1459 + the second word, transforms the query string into a list of 'Web_arg', and finally
  1460 + returns a datum of type 'HTTP_RequestLine' if no error arose.
  1461 +
  1462 +
  1463 +define Result(Error,HTTP_RequestType)
  1464 + identify_get_or_post
  1465 + (
  1466 + String s
  1467 + ) =
  1468 + with s = to_lower(s),
  1469 + if s = "get" then ok(get) else
  1470 + if s = "post" then ok(post) else
  1471 + error(not_get_or_post_request(s)).
  1472 +
  1473 +define Result(Error,HTTP_RequestLine)
  1474 + read_request_line
  1475 + (
  1476 + BufferedConnection connection,
  1477 + Int32 dead_line,
  1478 + DenialOfService dos
  1479 + ) =
  1480 + if read_word(connection,dead_line,dos) is
  1481 + {
  1482 + error(msg) then error(msg),
  1483 + ok(get_or_post) then if read_word(connection,dead_line,dos) is
  1484 + {
  1485 + error(msg) then error(msg),
  1486 + ok(uri_and_query_string) then if read_word(connection,dead_line,dos) is
  1487 + {
  1488 + error(msg) then error(msg),
  1489 + ok(http_version) then if read_new_line(connection,dead_line,dos) is
  1490 + {
  1491 + error(msg) then error(msg),
  1492 + ok(_) then if separate_uri_from_query_string(uri_and_query_string,0) is
  1493 + (uri,query_string) then if identify_get_or_post(get_or_post) is
  1494 + {
  1495 + error(msg) then error(msg),
  1496 + ok(request_type) then
  1497 + ok(request_line(request_type,uri,read_www_url_encoded_web_args(query_string,0)))
  1498 + }
  1499 + }
  1500 + }
  1501 + }
  1502 + }.
  1503 +
  1504 +
  1505 +
  1506 +
  1507 +
  1508 +
  1509 +
  1510 + *** [4.8] Reading the HTTP headers.
  1511 +
  1512 + Each header is made of a name (containing only letters, the underscore, digits and the
  1513 + minus sign), a colon, a value, and a new line. The first empty line ends the headers.
  1514 +
  1515 +
  1516 + The next function tests characters acceptable in a header name.
  1517 +
  1518 +define Bool
  1519 + is_header_name_char
  1520 + (
  1521 + Word8 c
  1522 + ) =
  1523 + with n = word8_to_int32(c),
  1524 + if ('a' =< n & n =< 'z') then true else
  1525 + if ('A' =< n & n =< 'Z') then true else
  1526 + if ('0' =< n & n =< '9') then true else
  1527 + if c = '-' then true else
  1528 + c = '_'.
  1529 +
  1530 +define Result(Error,String)
  1531 + read_header_name
  1532 + (
  1533 + BufferedConnection connection,
  1534 + Int32 dead_line,
  1535 + List(Word8) so_far,
  1536 + DenialOfService dos
  1537 + ) =
  1538 + if next_char(connection,dead_line,dos) is
  1539 + {
  1540 + error(msg) then error(msg),
  1541 + ok(c) then
  1542 + if is_header_name_char(c)
  1543 + then read_header_name(connection,dead_line,[to_lower(c) . so_far],dos)
  1544 + else unput(c); ok(implode(reverse(so_far)))
  1545 + }.
  1546 +
  1547 +define Result(Error,One)
  1548 + skip_colon
  1549 + (
  1550 + BufferedConnection connection,
  1551 + Int32 dead_line,
  1552 + DenialOfService dos
  1553 + ) =
  1554 + if skip_http_blanks(connection,dead_line,dos) is
  1555 + {
  1556 + error(msg) then error(msg),
  1557 + ok(_) then
  1558 + if next_char(connection,dead_line,dos) is
  1559 + {
  1560 + error(msg) then error(msg),
  1561 + ok(c) then
  1562 + if c = ':'
  1563 + then ok(unique)
  1564 + else error(colon_expected)
  1565 + }}.
  1566 +
  1567 +
  1568 +define Result(Error,String)
  1569 + read_header_value
  1570 + (
  1571 + BufferedConnection connection,
  1572 + Int32 dead_line,
  1573 + List(Word8) so_far,
  1574 + DenialOfService dos
  1575 + ) =
  1576 + if next_char(connection,dead_line,dos) is
  1577 + {
  1578 + error(msg) then error(msg),
  1579 + ok(c) then
  1580 + if c = 13
  1581 + then if next_char(connection,dead_line,dos) is
  1582 + {
  1583 + error(msg) then error(msg),
  1584 + ok(d) then
  1585 + if d = 10
  1586 + then if next_char(connection,dead_line,dos) is
  1587 + {
  1588 + error(msg) then error(msg),
  1589 + ok(e) then
  1590 + if is_strict_blank(e)
  1591 + then read_header_value(connection,dead_line,[e . so_far],dos)
  1592 + else (unput(e); ok(implode(reverse(so_far))))
  1593 + }
  1594 + else read_header_value(connection,dead_line,[d, c . so_far],dos)
  1595 + }
  1596 + else read_header_value(connection,dead_line,[c . so_far],dos)
  1597 + }.
  1598 +
  1599 +
  1600 + Reading a single header.
  1601 +
  1602 +define Result(Error,Maybe(HTTP_header))
  1603 + read_header
  1604 + (
  1605 + BufferedConnection connection,
  1606 + Int32 dead_line,
  1607 + DenialOfService dos
  1608 + ) =
  1609 + if read_header_name(connection,dead_line,[],dos) is
  1610 + {
  1611 + error(msg) then error(msg),
  1612 + ok(name) then
  1613 + if name = "" then
  1614 + if read_and_ignore(connection,dead_line,2,dos) /* 13 and 10 */ is
  1615 + {
  1616 + error(msg) then error(msg),
  1617 + ok(_) then // this is the blank line
  1618 + ok(failure) // end of headers
  1619 + }
  1620 + else if skip_colon(connection,dead_line,dos) is
  1621 + {
  1622 + error(msg) then error(msg),
  1623 + ok(_) then if skip_http_blanks(connection,dead_line,dos) is
  1624 + {
  1625 + error(msg) then error(msg),
  1626 + ok(_) then if read_header_value(connection,dead_line,[],dos) is
  1627 + {
  1628 + error(msg) then error(msg),
  1629 + ok(value) then
  1630 + ok(success(http_header(name,value)))
  1631 + }
  1632 + }
  1633 + }
  1634 + }.
  1635 +
  1636 +
  1637 +
  1638 + Reading all the headers.
  1639 +
  1640 +define Result(Error,List(HTTP_header))
  1641 + read_http_headers
  1642 + (
  1643 + BufferedConnection connection,
  1644 + Int32 dead_line,
  1645 + DenialOfService dos
  1646 + ) =
  1647 + if read_header(connection,dead_line,dos) is
  1648 + {
  1649 + error(msg) then error(msg),
  1650 + ok(mbh) then if mbh is
  1651 + {
  1652 + failure then ok([ ]),
  1653 + success(header) then
  1654 + if read_http_headers(connection,dead_line,dos) is
  1655 + {
  1656 + error(msg) then error(msg),
  1657 + ok(others) then ok([header . others])
  1658 + }
  1659 + }
  1660 + }.
  1661 +
  1662 +
  1663 +
  1664 +
  1665 +
  1666 +
  1667 +
  1668 + *** [4.9] Getting the size of the request's body.
  1669 +
  1670 + The size of the body of the request is given under the 'Content-Length' header. If this
  1671 + header is not present, the size is assumed to be zero.
  1672 +
  1673 +define Result(Error,Int32)
  1674 + get_body_size
  1675 + (
  1676 + List(HTTP_header) headers
  1677 + ) =
  1678 + if headers is
  1679 + {
  1680 + [ ] then ok(0),
  1681 + [h . t] then if h is http_header(name,value) then
  1682 + if name = "content-length"
  1683 + then if string_to_integer(value) is
  1684 + {
  1685 + failure then error(incorrect_content_length_value),
  1686 + success(n) then ok(n)
  1687 + }
  1688 + else get_body_size(t)
  1689 + }.
  1690 +
  1691 +
  1692 +
  1693 +
  1694 +
  1695 +
  1696 +
  1697 +
  1698 +
  1699 +
  1700 + *** [4.10] Reading the body of the request.
  1701 +
  1702 + The body of the request may be very big (it contains uploaded files, if any). We read
  1703 + it using the primitive 'read', which returns the number of bytes read, which may be
  1704 + less than the number of bytes we wanted to read. This is not an error, but simply due
  1705 + to the fact the buffer associated with the connection in the Linux (or MS-Windows)
  1706 + kernel has a limited size. Hence, we must read bytes again until we have read the
  1707 + required number of bytes. However, if the number of bytes read is zero, the connection
  1708 + may be broken. In that case, we must not try to read indefinitely. On the contrary, we
  1709 + make at most 10 retries, with a small sleeping time between any two of them.
  1710 +
  1711 +define Result(Error,ByteArray)
  1712 + read_http_body
  1713 + (
  1714 + BufferedConnection connection,
  1715 + Int32 body_size,
  1716 + ByteArray so_far, // when calling this function, 'so_far' is the empty byte array
  1717 + Int32 retries // this function is called with retries = 10
  1718 + ) =
  1719 + if body_size = 0 then ok(constant_byte_array(0,0)) else
  1720 + if retries =< 0 then error(cannot_read_from_connection) else
  1721 + if read(connection,body_size,60) is
  1722 + {
  1723 + error then error(cannot_read_from_connection),
  1724 + timeout then error(timeout(60)),
  1725 + ok(new_bytes) then with
  1726 + ba = so_far + new_bytes, // contains all the bytes read so far
  1727 + nr = length(ba), // total read since the beginning
  1728 + nn = length(new_bytes), // number of bytes just read
  1729 + if nr < body_size // must read more bytes
  1730 + then if nn > 0 // if connection seems to work
  1731 + then read_http_body(connection,body_size,ba,1000) // continue reading
  1732 + else sleep(100); // otherwise, sleep 1/10 of second
  1733 + read_http_body(connection,body_size,ba, // and retry reading
  1734 + retries-1) // but no more than 10 times
  1735 + else ok(ba) // required number of bytes has been read
  1736 + }.
  1737 +
  1738 +
  1739 + Note: During sleeping, 'anbexec' runs other machines. Actually, calling 'sleep', even
  1740 + for one millisecond, is some way of giving up explicitly, so that other virtual
  1741 + machines may work.
  1742 +
  1743 +
  1744 +
  1745 +
  1746 +
  1747 +
  1748 +
  1749 +
  1750 +
  1751 +
  1752 +
  1753 +
  1754 + *** [5] Making the HTTP answer.
  1755 +
  1756 + At that point we have read the request line, the headers and the body of the
  1757 + request, and we must decide what to do.
  1758 +
  1759 + Actually, we can do one of the following:
  1760 +
  1761 + - send a file,
  1762 + - execute 'tickets_and_web_page' in case of an ".awp" URI.
  1763 +
  1764 + The uploaded file (which are in the body of the request) are saved into temporary files
  1765 + below.
  1766 +
  1767 +
  1768 +
  1769 +
  1770 +
  1771 + *** [5.1] Avoiding illegal URIs.
  1772 +
  1773 + For security reasons, we must avoid illegal URIs, for example those which may climb up
  1774 + in the file hierarchy. First we accept only few characters in URIs.
  1775 +
  1776 +define Bool
  1777 + is_legal_uri_char
  1778 + (
  1779 + Word8 c
  1780 + ) =
  1781 + with n = word8_to_int32(c),
  1782 + if ('a' =< n & n =< 'z') then true else // accept 'a' to 'z'
  1783 + if ('A' =< n & n =< 'Z') then true else // accept 'A' to 'Z'
  1784 + if ('0' =< n & n =< '9') then true else // accept '0' to '9'
  1785 + if c = '.' then true else // accept '.' '-' '/' and '_'
  1786 + if c = '-' then true else
  1787 + if c = '/' then true else
  1788 + c = '_'.
  1789 +
  1790 + We do not accept ~ which is some way of climbing. Of course, we cannot disallow single
  1791 + dots, which are most often present in legal URIs, but we must avoid double dots ..
  1792 + which mean 'climb up'.
  1793 +
  1794 +define Bool
  1795 + is_illegal_uri
  1796 + (
  1797 + String uri,
  1798 + Int32 n
  1799 + ) =
  1800 + if nth(n,uri) is
  1801 + {
  1802 + failure then false,
  1803 + success(c) then
  1804 + if c = '.' // first dot
  1805 + then if nth(n+1,uri) is
  1806 + {
  1807 + failure then false,
  1808 + success(d) then
  1809 + if d = '.' // second dot
  1810 + then true
  1811 + else is_illegal_uri(uri,n+1)
  1812 + }
  1813 + else is_illegal_uri(uri,n+1)
  1814 + }.
  1815 +
  1816 +
  1817 +
  1818 +
  1819 +
  1820 +
  1821 + *** [5.2] Managing authorizations for downloading private files.
  1822 +
  1823 + Computing the authorization and making the authorization file (containing the absolute
  1824 + path of the file on the server).
  1825 +
  1826 +
  1827 +define String
  1828 + compute_authorization
  1829 + (
  1830 + String authorization_secret,
  1831 + String absolute_path
  1832 + ) =
  1833 + to_ascii(sha1((authorization_secret,
  1834 + absolute_path))).
  1835 +
  1836 +
  1837 +public define String
  1838 + make_authorization
  1839 + (
  1840 + String site_directory,
  1841 + String authorization_secret,
  1842 + String absolute_path
  1843 + ) =
  1844 + with private_download_dir = site_directory+"/private_download",
  1845 + auth = compute_authorization(authorization_secret,
  1846 + absolute_path),
  1847 + forget(save(absolute_path,
  1848 + private_download_dir+"/z"+auth));
  1849 + auth.
  1850 +
  1851 +
  1852 + The function 'send_file' defined below handles the recognition of authorizations.
  1853 +
  1854 +
  1855 +
  1856 +
  1857 +
  1858 + *** [5.3] Recognizing MIME types.
  1859 +
  1860 + The extension of the (redirected) URI must be either ".awp" or recognized as associated
  1861 + to a MIME type. Otherwise, the server will not send the file. This is for security, but
  1862 + also because, we must generate a 'Content-Type' header in the answer, with the right
  1863 + MIME type.
  1864 +
  1865 +define String
  1866 + get_uri_extension_aux
  1867 + (
  1868 + String uri,
  1869 + Int32 n // used for searching backwards
  1870 + ) =
  1871 + if nth(n,uri) is
  1872 + {
  1873 + failure then "",
  1874 + success(c) then
  1875 + if c = '.' then substr(uri,n,length(uri)-n)
  1876 + else if c = '/' then ""
  1877 + else get_uri_extension_aux(uri,n-1)
  1878 + }.
  1879 +
  1880 +public define String
  1881 + get_uri_extension
  1882 + (
  1883 + String uri
  1884 + ) =
  1885 + get_uri_extension_aux(uri,
  1886 + length(uri)-1). // search starts at the right end
  1887 +
  1888 +
  1889 +
  1890 +define Maybe(String)
  1891 + recognize_mime_type_from_ext
  1892 + (
  1893 + String ext,
  1894 + List(MIME) l
  1895 + ) =
  1896 + if l is
  1897 + {
  1898 + [ ] then success("application/octet-stream"), // failure,
  1899 + [h . t] then if h is mime(mime_type,extension) then
  1900 + if ext = extension
  1901 + then success(mime_type)
  1902 + else recognize_mime_type_from_ext(ext,t)
  1903 + }.
  1904 +
  1905 +define Maybe(String)
  1906 + recognize_mime_type_from_uri
  1907 + (
  1908 + Web_Site_Description desc,
  1909 + String uri
  1910 + ) =
  1911 + recognize_mime_type_from_ext(get_uri_extension(uri),known_mime_types(desc)).
  1912 +
  1913 +
  1914 +
  1915 +
  1916 +
  1917 +
  1918 +
  1919 +
  1920 + *** [5.4] Formating HTTP headers.
  1921 +
  1922 + This is the formating for sending to the client (hence, it has nothing to do with the
  1923 + component 'journal_headers' in the web site description).
  1924 +
  1925 +define Printable_tree
  1926 + format_headers
  1927 + (
  1928 + List(HTTP_header) headers
  1929 + ) =
  1930 + if headers is
  1931 + {
  1932 + [ ] then [ ],
  1933 + [h . t] then if h is http_header(name,value) then
  1934 + [name,": ",value,crlf . format_headers(t)]
  1935 + }.
  1936 +
  1937 +
  1938 +
  1939 +define String
  1940 + month_abrv
  1941 + (
  1942 + Date_and_Time d
  1943 + ) =
  1944 + if d.month = 1 then "Jan"
  1945 + else if d.month = 2 then "Feb"
  1946 + else if d.month = 3 then "Mar"
  1947 + else if d.month = 4 then "Apr"
  1948 + else if d.month = 5 then "May"
  1949 + else if d.month = 6 then "Jun"
  1950 + else if d.month = 7 then "Jul"
  1951 + else if d.month = 8 then "Aug"
  1952 + else if d.month = 9 then "Sep"
  1953 + else if d.month = 10 then "Oct"
  1954 + else if d.month = 11 then "Nov"
  1955 + else if d.month = 12 then "Dec"
  1956 + else
  1957 + println("Bad month value [" + d.month + "] on Date_and_Time");
  1958 + "XXX".
  1959 +
  1960 +define String
  1961 + weekday_abrv
  1962 + (
  1963 + Date_and_Time d
  1964 + ) =
  1965 + if d.week_day = 0 then "Sun"
  1966 + else if d.week_day = 1 then "Mon"
  1967 + else if d.week_day = 2 then "Tue"
  1968 + else if d.week_day = 3 then "Wed"
  1969 + else if d.week_day = 4 then "Thu"
  1970 + else if d.week_day = 5 then "Fri"
  1971 + else if d.week_day = 6 then "Sat"
  1972 + else
  1973 + println("Bad weekday value [" + d.week_day + "] on Date_and_Time");
  1974 + "XXX".
  1975 +
  1976 +/**
  1977 + * Format a date with the followin format : "Mon, 23 Jul 2007 11:33:43 GMT"
  1978 + * Currently, this function can't output a GMT time, but only local time.
  1979 + * So the final GMT is totally fake, but needed by protocol.
  1980 + */
  1981 +public define String
  1982 + format_http_date
  1983 + (
  1984 + Date_and_Time d
  1985 + ) =
  1986 + weekday_abrv(d) + ", " + zero_pad_n(2,day(d)) + " " + month_abrv(d) + " " + year(d)
  1987 + + " " + zero_pad_n(2,hour(d)) + ":" + zero_pad_n(2,minute(d)) + ":" + zero_pad_n(2,second(d)) + " GMT".
  1988 +
  1989 +/**
  1990 + * Same as previous format_http_date() function, but with seconds count from the UNIX epoch as input.
  1991 + */
  1992 +public define String
  1993 + format_http_date
  1994 + (
  1995 + Int32 date
  1996 + ) =
  1997 + format_http_date(convert_time(date)).
  1998 +
  1999 +
  2000 + *** [5.5] Sending a file.
  2001 +
  2002 + We send 2 headers 'Content-Type' and 'Content-Length'.
  2003 +
  2004 +define List(HTTP_header)
  2005 + headers_for_send_file
  2006 + (
  2007 + String mime_type,
  2008 + Int32 size,
  2009 + String etag,
  2010 + Maybe(FileTimes) mb_ftimes,
  2011 + ) =
  2012 + with headers = (List(HTTP_header))
  2013 + [
  2014 + http_header("Content-Type",mime_type),
  2015 + http_header("Etag", etag),
  2016 + http_header("Content-Length",integer_to_string(size)),
  2017 + ],
  2018 + if mb_ftimes is
  2019 + {
  2020 + failure then headers,
  2021 + success(ftimes) then [http_header("Last-Modified", format_http_date(ftimes.last_modified)) . headers]
  2022 + }
  2023 + .
  2024 +
  2025 +
  2026 +
  2027 + Sending the body of the answer (i.e. the file itself).
  2028 +
  2029 +define One
  2030 + send_file_body
  2031 + (
  2032 + Web_Site_Description desc,
  2033 + Connection connection, // connection with the client
  2034 + Connection file, // file to be sent already opened
  2035 + Int32 size, // size of file
  2036 + Int32 sent, // bytes already sent
  2037 + String filename // name of file
  2038 + ) =
  2039 + if sent >= size then unique else
  2040 + if read(file,min(10000,size-sent),60) is
  2041 + {
  2042 + error then log_journal_msg(desc,"Cannot read from file '"+filename+"'.\n"),
  2043 + timeout then log_journal_msg(desc,"Cannot read from file timeoput'"+filename+"'.\n"),
  2044 + ok(ba) then
  2045 + with nr = length(ba), // get the number of bytes read
  2046 + if reliable_write(connection,ba) is
  2047 + {
  2048 + failure then log_journal_msg(desc,"Cannot write into connection.\n"),
  2049 + success(nw) then
  2050 + send_file_body(desc,connection,file,size,sent+nw,filename)
  2051 + }
  2052 + }.
  2053 +
  2054 +
  2055 +define String
  2056 + compute_etag
  2057 + (
  2058 + String filename,
  2059 + Maybe(FileTimes) mb_ftimes,
  2060 + Int32 size,
  2061 + ) =
  2062 + if mb_ftimes is
  2063 + {
  2064 + failure then println("Warning: no file times for '" + filename + "', etag won't be very accurate."); to_ascii(sha1((filename, size))),
  2065 + success(ftimes) then to_ascii(sha1((filename, ftimes, size)))
  2066 + }.
  2067 +
  2068 +define Bool
  2069 + are_same_etag
  2070 + (
  2071 + Maybe(String) input_etag,
  2072 + String current_etag
  2073 + ) =
  2074 + if input_etag is
  2075 + {
  2076 + failure then false,
  2077 + success(etag) then etag = current_etag
  2078 + }.
  2079 +
  2080 + Sending the answer line, the headers and the body.
  2081 +
  2082 +define One
  2083 + send_file
  2084 + (
  2085 + Web_Site_Description desc,
  2086 + Connection connection,
  2087 + List(HTTP_header) input_headers,
  2088 + List(HTTP_header) headers,
  2089 + Int32 size,
  2090 + Connection file,
  2091 + String filename,
  2092 + String full_path,
  2093 + String mime_type,
  2094 + One -> One action_before_send_file
  2095 + ) =
  2096 + action_before_send_file(unique);
  2097 + with input_etag = http_header_value(input_headers, "If-None-Match"),
  2098 + mb_ftimes = get_file_times(full_path),
  2099 + current_etag = compute_etag(full_path, mb_ftimes, size),
  2100 + if are_same_etag(input_etag, current_etag) is
  2101 + {
  2102 + false then
  2103 + forget(reliable_write(connection,to_byte_array("HTTP/1.1 200 OK"+crlf)));
  2104 + forget(reliable_write(connection,[format_headers(headers + headers_for_send_file(mime_type, size, current_etag, mb_ftimes)) , crlf]));
  2105 + send_file_body(desc,connection,file,size,0,filename),
  2106 + true then
  2107 + forget(reliable_write(connection,to_byte_array("HTTP/1.1 304 Not Modified"+crlf)));
  2108 + forget(reliable_write(connection,[format_headers([http_header("Etag", current_etag) . headers]) , crlf]))
  2109 + //send_file_body(desc,connection,file,size,0,filename)
  2110 + }.
  2111 +
  2112 +
  2113 +
  2114 + Checking if a connection is under SSL.
  2115 +
  2116 +define Bool
  2117 + is_SSL
  2118 + (
  2119 + Connection c
  2120 + ) =
  2121 + if c is
  2122 + {
  2123 + file_r(_) then false,
  2124 + file_w(_) then false,
  2125 + file_rw(_) then false,
  2126 + tcp(_) then false,
  2127 + ssl(_) then true
  2128 + }.
  2129 +
  2130 +
  2131 +
  2132 + Before opening and sending a file, we check the MIME type. It must be recognized,
  2133 + except if there is a valid authorization for private download.
  2134 +
  2135 +define One
  2136 + send_file
  2137 + (
  2138 + Web_Site_Description desc,
  2139 + Connection connection,
  2140 + String uri,
  2141 + List(HTTP_header) input_headers,
  2142 + List(HTTP_header) output_headers,
  2143 + Maybe(String) mbauthorization,
  2144 + One -> One action_before_send_file
  2145 + ) =
  2146 + if mbauthorization is
  2147 + {
  2148 + //--- file without authorization: take it from public ---
  2149 + failure then if recognize_mime_type_from_uri(desc,uri) is
  2150 + {
  2151 + failure then log_journal_msg(desc,"No MIME type found for '"+uri+"'.\n"),
  2152 + success(mime_type) then
  2153 + with path = site_directory(desc)+"/public"+uri,
  2154 + if (Maybe(RStream))connect to file path is
  2155 + {
  2156 + failure then log_journal_msg(desc,"Cannot find file '"+path+"'.\n"),
  2157 + success(f) then with size = file_size(f),
  2158 + send_file(desc,
  2159 + connection,
  2160 + input_headers,
  2161 + output_headers,
  2162 + size,
  2163 + file(f),
  2164 + uri,
  2165 + path,
  2166 + mime_type,
  2167 + action_before_send_file)
  2168 + }
  2169 + },
  2170 +
  2171 + //--- file with authorization: apply 'private download' mecanism ---
  2172 + success(authorization) then
  2173 + with private_download_dir = site_directory(desc)+"/private_download",
  2174 + if (RetrieveResult(String))retrieve(private_download_dir+"/z"+authorization)
  2175 + is ok(absolute_path)
  2176 + then (
  2177 + with new_hash = compute_authorization(authorization_secret(desc),
  2178 + absolute_path),
  2179 + if (Maybe(RStream))connect to file absolute_path is
  2180 + {
  2181 + failure then log_journal_msg(desc,"Cannot find file '"+absolute_path+"'.\n"),
  2182 + success(f) then with size = file_size(f),
  2183 + mime_type = if recognize_mime_type_from_uri(desc,uri) is
  2184 + {
  2185 + failure then "application/octet-stream"
  2186 + success(mime_type) then mime_type
  2187 + },
  2188 + send_file(desc,
  2189 + connection,
  2190 + input_headers,
  2191 + output_headers,
  2192 + size,
  2193 + file(f),
  2194 + uri,
  2195 + absolute_path,
  2196 + mime_type,
  2197 + action_before_send_file)
  2198 + }
  2199 + )
  2200 + else log_journal_msg(desc,"Cannot find or read authorization file.\n")
  2201 + }.
  2202 +
  2203 +
  2204 +
  2205 +
  2206 +
  2207 +
  2208 +
  2209 +
  2210 + *** [5.6] Answering a www-url encoded request.
  2211 +
  2212 + Standard headers are for answering ".awp" requests.
  2213 +
  2214 +define List(HTTP_header)
  2215 + standard_headers
  2216 + =
  2217 + [
  2218 + http_header("Date", format_http_date(now)),
  2219 + http_header("Server", "Anubis Embedded Server v" + major_version_number + "." + minor_version_number)
  2220 + ].
  2221 +
  2222 +define List(HTTP_header)
  2223 + standard_headers_for_html
  2224 + (
  2225 + Int32 answer_body_size,
  2226 + String charset
  2227 + ) =
  2228 + [
  2229 + //http_header("Content-Type","text/html"),
  2230 + http_header("Content-Type","text/html; charset="+charset),
  2231 + http_header("Content-length",integer_to_string(answer_body_size))
  2232 + ].
  2233 +
  2234 +
  2235 +define One
  2236 + www_url_answer
  2237 + (
  2238 + String host_name,
  2239 + Web_Site_Description desc,
  2240 + Connection connection, // with the client
  2241 + Int32 ip_addr, // of the client
  2242 + HTTP_RequestLine request_line,
  2243 + List(HTTP_header) headers,
  2244 + ByteArray body,
  2245 + One -> String generate_tt // trust ticket generation
  2246 + ) =
  2247 + with all_web_args = query_string(request_line) +
  2248 + read_www_url_encoded_web_args(to_string(body),0),
  2249 + uri = uri(request_line),
  2250 + ext = get_uri_extension(uri),
  2251 + (if member(journal_extensions(desc),ext)
  2252 + then log_journal_msg(desc,
  2253 + format_request(desc,connection,request_line,headers,all_web_args))
  2254 + else unique);
  2255 + if is_illegal_uri(uri,0)
  2256 + then log_journal_msg(desc,"Received illegal URI: "+uri+"\n")
  2257 + else (if (ext = ".awp" | ext = "")
  2258 + then (with answer_headers_body = awp_handler(desc)(host_name,
  2259 + http_info(ip_addr,uri,headers,generate_tt),
  2260 + all_web_args,
  2261 + is_SSL(connection)),
  2262 + if answer_headers_body is (additional_headers,answer_body) then
  2263 + forget(reliable_write(connection,
  2264 + [ "HTTP/1.1 200 OK", crlf,
  2265 + format_headers(standard_headers),
  2266 + format_headers(standard_headers_for_html(length(answer_body),charset(desc))),
  2267 + format_headers(additional_headers),
  2268 + crlf .
  2269 + answer_body])))
  2270 + else (send_file(desc,
  2271 + connection,
  2272 + uri,
  2273 + headers,
  2274 + standard_headers,
  2275 + if web_arg_value(all_web_args,"zauth") is
  2276 + {
  2277 + not_found then failure,
  2278 + found(v) then success(v)
  2279 + },
  2280 + (One u) |-> before_send_file(desc)(all_web_args)))).
  2281 +
  2282 +
  2283 +
  2284 +
  2285 +
  2286 +
  2287 +
  2288 + *** [5.7] Answering a multipart/form-data encoded request.
  2289 +
  2290 + In order to support upload of files, we must be able to read web arguments which are
  2291 + encoded in a multipart/form-data body. The first thing to do is to find the
  2292 + boundary. The boundary is a special string which delimits the various parts of the
  2293 + 'multipart' body. It is found within the value of the 'Content-Type' HTTP header, as
  2294 + the value of the 'boundary' attribute.
  2295 +
  2296 +
  2297 +
  2298 +
  2299 +
  2300 + *** [5.7.1] Finding the boundary.
  2301 +
  2302 + Hence, we just have to find the string 'boundary=' within the value of the
  2303 + 'Content-Type' header, and read the value of the boundary from there.
  2304 +
  2305 +define Bool
  2306 + delimits_boundary
  2307 + (
  2308 + Word8 c
  2309 + ) =
  2310 + if c = ' ' then true else
  2311 + if c = 13 then true else
  2312 + if c = 10 then true else
  2313 + if c = 0 then true else
  2314 + if c = ',' then true else
  2315 + c = ';'.
  2316 +
  2317 +
  2318 +define Maybe(String)
  2319 + get_boundary_value_3
  2320 + (
  2321 + String s,
  2322 + Int32 i,
  2323 + List(Word8) so_far
  2324 + ) =
  2325 + if nth(i,s) is
  2326 + {
  2327 + failure then success(implode(reverse(so_far))),
  2328 + success(c) then
  2329 + if delimits_boundary(c)
  2330 + then success(implode(reverse(so_far)))
  2331 + else get_boundary_value_3(s,i+1,[c . so_far])
  2332 + }.
  2333 +
  2334 +
  2335 +
  2336 +define Maybe(String)
  2337 + get_boundary_value_2
  2338 + (
  2339 + String s,
  2340 + Int32 i,
  2341 + ) =
  2342 + if nth(i,s) is
  2343 + {
  2344 + failure then failure,
  2345 + success(c) then
  2346 + if is_blank(c)
  2347 + then get_boundary_value_2(s,i+1)
  2348 + else get_boundary_value_3(s,i+1,[c])
  2349 + }.
  2350 +
  2351 +define Maybe(String)
  2352 + get_boundary_value_1
  2353 + (
  2354 + String s, // string into which we must find '= ...'
  2355 + Int32 i // position of start of search
  2356 + ) =
  2357 + if nth(i,s) is
  2358 + {
  2359 + failure then failure,
  2360 + success(c) then
  2361 + if is_blank(c)
  2362 + then get_boundary_value_1(s,i+1)
  2363 + else if c = '='
  2364 + then get_boundary_value_2(s,i+1)
  2365 + else failure
  2366 + }.
  2367 +
  2368 +
  2369 +define Maybe(String)
  2370 + get_boundary
  2371 + (
  2372 + String content_type_header_value
  2373 + ) =
  2374 + if find("boundary",content_type_header_value,0) is
  2375 + {
  2376 + failure then failure,
  2377 + success(n) then // 'boundary' has been found at position n
  2378 + get_boundary_value_1(content_type_header_value,n+8)
  2379 + }.
  2380 +
  2381 +define Maybe(String)
  2382 + get_boundary
  2383 + (
  2384 + List(HTTP_header) headers
  2385 + ) =
  2386 + if headers is
  2387 + {
  2388 + [ ] then failure,
  2389 + [h . t] then if h is http_header(name,value) then
  2390 + if name = "content-type"
  2391 + then get_boundary(value)
  2392 + else get_boundary(t)
  2393 + }.
  2394 +
  2395 +
  2396 +
  2397 +
  2398 +
  2399 +
  2400 +
  2401 +
  2402 + *** [5.7.2] Reading attributes from a multipart entity.
  2403 +
  2404 + Entities in a multipart/form-data body are separated by instances of the string:
  2405 +
  2406 + --bbbbb
  2407 +
  2408 + where bbbbb is the boundary computed above. Actually, the body has the form:
  2409 +
  2410 + --bbbbb
  2411 + <entity 1>
  2412 + --bbbbb
  2413 + <entity 2>
  2414 + --bbbbb
  2415 + ...
  2416 + --bbbbb
  2417 + <last entity>
  2418 + --bbbbb
  2419 +
  2420 +
  2421 + We have to extract an entity which is in the body between offsets 'start' and 'end'
  2422 + (computed when boundaries have been localized). The entity itself is made of two parts:
  2423 + headers and body. The body is separated from the headers by a blank line. This blank
  2424 + line (a double crlf) marks the beginning of the body of the entity. Within the headers
  2425 + of the entity, we look for a 'Content-Disposition' header, which should look like this:
  2426 +
  2427 + Content-Disposition: form-data; name="..."; filename="..." crlf
  2428 +
  2429 + We are just interested in the name and the file name. Hence we first search
  2430 + 'Content-Disposition', then we search 'name' and read the value, and we do the same for
  2431 + 'filename'.
  2432 +
  2433 + If the 'filename' attribute is not present, the web arg is an ordinary one, otherwise,
  2434 + it is an uploaded file.
  2435 +
  2436 +
  2437 + Below is a variant of 'find' (see 'tools/findstring.anubis'), with an extra 'end'
  2438 + argument.
  2439 +
  2440 +define Maybe(Int32)
  2441 + find
  2442 + (
  2443 + String what,
  2444 + ByteArray where,
  2445 + Int32 start,
  2446 + Int32 end
  2447 + ) =
  2448 + if find(to_byte_array(what),where,start) is
  2449 + {
  2450 + failure then failure,
  2451 + success(n) then
  2452 + if n+length(what) >= end
  2453 + then failure
  2454 + else success(n)
  2455 + }.
  2456 +
  2457 +
  2458 +define String
  2459 + read_attribute_value
  2460 + (
  2461 + ByteArray where,
  2462 + Int32 start,
  2463 + Int32 end,
  2464 + List(Word8) so_far
  2465 + ) =
  2466 + if start >= end then implode(reverse(so_far)) else
  2467 + if nth(start,where) is
  2468 + {
  2469 + failure then implode(reverse(so_far)),
  2470 + success(c) then
  2471 + if c = '\"'
  2472 + then implode(reverse(so_far))
  2473 + else read_attribute_value(where,start+1,end,[c . so_far])
  2474 + }.
  2475 +
  2476 +define Maybe(String)
  2477 + find_attribute
  2478 + (
  2479 + String name,
  2480 + ByteArray where,
  2481 + Int32 start,
  2482 + Int32 end
  2483 + ) =
  2484 + with name = name+"=\"",
  2485 + if find(to_byte_array(name),where,start) is
  2486 + {
  2487 + failure then failure,
  2488 + success(n) then
  2489 + if n+length(name) >= end
  2490 + then failure
  2491 + else success(read_attribute_value(where,n+length(name),end,[]))
  2492 + }.
  2493 +
  2494 +
  2495 +
  2496 +define Maybe((String,Maybe(String)))
  2497 + find_name_and_filename
  2498 + (
  2499 + ByteArray body,
  2500 + Int32 start,
  2501 + Int32 end
  2502 + ) =
  2503 + if find(to_byte_array("Content-Disposition"),body,start) is
  2504 + {
  2505 + failure then failure,
  2506 + success(n) then
  2507 + if find_attribute("name",body,n+19,end) is
  2508 + {
  2509 + failure then failure,
  2510 + success(name_value) then if find_attribute("filename",body,n+19,end) is
  2511 + {
  2512 + failure then success((name_value,failure)),
  2513 + success(filename_value) then success((name_value,success(filename_value)))
  2514 + }
  2515 + }
  2516 + }.
  2517 +
  2518 +
  2519 +
  2520 +
  2521 +
  2522 +
  2523 +
  2524 +
  2525 +
  2526 +
  2527 + *** [5.7.3] Creating a temporary filename for an uploaded file.
  2528 +
  2529 +variable Int32 uploaded_file_count = 0.
  2530 +
  2531 + This variable is local to the virtual machine. Hence, its value is 0 each time a new
  2532 + requests arrives. Temporary uploaded files are stored in the directory represented by
  2533 + 'upload_temporary_directory'. The filenames have the form:
  2534 +
  2535 + _m_n
  2536 +
  2537 + where 'm' is the number of the virtual machine, and 'n' a number obtained by
  2538 + incrementing 'uploaded_file_count'. Notice that the program must do something with this
  2539 + file (move it to some directory/name), otherwise, it will probably be overwritten the
  2540 + next time the same machine works.
  2541 +
  2542 +
  2543 +
  2544 +
  2545 +
  2546 +
  2547 + *** [5.7.4] Saving an uploaded file under a temporary filename.
  2548 +
  2549 +define Maybe(String) // returns the temporary file name
  2550 + save_uploaded_file
  2551 + (
  2552 + Web_Site_Description desc,
  2553 + ByteArray body,
  2554 + Int32 start,
  2555 + Int32 end
  2556 + ) =
  2557 + uploaded_file_count <- 1 + *uploaded_file_count;
  2558 + with tfn = "_"+integer_to_string(virtual_machine_id)+"_"+integer_to_string(*uploaded_file_count),
  2559 + if (Maybe(WStream))connect to file site_directory(desc)+"/upload_temporary/"+tfn is
  2560 + {
  2561 + failure then failure,
  2562 + success(f) then
  2563 + if reliable_write(file(f),extract(body,start,end)) is
  2564 + {
  2565 + failure then failure,
  2566 + success(nw) then
  2567 + if nw = end - start
  2568 + then success(tfn)
  2569 + else failure
  2570 + }
  2571 + }.
  2572 +
  2573 +
  2574 +
  2575 +
  2576 +
  2577 +
  2578 +
  2579 +
  2580 + *** [5.7.5] Removing the path from a file name.
  2581 +
  2582 + When a file is uploaded, the browser sends the complete path of the file on the client
  2583 + machine as the file name. Actually, this is not quite normal. Nevertheless, we need to
  2584 + remove the path, and keep only the file name. This is achieved by 'remove_path' below.
  2585 +
  2586 +define Int32
  2587 + file_name_begin
  2588 + (
  2589 + String full_name,
  2590 + Int32 i
  2591 + ) =
  2592 + if nth(i,full_name) is
  2593 + {
  2594 + failure then 0,
  2595 + success(c) then
  2596 + if c = '/' then i+1 else
  2597 + if c = '\\' then i+1 else
  2598 + file_name_begin(full_name,i-1)
  2599 + }.
  2600 +
  2601 +define String
  2602 + remove_path
  2603 + (
  2604 + String full_name
  2605 + ) =
  2606 + with l = length(full_name),
  2607 + b = file_name_begin(full_name,l-1),
  2608 + substr(full_name,b,l-b).
  2609 +
  2610 +
  2611 +
  2612 +
  2613 +
  2614 + *** [5.7.6] Reading a multipart entity.
  2615 +
  2616 +define Maybe(Web_arg)
  2617 + get_multipart_entity
  2618 + (
  2619 + Web_Site_Description desc,
  2620 + ByteArray body,
  2621 + Int32 start,
  2622 + Int32 end
  2623 + ) =
  2624 + if find(to_byte_array(crlf+crlf),body,start) is
  2625 + {
  2626 + failure then failure,
  2627 + success(k) then
  2628 + if k >= end // must be within this entity, not the next one
  2629 + then failure
  2630 + else if find_name_and_filename(body,start,k) is
  2631 + {
  2632 + failure then failure,
  2633 + success(n_mbfn) then if n_mbfn is (name,mbfn) then
  2634 + if mbfn is
  2635 + {
  2636 + failure then
  2637 + success(web_arg(name,to_string(extract(body,k+4,end-2)))),
  2638 + // we must substract 2 to end because of crlf just before the boundary
  2639 +
  2640 + success(fn) then
  2641 + if save_uploaded_file(desc,body,k+4,end-2) is
  2642 + {
  2643 + failure then failure,
  2644 + success(tfn) then
  2645 + success(upload(name,remove_path(fn),
  2646 + site_directory(desc)+"/upload_temporary/"+tfn))
  2647 +
  2648 + }
  2649 + }
  2650 + }
  2651 + }.
  2652 +
  2653 +
  2654 +
  2655 +define List(Web_arg)
  2656 + read_multipart_form_data_encoded_web_args
  2657 + (
  2658 + Web_Site_Description desc,
  2659 + ByteArray body,
  2660 + ByteArray __boundary,
  2661 + Int32 i,
  2662 + ) =
  2663 + if find(__boundary,body,i) is
  2664 + {
  2665 + failure then [ ],
  2666 + success(n) then
  2667 + if find(__boundary,body,n+length(__boundary)) is
  2668 + {
  2669 + failure then [ ],
  2670 + success(m) then
  2671 + if get_multipart_entity(desc,body,n+length(__boundary),m) is
  2672 + {
  2673 + failure then [ ],
  2674 + success(wa) then
  2675 + [wa . read_multipart_form_data_encoded_web_args(desc,body,__boundary,m)]
  2676 + }
  2677 + }
  2678 + }.
  2679 +
  2680 +
  2681 +
  2682 +define One
  2683 + multipart_form_data_answer
  2684 + (
  2685 + String host_name,
  2686 + Web_Site_Description desc,
  2687 + Connection connection,
  2688 + Int32 ip_addr,
  2689 + HTTP_RequestLine request_line,
  2690 + List(HTTP_header) headers,
  2691 + ByteArray body,
  2692 + One -> String generate_tt
  2693 + ) =
  2694 + if get_boundary(headers) is
  2695 + {
  2696 + failure then unique,
  2697 + success(boundary) then
  2698 + with all_web_args = query_string(request_line) +
  2699 + read_multipart_form_data_encoded_web_args(desc,
  2700 + body,
  2701 + to_byte_array("--"+boundary),
  2702 + 0),
  2703 + uri = uri(request_line),
  2704 + ext = get_uri_extension(uri),
  2705 + log_journal_msg(desc,
  2706 + format_request(desc,connection,request_line,headers,all_web_args));
  2707 + if is_illegal_uri(uri,0)
  2708 + then log_journal_msg(desc,"Received illegal URI: "+uri+"\n")
  2709 + else
  2710 + if (ext = ".awp" | ext = "") then
  2711 + (with answer_headers_body = awp_handler(desc)(host_name,
  2712 + http_info(ip_addr,uri,headers,generate_tt),
  2713 + all_web_args,
  2714 + is_SSL(connection)),
  2715 + if answer_headers_body is (additional_headers,answer_body) then
  2716 + forget(reliable_write(connection,
  2717 + [ "HTTP/1.1 200 OK",crlf,
  2718 + format_headers(standard_headers_for_html(length(answer_body),charset(desc))),
  2719 + format_headers(additional_headers),
  2720 + crlf .
  2721 + answer_body])))
  2722 + else unique
  2723 + }.
  2724 +
  2725 +
  2726 +
  2727 +
  2728 +
  2729 +
  2730 +
  2731 +
  2732 + *** [5.8] Handling redirections.
  2733 +
  2734 + 'redirections' (of type 'List(Redirection)') contains redirection directives. Each one
  2735 + has the form:
  2736 +
  2737 + redirect(required_uri,required_host,corresponding_uri).
  2738 +
  2739 + The host required by the client may be found in the 'Host' HTTP header. The URI
  2740 + required by the client is given below as 'uri'. We just have to find the required host
  2741 + in the headers, and to find the corresponding redirection directive.
  2742 +
  2743 +
  2744 + In the next fonction, the required host and URI are known. We just have to search in
  2745 + the 'redirections' list.
  2746 +
  2747 +define String
  2748 + handle_redirection
  2749 + (
  2750 + String required_uri,
  2751 + String required_host,
  2752 + List(Redirection) redirections
  2753 + ) =
  2754 + if redirections is
  2755 + {
  2756 + [ ] then required_uri,
  2757 + [h . t] then if h is redirect(uri,host,target) then
  2758 + if host = required_host
  2759 + then if uri = required_uri
  2760 + then target
  2761 + else handle_redirection(required_uri,required_host,t)
  2762 + else handle_redirection(required_uri,required_host,t)
  2763 + }.
  2764 +
  2765 +
  2766 +
  2767 + The host name may be encumbered by a port number, like
  2768 +
  2769 + www.our-business.com:1607
  2770 +
  2771 + We must remove this port number, otherwise the host name may not be recognized.
  2772 +
  2773 +define String
  2774 + strip_port
  2775 + (
  2776 + String name,
  2777 + Int32 i
  2778 + ) =
  2779 + if nth(i,name) is
  2780 + {
  2781 + failure then name,
  2782 + success(c) then
  2783 + if c = ':'
  2784 + then substr(name,0,i)
  2785 + else strip_port(name,i+1)
  2786 + }.
  2787 +
  2788 +
  2789 +
  2790 +
  2791 +
  2792 + Finding the 'Host' header. No redirection is performed if this header is not found.
  2793 +
  2794 +define String
  2795 + handle_redirection // returns the redirected URI
  2796 + (
  2797 + List(Redirection) redirections,
  2798 + String uri, // original URI
  2799 + List(HTTP_header) headers
  2800 + ) =
  2801 + if headers is
  2802 + {
  2803 + [ ] then uri,
  2804 + [h . t] then if h is http_header(name,value) then
  2805 + if name = "host"
  2806 + then handle_redirection(uri,strip_port(value,0),redirections)
  2807 + else handle_redirection(redirections,uri,t)
  2808 + }.
  2809 +
  2810 +
  2811 +
  2812 +
  2813 +
  2814 +
  2815 +
  2816 +
  2817 + *** [5.9] Answering both sorts of requests.
  2818 +
  2819 + We must decide if the request is www-url encoded or multipart/form-data encoded. This
  2820 + is achieved through the header 'Content-Type'.
  2821 +
  2822 +define EncodingType
  2823 + get_encoding_type
  2824 + (
  2825 + List(HTTP_header) headers
  2826 + ) =
  2827 + if headers is
  2828 + {
  2829 + [ ] then www_url, // this is the default
  2830 + [h . t] then if h is http_header(name,value) then
  2831 + if name = "content-type"
  2832 + then if find("multipart/form-data",value,0) is
  2833 + {
  2834 + failure then www_url,
  2835 + success(_) then multipart_form_data
  2836 + }
  2837 + else get_encoding_type(t)
  2838 + }.
  2839 +
  2840 +
  2841 +
  2842 +define One
  2843 + send_answer
  2844 + (
  2845 + String host_name,
  2846 + Web_Site_Description desc,
  2847 + Connection connection,
  2848 + HTTP_RequestLine rqline,
  2849 + List(HTTP_header) headers,
  2850 + ByteArray body,
  2851 + One -> String generate_tt
  2852 + ) =
  2853 + if rqline is request_line(type,uri,qstring) then
  2854 + with rqline = request_line(type,handle_redirection(redirections(desc),uri,headers),qstring),
  2855 + if remote_IP_address_and_port(connection) is (ip_addr,_) then
  2856 + if get_encoding_type(headers) is
  2857 + {
  2858 + www_url then
  2859 + www_url_answer(host_name,desc,connection,ip_addr,rqline,headers,body,generate_tt),
  2860 + multipart_form_data then
  2861 + multipart_form_data_answer(host_name,desc,connection,ip_addr,rqline,headers,body,generate_tt)
  2862 + }.
  2863 +
  2864 +
  2865 +
  2866 +
  2867 +
  2868 +
  2869 +
  2870 + *** [6] The HTTP/HTTPS server.
  2871 +
  2872 + The command 'start_server' (declared in 'predefined.anubis') starts a virtual machine
  2873 + which opens a server TCP/IP connection, and which continuously listens to this
  2874 + connection. When a request arrives, this machine delegates the work of deciphering and
  2875 + answering the request to another virtual machine, and continues to listen. The job of
  2876 + the delegated machine is defined by the HTTP request handler below.
  2877 +
  2878 +
  2879 +
  2880 +
  2881 +
  2882 + *** [6.1] Determining the requested host.
  2883 +
  2884 + When a request arrives to one of our two servers, we must decide which site (host) is
  2885 + requested.
  2886 +
  2887 +define Maybe(String)
  2888 + get_host_header_value
  2889 + (
  2890 + List(HTTP_header) headers
  2891 + ) =
  2892 + if headers is
  2893 + {
  2894 + [ ] then failure,
  2895 + [h . t] then if h is http_header(name,value) then
  2896 + if name = "host"
  2897 + then success(strip_port(value,0))
  2898 + else get_host_header_value(t)
  2899 + }.
  2900 +
  2901 +define Maybe((String,Web_Site_Description))
  2902 + get_site
  2903 + (
  2904 + String requested_host,
  2905 + List(Web_Site_Description) sites
  2906 + ) =
  2907 + if sites is
  2908 + {
  2909 + [ ] then print("Requested host '"+requested_host+"' does not exist.\n"); failure,
  2910 + [site1 . others] then
  2911 + if site1 is web_site_description(common_names,_,_,_,_,_,_,_,_,_) then
  2912 + if member(common_names,requested_host)
  2913 + then success((requested_host,site1))
  2914 + else get_site(requested_host,others)
  2915 + }.
  2916 +
  2917 +
  2918 +define Maybe((String,Web_Site_Description))
  2919 + get_site
  2920 + (
  2921 + List(HTTP_header) headers,
  2922 + List(Web_Site_Description) sites
  2923 + ) =
  2924 + if get_host_header_value(headers) is
  2925 + {
  2926 + failure then print("No 'Host' HTTP header.\n"); failure,
  2927 + success(requested_host) then
  2928 + //here we treat the case with only one site. hence we accept any host request
  2929 + //print("*** there is " +length(sites) + " sites \n");
  2930 + if length(sites) = 1 then
  2931 + with site = force_nth(0, sites),
  2932 + //print("ONE server OK\n");
  2933 + success((requested_host, site))
  2934 + else
  2935 + get_site(requested_host,sites)
  2936 + }.
  2937 +
  2938 +
  2939 +
  2940 +
  2941 +
  2942 + *** [6.2] The HTTP request handler.
  2943 +
  2944 + Here is the HTTP/HTTPS handler. It is called at each new request in a separate virtual
  2945 + machine. It reads the headers of the HTTP request, determines the host, determines body
  2946 + size, reads the body of the HTTP request, and answers the request.
  2947 +
  2948 +
  2949 +
  2950 +define One -> String make_generate_trust_ticket(DenialOfService dos).
  2951 +
  2952 +
  2953 +define One
  2954 + http_https_handler
  2955 + (
  2956 + List(Web_Site_Description) sites,
  2957 + BufferedConnection connection,
  2958 + Bool is_https,
  2959 + DenialOfService dos
  2960 + ) =
  2961 + with start_time = (Int32)now,
  2962 + sttm <- start_time;
  2963 + if dos is denial_of_service(mc_v,rld_v,hd_v,ad_v,ld_v,ra_v) then
  2964 + if remote_IP_address_and_port(connection.conn) is (ip_addr,port) then
  2965 + if read_request_line(connection,start_time+*rld_v,dos) is
  2966 + {
  2967 + error(msg) then print(format(msg)),
  2968 + ok(request_line) then
  2969 + if read_http_headers(connection,start_time+*hd_v,dos) is
  2970 + {
  2971 + error(msg) then print(format(msg)),
  2972 + ok(headers) then if get_site(headers,sites) is
  2973 + {
  2974 + failure then unique,
  2975 + success(p) then if p is (host_name,desc) then
  2976 + if get_body_size(headers) is
  2977 + {
  2978 + error(msg) then log_journal_msg(desc,format(msg)),
  2979 + ok(body_size) then
  2980 + if read_http_body(connection,body_size,constant_byte_array(0,0),1000) is
  2981 + {
  2982 + error(msg) then log_journal_msg(desc,format(msg)),
  2983 + ok(body) then
  2984 + send_answer(host_name, desc,connection.conn, request_line, headers, body,
  2985 + make_generate_trust_ticket(dos))
  2986 + }
  2987 + }
  2988 + }
  2989 + }
  2990 + }.
  2991 +
  2992 +
  2993 + Below are the two tools for constructing the handlers required by 'start_server' and
  2994 + 'start_ssl_server' (see 'predefined.anubis').
  2995 +
  2996 +define Bool is_dubious_IP(Int32 ip, DenialOfService dos).
  2997 +
  2998 +define Server -> ((RWStream) -> One)
  2999 + make_http_handler
  3000 + (
  3001 + List(Web_Site_Description) sites,
  3002 + DenialOfService dos
  3003 + ) =
  3004 + (Server server) |-> (RWStream conn) |->
  3005 + if remote_IP_address_and_port(conn) is (addr,_) then
  3006 + if is_dubious_IP(addr,dos)
  3007 + then print("Rejecting dubious IP address "+ip_addr_to_string(addr)+"\n")
  3008 + else
  3009 + with connection = buffered_connection(tcp(conn), var(constant_byte_array(0, 0)), var(0)),
  3010 + http_https_handler(sites, connection, false, dos).
  3011 +
  3012 +define Server -> (SSL_Connection -> One)
  3013 + make_https_handler
  3014 + (
  3015 + List(Web_Site_Description) sites,
  3016 + DenialOfService dos
  3017 + ) =
  3018 + (Server server) |-> (SSL_Connection conn) |->
  3019 + with connection = buffered_connection(ssl(conn), var(constant_byte_array(0, 0)), var(0)),
  3020 + http_https_handler(sites, connection, true, dos).
  3021 +
  3022 +
  3023 +
  3024 +
  3025 + *** [6.3] Server's tasks.
  3026 +
  3027 + Some tasks must be executed periodically, for example for cleaning up directories from
  3028 + short life time files.
  3029 +
  3030 + The next function removes from the given directory (and recursively from its
  3031 + subdirectories) all the files which are more than 10 minutes old.
  3032 +
  3033 +define One
  3034 + cleanup_directory_10mn
  3035 + (
  3036 + String dir // path of private download directory (or subdirectory) with trailing slash
  3037 + ) =
  3038 + forget(map((FileDescription fd) |-> if fd is
  3039 + {
  3040 + no_info(name) then forget(remove(dir+name)),
  3041 + file(name,_,_,d) then if d+600 < now then forget(remove(dir+name)) else unique,
  3042 + link(name,_,_,d) then if d+600 < now then forget(remove(dir+name)) else unique,
  3043 + directory(name,_,_) then cleanup_directory_10mn(dir+name+"/"),
  3044 + },
  3045 + directory_full_list(dir,"*","*","*"))).
  3046 +
  3047 +
  3048 +define One
  3049 + http_servers_tasks
  3050 + (
  3051 + List(Web_Site_Description) sites,
  3052 + List(Server) servers,
  3053 + Int32 period,
  3054 + Int32 next_time,
  3055 + ) =
  3056 + if mapand(is_down,servers)
  3057 + then unique
  3058 + else if now > next_time
  3059 + then
  3060 + (
  3061 + /*
  3062 + forget(map((Web_Site_Description wsd) |->
  3063 + cleanup_directory_10mn(site_directory(wsd)+"/private_download/"),
  3064 + sites));
  3065 + */
  3066 + http_servers_tasks(sites,servers,period,next_time+period)
  3067 + )
  3068 + else
  3069 + (
  3070 + sleep(1000);
  3071 + http_servers_tasks(sites,servers,period,next_time)
  3072 + ).
  3073 +
  3074 +
  3075 +public define One
  3076 + start_http_servers_tasks
  3077 + (
  3078 + List(Web_Site_Description) sites,
  3079 + List(Server) servers,
  3080 + Int32 period
  3081 + ) =
  3082 + delegate http_servers_tasks(sites,servers,period,now),
  3083 + unique.
  3084 +
  3085 +
  3086 +
  3087 +
  3088 + *** [6.4] Protection against 'denial of service' attacks.
  3089 +
  3090 +
  3091 + *** [6.4.1] Counting connections.
  3092 +
  3093 +define Bool // returns false if the counter cannot be incremented (too many connections)
  3094 + increment_connections_counter
  3095 + (
  3096 + Var(Int32) counter
  3097 + ) =
  3098 + protect with n = *counter,
  3099 + if n >= 100
  3100 + then false
  3101 + else (counter <- (*counter)+1); true.
  3102 +
  3103 +define One
  3104 + decrement_connections_counter
  3105 + (
  3106 + Var(Int32) counter
  3107 + ) =
  3108 + protect counter <- (*counter)-1.
  3109 +
  3110 +
  3111 +
  3112 +
  3113 +
  3114 + *** [6.4.2] Recording dubious IP addresses.
  3115 +
  3116 +
  3117 +define List(DubiousIP)
  3118 + record_dubious_IP
  3119 + (
  3120 + Int32 ip,
  3121 + List(DubiousIP) l
  3122 + ) =
  3123 + if l is
  3124 + {
  3125 + [ ] then [dubious_ip(ip,now)],
  3126 + [h . t] then if h is dubious_ip(addr,time) then
  3127 + if addr = ip
  3128 + then [dubious_ip(addr,now) . t]
  3129 + else [h . record_dubious_IP(ip,t)]
  3130 + }.
  3131 +
  3132 +
  3133 +define One
  3134 + record_dubious_IP
  3135 + (
  3136 + Int32 dubious_IP,
  3137 + Var(List(DubiousIP)) v
  3138 + ) =
  3139 + protect v <- record_dubious_IP(dubious_IP,*v).
  3140 +
  3141 +
  3142 +define One
  3143 + record_dubious_IP
  3144 + (
  3145 + Int32 addr,
  3146 + DenialOfService dos
  3147 + ) =
  3148 + record_dubious_IP(addr,list_of_dubious(dos)).
  3149 +
  3150 +
  3151 +public define DenialOfService
  3152 + load_denial_of_service_info
  3153 + =
  3154 + if (RetrieveResult(DenialOfService))retrieve(my_anubis_directory+"/web_sites/dos_info") is
  3155 + ok(dos) then dos else denial_of_service(
  3156 + var(100),
  3157 + var(1000),
  3158 + var(1500),
  3159 + var(2000),
  3160 + var([]),
  3161 + var([])).
  3162 +
  3163 +
  3164 +
  3165 +
  3166 + *** [6.4.3] Testing if an address is dubious.
  3167 +
  3168 +define Bool
  3169 + is_dubious_IP
  3170 + (
  3171 + Int32 ip,
  3172 + List(DubiousIP) l
  3173 + ) =
  3174 + if l is
  3175 + {
  3176 + [ ] then false,
  3177 + [h . t] then if h is dubious_ip(addr,time) then
  3178 + if ip = addr
  3179 + then true
  3180 + else is_dubious_IP(ip,t)
  3181 + }.
  3182 +
  3183 +
  3184 +define Bool
  3185 + is_dubious_IP
  3186 + (
  3187 + Int32 ip,
  3188 + DenialOfService dos
  3189 + ) =
  3190 + if dos is
  3191 + {
  3192 + denial_of_service(mc_v,rld_v,hd_v,ad_v,ld_v,ra_v) then
  3193 + if member(*ra_v,ip) then false else
  3194 + is_dubious_IP(ip,*ld_v)
  3195 + }.
  3196 +
  3197 +
  3198 +
  3199 +
  3200 + *** [6.4.4] Removing inactive dubious IP addresses.
  3201 +
  3202 +define List(DubiousIP)
  3203 + remove_inactive_dubious_IP
  3204 + (
  3205 + List(DubiousIP) l,
  3206 + Int32 ref_time,
  3207 + ) =
  3208 + if l is
  3209 + {
  3210 + [ ] then [ ],
  3211 + [h . t] then if h is dubious_ip(addr,time) then
  3212 + if time < ref_time
  3213 + then (print(ip_addr_to_string(addr)+" removed from dubious addresses list.\n");
  3214 + remove_inactive_dubious_IP(t,ref_time))
  3215 + else [h . remove_inactive_dubious_IP(t,ref_time)]
  3216 + }.
  3217 +
  3218 +define One
  3219 + remove_inactive_dubious_IP
  3220 + (
  3221 + Var(List(DubiousIP)) v
  3222 + ) =
  3223 + protect
  3224 + with ref_time = now - 600, // 10 minutes
  3225 + v <- remove_inactive_dubious_IP(*v,ref_time).
  3226 +
  3227 +
  3228 + The above function will be executed periodically by the servers's tasks machine.
  3229 +
  3230 +
  3231 +
  3232 + *** [6.4.5] Making the function for generating trust tickets.
  3233 +
  3234 +define One -> String
  3235 + make_generate_trust_ticket
  3236 + (
  3237 + DenialOfService dos
  3238 + ) =
  3239 + (One _) |-> "".
  3240 +
  3241 +
  3242 +
  3243 +
  3244 +
  3245 +
  3246 +
  3247 + *** [6.5] Starting the HTTP/HTTPS server.
  3248 +
  3249 +
  3250 + The next function creates the directories for all sites (if they don't already exist).
  3251 +
  3252 +define One
  3253 + create_directories
  3254 + (
  3255 + List(Web_Site_Description) sites
  3256 + ) =
  3257 + if sites is
  3258 + {
  3259 + [ ] then unique,
  3260 + [s1 . others] then
  3261 + with site_dir = site_directory(s1),
  3262 + forget(make_directory(site_dir+"/public",default_directory_mode));
  3263 + forget(make_directory(site_dir+"/upload_temporary",default_directory_mode));
  3264 + forget(make_directory(site_dir+"/private_download",default_directory_mode));
  3265 + forget(make_directory(site_dir+"/journal",default_directory_mode));
  3266 + create_directories(others)
  3267 + }.
  3268 +
  3269 +
  3270 +
  3271 +
  3272 +
  3273 + Below are the commands for starting an HTTP server and an HTTPS server.
  3274 +
  3275 +
  3276 +define StartServerResult
  3277 + start_http_server
  3278 + (
  3279 + Int32 ip_address,
  3280 + Int32 port,
  3281 + Server -> ((RWStream) -> One) handler,
  3282 + Int32 retries,
  3283 + DenialOfService dos
  3284 + ) =
  3285 + if start_server(ip_address,
  3286 + port,
  3287 + handler,
  3288 + identity) is ok(server)
  3289 + then print(" \r");
  3290 + ok(server)
  3291 + else print("Port "+port+": retry number "+retries+"\r");
  3292 + sleep(1000);
  3293 + start_http_server(ip_address,port,handler,retries+1,dos).
  3294 +
  3295 +public define StartServerResult
  3296 + start_http_server
  3297 + (
  3298 + Int32 ip_address,
  3299 + Int32 port,
  3300 + List(Web_Site_Description) sites,
  3301 + DenialOfService dos
  3302 + ) =
  3303 + create_directories(sites);
  3304 + start_http_server(ip_address,port,
  3305 + make_http_handler(sites,dos),
  3306 + 0,
  3307 + dos).
  3308 +
  3309 +
  3310 + For the HTTPS server, we have a problem which is due to the fact that 'anbexec' is not
  3311 + yet able to manipulate several SSL server certificates. 'anbexec' and
  3312 + 'predefined.anubis' must be changed. Sorry ! This will be done as soon as possible. The
  3313 + 'solution' for the time being is to provide the common name of the unique SSL server
  3314 + certificate.
  3315 +
  3316 +
  3317 +define StartServerResult
  3318 + start_https_server
  3319 + (
  3320 + Int32 ip_address,
  3321 + Int32 port,
  3322 + String certificate_common_name,
  3323 + Server -> (SSL_Connection -> One) handler,
  3324 + Int32 retries,
  3325 + DenialOfService dos
  3326 + ) =
  3327 + if start_ssl_server(ip_address,
  3328 + port,
  3329 + certificate_common_name,
  3330 + handler,
  3331 + identity) is ok(server)
  3332 + then print(" \r");
  3333 + ok(server)
  3334 + else print("Port "+port+": retry number "+retries+"\r");
  3335 + sleep(1000);
  3336 + start_https_server(ip_address,port,
  3337 + certificate_common_name,
  3338 + handler,retries+1,
  3339 + dos).
  3340 +
  3341 +
  3342 +public define StartServerResult
  3343 + start_https_server
  3344 + (
  3345 + Int32 ip_address,
  3346 + Int32 port,
  3347 + String certificate_common_name, // of SSL server certificate
  3348 + List(Web_Site_Description) sites,
  3349 + DenialOfService dos
  3350 + ) =
  3351 + create_directories(sites);
  3352 + start_https_server(ip_address,port,certificate_common_name,
  3353 + make_https_handler(sites,dos),
  3354 + 0,dos).
  3355 +
  3356 +
  3357 +
  3358 +
  3359 +
  3360 +
  3361 +
  3362 +
  3363 +
  3364 + *** [7] The web dispatcher.
  3365 +
  3366 +
  3367 + *** [7.1] The dispatcher server.
  3368 +
  3369 +define One
  3370 + send_dispatching_page
  3371 + (
  3372 + RWStream conn,
  3373 + String common_name,
  3374 + Int32 port
  3375 + ) =
  3376 + print("Dispatching '"+common_name+"' to port "+port+"\n");
  3377 + forget(reliable_write(conn,to_byte_array(
  3378 + "<html><head><meta http-equiv=\"Refresh\" content=\"0;URL="+
  3379 + "http://"+common_name+":"+port+"/"+
  3380 + "\"></head><body></body></html>"
  3381 + ))).
  3382 +
  3383 +
  3384 +
  3385 +define Maybe(DispatcherInfo)
  3386 + find_host
  3387 + (
  3388 + List(DispatcherInfo) l,
  3389 + String host
  3390 + ) =
  3391 + if l is
  3392 + {
  3393 + [ ] then failure,
  3394 + [h . t] then if h is site(name,port) then
  3395 + if name = host
  3396 + then success(h)
  3397 + else find_host(t,host)
  3398 + }.
  3399 +
  3400 +
  3401 +
  3402 +define Server -> ((RWStream) -> One)
  3403 + make_dispatcher_handler
  3404 + (
  3405 + Var(List(DispatcherInfo)) info_v,
  3406 + DenialOfService dos
  3407 + ) =
  3408 + (Server server) |-> (RWStream conn) |->
  3409 + with start_time = (Int32)now,
  3410 + connection = buffered_connection(tcp(conn), var(constant_byte_array(0, 0)), var(0)),
  3411 + if read_request_line(connection, start_time+*request_line_delay(dos), dos) is
  3412 + {
  3413 + error(msg) then print(format(msg)),
  3414 + ok(request_line) then
  3415 + if read_http_headers(connection, start_time+*headers_delay(dos), dos) is
  3416 + {
  3417 + error(msg) then print(format(msg)),
  3418 + ok(headers) then if get_host_header_value(headers) is
  3419 + {
  3420 + failure then print("No 'HOST' HTTP header.\n"),
  3421 + success(host) then
  3422 + if find_host(*info_v,host) is
  3423 + {
  3424 + failure then print("Host: '"+host+"' not registered.\n"),
  3425 + success(s) then if s is site(common_name,ip_port) then
  3426 + send_dispatching_page(conn,common_name,ip_port)
  3427 + }
  3428 + }
  3429 + }
  3430 + }.
  3431 +
  3432 +
  3433 +define One
  3434 + dispatcher_update_error
  3435 + (
  3436 + String file_path
  3437 + ) =
  3438 + print("web_dispatcher: unable to reread file: '"+file_path+"'.\n").
  3439 +
  3440 +
  3441 +define Bool
  3442 + dispatcher_update_data
  3443 + (
  3444 + String info_file_path,
  3445 + Var(List(DispatcherInfo)) info_v,
  3446 + Var(Int32) info_date_v
  3447 + ) =
  3448 + if directory_full_list(my_anubis_directory+"/web_sites","dispatcher.info","","") is
  3449 + {
  3450 + [ ] then false,
  3451 + [h . t] then if h is
  3452 + {
  3453 + no_info(n) then false,
  3454 + file(n,_,_,d) then if n = "dispatcher.info"
  3455 + then (info_date_v <- d;
  3456 + if (RetrieveResult(List(DispatcherInfo)))retrieve(info_file_path) is
  3457 + {
  3458 + cannot_find_file then false,
  3459 + read_error then false,
  3460 + type_error then false,
  3461 + ok(info) then info_v <- info; true
  3462 + })
  3463 + else false,
  3464 + link(_,_,_,_) then false,
  3465 + directory(_,_,_) then false
  3466 + }
  3467 + }.
  3468 +
  3469 +
  3470 +
  3471 + The loop within which the dispatcher updates its data every 3 seconds:
  3472 +
  3473 +define One
  3474 + dispatcher_update_task
  3475 + (
  3476 + String info_file_path,
  3477 + Var(List(DispatcherInfo)) info_v,
  3478 + Var(Int32) info_date_v
  3479 + ) =
  3480 + sleep(3000);
  3481 + (if dispatcher_update_data(info_file_path,info_v,info_date_v)
  3482 + then unique
  3483 + else dispatcher_update_error(info_file_path));
  3484 + dispatcher_update_task(info_file_path,info_v,info_date_v).
  3485 +
  3486 +
  3487 +public define One
  3488 + start_web_dispatcher
  3489 + (
  3490 + Int32 ip_address, // address for listening (typically 0: listen on all interfaces)
  3491 + Int32 http_port, // typically 80
  3492 + DenialOfService dos
  3493 + ) =
  3494 + with info_file_path = my_anubis_directory+"/web_sites/dispatcher.info",
  3495 + info_v = var((List(DispatcherInfo))[]),
  3496 + info_date_v = var((Int32)0),
  3497 + if dispatcher_update_data(info_file_path,info_v,info_date_v)
  3498 + then if start_server(ip_address,
  3499 + http_port,
  3500 + make_dispatcher_handler(info_v,dos),
  3501 + (One u)|->u) is
  3502 + {
  3503 + cannot_create_the_socket then
  3504 + print("Cannot create the socket for HTTP server.\n"),
  3505 + cannot_bind_to_port then
  3506 + print("Cannot bind HTTP server to port "+http_port+".\n"),
  3507 + cannot_listen_on_port then
  3508 + print("HTTP server cannot listen on port "+http_port+".\n"),
  3509 + ok(http_server) then
  3510 + dispatcher_update_task(info_file_path,info_v,info_date_v)
  3511 + }
  3512 + else dispatcher_update_error(info_file_path).
  3513 +
  3514 +
  3515 +
  3516 + *** [7.2] The dispatcher web site.
  3517 +
  3518 + global define One
  3519 + web_dispatcher
  3520 + (
  3521 + List(String) args
  3522 + ) =
  3523 + start_web_dispatcher(0,80,load_denial_of_service_info).
  3524 +
  3525 +
  3526 +
  3527 +
  3528 +
  3529 +
  3530 + *** [7.3] Managing the info file.
  3531 +
  3532 +define Int32
  3533 + register_ip_address
  3534 + =
  3535 + if ip_address(prompt(" numerical IP address (for HTTP): ")) is
  3536 + {
  3537 + failure then print(" *** Error: incorrect IP address.\n");
  3538 + register_ip_address,
  3539 + success(n) then n
  3540 + }.
  3541 +
  3542 +
  3543 +define Int32
  3544 + register_ip_port
  3545 + =
  3546 + if string_to_integer(prompt(" IP port (for HTTP): ")) is
  3547 + {
  3548 + failure then print(" *** Error: incorrect IP port.\n");
  3549 + register_ip_port,
  3550 + success(p) then if (0 =< p & p =< 65535)
  3551 + then p
  3552 + else print(" *** Error: IP port out of bounds.\n");
  3553 + register_ip_port
  3554 + }.
  3555 +
  3556 +
  3557 +define One
  3558 + register_new_site
  3559 + (
  3560 + Var(List(DispatcherInfo)) info_v
  3561 + ) =
  3562 + print("\n");
  3563 + print(" Registering a new site:\n");
  3564 + with name = prompt(" Site name: "),
  3565 + with addr = register_ip_address,
  3566 + with port = register_ip_port,
  3567 + (protect info_v <- [site(name,port) . *info_v]);
  3568 + print(" Site "+name+" at "+ip_addr_to_string(addr)+":"+port+" added\n (but not saved to disk).\n").
  3569 +
  3570 +
  3571 +define List(DispatcherInfo)
  3572 + find_sites
  3573 + (
  3574 + List(DispatcherInfo) l,
  3575 + String name
  3576 + ) =
  3577 + if l is
  3578 + {
  3579 + [ ] then [ ],
  3580 + [h . t] then if h is site(n,_) then
  3581 + if find(name,n,0) is
  3582 + {
  3583 + failure then find_sites(t,name),
  3584 + success(_) then [h . find_sites(t,name)]
  3585 + }
  3586 + }.
  3587 +
  3588 +
  3589 +define String
  3590 + pad
  3591 + (
  3592 + String s,
  3593 + Int32 l
  3594 + ) =
  3595 + if length(s) >= l
  3596 + then s
  3597 + else s+constant_string(l-length(s),' ').
  3598 +
  3599 +
  3600 +
  3601 +define One
  3602 + show_sites_1
  3603 + (
  3604 + List(DispatcherInfo) l,
  3605 + Int32 i
  3606 + ) =
  3607 + if l is
  3608 + {
  3609 + [ ] then unique,
  3610 + [h . t] then if h is site(name,port) then
  3611 + print(" ["+i+"] "+pad(name,40)+" "+" "+port+"\n");
  3612 + show_sites_1(t,i+1)
  3613 + }.
  3614 +
  3615 +
  3616 +define One
  3617 + show_sites
  3618 + (
  3619 + List(DispatcherInfo) l,
  3620 + Int32 i
  3621 + ) =
  3622 + print(" Name Port\n");
  3623 + print(" --------------------------------------------------------\n");
  3624 + show_sites_1(l,i).
  3625 +
  3626 +define List(DispatcherInfo)
  3627 + replace_info
  3628 + (
  3629 + List(DispatcherInfo) l,
  3630 + String site_name,
  3631 + Int32 new_port
  3632 + ) =
  3633 + if l is
  3634 + {
  3635 + [ ] then print("ALERT: Empty list into replace_info() [" + __FILE__ + "]\n"); [],
  3636 + [h . t] then if h is site(n,_) then
  3637 + if n = site_name
  3638 + then [site(n,new_port) . t]
  3639 + else [h . replace_info(t,site_name,new_port)]
  3640 + }.
  3641 +
  3642 +define List(DispatcherInfo)
  3643 + delete_info
  3644 + (
  3645 + List(DispatcherInfo) l,
  3646 + String site_name,
  3647 + ) =
  3648 + if l is
  3649 + {
  3650 + [ ] then print("ALERT: Empty list into delete_info() [" + __FILE__ + "]\n"); [],
  3651 + [h . t] then if h is site(n,_) then
  3652 + if n = site_name
  3653 + then t
  3654 + else [h . delete_info(t,site_name)]
  3655 + }.
  3656 +
  3657 +
  3658 +define One
  3659 + update_site
  3660 + (
  3661 + Var(List(DispatcherInfo)) info_v,
  3662 + String site_name,
  3663 + Int32 old_port
  3664 + ) =
  3665 + print("\n");
  3666 + print(" Updating site '"+site_name+"': (currently: "+old_port+")\n");
  3667 + with new_port = register_ip_port,
  3668 + answer = prompt(" Update '"+site_name+"' as: "+new_port+" [Y/N] ? "),
  3669 + if (answer = "Y" | answer = "y")
  3670 + then info_v <- replace_info(*info_v,site_name,new_port)
  3671 + else unique.
  3672 +
  3673 +
  3674 +
  3675 +define Bool
  3676 + compare
  3677 + (
  3678 + DispatcherInfo d1,
  3679 + DispatcherInfo d2
  3680 + ) =
  3681 + if d1 is site(n1,_) then
  3682 + if d2 is site(n2,_) then
  3683 + string_less(n1,n2).
  3684 +
  3685 +
  3686 +
  3687 +define One
  3688 + update_site
  3689 + (
  3690 + Var(List(DispatcherInfo)) info_v
  3691 + ) =
  3692 + print("\n");
  3693 + with prefix = prompt(" Search for site to update: "),
  3694 + if find_sites(*info_v,prefix) is
  3695 + {
  3696 + [ ] then print(" No site found.\n");
  3697 + update_site(info_v),
  3698 + [h . t] then
  3699 + show_sites(qsort([h . t],compare),1);
  3700 + with i1 = prompt(" Choose a site to update [1/.../"+(length(t)+1)+"]: "),
  3701 + if string_to_integer(i1) is
  3702 + {
  3703 + failure then print(" *** Error: site number not recognized.\n");
  3704 + update_site(info_v),
  3705 + success(ii1) then if nth(ii1-1,*info_v) is
  3706 + {
  3707 + failure then print(" *** Error: site number "+i1+" does not exist.\n");
  3708 + update_site(info_v),
  3709 + success(site_info) then if site_info is site(name,old_port) then
  3710 + update_site(info_v,name,old_port)
  3711 + }
  3712 + }
  3713 + }.
  3714 +
  3715 +
  3716 +define One
  3717 + delete_site
  3718 + (
  3719 + Var(List(DispatcherInfo)) info_v,
  3720 + String site_name,
  3721 + Int32 old_port
  3722 + ) =
  3723 + print("\n");
  3724 + print(" Deleting site '"+site_name+"': (currently: "+old_port+")\n");
  3725 + with answer = prompt(" Are you sure you want to delete site: '"+site_name+"' [Y/N] ? "),
  3726 + if (answer = "Y" | answer = "y")
  3727 + then info_v <- delete_info(*info_v,site_name)
  3728 + else print(" Site '"+site_name+"' not deleted.\n").
  3729 +
  3730 +
  3731 +define One
  3732 + delete_site
  3733 + (
  3734 + Var(List(DispatcherInfo)) info_v
  3735 + ) =
  3736 + print("\n");
  3737 + with prefix = prompt(" Search for site to delete: "),
  3738 + if find_sites(*info_v,prefix) is
  3739 + {
  3740 + [ ] then print(" No site found.\n");
  3741 + delete_site(info_v),
  3742 + [h . t] then
  3743 + show_sites(qsort([h . t],compare),1);
  3744 + with i1 = prompt(" Choose a site to delete [1/.../"+(length(t)+1)+"]: "),
  3745 + if string_to_integer(i1) is
  3746 + {
  3747 + failure then print(" *** Error: site number not recognized.\n");
  3748 + delete_site(info_v),
  3749 + success(ii1) then if nth(ii1-1,*info_v) is
  3750 + {
  3751 + failure then print(" *** Error: site number "+i1+" does not exist.\n");
  3752 + delete_site(info_v),
  3753 + success(site_info) then if site_info is site(name,old_port) then
  3754 + delete_site(info_v,name,old_port)
  3755 + }
  3756 + }
  3757 + }.
  3758 +
  3759 +
  3760 +define One
  3761 + manager
  3762 + (
  3763 + Var(List(DispatcherInfo)) info_v,
  3764 + String file_path
  3765 + ) =
  3766 + print("\n");
  3767 + print(" --- Welcome to the Web Dispatcher Manager ---\n");
  3768 + with l = length(*info_v),
  3769 + print(" "+l+" site"+(if l>1 then "s" else "")+" currently registred.\n");
  3770 + print(" [L] List registered sites.\n");
  3771 + print(" [R] Register a new site.\n");
  3772 + print(" [U] Update a registred site.\n");
  3773 + print(" [D] Delete a registred site.\n");
  3774 + with propose_write_v = var((Bool)true),
  3775 + action = prompt(" Choose an action [L/R/U/D]: "),
  3776 + (if (action = "L" | action = "l") then (show_sites(*info_v,1); propose_write_v <- false) else
  3777 + if (action = "R" | action = "r") then register_new_site(info_v) else
  3778 + if (action = "U" | action = "u") then update_site(info_v) else
  3779 + if (action = "D" | action = "d") then delete_site(info_v) else
  3780 + print("Action not recognized.\n"));
  3781 + print("\n");
  3782 + if *propose_write_v then
  3783 + with result = prompt(" Write modifications to data base [Y/N] ?"),
  3784 + if (result = "Y" | result = "y")
  3785 + then if save(*info_v,file_path) is
  3786 + {
  3787 + cannot_open_file then print(" File '"+file_path+"' not found.\n"),
  3788 + write_error then print(" Error while writing file '"+file_path+"'.\n"),
  3789 + ok then print(" Data base has been modified.\n")
  3790 + }
  3791 + else print(" Data base not modified.\n")
  3792 + else unique.
  3793 +
  3794 +
  3795 +
  3796 +global define One
  3797 + manage_web_dispatcher
  3798 + (
  3799 + List(String) args
  3800 + ) =
  3801 + with info_v = var((List(DispatcherInfo))[]),
  3802 + with file_path = my_anubis_directory+"/web_sites/dispatcher.info",
  3803 + if (RetrieveResult(List(DispatcherInfo)))retrieve(file_path) is
  3804 + {
  3805 + cannot_find_file then print("File '"+file_path+"' does not exist.\n");
  3806 + with answer = prompt("Create it [Y/N] ? "),
  3807 + if (answer = "Y" | answer = "y")
  3808 + then if save((List(DispatcherInfo))[],file_path) is
  3809 + {
  3810 + cannot_open_file then
  3811 + print("Cannot create file '"+file_path+"'.\n"),
  3812 + write_error then
  3813 + print("Error while creating file '"+file_path+"'.\n"),
  3814 + ok then manager(info_v,file_path)
  3815 + }
  3816 + else unique,
  3817 + read_error then print("Error while reading file '"+file_path+"'.\n"),
  3818 + type_error then print("File '"+file_path+"' is corrupted.\n"),
  3819 + ok(info) then info_v <- info;
  3820 + manager(info_v,file_path)
  3821 + }.
  3822 +
  3823 +
  3824 +
  3825 +
  3826 +
calexium_lib/web/CXM_web_arg_encode.anubis
1 -  
2 - *Project* The Anubis Project  
3 -  
4 - *Title* Encoding data for web argument values.  
5 -  
6 - *Copyright* Copyright (c) Alain Prouté 2002.  
7 -  
8 -  
9 - *Author* Alain Prouté  
10 -  
11 -  
12 -  
13 - *Overview*  
14 - This file contains encoding and decoding functions which allow to put any serializable  
15 - datum as the value of a web argument. The datum is serialized, and the result of  
16 - serialization (a byte array) is encoded in such a way that it can safely be used as the  
17 - value of a web argument. The encoding process is similar to the standard process  
18 - 'base64', but nevertheless different, because base64 is not suitable for that purpose.  
19 -  
20 -  
21 -public define String  
22 - web_arg_encode  
23 - (  
24 - $T datum  
25 - ).  
26 -  
27 -public define Maybe($T)  
28 - web_arg_decode  
29 - (  
30 - String encoded_value  
31 - ).  
32 -  
33 - Of course, since the type of the datum is not available from 'encoded_value', a term  
34 - like 'web_arg_decode(my_string)' must generally be explicitly typed, like this:  
35 -  
36 - (Maybe(MyType))web_arg_decode(my_string)  
37 -  
38 -  
39 - These functions are used for example in 'anubis/library/web/kernel.anubis'.  
40 -  
41 -  
42 -  
43 -  
44 -  
45 - --- That's all for the public part. ---------------------------------------------------  
46 -  
47 -read tools/basis.anubis  
48 -  
49 - Our algorithms are copy-pasted from 'base64.anubis' and slightly modified. The point is  
50 - twofold:  
51 -  
52 - (1) base64 encoding inserts carriage return (CR) and line feed (LF) characters every  
53 - 76 character, but CR and LF are not suitable in the values of a web argument,  
54 -  
55 - (2) the base64 alphabet uses '+' and '/', which are also not suitable in the value of  
56 - a web argument, because they have special meanings.  
57 -  
58 - Hence, we just have to modify the base64 algorithms, so as not to generate any CR or  
59 - LF, and use '-' and '_' instead of '+' and '/'. Also, we do not use padding characters  
60 - '=', which are needless (as remarked in 'anubis/library/tools/base64.anubis').  
61 -  
62 -  
63 -  
64 - *** Encoding. *************************************************************************  
65 -  
66 - Translate an index into a wa64 character.  
67 -  
68 -define Word8  
69 - wa64_alphabet  
70 - (  
71 - Int32 index // the index is assumed to be >= 0 and < 64  
72 - ) =  
73 - if index < 0 then alert else  
74 - if index < 26 then truncate_to_word8(index+'A') else  
75 - if index < 52 then truncate_to_word8(index-26+'a') else  
76 - if index < 62 then truncate_to_word8(index-52+'0') else  
77 - if index = 62 then '-' else  
78 - if index = 63 then '_' else  
79 - alert.  
80 -  
81 -  
82 -  
83 - Transform a group of 3 bytes into a group of 4 wa64 letters.  
84 -  
85 -define (Word8,Word8,Word8,Word8)  
86 - transform_group  
87 - (  
88 - Word8 byte1,  
89 - Word8 byte2,  
90 - Word8 byte3  
91 - ) =  
92 - with n1 = word8_to_int32(byte1),  
93 - n2 = word8_to_int32(byte2),  
94 - n3 = word8_to_int32(byte3),  
95 - (  
96 - wa64_alphabet(n1>>2),  
97 - wa64_alphabet(((n1&3)<<4)|(n2>>4)),  
98 - wa64_alphabet(((n2&15)<<2)|(n3>>6)),  
99 - wa64_alphabet(n3&63)  
100 - ).  
101 -  
102 -  
103 - Transform a group of two bytes.  
104 -  
105 -define ByteArray  
106 - two_mod_three  
107 - (  
108 - ByteArray result,  
109 - Int32 result_index,  
110 - Word8 byte1,  
111 - Word8 byte2  
112 - ) =  
113 - with n1 = word8_to_int32(byte1),  
114 - n2 = word8_to_int32(byte2),  
115 - forget(put(result,result_index ,wa64_alphabet(n1>>2)));  
116 - forget(put(result,result_index+1,wa64_alphabet(((n1&3)<<4)|(n2>>4))));  
117 - forget(put(result,result_index+2,wa64_alphabet((n2&15)<<2)));  
118 - forget(put(result,result_index+4,0));  
119 - result.  
120 -  
121 -  
122 -  
123 - Transform a 'group of one byte'.  
124 -  
125 -define ByteArray  
126 - one_mod_three  
127 - (  
128 - ByteArray result,  
129 - Int32 result_index,  
130 - Word8 byte1  
131 - ) =  
132 - with n1 = word8_to_int32(byte1),  
133 - forget(put(result,result_index ,wa64_alphabet(n1>>2)));  
134 - forget(put(result,result_index+1,wa64_alphabet((n1&3)<<4)));  
135 - forget(put(result,result_index+4,0));  
136 - result.  
137 -  
138 -  
139 -  
140 -define ByteArray  
141 - wa64_encode  
142 - (  
143 - ByteArray ba,  
144 - Int32 ba_index, // index into byte array  
145 - ByteArray result,  
146 - Int32 result_index  
147 - ) =  
148 - if nth(ba_index,ba) is  
149 - {  
150 - failure then forget(put(result,result_index,0)); result, // no new block of 3 bytes  
151 - success(byte1) then  
152 - if nth(ba_index+1,ba) is  
153 - {  
154 - failure then one_mod_three(result,result_index,byte1),  
155 - success(byte2) then  
156 - if nth(ba_index+2,ba) is  
157 - {  
158 - failure then two_mod_three(result,result_index,byte1,byte2),  
159 - success(byte3) then  
160 - if transform_group(byte1,byte2,byte3) is (c1,c2,c3,c4) then  
161 - (  
162 - forget(put(result,result_index,c1));  
163 - forget(put(result,result_index+1,c2));  
164 - forget(put(result,result_index+2,c3));  
165 - forget(put(result,result_index+3,c4));  
166 - wa64_encode(ba,  
167 - ba_index+3,  
168 - result,  
169 - result_index+4)  
170 - )  
171 - }  
172 - }  
173 - }.  
174 -  
175 -  
176 -  
177 -define ByteArray  
178 - wa64_encode  
179 - (  
180 - ByteArray ba  
181 - ) =  
182 - with l = length(ba),  
183 - wa64_encode(ba,0,  
184 - constant_byte_array((((l/57)+1)*76)+10,0),0).  
185 -  
186 -  
187 -public define String  
188 - web_arg_encode  
189 - (  
190 - $T datum  
191 - ) =  
192 - to_string(wa64_encode(serialize(datum))).  
193 -  
194 -  
195 -  
196 -  
197 -  
198 - *** Decoding. *************************************************************************  
199 -  
200 - See the comments in 'anubis/library/tools/base64.anubis'.  
201 -  
202 - Checking if a character belongs to the wa64 alphabet. If true, the function returns the  
203 - index of the character in the alphabet.  
204 -  
205 -define Maybe(Int32)  
206 - is_wa64_char  
207 - (  
208 - Word8 c  
209 - ) =  
210 - with n = word8_to_int32(c),  
211 - if ('A' =< n & n =< 'Z') then success(n - 'A') else  
212 - if ('a' =< n & n =< 'z') then success(n - 'a' + 26) else  
213 - if ('0' =< n & n =< '9') then success(n - '0' + 52) else  
214 - if n = '-' then success(62) else  
215 - if n = '_' then success(63) else  
216 - failure.  
217 -  
218 -  
219 -  
220 - Getting the next wa64 character from the input. The function returns the next position  
221 - for reading. The function does not return the character itself, but its index in the  
222 - alphabet.  
223 -  
224 -define Maybe((Int32, // next position for reading  
225 - Int32)) // index of character in wa64 alphabet  
226 - get_next_character  
227 - (  
228 - ByteArray ba,  
229 - Int32 n  
230 - ) =  
231 - if nth(n,ba) is  
232 - {  
233 - failure then failure,  
234 - success(c) then  
235 - if is_wa64_char(c) is  
236 - {  
237 - failure then failure,  
238 - success(i) then success((n+1,i))  
239 - }  
240 - }.  
241 -  
242 -  
243 - Translating a group of characters into a group of bytes.  
244 -  
245 -type TranslateGroupResult:  
246 - three_bytes (Int32 new_pos, Word8 b1, Word8 b2, Word8 b3),  
247 - two_bytes ( Word8 b1, Word8 b2 ),  
248 - one_byte ( Word8 b1 ),  
249 - zero_bytes,  
250 - error.  
251 -  
252 -  
253 -define TranslateGroupResult  
254 - translate_group  
255 - (  
256 - ByteArray ba,  
257 - Int32 n  
258 - ) =  
259 - if get_next_character(ba,n) is  
260 - {  
261 - failure then zero_bytes,  
262 - success(p1) then if p1 is (n1,i1) then  
263 - if get_next_character(ba,n1) is  
264 - {  
265 - failure then error,  
266 - success(p2) then if p2 is (n2,i2) then  
267 - if get_next_character(ba,n2) is  
268 - {  
269 - failure then // we don't check the padding characters  
270 - one_byte(truncate_to_word8((i1<<2)|(i2>>4))),  
271 - success(p3) then if p3 is (n3,i3) then  
272 - if get_next_character(ba,n3) is  
273 - {  
274 - failure then  
275 - two_bytes(truncate_to_word8((i1<<2)|(i2>>4)),  
276 - truncate_to_word8(((i2&15)<<4)|(i3>>2))),  
277 - success(p4) then if p4 is (n4,i4) then  
278 - three_bytes(n4,truncate_to_word8((i1<<2)|(i2>>4)),  
279 - truncate_to_word8(((i2&15)<<4)|(i3>>2)),  
280 - truncate_to_word8(((i3&3)<<6)|i4))  
281 - }  
282 - }  
283 - }  
284 - }.  
285 -  
286 -  
287 -define Int32 // returns the size of the decoded array of bytes  
288 - translate_groups  
289 - (  
290 - ByteArray source,  
291 - Int32 n, // position in source  
292 - ByteArray target,  
293 - Int32 m // position in target  
294 - ) =  
295 - if translate_group(source,n) is  
296 - {  
297 - three_bytes(n1,b1,b2,b3) then  
298 - forget(put(target,m,b1));  
299 - forget(put(target,m+1,b2));  
300 - forget(put(target,m+2,b3));  
301 - translate_groups(source,n1,target,m+3),  
302 -  
303 - two_bytes(b1,b2) then  
304 - forget(put(target,m,b1));  
305 - forget(put(target,m+1,b2));  
306 - m+2,  
307 -  
308 - one_byte(b1) then  
309 - forget(put(target,m,b1));  
310 - m+1,  
311 -  
312 - zero_bytes then  
313 - m,  
314 -  
315 - error then  
316 - m  
317 - }.  
318 -  
319 -  
320 -define ByteArray  
321 - wa64_decode  
322 - (  
323 - ByteArray ba  
324 - ) =  
325 - with l = length(ba),  
326 - result = constant_byte_array(l,'0'),  
327 - truncate(result,translate_groups(ba,0,result,0));  
328 - result.  
329 -  
330 -public define Maybe($T)  
331 - web_arg_decode  
332 - (  
333 - String encoded_datum  
334 - ) =  
335 - (Maybe($T))unserialize(wa64_decode(to_byte_array(encoded_datum))).  
336 -  
337 -  
338 -  
339 -  
340 - 1 +
  2 + *Project* The Anubis Project
  3 +
  4 + *Title* Encoding data for web argument values.
  5 +
  6 + *Copyright* Copyright (c) Alain Prouté 2002.
  7 +
  8 +
  9 + *Author* Alain Prouté
  10 +
  11 +
  12 +
  13 + *Overview*
  14 + This file contains encoding and decoding functions which allow to put any serializable
  15 + datum as the value of a web argument. The datum is serialized, and the result of
  16 + serialization (a byte array) is encoded in such a way that it can safely be used as the
  17 + value of a web argument. The encoding process is similar to the standard process
  18 + 'base64', but nevertheless different, because base64 is not suitable for that purpose.
  19 +
  20 +
  21 +public define String
  22 + web_arg_encode
  23 + (
  24 + $T datum
  25 + ).
  26 +
  27 +public define Maybe($T)
  28 + web_arg_decode
  29 + (
  30 + String encoded_value
  31 + ).
  32 +
  33 + Of course, since the type of the datum is not available from 'encoded_value', a term
  34 + like 'web_arg_decode(my_string)' must generally be explicitly typed, like this:
  35 +
  36 + (Maybe(MyType))web_arg_decode(my_string)
  37 +
  38 +
  39 + These functions are used for example in 'anubis/library/web/kernel.anubis'.
  40 +
  41 +
  42 +
  43 +
  44 +
  45 + --- That's all for the public part. ---------------------------------------------------
  46 +
  47 +read tools/basis.anubis
  48 +
  49 + Our algorithms are copy-pasted from 'base64.anubis' and slightly modified. The point is
  50 + twofold:
  51 +
  52 + (1) base64 encoding inserts carriage return (CR) and line feed (LF) characters every
  53 + 76 character, but CR and LF are not suitable in the values of a web argument,
  54 +
  55 + (2) the base64 alphabet uses '+' and '/', which are also not suitable in the value of
  56 + a web argument, because they have special meanings.
  57 +
  58 + Hence, we just have to modify the base64 algorithms, so as not to generate any CR or
  59 + LF, and use '-' and '_' instead of '+' and '/'. Also, we do not use padding characters
  60 + '=', which are needless (as remarked in 'anubis/library/tools/base64.anubis').
  61 +
  62 +
  63 +
  64 + *** Encoding. *************************************************************************
  65 +
  66 + Translate an index into a wa64 character.
  67 +
  68 +define Word8
  69 + wa64_alphabet
  70 + (
  71 + Int32 index // the index is assumed to be >= 0 and < 64
  72 + ) =
  73 + if index < 0 then print("Bad index [" + index + "] in wa64_alphabet()\n"); '_' else
  74 + if index < 26 then truncate_to_word8(index+'A') else
  75 + if index < 52 then truncate_to_word8(index-26+'a') else
  76 + if index < 62 then truncate_to_word8(index-52+'0') else
  77 + if index = 62 then '-' else
  78 + if index = 63 then '_' else
  79 + print("Bad index [" + index + "] in wa64_alphabet()\n"); '_'.
  80 +
  81 +
  82 +
  83 + Transform a group of 3 bytes into a group of 4 wa64 letters.
  84 +
  85 +define (Word8,Word8,Word8,Word8)
  86 + transform_group
  87 + (
  88 + Word8 byte1,
  89 + Word8 byte2,
  90 + Word8 byte3
  91 + ) =
  92 + with n1 = word8_to_int32(byte1),
  93 + n2 = word8_to_int32(byte2),
  94 + n3 = word8_to_int32(byte3),
  95 + (
  96 + wa64_alphabet(n1>>2),
  97 + wa64_alphabet(((n1&3)<<4)|(n2>>4)),
  98 + wa64_alphabet(((n2&15)<<2)|(n3>>6)),
  99 + wa64_alphabet(n3&63)
  100 + ).
  101 +
  102 +
  103 + Transform a group of two bytes.
  104 +
  105 +define ByteArray
  106 + two_mod_three
  107 + (
  108 + ByteArray result,
  109 + Int32 result_index,
  110 + Word8 byte1,
  111 + Word8 byte2
  112 + ) =
  113 + with n1 = word8_to_int32(byte1),
  114 + n2 = word8_to_int32(byte2),
  115 + forget(put(result,result_index ,wa64_alphabet(n1>>2)));
  116 + forget(put(result,result_index+1,wa64_alphabet(((n1&3)<<4)|(n2>>4))));
  117 + forget(put(result,result_index+2,wa64_alphabet((n2&15)<<2)));
  118 + forget(put(result,result_index+4,0));
  119 + result.
  120 +
  121 +
  122 +
  123 + Transform a 'group of one byte'.
  124 +
  125 +define ByteArray
  126 + one_mod_three
  127 + (
  128 + ByteArray result,
  129 + Int32 result_index,
  130 + Word8 byte1
  131 + ) =
  132 + with n1 = word8_to_int32(byte1),
  133 + forget(put(result,result_index ,wa64_alphabet(n1>>2)));
  134 + forget(put(result,result_index+1,wa64_alphabet((n1&3)<<4)));
  135 + forget(put(result,result_index+4,0));
  136 + result.
  137 +
  138 +
  139 +
  140 +define ByteArray
  141 + wa64_encode
  142 + (
  143 + ByteArray ba,
  144 + Int32 ba_index, // index into byte array
  145 + ByteArray result,
  146 + Int32 result_index
  147 + ) =
  148 + if nth(ba_index,ba) is
  149 + {
  150 + failure then forget(put(result,result_index,0)); result, // no new block of 3 bytes
  151 + success(byte1) then
  152 + if nth(ba_index+1,ba) is
  153 + {
  154 + failure then one_mod_three(result,result_index,byte1),
  155 + success(byte2) then
  156 + if nth(ba_index+2,ba) is
  157 + {
  158 + failure then two_mod_three(result,result_index,byte1,byte2),
  159 + success(byte3) then
  160 + if transform_group(byte1,byte2,byte3) is (c1,c2,c3,c4) then
  161 + (
  162 + forget(put(result,result_index,c1));
  163 + forget(put(result,result_index+1,c2));
  164 + forget(put(result,result_index+2,c3));
  165 + forget(put(result,result_index+3,c4));
  166 + wa64_encode(ba,
  167 + ba_index+3,
  168 + result,
  169 + result_index+4)
  170 + )
  171 + }
  172 + }
  173 + }.
  174 +
  175 +
  176 +
  177 +define ByteArray
  178 + wa64_encode
  179 + (
  180 + ByteArray ba
  181 + ) =
  182 + with l = length(ba),
  183 + wa64_encode(ba,0,
  184 + constant_byte_array((((l/57)+1)*76)+10,0),0).
  185 +
  186 +
  187 +public define String
  188 + web_arg_encode
  189 + (
  190 + $T datum
  191 + ) =
  192 + to_string(wa64_encode(serialize(datum))).
  193 +
  194 +
  195 +
  196 +
  197 +
  198 + *** Decoding. *************************************************************************
  199 +
  200 + See the comments in 'anubis/library/tools/base64.anubis'.
  201 +
  202 + Checking if a character belongs to the wa64 alphabet. If true, the function returns the
  203 + index of the character in the alphabet.
  204 +
  205 +define Maybe(Int32)
  206 + is_wa64_char
  207 + (
  208 + Word8 c
  209 + ) =
  210 + with n = word8_to_int32(c),
  211 + if ('A' =< n & n =< 'Z') then success(n - 'A') else
  212 + if ('a' =< n & n =< 'z') then success(n - 'a' + 26) else
  213 + if ('0' =< n & n =< '9') then success(n - '0' + 52) else
  214 + if n = '-' then success(62) else
  215 + if n = '_' then success(63) else
  216 + failure.
  217 +
  218 +
  219 +
  220 + Getting the next wa64 character from the input. The function returns the next position
  221 + for reading. The function does not return the character itself, but its index in the
  222 + alphabet.
  223 +
  224 +define Maybe((Int32, // next position for reading
  225 + Int32)) // index of character in wa64 alphabet
  226 + get_next_character
  227 + (
  228 + ByteArray ba,
  229 + Int32 n
  230 + ) =
  231 + if nth(n,ba) is
  232 + {
  233 + failure then failure,
  234 + success(c) then
  235 + if is_wa64_char(c) is
  236 + {
  237 + failure then failure,
  238 + success(i) then success((n+1,i))
  239 + }
  240 + }.
  241 +
  242 +
  243 + Translating a group of characters into a group of bytes.
  244 +
  245 +type TranslateGroupResult:
  246 + three_bytes (Int32 new_pos, Word8 b1, Word8 b2, Word8 b3),
  247 + two_bytes ( Word8 b1, Word8 b2 ),
  248 + one_byte ( Word8 b1 ),
  249 + zero_bytes,
  250 + error.
  251 +
  252 +
  253 +define TranslateGroupResult
  254 + translate_group
  255 + (
  256 + ByteArray ba,
  257 + Int32 n
  258 + ) =
  259 + if get_next_character(ba,n) is
  260 + {
  261 + failure then zero_bytes,
  262 + success(p1) then if p1 is (n1,i1) then
  263 + if get_next_character(ba,n1) is
  264 + {
  265 + failure then error,
  266 + success(p2) then if p2 is (n2,i2) then
  267 + if get_next_character(ba,n2) is
  268 + {
  269 + failure then // we don't check the padding characters
  270 + one_byte(truncate_to_word8((i1<<2)|(i2>>4))),
  271 + success(p3) then if p3 is (n3,i3) then
  272 + if get_next_character(ba,n3) is
  273 + {
  274 + failure then
  275 + two_bytes(truncate_to_word8((i1<<2)|(i2>>4)),
  276 + truncate_to_word8(((i2&15)<<4)|(i3>>2))),
  277 + success(p4) then if p4 is (n4,i4) then
  278 + three_bytes(n4,truncate_to_word8((i1<<2)|(i2>>4)),
  279 + truncate_to_word8(((i2&15)<<4)|(i3>>2)),
  280 + truncate_to_word8(((i3&3)<<6)|i4))
  281 + }
  282 + }
  283 + }
  284 + }.
  285 +
  286 +
  287 +define Int32 // returns the size of the decoded array of bytes
  288 + translate_groups
  289 + (
  290 + ByteArray source,
  291 + Int32 n, // position in source
  292 + ByteArray target,
  293 + Int32 m // position in target
  294 + ) =
  295 + if translate_group(source,n) is
  296 + {
  297 + three_bytes(n1,b1,b2,b3) then
  298 + forget(put(target,m,b1));
  299 + forget(put(target,m+1,b2));
  300 + forget(put(target,m+2,b3));
  301 + translate_groups(source,n1,target,m+3),
  302 +
  303 + two_bytes(b1,b2) then
  304 + forget(put(target,m,b1));
  305 + forget(put(target,m+1,b2));
  306 + m+2,
  307 +
  308 + one_byte(b1) then
  309 + forget(put(target,m,b1));
  310 + m+1,
  311 +
  312 + zero_bytes then
  313 + m,
  314 +
  315 + error then
  316 + m
  317 + }.
  318 +
  319 +
  320 +define ByteArray
  321 + wa64_decode
  322 + (
  323 + ByteArray ba
  324 + ) =
  325 + with l = length(ba),
  326 + result = constant_byte_array(l,'0'),
  327 + truncate(result,translate_groups(ba,0,result,0));
  328 + result.
  329 +
  330 +public define Maybe($T)
  331 + web_arg_decode
  332 + (
  333 + String encoded_datum
  334 + ) =
  335 + (Maybe($T))unserialize(wa64_decode(to_byte_array(encoded_datum))).
  336 +
  337 +
  338 +
  339 +
  340 +