Commit 8adc07dfc2e7fd7e617e803991fbe0cfeb9d39b6

Authored by Cédric RICARD
1 parent df3ae302

Deleting some 'alert' calls

calexium_lib/web/CXM_multihost_http_server.anubis
Changes suppressed. Click to show
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 +
... ...